Untitled
unknown
plain_text
a year ago
18 kB
7
Indexable
#!/usr/bin/env node // ******************************** INFO ************************************** // // Version 1.0 2024/01/23 // for Zabbix 6.0.25 // // sync-request required // installation: npm install sync-request // // ******************************* CONFIG ************************************* // Definition of API connection var APIADR='http://localhost/zabbix/api_jsonrpc.php'; var APIUSR='admin'; var APIPWD='zabbix'; // The status file contains a record of created graphs - it's needed to delete graphs that become obsolete var STATUSFILE='/opt/zabbix-discoverygraphs/zabbix-discoverygraphs.status'; // Graph color palette var PALETTE=['69EF7B','C052E4','B6866E','145A6A','B2D097','943112','5B588F', 'EBCECB','359721','FA6CA9','20D8FD','D07D09','4F28AF','FC2C44','738C4E', 'A92C68','79ACFD','FA1BFC','0B5313','AC8BF8','CFDF34','62400C']; // The name of Zabbix macro(s) that contains a JSON array of the discovery graph templates var ZABBIXMACRO='{$DISCOVERY_GRAPH_TEMPLATES}'; // ******************************** CODE ************************************** // Generic API call. The first parameter contains an API method. The second parameter contains a data for the metod. function APICALL (APIMETHOD,APIMETHODDATA) { try { var APIRESPONSESTRING=null; var APIRESPONSECODE=null; var APIREQUEST=require ('sync-request'); var APIRESPONSE=APIREQUEST ('POST',APIADR,{ json:{jsonrpc:"2.0",id:"1",auth:APIAUTH,method:APIMETHOD,params:APIMETHODDATA}, timeout:30000 }); APIRESPONSESTRING=APIRESPONSE.getBody ('utf8').toString (); APIRESPONSECODE=APIRESPONSE.statusCode; if (APIRESPONSECODE!=200) { throw new Error ('Bad response code'); } APIRESPONSE=JSON.parse (APIRESPONSESTRING).result; if (typeof APIRESPONSE=='undefined') { throw new Error ('No "result" item in API response'); } return APIRESPONSE; } catch (E) { LOG (0,'!!! API HTTP request error'); LOG (0,E.name+': '+E.message); LOG (0,'URL: '+APIADR); LOG (0,'Method: '+APIMETHOD); LOG (0,'Method data: '+JSON.stringify (APIMETHODDATA)); LOG (0,'HTTP status: '+APIRESPONSECODE); LOG (0,'HTTP response: '+APIRESPONSESTRING); process.exit (1); } } // Load status file - return status object created from JSON in the status file function LOADSTATUS () { try { var FS=require ('fs'); var FILECONTENT=FS.readFileSync (STATUSFILE,'utf-8'); var STAT=JSON.parse (FILECONTENT); return STAT; } catch (E) { LOG (0.5,'!!! WARNING: Cannot read status file \''+STATUSFILE+'\''); LOG (0.5,E.name+': '+E.message); LOG (0.5,'The obsoleting discovery graphs will not be deleted'); LOG (0.5,'It\'s OK if the file is missing during the first run - the file will be created\n'); return {}; } } // Save status file - write status object as JSON in the status file function SAVESTATUS (STAT) { try { var FS=require ('fs'); FS.writeFileSync (STATUSFILE,JSON.stringify (STAT,null,4),'utf-8'); } catch (E) { LOG (0.5,'!!! WARNING: Cannot write status file \''+STATUSFILE+'\''); LOG (0.5,E.name+': '+E.message); LOG (0.5,'The obsoleting discovery graphs will not be deleted during next run\n'); return {}; } } // Examnine object (host or template) if it has link to template(s) containing discovery graphs // HOSTID = id of host the current examination is for // OBJECTID = id of object to examnine in set of hosts or set of templates (note: if the object is host, then HOSTID=OBJECTID) // OBJECTSET = the object set containing examined object (pass HOSTS or TEMPLATES variable here) // PRECEDENTPATH = informational string of parent hosts/templates hierarchy function EXAMINE (HOSTID,OBJECTID,OBJECTSET,PRECEDENTPATH) { // Get hierarchy path to examined object if (!PRECEDENTPATH) {var PATH=OBJECTSET[OBJECTID].host;} else {var PATH=PRECEDENTPATH+' -> '+OBJECTSET[OBJECTID].host;} // If object has macro containing link to discovery graph template(s) then eximanine that macro for (var MACROINDEX in OBJECTSET[OBJECTID].macros) { if (OBJECTSET[OBJECTID].macros[MACROINDEX].macro==ZABBIXMACRO) { EXAMINEMACRO (HOSTID,OBJECTSET[OBJECTID].macros[MACROINDEX].value,PATH); } } // Examine parent templates for (var PTEMPINDEX in OBJECTSET[OBJECTID].parentTemplates) { EXAMINE (HOSTID,OBJECTSET[OBJECTID].parentTemplates[PTEMPINDEX].templateid,TEMPLATES,PATH); } } // Examine string given in macro which link to discovery graph templates // HOSTID = id of host the current examination is for // MACROSTRING = value from the macro. Should contain JSON array of strings, each the string contains name of discovery graph template // PATH = informational string of hosts/templates hierarchy function EXAMINEMACRO (HOSTID,MACROSTRING,PATH) { var LINKEDTEMPLATES=[]; // Parse JSON string to array try { LINKEDTEMPLATES=JSON.parse (MACROSTRING); if (!Array.isArray(LINKEDTEMPLATES)) { throw new Error ('Not an array'); } if (LINKEDTEMPLATES.length==0) { throw new Error ('Empty array'); } for (var LTEMPINDEX in LINKEDTEMPLATES) { if (typeof LINKEDTEMPLATES[LTEMPINDEX]!='string') { throw new Error ('Not an array of strings'); } } } catch (E) { LOG (0.5,'!!! WARNING: Cannot parse macro value \''+MACROSTRING+'\''); LOG (0.5,E.name+': '+E.message); LOG (0.5,'Macro '+ZABBIXMACRO+' in '+PATH); LOG (0.5,'The macro should contain JSON array of strings, each the string contains name of discovery graph template\n'); return; } // Save linked discovery graph templates for host for (var LTEMPINDEX in LINKEDTEMPLATES) { LINKEDTEMPLATEID=TEMPLATENAMETOID(LINKEDTEMPLATES[LTEMPINDEX]); if (!LINKEDTEMPLATEID) { LOG (0.5,'!!! WARNING: Cannot find template \''+LINKEDTEMPLATES[LTEMPINDEX]+'\''); LOG (0.5,'Macro: '+ZABBIXMACRO+' in '+PATH+'\n'); } else { // If there is no record for currently examined host in HOSTSTOUPDATE then create one if (!HOSTSTOUPDATE[HOSTID]) { HOSTSTOUPDATE[HOSTID]=[]; } // If the found template is not in record for currently examined host in HOSTSTOUPDATE // then save it if (!HOSTSTOUPDATE[HOSTID].find (function (T) {return T.TEMPLATEID==LINKEDTEMPLATEID;})) { HOSTSTOUPDATE[HOSTID].push ({TEMPLATEID:LINKEDTEMPLATEID,PATH:PATH}); LOG (2,PATH+' ===> '+LINKEDTEMPLATES[LTEMPINDEX]); } // If the found template is in record for currently examined host in HOSTSTOUPDATE // then it is a duplicate link else { LOG (2,PATH+' ===> '+LINKEDTEMPLATES[LTEMPINDEX]+' (duplicity, skipping)'); } } } } // Find templateid for template name. Return null if not found. function TEMPLATENAMETOID (TEMPLATENAME) { for (var TEMPLATEID in TEMPLATES) { if (TEMPLATES[TEMPLATEID].host==TEMPLATENAME) { return TEMPLATEID; } } return null; } // Create/update discovery graph for host // HOSTID = the graph will be created for this host // GRAPHID = id of source graph from discovery graph template which will be used as a model for the created/updated graph // TEMPLATEID = id of template which graph with GRAPHID belongs to // PATH = informational string of hosts/templates hierarchy function UPDATEGRAPH (HOSTID,GRAPHID,TEMPLATEID,PATH) { // Prepare data. Clone new graph from model graph in discovery graph template var HOST=HOSTS[HOSTID]; var GRAPHMODEL=GRAPHS[GRAPHID]; var GRAPHNAME=GRAPHMODEL.name; var GRAPHNEW=JSON.parse (JSON.stringify (GRAPHMODEL)); delete GRAPHNEW.graphid; delete GRAPHNEW.templateid; delete GRAPHNEW.flags; delete GRAPHNEW.discover; delete GRAPHNEW.gitems; var TEMPLATE=TEMPLATES[TEMPLATEID]; LOG (2,'* Handling graph \''+GRAPHNAME+'\' from template \''+TEMPLATE.host+'\' for host \''+HOST.host+'\' ...'); // Check for graph name duplicity within the host if (STATUSNEW[HOSTID]) { if (STATUSNEW[HOSTID].find (function (G) {return G.NAME==GRAPHNAME;})) { LOG (0.5,'!!! WARNING: Graph of the same name within the host is defined in another discovery graph template'); LOG (0.5,'Graph \''+GRAPHNAME+'\' from template \''+TEMPLATE.host+'\''); LOG (0.5,'for '+PATH); LOG (0.5,'Run with -d parameter to check debug info. Skipping the graph.\n'); return; } } // Prepare items for new graph // Copy array of gitems from model graph var GITEMSMODEL=JSON.parse (JSON.stringify (GRAPHMODEL.gitems)); var GITEMS=[]; var COLORINDEX=0; var SORTORDERINDEX=0; // Sort the gitems array to order as the graph items are displayed GITEMSMODEL.sort (function (GI1,GI2) {return GI1.sortorder-GI2.sortorder;}); // Iterate through all model graph gitems and find according items in host for (var GITEMMODELINDEX in GITEMSMODEL) { // Find item of discovery graph template corresponding to model graph's gitem var GITEMMODEL=GITEMSMODEL[GITEMMODELINDEX]; var ITEMMODELID=GITEMMODEL.itemid; var ITEMMODEL=TEMPLATE.items.find (function (I) {return I.itemid==ITEMMODELID;}); // If item used in model graph is not found within the model graph's template then skip the graph if (!ITEMMODEL) { LOG (0.5,'!!! WARNING: Cannot find item for discovery graph \''+GRAPHNAME+'\''); LOG (0.5,'The item nr. '+ITEMID+' used in the graph not found in graph\'s template:'); LOG (0.5,PATH+' ===> '+TEMPLATE.host); LOG (0.5,'Skipping the graph\n'); return; } var GITEMMODELNAME=ITEMMODEL.name; // Find items in the host according to model graph's item name var FOUNDITEMIDS=[]; var FOUNDITEMNAMES=[]; for (var ITEMINDEX in HOST.items) { if (HOST.items[ITEMINDEX].name.match (GITEMMODELNAME)) { FOUNDITEMIDS.push (HOST.items[ITEMINDEX].itemid); FOUNDITEMNAMES.push (HOST.items[ITEMINDEX].name); } } LOG (2,'Discovery graph item \''+GITEMMODELNAME+'\' - found these host items: \''+FOUNDITEMNAMES.join ('\', \'')+'\''); // Create gitems for new graph var FIRSTGENITEM=true; for (var FOUNDITEMINDEX in FOUNDITEMIDS) { // Copy gitem from model graph gitem to the new gitem var GITEM=JSON.parse (JSON.stringify (GITEMMODEL)); delete GITEM.itemid; delete GITEM.graphid; // Choose color if (!FIRSTGENITEM) { GITEM.color=GETCOLOR (COLORINDEX); COLORINDEX++; } FIRSTGENITEM=false; // Set display order GITEM.sortorder=SORTORDERINDEX; SORTORDERINDEX++; // Associate the new gitem to corresponding host item GITEM.itemid=FOUNDITEMIDS[FOUNDITEMINDEX]; GITEMS.push (GITEM); } } // Prepare new graph GRAPHNEW.gitems=GITEMS; if (GITEMS.length==0) { LOG (0.5,'!!! WARNING: No host items found for discovery graph'); LOG (0.5,'Graph \''+GRAPHNAME+'\' from template \''+TEMPLATE.host+'\''); LOG (0.5,'for '+PATH); LOG (0.5,'Run with -d parameter to check debug info. Skipping the graph.\n'); return; } // Check if graph already exist within the host var GRAPHCHECK=HOST.graphs.find (function (G) {return G.name==GRAPHNAME;}); // If the graph does not exist within the host yet, it will be created if (!GRAPHCHECK) { var METHOD='graph.create'; LOG (1,'Creating graph \''+GRAPHNAME+'\' for host \''+HOST.host+'\''); } // If the graph already exist, check if it needs to be updated else { // Compare new graph and existing graph var GRAPHEXISTING=GRAPHS[GRAPHCHECK.graphid]; var GDIFFERS=false; for (var GRAPHNEWINDEX in GRAPHNEW) { if (GRAPHNEWINDEX!='gitems') { GDIFFERS=(GDIFFERS || (GRAPHNEW[GRAPHNEWINDEX]!=GRAPHEXISTING[GRAPHNEWINDEX])); } } GDIFFERS=(GDIFFERS || (GRAPHNEW.gitems.length!=GRAPHEXISTING.gitems.length)) for (var GRAPHNEWGITEMINDEX in GRAPHNEW.gitems) { var GRAPHNEWGITEM=GRAPHNEW.gitems[GRAPHNEWGITEMINDEX]; var GRAPHEXISTINGGITEM=GRAPHEXISTING.gitems.find (function (G) {return G.itemid==GRAPHNEWGITEM.itemid;}); if (!GRAPHEXISTINGGITEM) { GDIFFERS=true; } else { for (var GRAPHNEWGITEMRECORD in GRAPHNEWGITEM) { GDIFFERS=(GDIFFERS || (GRAPHNEWGITEM[GRAPHNEWGITEMRECORD]!=GRAPHEXISTINGGITEM[GRAPHNEWGITEMRECORD])); } } } // If the existing and the new graph differs let update it if (GDIFFERS) { var METHOD='graph.update'; GRAPHNEW.graphid=GRAPHEXISTING.graphid; LOG (1,'Updating graph \''+GRAPHNAME+'\' for host \''+HOST.host+'\''); } // If the existing and the new graph is the same let it stay as it is else { var METHOD=null; var NEWGRAPHID=GRAPHCHECK.graphid; LOG (2,'Graph not changed - no update needed'); } } // Call API to create/update the graph for the host if (METHOD) { var NEWGRAPHID=APICALL (METHOD,GRAPHNEW).graphids[0]; } // Record the graph handling into the status variable if (!STATUSNEW[HOST.hostid]) { STATUSNEW[HOST.hostid]=[]; } STATUSNEW[HOST.hostid].push ({GRAPHID:NEWGRAPHID,GRAPHNAME:GRAPHNAME}); } // Delete graph with GRAPHID from host with HOSTID // GRAPHID = id of the graph to be deleted // HOSTID = id of the host the graph was created for // ORIGGRAPHNAME = name of the graph - just to print debug info in case the graph does not exist function DELETEGRAPH (GRAPHID,HOSTID,ORIGGRAPHNAME) { // Check if the host exists var DEBUGHOSTINF=''; var DEBUGGRAPHADJ=''; if (HOSTS[HOSTID]) { DEBUGHOSTINF=' from host \''+HOSTS[HOSTID].host+'\''; } else { DEBUGGRAPHADJ='orphaned '; } // Check if the graph exists. If yes, delete it if (GRAPHS[GRAPHID]) { LOG (1,'Deleting '+DEBUGGRAPHADJ+'graph \''+GRAPHS[GRAPHID].name+'\''+DEBUGHOSTINF); APICALL ('graph.delete',[GRAPHID]); } else { LOG (2,'Cannot delete '+DEBUGGRAPHADJ+'graph nr. '+GRAPHID+' \''+ORIGGRAPHNAME+'\''+DEBUGHOSTINF+' - the graph no longer exist'); } } // Get color from palette function GETCOLOR (COLORINDEX) { var COLOR=PALETTE[COLORINDEX]; if (!COLOR) {COLOR='000000';}; return COLOR; } // Write output depending on command line parameter // -d0 or -s = errors only // -d1 or without parameter = errors, warnings, API wOCrite // -d2 or -d = errors, warnings, API write, processing details // The first parameter is severity number (according to -d parameter). The warning number is 0.5. Messages with severity below 1 go to stderr. // The second parameter is message. function LOG (SEVERITY,MESSAGE) { var REQUESTEDSEVERITY=1; if (process.argv[2]=='-d0') {REQUESTEDSEVERITY=0;} if (process.argv[2]=='-d2' || process.argv[2]=='-d') {REQUESTEDSEVERITY=2;} if (SEVERITY==0) { console.error (MESSAGE); return; } if (SEVERITY==0.5 && REQUESTEDSEVERITY>=1) { console.error (MESSAGE); return; } if (SEVERITY<=REQUESTEDSEVERITY) { console.log (MESSAGE); } } // API authentication LOG (2,'* API authentication ...'); var APIAUTH=null; APIAUTH=APICALL ('user.login',{user:APIUSR,password:APIPWD}); // Get all hosts LOG (2,'* Gathering hosts ...'); HOSTS=APICALL ('host.get',{ output:["host"], selectMacros:["hostmacroid","macro","value"], selectGraphs:["graphid","name"], selectItems:["itemid","name"], selectParentTemplates:["templateid"], preservekeys:true }); // Get global macro containing discovery graph templates LOG (2,'* Gathering global macros ...'); GLOBALMACROS=APICALL ('usermacro.get',{ output:["globalmacroid","value"], search:{macro:[ZABBIXMACRO]}, globalmacro:true }); // Get all templates LOG (2,'* Gathering templates ...'); TEMPLATES=APICALL ('template.get',{ output:["host"], selectMacros:["hostmacroid","macro","value"], selectGraphs:["graphid","name"], selectItems:["itemid","name"], selectParentTemplates:["templateid"], preservekeys:true }); // If global macro is present add it as virtual template and link it to all hosts if (GLOBALMACROS[0]) { TEMPLATES["-1"]={ host:"GLOBAL MACRO", templateid:"-1", parentTemplates:[], graphs:[], macros:[{ hostmacroid:0-GLOBALMACROS[0].globalmacroid, macro:ZABBIXMACRO, value:GLOBALMACROS[0].value }] } for (var HOSTID in HOSTS) { HOSTS[HOSTID].parentTemplates.push({templateid:"-1"}); } } // Get all graphs LOG (2,'* Gathering graphs ...'); GRAPHS=APICALL ('graph.get',{ selectGraphItems:["itemid","color","calc_fnc","drawtype","sortorder","type","yaxisside"], preservekeys:true }); // Load status LOG (2,'* Loading status file ...'); var STATUS=LOADSTATUS (); // Get discovery graphs templates for hosts LOG (2,'* Browsing hosts/templates topology ...'); var HOSTSTOUPDATE={}; for (var HOSTID in HOSTS) { EXAMINE (HOSTID,HOSTID,HOSTS,''); } // Print debug info of result for (var HOSTID in HOSTSTOUPDATE) { var DEBUGINFO=HOSTS[HOSTID].host+' will use template(s) '; var FIRSTTEMPLATE=true; for (var TEMPLATEINDEX in HOSTSTOUPDATE[HOSTID]) { if (!FIRSTTEMPLATE) { DEBUGINFO=DEBUGINFO+', '; } DEBUGINFO=DEBUGINFO+TEMPLATES[HOSTSTOUPDATE[HOSTID][TEMPLATEINDEX].TEMPLATEID].host; FIRSTTEMPLATE=false; } LOG (2,DEBUGINFO); } // Create/update discovery graphs for hosts var STATUSNEW={}; for (var HOSTID in HOSTSTOUPDATE) { for (var TEMPLATEINDEX in HOSTSTOUPDATE[HOSTID]) { var TEMPLATEID=HOSTSTOUPDATE[HOSTID][TEMPLATEINDEX].TEMPLATEID; var PATH=HOSTSTOUPDATE[HOSTID][TEMPLATEINDEX].PATH; if (TEMPLATES[TEMPLATEID].graphs.length==0) { LOG (0.5,'!!! WARNING: Discovery graph template \''+TEMPLATES[TEMPLATEID].host+'\' contains no graphs'); LOG (0.5,'The template is used by '+PATH+'\n'); } for (var GRAPHINDEX in TEMPLATES[TEMPLATEID].graphs) { UPDATEGRAPH (HOSTID,TEMPLATES[TEMPLATEID].graphs[GRAPHINDEX].graphid,TEMPLATEID,PATH); } } } // Delete obsolete discovery graphs LOG (2,'* Checking obsolete discovery graphs ...'); for (var HOSTID in STATUS) { for (var GRAPHINDEX in STATUS[HOSTID]) { var GRAPHID=STATUS[HOSTID][GRAPHINDEX].GRAPHID; var GRAPHNAME=STATUS[HOSTID][GRAPHINDEX].GRAPHNAME; if (!STATUSNEW[HOSTID] || !STATUSNEW[HOSTID].find (function (G) {return G.GRAPHID==GRAPHID;})) { DELETEGRAPH (GRAPHID,HOSTID,GRAPHNAME); } } } // Save status LOG (2,'* Saving status file ...'); SAVESTATUS (STATUSNEW);
Editor is loading...
Leave a Comment