e8d5cf9da8a1651fcb0bc30aab27659072a9adda
[public/netxms.git] / src / server / core / dcst.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 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: dcst.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Modify DCI summary table. Will create new table if id is 0.
27 *
28 * @return RCC ready to be sent to client
29 */
30 UINT32 ModifySummaryTable(NXCPMessage *msg, LONG *newId)
31 {
32 LONG id = msg->getFieldAsUInt32(VID_SUMMARY_TABLE_ID);
33 if (id == 0)
34 {
35 id = CreateUniqueId(IDG_DCI_SUMMARY_TABLE);
36 }
37 *newId = id;
38
39 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
40
41 bool isNew = !IsDatabaseRecordExist(hdb, _T("dci_summary_tables"), _T("id"), (UINT32)id);
42 DB_STATEMENT hStmt;
43 if (isNew)
44 {
45 hStmt = DBPrepare(hdb, _T("INSERT INTO dci_summary_tables (menu_path,title,node_filter,flags,columns,table_dci_name,id,guid) VALUES (?,?,?,?,?,?,?,?)"));
46 }
47 else
48 {
49 hStmt = DBPrepare(hdb, _T("UPDATE dci_summary_tables SET menu_path=?,title=?,node_filter=?,flags=?,columns=?,table_dci_name=? WHERE id=?"));
50 }
51
52 UINT32 rcc;
53 if (hStmt != NULL)
54 {
55 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, msg->getFieldAsString(VID_MENU_PATH), DB_BIND_DYNAMIC);
56 DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, msg->getFieldAsString(VID_TITLE), DB_BIND_DYNAMIC);
57 DBBind(hStmt, 3, DB_SQLTYPE_TEXT, msg->getFieldAsString(VID_FILTER), DB_BIND_DYNAMIC);
58 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, msg->getFieldAsUInt32(VID_FLAGS));
59 DBBind(hStmt, 5, DB_SQLTYPE_TEXT, msg->getFieldAsString(VID_COLUMNS), DB_BIND_DYNAMIC);
60 DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, msg->getFieldAsString(VID_DCI_NAME), DB_BIND_DYNAMIC);
61 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, id);
62
63 if (isNew)
64 {
65 DBBind(hStmt, 8, DB_SQLTYPE_VARCHAR, uuid::generate());
66 }
67
68 rcc = DBExecute(hStmt) ? RCC_SUCCESS : RCC_DB_FAILURE;
69 if (rcc == RCC_SUCCESS)
70 NotifyClientSessions(NX_NOTIFY_DCISUMTBL_CHANGED, (UINT32)id);
71
72 DBFreeStatement(hStmt);
73 }
74 else
75 {
76 rcc = RCC_DB_FAILURE;
77 }
78
79 DBConnectionPoolReleaseConnection(hdb);
80 return rcc;
81 }
82
83 /**
84 * Delete DCI summary table
85 */
86 UINT32 DeleteSummaryTable(LONG tableId)
87 {
88 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
89 UINT32 rcc = RCC_DB_FAILURE;
90 DB_STATEMENT hStmt = DBPrepare(hdb, _T("DELETE FROM dci_summary_tables WHERE id=?"));
91 if (hStmt != NULL)
92 {
93 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, tableId);
94 if (DBExecute(hStmt))
95 {
96 rcc = RCC_SUCCESS;
97 NotifyClientSessions(NX_NOTIFY_DCISUMTBL_DELETED, (UINT32)tableId);
98 }
99 DBFreeStatement(hStmt);
100 }
101 DBConnectionPoolReleaseConnection(hdb);
102 return rcc;
103 }
104
105 /**
106 * Create column definition from NXCP message
107 */
108 SummaryTableColumn::SummaryTableColumn(NXCPMessage *msg, UINT32 baseId)
109 {
110 msg->getFieldAsString(baseId, m_name, MAX_DB_STRING);
111 msg->getFieldAsString(baseId + 1, m_dciName, MAX_PARAM_NAME);
112 m_flags = msg->getFieldAsUInt32(baseId + 2);
113 if (msg->isFieldExist(baseId + 3))
114 msg->getFieldAsString(baseId + 3, m_separator, 16);
115 else
116 _tcscpy(m_separator, _T(";"));
117 }
118
119 /**
120 * Create export record for column
121 */
122 void SummaryTableColumn::createExportRecord(String &xml, int id)
123 {
124 xml.append(_T("\t\t\t\t<column id=\""));
125 xml.append(id);
126 xml.append(_T("\">\n\t\t\t\t\t<name>"));
127 xml.appendPreallocated(EscapeStringForXML(m_name, -1));
128 xml.append(_T("</name>\n\t\t\t\t\t<dci>"));
129 xml.appendPreallocated(EscapeStringForXML(m_dciName, -1));
130 xml.append(_T("</dci>\n\t\t\t\t\t<flags>"));
131 xml.append(m_flags);
132 xml.append(_T("</flags>\n\t\t\t\t\t<separator>\n"));
133 xml.append(m_separator);
134 xml.append(_T("</separator>\n\t\t\t\t</column>\n"));
135 }
136
137 /**
138 * Create column definition from configuration string
139 */
140 SummaryTableColumn::SummaryTableColumn(TCHAR *configStr)
141 {
142 TCHAR *ptr = _tcsstr(configStr, _T("^#^"));
143 if (ptr != NULL)
144 {
145 *ptr = 0;
146 ptr += 3;
147 TCHAR *opt = _tcsstr(ptr, _T("^#^"));
148 if (opt != NULL)
149 {
150 *opt = 0;
151 opt += 3;
152 TCHAR *sep = _tcsstr(opt, _T("^#^"));
153 if (sep != NULL)
154 {
155 *sep = 0;
156 sep += 3;
157 nx_strncpy(m_separator, sep, 16);
158 }
159 else
160 {
161 _tcscpy(m_separator, _T(";"));
162 }
163 m_flags = _tcstoul(opt, NULL, 10);
164 }
165 else
166 {
167 m_flags = 0;
168 }
169 nx_strncpy(m_dciName, ptr, MAX_PARAM_NAME);
170 }
171 else
172 {
173 nx_strncpy(m_dciName, configStr, MAX_PARAM_NAME);
174 m_flags = 0;
175 }
176 nx_strncpy(m_name, configStr, MAX_DB_STRING);
177 }
178
179 /**
180 * Create ad-hoc summary table definition from NXCP message
181 */
182 SummaryTable::SummaryTable(NXCPMessage *msg)
183 {
184 m_id = 0;
185 m_guid = uuid::generate();
186 m_title[0] = 0;
187 m_menuPath[0] = 0;
188 m_flags = msg->getFieldAsUInt32(VID_FLAGS);
189 m_filterSource = NULL;
190 m_filter = NULL;
191 m_aggregationFunction = (AggregationFunction)msg->getFieldAsInt16(VID_FUNCTION);
192 m_periodStart = msg->getFieldAsTime(VID_TIME_FROM);
193 m_periodEnd = msg->getFieldAsTime(VID_TIME_TO);
194
195 int count = msg->getFieldAsInt32(VID_NUM_COLUMNS);
196 m_columns = new ObjectArray<SummaryTableColumn>(count, 16, true);
197
198 UINT32 id = VID_COLUMN_INFO_BASE;
199 for(int i = 0; i < count; i++)
200 {
201 m_columns->add(new SummaryTableColumn(msg, id));
202 id += 10;
203 }
204 msg->getFieldAsString(VID_DCI_NAME, m_tableDciName, MAX_PARAM_NAME);
205 }
206
207 /**
208 * Create summary table definition from DB data
209 */
210 SummaryTable::SummaryTable(INT32 id, DB_RESULT hResult)
211 {
212 m_id = id;
213
214 DBGetField(hResult, 0, 0, m_title, MAX_DB_STRING);
215 m_flags = DBGetFieldULong(hResult, 0, 1);
216 m_guid = DBGetFieldGUID(hResult, 0, 2);
217 DBGetField(hResult, 0, 3, m_menuPath, MAX_DB_STRING);
218
219 m_aggregationFunction = DCI_AGG_LAST;
220 m_periodStart = 0;
221 m_periodEnd = 0;
222
223 // Filter script
224 m_filterSource = DBGetField(hResult, 0, 4, NULL, 0);
225 if (m_filterSource != NULL)
226 {
227 StrStrip(m_filterSource);
228 if (*m_filterSource != 0)
229 {
230 TCHAR errorText[1024];
231 m_filter = NXSLCompileAndCreateVM(m_filterSource, errorText, 1024, new NXSL_ServerEnv);
232 if (m_filter == NULL)
233 {
234 nxlog_debug(4, _T("Error compiling filter script for DCI summary table: %s"), errorText);
235 }
236 }
237 else
238 {
239 m_filter = NULL;
240 }
241 }
242 else
243 {
244 m_filter = NULL;
245 }
246
247 // Columns
248 m_columns = new ObjectArray<SummaryTableColumn>(16, 16, true);
249 TCHAR *config = DBGetField(hResult, 0, 5, NULL, 0);
250 if ((config != NULL) && (*config != 0))
251 {
252 TCHAR *curr = config;
253 while(curr != NULL)
254 {
255 TCHAR *next = _tcsstr(curr, _T("^~^"));
256 if (next != NULL)
257 {
258 *next = 0;
259 next += 3;
260 }
261 m_columns->add(new SummaryTableColumn(curr));
262 curr = next;
263 }
264 free(config);
265 }
266 DBGetField(hResult, 0, 6, m_tableDciName, MAX_PARAM_NAME);
267 }
268
269 /**
270 * Load summary table object from database
271 */
272 SummaryTable *SummaryTable::loadFromDB(INT32 id, UINT32 *rcc)
273 {
274 nxlog_debug(4, _T("Loading configuration for DCI summary table %d"), id);
275 SummaryTable *table = NULL;
276 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
277 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT title,flags,guid,menu_path,node_filter,columns,table_dci_name FROM dci_summary_tables WHERE id=?"));
278 if (hStmt != NULL)
279 {
280 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, id);
281 DB_RESULT hResult = DBSelectPrepared(hStmt);
282 if (hResult != NULL)
283 {
284 if (DBGetNumRows(hResult) > 0)
285 {
286 table = new SummaryTable(id, hResult);
287 *rcc = RCC_SUCCESS;
288 }
289 else
290 {
291 *rcc = RCC_INVALID_SUMMARY_TABLE_ID;
292 }
293 DBFreeResult(hResult);
294 }
295 else
296 {
297 *rcc = RCC_DB_FAILURE;
298 }
299 DBFreeStatement(hStmt);
300 }
301 else
302 {
303 *rcc = RCC_DB_FAILURE;
304 }
305 DBConnectionPoolReleaseConnection(hdb);
306 nxlog_debug(4, _T("SummaryTable::loadFromDB(%d): object=%p, rcc=%d"), id, table, (int)*rcc);
307 return table;
308 }
309
310 /**
311 * Destructor
312 */
313 SummaryTable::~SummaryTable()
314 {
315 delete m_columns;
316 delete m_filter;
317 free(m_filterSource);
318 }
319
320 /**
321 * Pass node through filter
322 */
323 bool SummaryTable::filter(DataCollectionTarget *object)
324 {
325 if (m_filter == NULL)
326 return true; // no filtering
327
328 bool result = true;
329 m_filter->setGlobalVariable(_T("$object"), object->createNXSLObject());
330 if (object->getObjectClass() == OBJECT_NODE)
331 m_filter->setGlobalVariable(_T("$node"), object->createNXSLObject());
332 if (m_filter->run())
333 {
334 NXSL_Value *value = m_filter->getResult();
335 if (value != NULL)
336 {
337 result = value->getValueAsInt32() ? true : false;
338 }
339 }
340 else
341 {
342 nxlog_debug(4, _T("Error executing filter script for DCI summary table: %s"), m_filter->getErrorText());
343 }
344 return result;
345 }
346
347 /**
348 * Create empty result table
349 */
350 Table *SummaryTable::createEmptyResultTable(const Table *source)
351 {
352 Table *result = new Table();
353 result->setTitle(m_title);
354 result->setExtendedFormat(true);
355 result->addColumn(_T("Node"), DCI_DT_STRING);
356 if (m_flags & SUMMARY_TABLE_MULTI_INSTANCE)
357 result->addColumn(_T("Instance"), DCI_DT_STRING);
358
359 if (m_flags & SUMMARY_TABLE_TABLE_VALUE)
360 {
361 for(int i = 0; i < source->getNumColumns(); i++)
362 {
363 result->addColumn(source->getColumnName(i), source->getColumnDataType(i));
364 }
365 }
366 else
367 {
368 for(int i = 0; i < m_columns->size(); i++)
369 {
370 result->addColumn(m_columns->get(i)->m_name, DCI_DT_STRING);
371 }
372 }
373 return result;
374 }
375
376 /**
377 * Create export record
378 */
379 void SummaryTable::createExportRecord(String &xml)
380 {
381 TCHAR buffer[64];
382
383 xml.append(_T("\t\t<table id=\""));
384 xml.append(m_id);
385 xml.append(_T("\">\n\t\t\t<guid>"));
386 xml.append(m_guid.toString(buffer));
387 xml.append(_T("</guid>\n\t\t\t<title>"));
388 xml.appendPreallocated(EscapeStringForXML(m_title, -1));
389 xml.append(_T("</title>\n\t\t\t<flags>"));
390 xml.append(m_flags);
391 xml.append(_T("</flags>\n\t\t\t<path>"));
392 xml.appendPreallocated(EscapeStringForXML(m_menuPath, -1));
393 xml.append(_T("</path>\n\t\t\t<filter>"));
394 xml.appendPreallocated(EscapeStringForXML(m_filterSource, -1));
395 xml.append(_T("</filter>\n\t\t\t<columns>\n"));
396 for(int i = 0; i < m_columns->size(); i++)
397 {
398 m_columns->get(i)->createExportRecord(xml, i + 1);
399 }
400 xml.append(_T("\t\t\t</columns>\n\t\t</table>\n"));
401 }
402
403 /**
404 * Query summary table
405 */
406 Table *QuerySummaryTable(LONG tableId, SummaryTable *adHocDefinition, UINT32 baseObjectId, UINT32 userId, UINT32 *rcc)
407 {
408 NetObj *object = FindObjectById(baseObjectId);
409 if (object == NULL)
410 {
411 *rcc = RCC_INVALID_OBJECT_ID;
412 return NULL;
413 }
414 if (!object->checkAccessRights(userId, OBJECT_ACCESS_READ))
415 {
416 *rcc = RCC_ACCESS_DENIED;
417 return NULL;
418 }
419 if ((object->getObjectClass() != OBJECT_CONTAINER) && (object->getObjectClass() != OBJECT_CLUSTER) &&
420 (object->getObjectClass() != OBJECT_SERVICEROOT) && (object->getObjectClass() != OBJECT_SUBNET) &&
421 (object->getObjectClass() != OBJECT_ZONE) && (object->getObjectClass() != OBJECT_NETWORK))
422 {
423 *rcc = RCC_INCOMPATIBLE_OPERATION;
424 return NULL;
425 }
426
427 SummaryTable *tableDefinition = (adHocDefinition != NULL) ? adHocDefinition : SummaryTable::loadFromDB(tableId, rcc);
428 if (tableDefinition == NULL)
429 return NULL;
430
431 ObjectArray<NetObj> *childObjects = object->getFullChildList(true, true);
432 Table *tableData = NULL, *lastValue = NULL;
433
434 if (tableDefinition->getFlags() & SUMMARY_TABLE_TABLE_VALUE)
435 {
436 for(int i = 0; i < childObjects->size(); i++)
437 {
438 if (((childObjects->get(i)->getObjectClass() == OBJECT_NODE) || (childObjects->get(i)->getObjectClass() == OBJECT_MOBILEDEVICE) ||
439 (childObjects->get(i)->getObjectClass() == OBJECT_SENSOR)) && childObjects->get(i)->checkAccessRights(userId, OBJECT_ACCESS_READ))
440 {
441 DCObject *o = ((DataCollectionTarget *)childObjects->get(i))->getDCObjectByName(tableDefinition->getTableDciName());
442 if (o != NULL && (o->getType() == DCO_TYPE_TABLE))
443 {
444 lastValue = ((DCTable *)o)->getLastValue();
445 if (lastValue != NULL)
446 {
447 tableData = tableDefinition->createEmptyResultTable(((DCTable *)o)->getLastValue());
448 break;
449 }
450 }
451 }
452 }
453 }
454 else
455 tableData = tableDefinition->createEmptyResultTable();
456
457 for(int i = 0; i < childObjects->size(); i++)
458 {
459 NetObj *obj = childObjects->get(i);
460 if (((obj->getObjectClass() != OBJECT_NODE) && (obj->getObjectClass() != OBJECT_MOBILEDEVICE) &&
461 (obj->getObjectClass() != OBJECT_SENSOR)) || !obj->checkAccessRights(userId, OBJECT_ACCESS_READ))
462 {
463 obj->decRefCount();
464 continue;
465 }
466
467 if (tableDefinition->filter((DataCollectionTarget *)obj))
468 {
469 if (tableDefinition->getFlags() & SUMMARY_TABLE_TABLE_VALUE)
470 ((DataCollectionTarget *)obj)->getDciValuesSummaryTableValue(tableDefinition, tableData);
471 else
472 ((DataCollectionTarget *)obj)->getDciValuesSummarySingleValue(tableDefinition, tableData);
473 }
474 obj->decRefCount();
475 }
476
477 delete childObjects;
478 delete tableDefinition;
479
480 return tableData;
481 }
482
483 /**
484 * Create export record for summary table
485 */
486 bool CreateSummaryTableExportRecord(INT32 id, String &xml)
487 {
488 UINT32 rcc;
489 SummaryTable *t = SummaryTable::loadFromDB(id, &rcc);
490 if (t == NULL)
491 return false;
492 t->createExportRecord(xml);
493 delete t;
494 return true;
495 }
496
497 /**
498 * Build column list
499 */
500 static TCHAR *BuildColumnList(ConfigEntry *root)
501 {
502 if (root == NULL)
503 return _tcsdup(_T(""));
504
505 String s;
506 ObjectArray<ConfigEntry> *columns = root->getOrderedSubEntries(_T("column#*"));
507 for(int i = 0; i < columns->size(); i++)
508 {
509 if (i > 0)
510 s.append(_T("^~^"));
511
512 ConfigEntry *c = columns->get(i);
513 s.append(c->getSubEntryValue(_T("name")));
514 s.append(_T("^#^"));
515 s.append(c->getSubEntryValue(_T("dci")));
516 s.append(_T("^#^"));
517 s.append(c->getSubEntryValueAsUInt(_T("flags")));
518 s.append(_T("^#^"));
519 s.append(c->getSubEntryValue(_T("separator")));
520 }
521 delete columns;
522 return _tcsdup((const TCHAR *)s);
523 }
524
525 /**
526 * Import failure exit
527 */
528 static bool ImportFailure(DB_HANDLE hdb, DB_STATEMENT hStmt)
529 {
530 if (hStmt != NULL)
531 DBFreeStatement(hStmt);
532 DBRollback(hdb);
533 DBConnectionPoolReleaseConnection(hdb);
534 DbgPrintf(4, _T("ImportObjectTool: database failure"));
535 return false;
536 }
537
538 /**
539 * Import summary table
540 */
541 bool ImportSummaryTable(ConfigEntry *config)
542 {
543 const TCHAR *guid = config->getSubEntryValue(_T("guid"));
544 if (guid == NULL)
545 {
546 DbgPrintf(4, _T("ImportSummaryTable: missing GUID"));
547 return false;
548 }
549
550 uuid_t temp;
551 if (_uuid_parse(guid, temp) == -1)
552 {
553 DbgPrintf(4, _T("ImportSummaryTable: GUID (%s) is invalid"), guid);
554 return false;
555 }
556
557 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
558
559 // Step 1: find existing tool ID by GUID
560 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT id FROM dci_summary_tables WHERE guid=?"));
561 if (hStmt == NULL)
562 {
563 return ImportFailure(hdb, NULL);
564 }
565
566 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, guid, DB_BIND_STATIC);
567 DB_RESULT hResult = DBSelectPrepared(hStmt);
568 if (hResult == NULL)
569 {
570 return ImportFailure(hdb, hStmt);
571 }
572
573 UINT32 id;
574 if (DBGetNumRows(hResult) > 0)
575 {
576 id = DBGetFieldULong(hResult, 0, 0);
577 }
578 else
579 {
580 id = 0;
581 }
582 DBFreeResult(hResult);
583 DBFreeStatement(hStmt);
584
585 // Step 2: create or update summary table configuration record
586 if (id == 0)
587 {
588 id = CreateUniqueId(IDG_DCI_SUMMARY_TABLE);
589 hStmt = DBPrepare(hdb, _T("INSERT INTO dci_summary_tables (menu_path,title,node_filter,flags,columns,guid,id) VALUES (?,?,?,?,?,?,?)"));
590 }
591 else
592 {
593 hStmt = DBPrepare(hdb, _T("UPDATE dci_summary_tables SET menu_path=?,title=?,node_filter=?,flags=?,columns=?,guid=? WHERE id=?"));
594 }
595 if (hStmt == NULL)
596 {
597 return ImportFailure(hdb, NULL);
598 }
599
600 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, config->getSubEntryValue(_T("path")), DB_BIND_STATIC);
601 DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, config->getSubEntryValue(_T("title")), DB_BIND_STATIC);
602 DBBind(hStmt, 3, DB_SQLTYPE_TEXT, config->getSubEntryValue(_T("filter")), DB_BIND_STATIC);
603 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, config->getSubEntryValueAsUInt(_T("flags")));
604 DBBind(hStmt, 5, DB_SQLTYPE_TEXT, BuildColumnList(config->findEntry(_T("columns"))), DB_BIND_DYNAMIC);
605 DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, guid, DB_BIND_STATIC);
606 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, id);
607
608 if (!DBExecute(hStmt))
609 {
610 return ImportFailure(hdb, hStmt);
611 }
612
613 NotifyClientSessions(NX_NOTIFY_DCISUMTBL_CHANGED, (UINT32)id);
614
615 DBFreeStatement(hStmt);
616 DBConnectionPoolReleaseConnection(hdb);
617 return true;
618 }