summary tables for table DCIs refactoring and minor fixes
[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()
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_DCI_SOURCE))
360 {
361 for(int i = 0; i < m_columns->size(); i++)
362 {
363 result->addColumn(m_columns->get(i)->m_name, DCI_DT_STRING);
364 }
365 }
366 return result;
367 }
368
369 /**
370 * Create export record
371 */
372 void SummaryTable::createExportRecord(String &xml) const
373 {
374 TCHAR buffer[64];
375
376 xml.append(_T("\t\t<table id=\""));
377 xml.append(m_id);
378 xml.append(_T("\">\n\t\t\t<guid>"));
379 xml.append(m_guid.toString(buffer));
380 xml.append(_T("</guid>\n\t\t\t<title>"));
381 xml.appendPreallocated(EscapeStringForXML(m_title, -1));
382 xml.append(_T("</title>\n\t\t\t<flags>"));
383 xml.append(m_flags);
384 xml.append(_T("</flags>\n\t\t\t<path>"));
385 xml.appendPreallocated(EscapeStringForXML(m_menuPath, -1));
386 xml.append(_T("</path>\n\t\t\t<filter>"));
387 xml.appendPreallocated(EscapeStringForXML(m_filterSource, -1));
388 xml.append(_T("</filter>\n\t\t\t<tableDci>\n"));
389 xml.appendPreallocated(EscapeStringForXML(m_tableDciName, -1));
390 xml.append(_T("</tableDci>\n\t\t\t<columns>\n"));
391 for(int i = 0; i < m_columns->size(); i++)
392 {
393 m_columns->get(i)->createExportRecord(xml, i + 1);
394 }
395 xml.append(_T("\t\t\t</columns>\n\t\t</table>\n"));
396 }
397
398 /**
399 * Query summary table
400 */
401 Table *QuerySummaryTable(LONG tableId, SummaryTable *adHocDefinition, UINT32 baseObjectId, UINT32 userId, UINT32 *rcc)
402 {
403 NetObj *object = FindObjectById(baseObjectId);
404 if (object == NULL)
405 {
406 *rcc = RCC_INVALID_OBJECT_ID;
407 return NULL;
408 }
409 if (!object->checkAccessRights(userId, OBJECT_ACCESS_READ))
410 {
411 *rcc = RCC_ACCESS_DENIED;
412 return NULL;
413 }
414 if ((object->getObjectClass() != OBJECT_CONTAINER) && (object->getObjectClass() != OBJECT_CLUSTER) &&
415 (object->getObjectClass() != OBJECT_SERVICEROOT) && (object->getObjectClass() != OBJECT_SUBNET) &&
416 (object->getObjectClass() != OBJECT_ZONE) && (object->getObjectClass() != OBJECT_NETWORK))
417 {
418 *rcc = RCC_INCOMPATIBLE_OPERATION;
419 return NULL;
420 }
421
422 SummaryTable *tableDefinition = (adHocDefinition != NULL) ? adHocDefinition : SummaryTable::loadFromDB(tableId, rcc);
423 if (tableDefinition == NULL)
424 return NULL;
425
426 ObjectArray<NetObj> *childObjects = object->getFullChildList(true, true);
427 Table *tableData = tableDefinition->createEmptyResultTable();
428 for(int i = 0; i < childObjects->size(); i++)
429 {
430 NetObj *obj = childObjects->get(i);
431 if (!obj->isDataCollectionTarget() || !obj->checkAccessRights(userId, OBJECT_ACCESS_READ))
432 {
433 obj->decRefCount();
434 continue;
435 }
436
437 if (tableDefinition->filter((DataCollectionTarget *)obj))
438 {
439 ((DataCollectionTarget *)obj)->getDciValuesSummary(tableDefinition, tableData);
440 }
441 obj->decRefCount();
442 }
443
444 delete childObjects;
445 delete tableDefinition;
446
447 return tableData;
448 }
449
450 /**
451 * Create export record for summary table
452 */
453 bool CreateSummaryTableExportRecord(INT32 id, String &xml)
454 {
455 UINT32 rcc;
456 SummaryTable *t = SummaryTable::loadFromDB(id, &rcc);
457 if (t == NULL)
458 return false;
459 t->createExportRecord(xml);
460 delete t;
461 return true;
462 }
463
464 /**
465 * Build column list
466 */
467 static TCHAR *BuildColumnList(ConfigEntry *root)
468 {
469 if (root == NULL)
470 return _tcsdup(_T(""));
471
472 String s;
473 ObjectArray<ConfigEntry> *columns = root->getOrderedSubEntries(_T("column#*"));
474 for(int i = 0; i < columns->size(); i++)
475 {
476 if (i > 0)
477 s.append(_T("^~^"));
478
479 ConfigEntry *c = columns->get(i);
480 s.append(c->getSubEntryValue(_T("name")));
481 s.append(_T("^#^"));
482 s.append(c->getSubEntryValue(_T("dci")));
483 s.append(_T("^#^"));
484 s.append(c->getSubEntryValueAsUInt(_T("flags")));
485 s.append(_T("^#^"));
486 s.append(c->getSubEntryValue(_T("separator")));
487 }
488 delete columns;
489 return _tcsdup((const TCHAR *)s);
490 }
491
492 /**
493 * Import failure exit
494 */
495 static bool ImportFailure(DB_HANDLE hdb, DB_STATEMENT hStmt)
496 {
497 if (hStmt != NULL)
498 DBFreeStatement(hStmt);
499 DBRollback(hdb);
500 DBConnectionPoolReleaseConnection(hdb);
501 DbgPrintf(4, _T("ImportObjectTool: database failure"));
502 return false;
503 }
504
505 /**
506 * Import summary table
507 */
508 bool ImportSummaryTable(ConfigEntry *config)
509 {
510 const TCHAR *guid = config->getSubEntryValue(_T("guid"));
511 if (guid == NULL)
512 {
513 DbgPrintf(4, _T("ImportSummaryTable: missing GUID"));
514 return false;
515 }
516
517 uuid_t temp;
518 if (_uuid_parse(guid, temp) == -1)
519 {
520 DbgPrintf(4, _T("ImportSummaryTable: GUID (%s) is invalid"), guid);
521 return false;
522 }
523
524 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
525
526 // Step 1: find existing tool ID by GUID
527 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT id FROM dci_summary_tables WHERE guid=?"));
528 if (hStmt == NULL)
529 {
530 return ImportFailure(hdb, NULL);
531 }
532
533 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, guid, DB_BIND_STATIC);
534 DB_RESULT hResult = DBSelectPrepared(hStmt);
535 if (hResult == NULL)
536 {
537 return ImportFailure(hdb, hStmt);
538 }
539
540 UINT32 id;
541 if (DBGetNumRows(hResult) > 0)
542 {
543 id = DBGetFieldULong(hResult, 0, 0);
544 }
545 else
546 {
547 id = 0;
548 }
549 DBFreeResult(hResult);
550 DBFreeStatement(hStmt);
551
552 // Step 2: create or update summary table configuration record
553 if (id == 0)
554 {
555 id = CreateUniqueId(IDG_DCI_SUMMARY_TABLE);
556 hStmt = DBPrepare(hdb, _T("INSERT INTO dci_summary_tables (menu_path,title,node_filter,flags,columns,guid,id) VALUES (?,?,?,?,?,?,?)"));
557 }
558 else
559 {
560 hStmt = DBPrepare(hdb, _T("UPDATE dci_summary_tables SET menu_path=?,title=?,node_filter=?,flags=?,columns=?,guid=? WHERE id=?"));
561 }
562 if (hStmt == NULL)
563 {
564 return ImportFailure(hdb, NULL);
565 }
566
567 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, config->getSubEntryValue(_T("path")), DB_BIND_STATIC);
568 DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, config->getSubEntryValue(_T("title")), DB_BIND_STATIC);
569 DBBind(hStmt, 3, DB_SQLTYPE_TEXT, config->getSubEntryValue(_T("filter")), DB_BIND_STATIC);
570 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, config->getSubEntryValueAsUInt(_T("flags")));
571 DBBind(hStmt, 5, DB_SQLTYPE_TEXT, BuildColumnList(config->findEntry(_T("columns"))), DB_BIND_DYNAMIC);
572 DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, guid, DB_BIND_STATIC);
573 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, id);
574
575 if (!DBExecute(hStmt))
576 {
577 return ImportFailure(hdb, hStmt);
578 }
579
580 NotifyClientSessions(NX_NOTIFY_DCISUMTBL_CHANGED, (UINT32)id);
581
582 DBFreeStatement(hStmt);
583 DBConnectionPoolReleaseConnection(hdb);
584 return true;
585 }