01b389b5fea6d89ff694fdaf612bbfe4e9092e2a
[public/netxms.git] / src / server / core / slmcheck.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Raden Solutions
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: slmcheck.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25
26 //
27 // Static data
28 //
29
30 NXSL_VariableSystem SlmCheck::m_nxslConstants;
31
32 /**
33 * Initialize static members
34 */
35 void SlmCheck::init()
36 {
37 m_nxslConstants.create(_T("OK"), new NXSL_Value((LONG)0));
38 m_nxslConstants.create(_T("FAIL"), new NXSL_Value((LONG)1));
39 }
40
41 /**
42 * SLM check default constructor
43 */
44 SlmCheck::SlmCheck() : NetObj()
45 {
46 _tcscpy(m_name, _T("Default"));
47 m_type = SlmCheck::check_script;
48 m_script = NULL;
49 m_pCompiledScript = NULL;
50 m_threshold = NULL;
51 m_reason[0] = 0;
52 m_isTemplate = false;
53 m_templateId = 0;
54 m_currentTicketId = 0;
55 }
56
57 /**
58 * Constructor for new check object
59 */
60 SlmCheck::SlmCheck(const TCHAR *name, bool isTemplate) : NetObj()
61 {
62 m_isHidden = true;
63 nx_strncpy(m_name, name, MAX_OBJECT_NAME);
64 m_type = SlmCheck::check_script;
65 m_script = NULL;
66 m_pCompiledScript = NULL;
67 m_threshold = NULL;
68 m_reason[0] = 0;
69 m_isTemplate = isTemplate;
70 m_templateId = 0;
71 m_currentTicketId = 0;
72 }
73
74
75 //
76 // Used to create a new object from a check template
77 //
78
79 SlmCheck::SlmCheck(SlmCheck *tmpl) : NetObj()
80 {
81 m_isHidden = true;
82 nx_strncpy(m_name, tmpl->m_name, MAX_OBJECT_NAME);
83 m_type = tmpl->m_type;
84 m_script = ((m_type == check_script) && (tmpl->m_script != NULL)) ? _tcsdup(tmpl->m_script) : NULL;
85 m_threshold = NULL;
86 m_reason[0] = 0;
87 m_isTemplate = false;
88 m_templateId = tmpl->getId();
89 m_currentTicketId = 0;
90 compileScript();
91 }
92
93
94 //
95 // Service class destructor
96 //
97
98 SlmCheck::~SlmCheck()
99 {
100 delete m_threshold;
101 safe_free(m_script);
102 delete m_pCompiledScript;
103 }
104
105
106 //
107 // Update this check from a check template
108 //
109
110 void SlmCheck::updateFromTemplate(SlmCheck *tmpl)
111 {
112 lockProperties();
113 tmpl->lockProperties();
114 DbgPrintf(4, _T("Updating service check %s [%d] from service check template template %s [%d]"), m_name, m_id, tmpl->getName(), tmpl->getId());
115
116 delete m_threshold;
117 safe_free(m_script);
118 delete m_pCompiledScript;
119
120 nx_strncpy(m_name, tmpl->m_name, MAX_OBJECT_NAME);
121 m_type = tmpl->m_type;
122 m_script = ((m_type == check_script) && (tmpl->m_script != NULL)) ? _tcsdup(tmpl->m_script) : NULL;
123 m_threshold = NULL;
124 m_reason[0] = 0;
125 m_isTemplate = false;
126 compileScript();
127
128 tmpl->unlockProperties();
129
130 setModified();
131 unlockProperties();
132 }
133
134 /**
135 * Compile script if there is one
136 */
137 void SlmCheck::compileScript()
138 {
139 if (m_type == check_script && m_script != NULL)
140 {
141 const int errorMsgLen = 512;
142 TCHAR errorMsg[errorMsgLen];
143
144 m_pCompiledScript = NXSLCompileAndCreateVM(m_script, errorMsg, errorMsgLen, new NXSL_ServerEnv);
145 if (m_pCompiledScript == NULL)
146 nxlog_write(MSG_SLMCHECK_SCRIPT_COMPILATION_ERROR, NXLOG_WARNING, "dss", m_id, m_name, errorMsg);
147 }
148 }
149
150 /**
151 * Create object from database data
152 */
153 bool SlmCheck::loadFromDatabase(DB_HANDLE hdb, UINT32 id)
154 {
155 UINT32 thresholdId;
156
157 m_id = id;
158
159 if (!loadCommonProperties(hdb))
160 return false;
161
162 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT type,content,threshold_id,template_id,current_ticket,is_template,reason FROM slm_checks WHERE id=?"));
163 if (hStmt == NULL)
164 {
165 DbgPrintf(4, _T("Cannot prepare select from slm_checks"));
166 return false;
167 }
168 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
169
170 DB_RESULT hResult = DBSelectPrepared(hStmt);
171 if (hResult == NULL)
172 {
173 DBFreeStatement(hStmt);
174 return false;
175 }
176
177 if (DBGetNumRows(hResult) == 0)
178 {
179 DBFreeResult(hResult);
180 DBFreeStatement(hStmt);
181 DbgPrintf(4, _T("Cannot load service check object %d - record missing"), (int)m_id);
182 return false;
183 }
184
185 m_type = SlmCheck::CheckType(DBGetFieldLong(hResult, 0, 0));
186 m_script = DBGetField(hResult, 0, 1, NULL, 0);
187 thresholdId = DBGetFieldULong(hResult, 0, 2);
188 m_templateId = DBGetFieldULong(hResult, 0, 3);
189 m_currentTicketId = DBGetFieldULong(hResult, 0, 4);
190 m_isTemplate = DBGetFieldLong(hResult, 0, 5) ? true : false;
191 DBGetField(hResult, 0, 6, m_reason, 256);
192
193 if (thresholdId > 0)
194 {
195 // FIXME: load threshold
196 }
197
198 compileScript();
199
200 DBFreeResult(hResult);
201 DBFreeStatement(hStmt);
202
203 // Load access list
204 loadACLFromDB(hdb);
205
206 return TRUE;
207 }
208
209 /**
210 * Save service check to database
211 */
212 bool SlmCheck::saveToDatabase(DB_HANDLE hdb)
213 {
214 bool ret = false;
215
216 lockProperties();
217
218 saveCommonProperties(hdb);
219
220 DB_STATEMENT hStmt;
221 if (IsDatabaseRecordExist(hdb, _T("slm_checks"), _T("id"), m_id))
222 {
223 hStmt = DBPrepare(hdb, _T("UPDATE slm_checks SET type=?,content=?,threshold_id=?,reason=?,is_template=?,template_id=?,current_ticket=? WHERE id=?"));
224 }
225 else
226 {
227 hStmt = DBPrepare(hdb, _T("INSERT INTO slm_checks (type,content,threshold_id,reason,is_template,template_id,current_ticket,id) VALUES (?,?,?,?,?,?,?,?)"));
228 }
229
230 if (hStmt != NULL)
231 {
232 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, (UINT32)m_type);
233 DBBind(hStmt, 2, DB_SQLTYPE_TEXT, m_script, DB_BIND_STATIC);
234 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, m_threshold ? m_threshold->getId() : 0);
235 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, m_reason, DB_BIND_STATIC);
236 DBBind(hStmt, 5, DB_SQLTYPE_INTEGER, (LONG)(m_isTemplate ? 1 : 0));
237 DBBind(hStmt, 6, DB_SQLTYPE_INTEGER, m_templateId);
238 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, m_currentTicketId);
239 DBBind(hStmt, 8, DB_SQLTYPE_INTEGER, m_id);
240
241 ret = DBExecute(hStmt);
242 DBFreeStatement(hStmt);
243
244 if (ret)
245 ret = saveACLToDB(hdb);
246 }
247
248 m_isModified = false;
249 unlockProperties();
250 return ret;
251 }
252
253 /**
254 * Delete object from database
255 */
256 bool SlmCheck::deleteFromDatabase(DB_HANDLE hdb)
257 {
258 bool success = NetObj::deleteFromDatabase(hdb);
259 if (success)
260 success = executeQueryOnObject(hdb, _T("DELETE FROM slm_checks WHERE id=?"));
261 return success;
262 }
263
264 /**
265 * Create NXCP message with object's data
266 */
267 void SlmCheck::fillMessageInternal(NXCPMessage *pMsg)
268 {
269 NetObj::fillMessageInternal(pMsg);
270 pMsg->setField(VID_SLMCHECK_TYPE, UINT32(m_type));
271 pMsg->setField(VID_SCRIPT, CHECK_NULL_EX(m_script));
272 pMsg->setField(VID_REASON, m_reason);
273 pMsg->setField(VID_TEMPLATE_ID, m_templateId);
274 pMsg->setField(VID_IS_TEMPLATE, (WORD)(m_isTemplate ? 1 : 0));
275 if (m_threshold != NULL)
276 m_threshold->createMessage(pMsg, VID_THRESHOLD_BASE);
277 }
278
279 /**
280 * Modify object from message
281 */
282 UINT32 SlmCheck::modifyFromMessageInternal(NXCPMessage *pRequest)
283 {
284 if (pRequest->isFieldExist(VID_SLMCHECK_TYPE))
285 m_type = CheckType(pRequest->getFieldAsUInt32(VID_SLMCHECK_TYPE));
286
287 if (pRequest->isFieldExist(VID_SCRIPT))
288 {
289 TCHAR *script = pRequest->getFieldAsString(VID_SCRIPT);
290 setScript(script);
291 safe_free(script);
292 }
293
294 if (pRequest->isFieldExist(VID_THRESHOLD_BASE))
295 {
296 if (m_threshold == NULL)
297 m_threshold = new Threshold;
298 m_threshold->updateFromMessage(pRequest, VID_THRESHOLD_BASE);
299 }
300
301 return NetObj::modifyFromMessageInternal(pRequest);
302 }
303
304 /**
305 * Callback for post-modify hook
306 */
307 static void UpdateFromTemplateCallback(NetObj *object, void *data)
308 {
309 SlmCheck *check = (SlmCheck *)object;
310 SlmCheck *tmpl = (SlmCheck *)data;
311
312 if (check->getTemplateId() == tmpl->getId())
313 check->updateFromTemplate(tmpl);
314 }
315
316 /**
317 * Post-modify hook
318 */
319 void SlmCheck::postModify()
320 {
321 if (m_isTemplate)
322 g_idxServiceCheckById.forEach(UpdateFromTemplateCallback, this);
323 }
324
325 /**
326 * Set check script
327 */
328 void SlmCheck::setScript(const TCHAR *script)
329 {
330 if (script != NULL)
331 {
332 safe_free(m_script);
333 delete m_pCompiledScript;
334 m_script = _tcsdup(script);
335 if (m_script != NULL)
336 {
337 TCHAR error[256];
338
339 m_pCompiledScript = NXSLCompileAndCreateVM(m_script, error, 256, new NXSL_ServerEnv);
340 if (m_pCompiledScript == NULL)
341 nxlog_write(MSG_SLMCHECK_SCRIPT_COMPILATION_ERROR, NXLOG_WARNING, "dss", m_id, m_name, error);
342 }
343 else
344 {
345 m_pCompiledScript = NULL;
346 }
347 }
348 else
349 {
350 delete_and_null(m_pCompiledScript);
351 safe_free_and_null(m_script);
352 }
353 setModified();
354 }
355
356 /**
357 * Execute check
358 */
359 void SlmCheck::execute()
360 {
361 if (m_isTemplate)
362 return;
363
364 UINT32 oldStatus = m_status;
365
366 switch (m_type)
367 {
368 case check_script:
369 if (m_pCompiledScript != NULL)
370 {
371 NXSL_VariableSystem *pGlobals = NULL;
372
373 m_pCompiledScript->setGlobalVariable(_T("$reason"), new NXSL_Value(m_reason));
374 m_pCompiledScript->setGlobalVariable(_T("$node"), getNodeObjectForNXSL());
375 if (m_pCompiledScript->run(0, NULL, NULL, &pGlobals, &m_nxslConstants))
376 {
377 NXSL_Value *pValue = m_pCompiledScript->getResult();
378 m_status = (pValue->getValueAsInt32() == 0) ? STATUS_NORMAL : STATUS_CRITICAL;
379 if (m_status == STATUS_CRITICAL)
380 {
381 NXSL_Variable *reason = pGlobals->find(_T("$reason"));
382 setReason((reason != NULL) ? reason->getValue()->getValueAsCString() : _T("Check script returns error"));
383 }
384 DbgPrintf(6, _T("SlmCheck::execute: %s [%ld] return value %d"), m_name, (long)m_id, pValue->getValueAsInt32());
385 }
386 else
387 {
388 TCHAR buffer[1024];
389
390 _sntprintf(buffer, 1024, _T("ServiceCheck::%s::%d"), m_name, m_id);
391 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, m_pCompiledScript->getErrorText(), m_id);
392 nxlog_write(MSG_SLMCHECK_SCRIPT_EXECUTION_ERROR, NXLOG_WARNING, "dss", m_id, m_name, m_pCompiledScript->getErrorText());
393 m_status = STATUS_UNKNOWN;
394 }
395 delete pGlobals;
396 }
397 else
398 {
399 m_status = STATUS_UNKNOWN;
400 }
401 break;
402 case check_threshold:
403 default:
404 DbgPrintf(4, _T("SlmCheck::execute() called for undefined check type, check %s/%ld"), m_name, (long)m_id);
405 m_status = STATUS_UNKNOWN;
406 break;
407 }
408
409 lockProperties();
410 if (m_status != oldStatus)
411 {
412 if (m_status == STATUS_CRITICAL)
413 insertTicket();
414 else
415 closeTicket();
416 setModified();
417 }
418 unlockProperties();
419 }
420
421 /**
422 * Insert ticket for this check into slm_tickets
423 */
424 bool SlmCheck::insertTicket()
425 {
426 DbgPrintf(4, _T("SlmCheck::insertTicket() called for %s [%d], reason='%s'"), m_name, (int)m_id, m_reason);
427
428 if (m_status == STATUS_NORMAL)
429 return false;
430
431 m_currentTicketId = CreateUniqueId(IDG_SLM_TICKET);
432
433 bool success = false;
434 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
435 DB_STATEMENT hStmt = DBPrepare(hdb, _T("INSERT INTO slm_tickets (ticket_id,check_id,service_id,create_timestamp,close_timestamp,reason) VALUES (?,?,?,?,0,?)"));
436 if (hStmt != NULL)
437 {
438 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_currentTicketId);
439 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_id);
440 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, getOwnerId());
441 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, (UINT32)time(NULL));
442 DBBind(hStmt, 5, DB_SQLTYPE_VARCHAR, m_reason, DB_BIND_TRANSIENT);
443 success = DBExecute(hStmt) ? true : false;
444 DBFreeStatement(hStmt);
445 }
446 DBConnectionPoolReleaseConnection(hdb);
447 return success;
448 }
449
450 /**
451 * Close current ticket
452 */
453 void SlmCheck::closeTicket()
454 {
455 DbgPrintf(4, _T("SlmCheck::closeTicket() called for %s [%d], ticketId=%d"), m_name, (int)m_id, (int)m_currentTicketId);
456 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
457 DB_STATEMENT hStmt = DBPrepare(hdb, _T("UPDATE slm_tickets SET close_timestamp=? WHERE ticket_id=?"));
458 if (hStmt != NULL)
459 {
460 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, (UINT32)time(NULL));
461 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_currentTicketId);
462 DBExecute(hStmt);
463 DBFreeStatement(hStmt);
464 }
465 DBConnectionPoolReleaseConnection(hdb);
466 m_currentTicketId = 0;
467 }
468
469 /**
470 * Get ID of owning SLM object (business service or node link)
471 */
472 UINT32 SlmCheck::getOwnerId()
473 {
474 UINT32 ownerId = 0;
475
476 lockParentList(false);
477 for(int i = 0; i < m_parentList->size(); i++)
478 {
479 NetObj *object = m_parentList->get(i);
480 if ((object->getObjectClass() == OBJECT_BUSINESSSERVICE) ||
481 (object->getObjectClass() == OBJECT_NODELINK))
482 {
483 ownerId = object->getId();
484 break;
485 }
486 }
487 unlockParentList();
488 return ownerId;
489 }
490
491 /**
492 * Get related node object for use in NXSL script
493 * Will return NXSL_Value of type NULL if there are no associated node
494 */
495 NXSL_Value *SlmCheck::getNodeObjectForNXSL()
496 {
497 NXSL_Value *value = NULL;
498 UINT32 nodeId = 0;
499
500 lockParentList(false);
501 for(int i = 0; i < m_parentList->size(); i++)
502 {
503 NetObj *object = m_parentList->get(i);
504 if (object->getObjectClass() == OBJECT_NODELINK)
505 {
506 nodeId = ((NodeLink *)object)->getNodeId();
507 break;
508 }
509 }
510 unlockParentList();
511
512 if (nodeId != 0)
513 {
514 NetObj *node = FindObjectById(nodeId);
515 if ((node != NULL) && (node->getObjectClass() == OBJECT_NODE))
516 {
517 value = new NXSL_Value(new NXSL_Object(&g_nxslNodeClass, node));
518 }
519 }
520
521 return (value != NULL) ? value : new NXSL_Value;
522 }
523
524 /**
525 * Object deletion handler
526 */
527 void SlmCheck::onObjectDelete(UINT32 objectId)
528 {
529 // Delete itself if object curemtly being deleted is
530 // a template used to create this check
531 if (objectId == m_templateId)
532 {
533 DbgPrintf(4, _T("SlmCheck %s [%d] delete itself because of template deletion"), m_name, (int)m_id);
534 deleteObject();
535 }
536 NetObj::onObjectDelete(objectId);
537 }