"alarm details" view mostly working; event history for active alarms preserved; minor...
[public/netxms.git] / src / server / core / alarm.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2012 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: alarm.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Global instance of alarm manager
27 */
28 AlarmManager g_alarmMgr;
29
30 /**
31 * Fill NXCP message with alarm data
32 */
33 void FillAlarmInfoMessage(CSCPMessage *pMsg, NXC_ALARM *pAlarm)
34 {
35 pMsg->SetVariable(VID_ALARM_ID, pAlarm->dwAlarmId);
36 pMsg->SetVariable(VID_ACK_BY_USER, pAlarm->dwAckByUser);
37 pMsg->SetVariable(VID_RESOLVED_BY_USER, pAlarm->dwResolvedByUser);
38 pMsg->SetVariable(VID_TERMINATED_BY_USER, pAlarm->dwTermByUser);
39 pMsg->SetVariable(VID_EVENT_CODE, pAlarm->dwSourceEventCode);
40 pMsg->SetVariable(VID_EVENT_ID, pAlarm->qwSourceEventId);
41 pMsg->SetVariable(VID_OBJECT_ID, pAlarm->dwSourceObject);
42 pMsg->SetVariable(VID_CREATION_TIME, pAlarm->dwCreationTime);
43 pMsg->SetVariable(VID_LAST_CHANGE_TIME, pAlarm->dwLastChangeTime);
44 pMsg->SetVariable(VID_ALARM_KEY, pAlarm->szKey);
45 pMsg->SetVariable(VID_ALARM_MESSAGE, pAlarm->szMessage);
46 pMsg->SetVariable(VID_STATE, (WORD)(pAlarm->nState & ALARM_STATE_MASK)); // send only state to client, without flags
47 pMsg->SetVariable(VID_IS_STICKY, (WORD)((pAlarm->nState & ALARM_STATE_STICKY) ? 1 : 0));
48 pMsg->SetVariable(VID_CURRENT_SEVERITY, (WORD)pAlarm->nCurrentSeverity);
49 pMsg->SetVariable(VID_ORIGINAL_SEVERITY, (WORD)pAlarm->nOriginalSeverity);
50 pMsg->SetVariable(VID_HELPDESK_STATE, (WORD)pAlarm->nHelpDeskState);
51 pMsg->SetVariable(VID_HELPDESK_REF, pAlarm->szHelpDeskRef);
52 pMsg->SetVariable(VID_REPEAT_COUNT, pAlarm->dwRepeatCount);
53 pMsg->SetVariable(VID_ALARM_TIMEOUT, pAlarm->dwTimeout);
54 pMsg->SetVariable(VID_ALARM_TIMEOUT_EVENT, pAlarm->dwTimeoutEvent);
55 pMsg->SetVariable(VID_NUM_COMMENTS, pAlarm->noteCount);
56 }
57
58 /**
59 * Fill NXCP message with event data from SQL query
60 * Expected field order: event_id,event_code,event_name,severity,source_object_id,event_timestamp,message
61 */
62 static void FillEventData(CSCPMessage *msg, DWORD baseId, DB_RESULT hResult, int row, QWORD rootId)
63 {
64 TCHAR buffer[MAX_DB_STRING];
65
66 msg->SetVariable(baseId, DBGetFieldUInt64(hResult, row, 0));
67 msg->SetVariable(baseId + 1, rootId);
68 msg->SetVariable(baseId + 2, DBGetFieldULong(hResult, row, 1));
69 msg->SetVariable(baseId + 3, DBGetField(hResult, row, 2, buffer, MAX_DB_STRING));
70 msg->SetVariable(baseId + 4, (WORD)DBGetFieldLong(hResult, row, 3)); // severity
71 msg->SetVariable(baseId + 5, DBGetFieldULong(hResult, row, 4)); // source object
72 msg->SetVariable(baseId + 6, DBGetFieldULong(hResult, row, 5)); // timestamp
73 msg->SetVariable(baseId + 7, DBGetField(hResult, row, 6, buffer, MAX_DB_STRING));
74 }
75
76 /**
77 * Get events correlated to given event into NXCP message
78 *
79 * @return number of consumed variable identifiers
80 */
81 static DWORD GetCorrelatedEvents(QWORD eventId, CSCPMessage *msg, DWORD baseId, DB_HANDLE hdb)
82 {
83 DWORD varId = baseId;
84 DB_STATEMENT hStmt = DBPrepare(hdb,
85 _T("SELECT e.event_id,e.event_code,c.event_name,e.event_severity,e.event_source,e.event_timestamp,e.event_message ")
86 _T("FROM event_log e,event_cfg c WHERE e.root_event_id=? AND c.event_code=e.event_code"));
87 if (hStmt != NULL)
88 {
89 DBBind(hStmt, 1, DB_SQLTYPE_BIGINT, eventId);
90 DB_RESULT hResult = DBSelectPrepared(hStmt);
91 if (hResult != NULL)
92 {
93 int count = DBGetNumRows(hResult);
94 for(int i = 0; i < count; i++)
95 {
96 FillEventData(msg, varId, hResult, i, eventId);
97 varId += 10;
98 QWORD eventId = DBGetFieldUInt64(hResult, i, 0);
99 varId += GetCorrelatedEvents(eventId, msg, varId, hdb);
100 }
101 DBFreeResult(hResult);
102 }
103 DBFreeStatement(hStmt);
104 }
105 return varId - baseId;
106 }
107
108 /**
109 * Fill NXCP message with alarm's related events
110 */
111 static void FillAlarmEventsMessage(CSCPMessage *msg, DWORD alarmId)
112 {
113 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
114 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT event_id,event_code,event_name,severity,source_object_id,event_timestamp,message FROM alarm_events WHERE alarm_id=?"));
115 if (hStmt != NULL)
116 {
117 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, alarmId);
118 DB_RESULT hResult = DBSelectPrepared(hStmt);
119 if (hResult != NULL)
120 {
121 int count = DBGetNumRows(hResult);
122 DWORD varId = VID_ELEMENT_LIST_BASE;
123 for(int i = 0; i < count; i++)
124 {
125 FillEventData(msg, varId, hResult, i, 0);
126 varId += 10;
127 QWORD eventId = DBGetFieldUInt64(hResult, i, 0);
128 varId += GetCorrelatedEvents(eventId, msg, varId, hdb);
129 }
130 DBFreeResult(hResult);
131 msg->SetVariable(VID_NUM_ELEMENTS, (varId - VID_ELEMENT_LIST_BASE) / 10);
132 }
133 DBFreeStatement(hStmt);
134 }
135 DBConnectionPoolReleaseConnection(hdb);
136 }
137
138 /**
139 * Alarm manager constructor
140 */
141 AlarmManager::AlarmManager()
142 {
143 m_dwNumAlarms = 0;
144 m_pAlarmList = NULL;
145 m_mutex = MutexCreate();
146 m_condShutdown = ConditionCreate(FALSE);
147 m_hWatchdogThread = INVALID_THREAD_HANDLE;
148 }
149
150 /**
151 * Alarm manager destructor
152 */
153 AlarmManager::~AlarmManager()
154 {
155 safe_free(m_pAlarmList);
156 MutexDestroy(m_mutex);
157 ConditionSet(m_condShutdown);
158 ThreadJoin(m_hWatchdogThread);
159 }
160
161 /**
162 * Watchdog thread starter
163 */
164 static THREAD_RESULT THREAD_CALL WatchdogThreadStarter(void *pArg)
165 {
166 ((AlarmManager *)pArg)->watchdogThread();
167 return THREAD_OK;
168 }
169
170 /**
171 * Get number of notes for alarm
172 */
173 static DWORD GetNoteCount(DB_HANDLE hdb, DWORD alarmId)
174 {
175 DWORD value = 0;
176 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT count(*) FROM alarm_notes WHERE alarm_id=?"));
177 if (hStmt != NULL)
178 {
179 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, alarmId);
180 DB_RESULT hResult = DBSelectPrepared(hStmt);
181 if (hResult != NULL)
182 {
183 if (DBGetNumRows(hResult) > 0)
184 value = DBGetFieldULong(hResult, 0, 0);
185 DBFreeResult(hResult);
186 }
187 DBFreeStatement(hStmt);
188 }
189 return value;
190 }
191
192 /**
193 * Initialize alarm manager at system startup
194 */
195 BOOL AlarmManager::init()
196 {
197 DB_RESULT hResult;
198 DWORD i;
199
200 // Load active alarms into memory
201 hResult = DBSelect(g_hCoreDB, _T("SELECT alarm_id,source_object_id,")
202 _T("source_event_code,source_event_id,message,")
203 _T("original_severity,current_severity,")
204 _T("alarm_key,creation_time,last_change_time,")
205 _T("hd_state,hd_ref,ack_by,repeat_count,")
206 _T("alarm_state,timeout,timeout_event,resolved_by ")
207 _T("FROM alarms WHERE alarm_state<>3"));
208 if (hResult == NULL)
209 return FALSE;
210
211 m_dwNumAlarms = DBGetNumRows(hResult);
212 if (m_dwNumAlarms > 0)
213 {
214 m_pAlarmList = (NXC_ALARM *)malloc(sizeof(NXC_ALARM) * m_dwNumAlarms);
215 memset(m_pAlarmList, 0, sizeof(NXC_ALARM) * m_dwNumAlarms);
216 for(i = 0; i < m_dwNumAlarms; i++)
217 {
218 m_pAlarmList[i].dwAlarmId = DBGetFieldULong(hResult, i, 0);
219 m_pAlarmList[i].dwSourceObject = DBGetFieldULong(hResult, i, 1);
220 m_pAlarmList[i].dwSourceEventCode = DBGetFieldULong(hResult, i, 2);
221 m_pAlarmList[i].qwSourceEventId = DBGetFieldUInt64(hResult, i, 3);
222 DBGetField(hResult, i, 4, m_pAlarmList[i].szMessage, MAX_DB_STRING);
223 m_pAlarmList[i].nOriginalSeverity = (BYTE)DBGetFieldLong(hResult, i, 5);
224 m_pAlarmList[i].nCurrentSeverity = (BYTE)DBGetFieldLong(hResult, i, 6);
225 DBGetField(hResult, i, 7, m_pAlarmList[i].szKey, MAX_DB_STRING);
226 m_pAlarmList[i].dwCreationTime = DBGetFieldULong(hResult, i, 8);
227 m_pAlarmList[i].dwLastChangeTime = DBGetFieldULong(hResult, i, 9);
228 m_pAlarmList[i].nHelpDeskState = (BYTE)DBGetFieldLong(hResult, i, 10);
229 DBGetField(hResult, i, 11, m_pAlarmList[i].szHelpDeskRef, MAX_HELPDESK_REF_LEN);
230 m_pAlarmList[i].dwAckByUser = DBGetFieldULong(hResult, i, 12);
231 m_pAlarmList[i].dwRepeatCount = DBGetFieldULong(hResult, i, 13);
232 m_pAlarmList[i].nState = (BYTE)DBGetFieldLong(hResult, i, 14);
233 m_pAlarmList[i].dwTimeout = DBGetFieldULong(hResult, i, 15);
234 m_pAlarmList[i].dwTimeoutEvent = DBGetFieldULong(hResult, i, 16);
235 m_pAlarmList[i].noteCount = GetNoteCount(g_hCoreDB, m_pAlarmList[i].dwAlarmId);
236 m_pAlarmList[i].dwResolvedByUser = DBGetFieldULong(hResult, i, 17);
237 }
238 }
239
240 DBFreeResult(hResult);
241
242 m_hWatchdogThread = ThreadCreateEx(WatchdogThreadStarter, 0, this);
243 return TRUE;
244 }
245
246 /**
247 * Create new alarm
248 */
249 void AlarmManager::newAlarm(TCHAR *pszMsg, TCHAR *pszKey, int nState,
250 int iSeverity, DWORD dwTimeout,
251 DWORD dwTimeoutEvent, Event *pEvent)
252 {
253 NXC_ALARM alarm;
254 TCHAR *pszExpMsg, *pszExpKey, szQuery[2048];
255 DWORD i, dwObjectId = 0;
256 BOOL bNewAlarm = TRUE;
257
258 // Expand alarm's message and key
259 pszExpMsg = pEvent->expandText(pszMsg);
260 pszExpKey = pEvent->expandText(pszKey);
261
262 // Check if we have a duplicate alarm
263 if (((nState & ALARM_STATE_MASK) != ALARM_STATE_TERMINATED) && (*pszExpKey != 0))
264 {
265 lock();
266
267 for(i = 0; i < m_dwNumAlarms; i++)
268 if (!_tcscmp(pszExpKey, m_pAlarmList[i].szKey))
269 {
270 m_pAlarmList[i].dwRepeatCount++;
271 m_pAlarmList[i].dwLastChangeTime = (DWORD)time(NULL);
272 m_pAlarmList[i].dwSourceObject = pEvent->getSourceId();
273 if ((m_pAlarmList[i].nState & ALARM_STATE_STICKY) == 0)
274 m_pAlarmList[i].nState = nState;
275 m_pAlarmList[i].nCurrentSeverity = iSeverity;
276 m_pAlarmList[i].dwTimeout = dwTimeout;
277 m_pAlarmList[i].dwTimeoutEvent = dwTimeoutEvent;
278 nx_strncpy(m_pAlarmList[i].szMessage, pszExpMsg, MAX_DB_STRING);
279
280 notifyClients(NX_NOTIFY_ALARM_CHANGED, &m_pAlarmList[i]);
281 updateAlarmInDB(&m_pAlarmList[i]);
282
283 bNewAlarm = FALSE;
284 break;
285 }
286
287 unlock();
288 }
289
290 if (bNewAlarm)
291 {
292 // Create new alarm structure
293 memset(&alarm, 0, sizeof(NXC_ALARM));
294 alarm.dwAlarmId = CreateUniqueId(IDG_ALARM);
295 alarm.qwSourceEventId = pEvent->getId();
296 alarm.dwSourceEventCode = pEvent->getCode();
297 alarm.dwSourceObject = pEvent->getSourceId();
298 alarm.dwCreationTime = (DWORD)time(NULL);
299 alarm.dwLastChangeTime = alarm.dwCreationTime;
300 alarm.nState = nState;
301 alarm.nOriginalSeverity = iSeverity;
302 alarm.nCurrentSeverity = iSeverity;
303 alarm.dwRepeatCount = 1;
304 alarm.nHelpDeskState = ALARM_HELPDESK_IGNORED;
305 alarm.dwTimeout = dwTimeout;
306 alarm.dwTimeoutEvent = dwTimeoutEvent;
307 alarm.noteCount = 0;
308 nx_strncpy(alarm.szMessage, pszExpMsg, MAX_DB_STRING);
309 nx_strncpy(alarm.szKey, pszExpKey, MAX_DB_STRING);
310
311 // Add new alarm to active alarm list if needed
312 if ((alarm.nState & ALARM_STATE_MASK) != ALARM_STATE_TERMINATED)
313 {
314 lock();
315
316 DbgPrintf(7, _T("AlarmManager: adding new active alarm, current alarm count %d"), (int)m_dwNumAlarms);
317 m_dwNumAlarms++;
318 m_pAlarmList = (NXC_ALARM *)realloc(m_pAlarmList, sizeof(NXC_ALARM) * m_dwNumAlarms);
319 memcpy(&m_pAlarmList[m_dwNumAlarms - 1], &alarm, sizeof(NXC_ALARM));
320 dwObjectId = alarm.dwSourceObject;
321
322 unlock();
323 }
324
325 // Save alarm to database
326 _sntprintf(szQuery, 2048,
327 _T("INSERT INTO alarms (alarm_id,creation_time,last_change_time,")
328 _T("source_object_id,source_event_code,message,original_severity,")
329 _T("current_severity,alarm_key,alarm_state,ack_by,resolved_by,hd_state,")
330 _T("hd_ref,repeat_count,term_by,timeout,timeout_event,source_event_id) VALUES ")
331 _T("(%d,%d,%d,%d,%d,%s,%d,%d,%s,%d,%d,%d,%d,%s,%d,%d,%d,%d,") UINT64_FMT _T(")"),
332 alarm.dwAlarmId, alarm.dwCreationTime, alarm.dwLastChangeTime,
333 alarm.dwSourceObject, alarm.dwSourceEventCode,
334 (const TCHAR *)DBPrepareString(g_hCoreDB, alarm.szMessage),
335 alarm.nOriginalSeverity, alarm.nCurrentSeverity,
336 (const TCHAR *)DBPrepareString(g_hCoreDB, alarm.szKey),
337 alarm.nState, alarm.dwAckByUser, alarm.dwResolvedByUser, alarm.nHelpDeskState,
338 (const TCHAR *)DBPrepareString(g_hCoreDB, alarm.szHelpDeskRef),
339 alarm.dwRepeatCount, alarm.dwTermByUser, alarm.dwTimeout,
340 alarm.dwTimeoutEvent, alarm.qwSourceEventId);
341 QueueSQLRequest(szQuery);
342
343 // Notify connected clients about new alarm
344 notifyClients(NX_NOTIFY_NEW_ALARM, &alarm);
345 }
346
347 // Update status of related object if needed
348 if ((dwObjectId != 0) && ((alarm.nState & ALARM_STATE_MASK) != ALARM_STATE_TERMINATED))
349 updateObjectStatus(dwObjectId);
350
351 // Add record to alarm_events table
352 TCHAR valAlarmId[16], valEventId[32], valEventCode[16], valSeverity[16], valSource[16], valTimestamp[16];
353 const TCHAR *values[8] = { valAlarmId, valEventId, valEventCode, pEvent->getName(), valSeverity, valSource, valTimestamp, pEvent->getMessage() };
354 _sntprintf(valAlarmId, 16, _T("%d"), (int)alarm.dwAlarmId);
355 _sntprintf(valEventId, 32, UINT64_FMT, pEvent->getId());
356 _sntprintf(valEventCode, 16, _T("%d"), (int)pEvent->getCode());
357 _sntprintf(valSeverity, 16, _T("%d"), (int)pEvent->getSeverity());
358 _sntprintf(valSource, 16, _T("%d"), pEvent->getSourceId());
359 _sntprintf(valTimestamp, 16, _T("%u"), (DWORD)pEvent->getTimeStamp());
360 static int sqlTypes[8] = { DB_SQLTYPE_INTEGER, DB_SQLTYPE_BIGINT, DB_SQLTYPE_INTEGER, DB_SQLTYPE_VARCHAR, DB_SQLTYPE_INTEGER, DB_SQLTYPE_INTEGER, DB_SQLTYPE_INTEGER, DB_SQLTYPE_VARCHAR };
361 QueueSQLRequest(_T("INSERT INTO alarm_events (alarm_id,event_id,event_code,event_name,severity,source_object_id,event_timestamp,message) VALUES (?,?,?,?,?,?,?,?)"),
362 8, sqlTypes, values);
363
364 free(pszExpMsg);
365 free(pszExpKey);
366 }
367
368 /**
369 * Acknowledge alarm with given ID
370 */
371 DWORD AlarmManager::ackById(DWORD dwAlarmId, DWORD dwUserId, bool sticky)
372 {
373 DWORD i, dwObject, dwRet = RCC_INVALID_ALARM_ID;
374
375 lock();
376 for(i = 0; i < m_dwNumAlarms; i++)
377 if (m_pAlarmList[i].dwAlarmId == dwAlarmId)
378 {
379 if ((m_pAlarmList[i].nState & ALARM_STATE_MASK) == ALARM_STATE_OUTSTANDING)
380 {
381 m_pAlarmList[i].nState = ALARM_STATE_ACKNOWLEDGED;
382 if (sticky)
383 m_pAlarmList[i].nState |= ALARM_STATE_STICKY;
384 m_pAlarmList[i].dwAckByUser = dwUserId;
385 m_pAlarmList[i].dwLastChangeTime = (DWORD)time(NULL);
386 dwObject = m_pAlarmList[i].dwSourceObject;
387 notifyClients(NX_NOTIFY_ALARM_CHANGED, &m_pAlarmList[i]);
388 updateAlarmInDB(&m_pAlarmList[i]);
389 dwRet = RCC_SUCCESS;
390 }
391 else
392 {
393 dwRet = RCC_ALARM_NOT_OUTSTANDING;
394 }
395 break;
396 }
397 unlock();
398
399 if (dwRet == RCC_SUCCESS)
400 updateObjectStatus(dwObject);
401 return dwRet;
402 }
403
404 /**
405 * Resolve and possibly terminate alarm with given ID
406 * Should return RCC which can be sent to client
407 */
408 DWORD AlarmManager::resolveById(DWORD dwAlarmId, DWORD dwUserId, bool terminate)
409 {
410 DWORD i, dwObject, dwRet = RCC_INVALID_ALARM_ID;
411
412 lock();
413 for(i = 0; i < m_dwNumAlarms; i++)
414 if (m_pAlarmList[i].dwAlarmId == dwAlarmId)
415 {
416 // If alarm is open in helpdesk, it cannot be terminated
417 if (m_pAlarmList[i].nHelpDeskState != ALARM_HELPDESK_OPEN)
418 {
419 dwObject = m_pAlarmList[i].dwSourceObject;
420 if (terminate)
421 m_pAlarmList[i].dwTermByUser = dwUserId;
422 else
423 m_pAlarmList[i].dwResolvedByUser = dwUserId;
424 m_pAlarmList[i].dwLastChangeTime = (DWORD)time(NULL);
425 m_pAlarmList[i].nState = terminate ? ALARM_STATE_TERMINATED : ALARM_STATE_RESOLVED;
426 notifyClients(terminate ? NX_NOTIFY_ALARM_TERMINATED : NX_NOTIFY_ALARM_CHANGED, &m_pAlarmList[i]);
427 updateAlarmInDB(&m_pAlarmList[i]);
428 if (terminate)
429 {
430 m_dwNumAlarms--;
431 memmove(&m_pAlarmList[i], &m_pAlarmList[i + 1], sizeof(NXC_ALARM) * (m_dwNumAlarms - i));
432 }
433 dwRet = RCC_SUCCESS;
434 }
435 else
436 {
437 dwRet = RCC_ALARM_OPEN_IN_HELPDESK;
438 }
439 break;
440 }
441 unlock();
442
443 if (dwRet == RCC_SUCCESS)
444 updateObjectStatus(dwObject);
445 return dwRet;
446 }
447
448 /**
449 * Resolve and possibly terminate all alarms with given key
450 */
451 void AlarmManager::resolveByKey(const TCHAR *pszKey, bool useRegexp, bool terminate)
452 {
453 DWORD i, j, dwNumObjects, *pdwObjectList, dwCurrTime;
454
455 pdwObjectList = (DWORD *)malloc(sizeof(DWORD) * m_dwNumAlarms);
456
457 lock();
458 dwCurrTime = (DWORD)time(NULL);
459 for(i = 0, dwNumObjects = 0; i < m_dwNumAlarms; i++)
460 if ((useRegexp ? RegexpMatch(m_pAlarmList[i].szKey, pszKey, TRUE) : !_tcscmp(pszKey, m_pAlarmList[i].szKey)) &&
461 (m_pAlarmList[i].nHelpDeskState != ALARM_HELPDESK_OPEN))
462 {
463 // Add alarm's source object to update list
464 for(j = 0; j < dwNumObjects; j++)
465 {
466 if (pdwObjectList[j] == m_pAlarmList[i].dwSourceObject)
467 break;
468 }
469 if (j == dwNumObjects)
470 {
471 pdwObjectList[dwNumObjects++] = m_pAlarmList[i].dwSourceObject;
472 }
473
474 // Resolve or terminate alarm
475 m_pAlarmList[i].nState = terminate ? ALARM_STATE_TERMINATED : ALARM_STATE_RESOLVED;
476 m_pAlarmList[i].dwLastChangeTime = dwCurrTime;
477 if (terminate)
478 m_pAlarmList[i].dwTermByUser = 0;
479 else
480 m_pAlarmList[i].dwResolvedByUser = 0;
481 notifyClients(terminate ? NX_NOTIFY_ALARM_TERMINATED : NX_NOTIFY_ALARM_CHANGED, &m_pAlarmList[i]);
482 updateAlarmInDB(&m_pAlarmList[i]);
483 if (terminate)
484 {
485 m_dwNumAlarms--;
486 memmove(&m_pAlarmList[i], &m_pAlarmList[i + 1], sizeof(NXC_ALARM) * (m_dwNumAlarms - i));
487 i--;
488 }
489 }
490 unlock();
491
492 // Update status of objects
493 for(i = 0; i < dwNumObjects; i++)
494 updateObjectStatus(pdwObjectList[i]);
495 free(pdwObjectList);
496 }
497
498 /**
499 * Delete alarm with given ID
500 */
501 void AlarmManager::deleteAlarm(DWORD dwAlarmId)
502 {
503 DWORD i, dwObject;
504 TCHAR szQuery[256];
505
506 // Delete alarm from in-memory list
507 lock();
508 for(i = 0; i < m_dwNumAlarms; i++)
509 if (m_pAlarmList[i].dwAlarmId == dwAlarmId)
510 {
511 dwObject = m_pAlarmList[i].dwSourceObject;
512 notifyClients(NX_NOTIFY_ALARM_DELETED, &m_pAlarmList[i]);
513 m_dwNumAlarms--;
514 memmove(&m_pAlarmList[i], &m_pAlarmList[i + 1], sizeof(NXC_ALARM) * (m_dwNumAlarms - i));
515 break;
516 }
517 unlock();
518
519 // Delete from database
520 _sntprintf(szQuery, 256, _T("DELETE FROM alarms WHERE alarm_id=%d"), (int)dwAlarmId);
521 QueueSQLRequest(szQuery);
522 _sntprintf(szQuery, 256, _T("DELETE FROM alarm_events WHERE alarm_id=%d"), (int)dwAlarmId);
523 QueueSQLRequest(szQuery);
524
525 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
526 DeleteAlarmNotes(hdb, dwAlarmId);
527 DBConnectionPoolReleaseConnection(hdb);
528
529 updateObjectStatus(dwObject);
530 }
531
532 /**
533 * Update alarm information in database
534 */
535 void AlarmManager::updateAlarmInDB(NXC_ALARM *pAlarm)
536 {
537 TCHAR szQuery[2048];
538
539 _sntprintf(szQuery, 2048, _T("UPDATE alarms SET alarm_state=%d,ack_by=%d,term_by=%d,")
540 _T("last_change_time=%d,current_severity=%d,repeat_count=%d,")
541 _T("hd_state=%d,hd_ref=%s,timeout=%d,timeout_event=%d,")
542 _T("message=%s,resolved_by=%d WHERE alarm_id=%d"),
543 pAlarm->nState, pAlarm->dwAckByUser, pAlarm->dwTermByUser,
544 pAlarm->dwLastChangeTime, pAlarm->nCurrentSeverity,
545 pAlarm->dwRepeatCount, pAlarm->nHelpDeskState,
546 (const TCHAR *)DBPrepareString(g_hCoreDB, pAlarm->szHelpDeskRef),
547 pAlarm->dwTimeout, pAlarm->dwTimeoutEvent,
548 (const TCHAR *)DBPrepareString(g_hCoreDB, pAlarm->szMessage),
549 pAlarm->dwResolvedByUser, pAlarm->dwAlarmId);
550 QueueSQLRequest(szQuery);
551
552 if (pAlarm->nState == ALARM_STATE_TERMINATED)
553 {
554 _sntprintf(szQuery, 256, _T("DELETE FROM alarm_events WHERE alarm_id=%d"), (int)pAlarm->dwAlarmId);
555 QueueSQLRequest(szQuery);
556 }
557 }
558
559 /**
560 * Callback for client session enumeration
561 */
562 void AlarmManager::sendAlarmNotification(ClientSession *pSession, void *pArg)
563 {
564 pSession->onAlarmUpdate(((AlarmManager *)pArg)->m_dwNotifyCode,
565 ((AlarmManager *)pArg)->m_pNotifyAlarmInfo);
566 }
567
568 /**
569 * Notify connected clients about changes
570 */
571 void AlarmManager::notifyClients(DWORD dwCode, NXC_ALARM *pAlarm)
572 {
573 m_dwNotifyCode = dwCode;
574 m_pNotifyAlarmInfo = pAlarm;
575 EnumerateClientSessions(sendAlarmNotification, this);
576 }
577
578 /**
579 * Send all alarms to client
580 */
581 void AlarmManager::sendAlarmsToClient(DWORD dwRqId, ClientSession *pSession)
582 {
583 DWORD i, dwUserId;
584 NetObj *pObject;
585 CSCPMessage msg;
586
587 dwUserId = pSession->getUserId();
588
589 // Prepare message
590 msg.SetCode(CMD_ALARM_DATA);
591 msg.SetId(dwRqId);
592
593 lock();
594 for(i = 0; i < m_dwNumAlarms; i++)
595 {
596 pObject = FindObjectById(m_pAlarmList[i].dwSourceObject);
597 if (pObject != NULL)
598 {
599 if (pObject->CheckAccessRights(dwUserId, OBJECT_ACCESS_READ_ALARMS))
600 {
601 FillAlarmInfoMessage(&msg, &m_pAlarmList[i]);
602 pSession->sendMessage(&msg);
603 msg.DeleteAllVariables();
604 }
605 }
606 }
607 unlock();
608
609 // Send end-of-list indicator
610 msg.SetVariable(VID_ALARM_ID, (DWORD)0);
611 pSession->sendMessage(&msg);
612 }
613
614 /**
615 * Get alarm with given ID into NXCP message
616 * Should return RCC that can be sent to client
617 */
618 DWORD AlarmManager::getAlarm(DWORD dwAlarmId, CSCPMessage *msg)
619 {
620 DWORD i, dwRet = RCC_INVALID_ALARM_ID;
621
622 lock();
623 for(i = 0; i < m_dwNumAlarms; i++)
624 if (m_pAlarmList[i].dwAlarmId == dwAlarmId)
625 {
626 FillAlarmInfoMessage(msg, &m_pAlarmList[i]);
627 dwRet = RCC_SUCCESS;
628 break;
629 }
630 unlock();
631
632 return dwRet;
633 }
634
635 /**
636 * Get all related events for alarm with given ID into NXCP message
637 * Should return RCC that can be sent to client
638 */
639 DWORD AlarmManager::getAlarmEvents(DWORD dwAlarmId, CSCPMessage *msg)
640 {
641 DWORD i, dwRet = RCC_INVALID_ALARM_ID;
642
643 lock();
644 for(i = 0; i < m_dwNumAlarms; i++)
645 if (m_pAlarmList[i].dwAlarmId == dwAlarmId)
646 {
647 dwRet = RCC_SUCCESS;
648 break;
649 }
650 unlock();
651
652 // we don't call FillAlarmEventsMessage from within loop
653 // to prevent alarm list lock for a long time
654 if (dwRet == RCC_SUCCESS)
655 FillAlarmEventsMessage(msg, dwAlarmId);
656
657 return dwRet;
658 }
659
660 /**
661 * Get source object for given alarm id
662 */
663 NetObj *AlarmManager::getAlarmSourceObject(DWORD dwAlarmId)
664 {
665 DWORD i, dwObjectId = 0;
666 TCHAR szQuery[256];
667 DB_RESULT hResult;
668
669 // First, look at our in-memory list
670 lock();
671 for(i = 0; i < m_dwNumAlarms; i++)
672 if (m_pAlarmList[i].dwAlarmId == dwAlarmId)
673 {
674 dwObjectId = m_pAlarmList[i].dwSourceObject;
675 break;
676 }
677 unlock();
678
679 // If not found, search database
680 if (i == m_dwNumAlarms)
681 {
682 _sntprintf(szQuery, sizeof(szQuery) / sizeof(TCHAR), _T("SELECT source_object_id FROM alarms WHERE alarm_id=%d"), dwAlarmId);
683 hResult = DBSelect(g_hCoreDB, szQuery);
684 if (hResult != NULL)
685 {
686 if (DBGetNumRows(hResult) > 0)
687 {
688 dwObjectId = DBGetFieldULong(hResult, 0, 0);
689 }
690 DBFreeResult(hResult);
691 }
692 }
693
694 return FindObjectById(dwObjectId);
695 }
696
697 /**
698 * Get most critical status among active alarms for given object
699 * Will return STATUS_UNKNOWN if there are no active alarms
700 */
701 int AlarmManager::getMostCriticalStatusForObject(DWORD dwObjectId)
702 {
703 DWORD i;
704 int iStatus = STATUS_UNKNOWN;
705
706 lock();
707 for(i = 0; i < m_dwNumAlarms; i++)
708 {
709 if ((m_pAlarmList[i].dwSourceObject == dwObjectId) &&
710 ((m_pAlarmList[i].nState & ALARM_STATE_MASK) < ALARM_STATE_RESOLVED) &&
711 ((m_pAlarmList[i].nCurrentSeverity > iStatus) || (iStatus == STATUS_UNKNOWN)))
712 {
713 iStatus = (int)m_pAlarmList[i].nCurrentSeverity;
714 }
715 }
716 unlock();
717 return iStatus;
718 }
719
720
721 //
722 // Update object status after alarm acknowledgement or deletion
723 //
724
725 void AlarmManager::updateObjectStatus(DWORD dwObjectId)
726 {
727 NetObj *pObject;
728
729 pObject = FindObjectById(dwObjectId);
730 if (pObject != NULL)
731 pObject->calculateCompoundStatus();
732 }
733
734
735 //
736 // Fill message with alarm stats
737 //
738
739 void AlarmManager::getAlarmStats(CSCPMessage *pMsg)
740 {
741 DWORD i, dwCount[5];
742
743 lock();
744 pMsg->SetVariable(VID_NUM_ALARMS, m_dwNumAlarms);
745 memset(dwCount, 0, sizeof(DWORD) * 5);
746 for(i = 0; i < m_dwNumAlarms; i++)
747 dwCount[m_pAlarmList[i].nCurrentSeverity]++;
748 unlock();
749 pMsg->SetVariableToInt32Array(VID_ALARMS_BY_SEVERITY, 5, dwCount);
750 }
751
752
753 //
754 // Watchdog thread
755 //
756
757 void AlarmManager::watchdogThread()
758 {
759 DWORD i;
760 time_t now;
761
762 while(1)
763 {
764 if (ConditionWait(m_condShutdown, 1000))
765 break;
766
767 lock();
768 now = time(NULL);
769 for(i = 0; i < m_dwNumAlarms; i++)
770 {
771 if ((m_pAlarmList[i].dwTimeout > 0) &&
772 ((m_pAlarmList[i].nState & ALARM_STATE_MASK) == ALARM_STATE_OUTSTANDING) &&
773 (((time_t)m_pAlarmList[i].dwLastChangeTime + (time_t)m_pAlarmList[i].dwTimeout) < now))
774 {
775 DbgPrintf(5, _T("Alarm timeout: alarm_id=%d, last_change=%d, timeout=%d, now=%d"),
776 m_pAlarmList[i].dwAlarmId, m_pAlarmList[i].dwLastChangeTime,
777 m_pAlarmList[i].dwTimeout, now);
778
779 PostEvent(m_pAlarmList[i].dwTimeoutEvent, m_pAlarmList[i].dwSourceObject, "dssd",
780 m_pAlarmList[i].dwAlarmId, m_pAlarmList[i].szMessage,
781 m_pAlarmList[i].szKey, m_pAlarmList[i].dwSourceEventCode);
782 m_pAlarmList[i].dwTimeout = 0; // Disable repeated timeout events
783 updateAlarmInDB(&m_pAlarmList[i]);
784 }
785 }
786 unlock();
787 }
788 }
789
790
791 //
792 // Check if givel alram/note id pair is valid
793 //
794
795 static bool IsValidNoteId(DWORD alarmId, DWORD noteId)
796 {
797 bool isValid = false;
798 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
799 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT note_id FROM alarm_notes WHERE alarm_id=? AND note_id=?"));
800 if (hStmt != NULL)
801 {
802 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, alarmId);
803 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, noteId);
804 DB_RESULT hResult = DBSelectPrepared(hStmt);
805 if (hResult != NULL)
806 {
807 isValid = (DBGetNumRows(hResult) > 0);
808 DBFreeResult(hResult);
809 }
810 DBFreeStatement(hStmt);
811 }
812 DBConnectionPoolReleaseConnection(hdb);
813 return isValid;
814 }
815
816
817 //
818 // Update alarm's note
819 //
820
821 DWORD AlarmManager::updateAlarmNote(DWORD alarmId, DWORD noteId, const TCHAR *text, DWORD userId)
822 {
823 DWORD rcc = RCC_INVALID_ALARM_ID;
824
825 lock();
826 for(DWORD i = 0; i < m_dwNumAlarms; i++)
827 if (m_pAlarmList[i].dwAlarmId == alarmId)
828 {
829 if (noteId != 0)
830 {
831 if (IsValidNoteId(alarmId, noteId))
832 {
833 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
834 DB_STATEMENT hStmt = DBPrepare(hdb, _T("UPDATE alarm_notes SET change_time=?,user_id=?,note_text=? WHERE note_id=?"));
835 if (hStmt != NULL)
836 {
837 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, (DWORD)time(NULL));
838 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, userId);
839 DBBind(hStmt, 3, DB_SQLTYPE_TEXT, text, DB_BIND_STATIC);
840 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, noteId);
841 rcc = DBExecute(hStmt) ? RCC_SUCCESS : RCC_DB_FAILURE;
842 DBFreeStatement(hStmt);
843 }
844 else
845 {
846 rcc = RCC_DB_FAILURE;
847 }
848 DBConnectionPoolReleaseConnection(hdb);
849 }
850 else
851 {
852 rcc = RCC_INVALID_ALARM_NOTE_ID;
853 }
854 }
855 else
856 {
857 // new note
858 noteId = CreateUniqueId(IDG_ALARM_NOTE);
859 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
860 DB_STATEMENT hStmt = DBPrepare(hdb, _T("INSERT INTO alarm_notes (note_id,alarm_id,change_time,user_id,note_text) VALUES (?,?,?,?,?)"));
861 if (hStmt != NULL)
862 {
863 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, noteId);
864 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, alarmId);
865 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, (DWORD)time(NULL));
866 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, userId);
867 DBBind(hStmt, 5, DB_SQLTYPE_TEXT, text, DB_BIND_STATIC);
868 rcc = DBExecute(hStmt) ? RCC_SUCCESS : RCC_DB_FAILURE;
869 DBFreeStatement(hStmt);
870 }
871 else
872 {
873 rcc = RCC_DB_FAILURE;
874 }
875 DBConnectionPoolReleaseConnection(hdb);
876 }
877 if (rcc == RCC_SUCCESS)
878 {
879 m_pAlarmList[i].noteCount++;
880 notifyClients(NX_NOTIFY_ALARM_CHANGED, &m_pAlarmList[i]);
881 }
882 break;
883 }
884 unlock();
885
886 return rcc;
887 }
888
889
890 //
891 // Get alarm's notes
892 //
893
894 DWORD AlarmManager::getAlarmNotes(DWORD alarmId, CSCPMessage *msg)
895 {
896 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
897 DWORD rcc = RCC_DB_FAILURE;
898
899 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT note_id,change_time,user_id,note_text FROM alarm_notes WHERE alarm_id=?"));
900 if (hStmt != NULL)
901 {
902 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, alarmId);
903 DB_RESULT hResult = DBSelectPrepared(hStmt);
904 if (hResult != NULL)
905 {
906 int count = DBGetNumRows(hResult);
907 msg->SetVariable(VID_NUM_ELEMENTS, (DWORD)count);
908
909 DWORD varId = VID_ELEMENT_LIST_BASE;
910 for(int i = 0; i < count; i++)
911 {
912 msg->SetVariable(varId++, DBGetFieldULong(hResult, i, 0));
913 msg->SetVariable(varId++, alarmId);
914 msg->SetVariable(varId++, DBGetFieldULong(hResult, i, 1));
915 msg->SetVariable(varId++, DBGetFieldULong(hResult, i, 2));
916 TCHAR *text = DBGetField(hResult, i, 3, NULL, 0);
917 msg->SetVariable(varId++, CHECK_NULL_EX(text));
918 safe_free(text);
919 varId += 5;
920 }
921 DBFreeResult(hResult);
922 rcc = RCC_SUCCESS;
923 }
924 DBFreeStatement(hStmt);
925 }
926
927 DBConnectionPoolReleaseConnection(hdb);
928 return rcc;
929 }