Untitled
unknown
plain_text
2 years ago
18 kB
10
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