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