Name
sn_agent.MidSelectionHandler
Description
Mid Selection handler
Script
var MidSelectionHandler = Class.create();
MidSelectionHandler.prototype = {
initialize: function() {
this.ENABLE_AUTO_MID_SELECTION = "sn_agent.enable_auto_mid_selection";
this.AGENTNOW_CAPABILITY = "637b14075314730034b8ddeeff7b12cc";
this.MID_SELECTION_NEW_AGENT_LAST_CALC = "mid_selection_new_agent_last_calc";
this.DEBUG_PREFIX = "sn.agent::auto_mid_selection: ";
this.midToCapabilityMap = {};
this.midInfoMap = {};
this.capabilityToMidsMap = {};
},
handleMidSelection: function(getAllAgents) {
if (gs.getProperty(this.ENABLE_AUTO_MID_SELECTION, "true") !== "true")
return;
var agentsPerMid;
if (getAllAgents)
agentsPerMid = this.populateAllAvailableAgents();
else
agentsPerMid = this.populateAllNewAgents();
gs.debug(this.DEBUG_PREFIX + "Calculating mids per agents: " + JSON.stringify(agentsPerMid));
if (Object.keys(agentsPerMid).length > 0)
// if we are generating and sending mid lists only for new agents, instruct those agents to perform connectivity test
this.generateAndSendMidListJson(agentsPerMid, !getAllAgents);
},
/*
* Performs a MID list update for all agents attached to the specified MID server
* and instructs them perform a connectivity test so that they reconnect to the best MID
* If the parameter updateAgentCapabiilities is true, then the attached agents will have their
* required ACC capabilities updated with the current MID server's ACC capabilities
* Returns true if MID selection was successfully triggered for the MID server
*/
runMidSelectionForAgentsOnMid: function(midSysId, updateAgentCapabiilities) {
// populate agents for this MID, but do not update the agent to capability mappings for these agents
var agentsPerMid = this.populateAgentsForMid(midSysId, updateAgentCapabiilities);
if (Object.keys(agentsPerMid).length > 0) {
// generate and send MID lists to agents, and instruct them to reconnect
this.generateAndSendMidListJson(agentsPerMid, true);
gs.debug(this.DEBUG_PREIX + 'Successfully triggered all agents associated with mid : ' + midSysId);
return true;
}
return false;
},
populateAllNewAgents: function() {
var agentsPerMid = {};
var delayedTime = new GlideDateTime();
delayedTime.addSeconds(-900); // 15 minutes before now
var hostDataCompletedStatus = ['0', '2']; // Host data is completed or failed
var gr = new GlideRecord("sn_agent_ci_extended_info");
gr.addQuery("status", "0"); //only status UP
if (GlidePluginManager.isActive('com.sn_itom_cloud_services')) {
gr.addQuery("use_cloud_services", "!=","true"); // MID selection is irrelevant for cloud services.
}
gr.addQuery("is_duplicate", false);
gr.addNullQuery('last_time_auto_mid_select_triggered');
gr.addQuery('host_data', hostDataCompletedStatus).addOrCondition('sys_created_on', '<', delayedTime); // if created time is more than configured -> perform AMS
gr.query();
while (gr.next()) {
// for the new agent, create required capability records based on the currently connected MID server
var midSysId = gr.getValue('mid');
var currentMidCapabilities = this.getMidAccCapabilities(midSysId);
this.ensureCapabilitiesForAgent(gr, currentMidCapabilities);
//add new agents results to the agentsPerMid map
this.addAgent(gr, agentsPerMid);
}
return agentsPerMid;
},
populateAgentsForMid: function(midSysId, updateAgentRequiredCapabilities) {
var agentsPerMid = {};
var gr = new GlideRecord("sn_agent_ci_extended_info");
gr.addQuery("status", "0"); //only UP agents
if (GlidePluginManager.isActive('com.sn_itom_cloud_services')) {
gr.addQuery("use_cloud_services", "!=","true"); // MID selection is irrelevant for cloud services.
}
gr.addQuery("mid", midSysId);
gr.addQuery("is_duplicate", false);
gr.query();
while (gr.next()) {
if (updateAgentRequiredCapabilities) {
var currentMidCapabilities = this.getMidAccCapabilities(midSysId);
this.ensureCapabilitiesForAgent(gr, currentMidCapabilities);
}
this.addAgent(gr, agentsPerMid);
}
return agentsPerMid;
},
populateAllAvailableAgents: function() {
var agentsPerMid = {};
var gr = new GlideRecord("sn_agent_ci_extended_info");
gr.addQuery("status", "0"); //only UP agents
if (GlidePluginManager.isActive('com.sn_itom_cloud_services')) {
gr.addQuery("use_cloud_services", "!=","true"); // MID selection is irrelevant for cloud services.
}
gr.addQuery("is_duplicate", false);
gr.query();
while (gr.next())
this.addAgent(gr, agentsPerMid);
return agentsPerMid;
},
addAgent: function(agentGr, agentsPerMid) {
var midId = agentGr.getValue("mid");
var agentId = agentGr.getValue("agent_id");
var agentDomain = agentGr.getValue("sys_domain");
var noSuitableMids = global.JSUtil.getBooleanValue(agentGr, "no_suitable_mids");
var agentSysId = agentGr.getUniqueValue();
var agents = agentsPerMid[midId];
if (!agents) {
agents = [];
agentsPerMid[midId] = agents;
}
agents.push({
id: agentId,
sysId: agentSysId,
domain: agentDomain,
hadNoSuitableMids: noSuitableMids
});
},
getAndUpdateLastCalcTimestamp: function(timestamp) {
var lastCalc = "";
hashGr = new GlideRecord("sa_hash");
hashGr.addQuery("name", this.MID_SELECTION_NEW_AGENT_LAST_CALC);
hashGr.query();
if (hashGr.next()) {
lastCalc = hashGr.getValue("hash");
hashGr.setValue("hash", timestamp);
hashGr.update();
} else {
hashGr.setValue("name", this.MID_SELECTION_NEW_AGENT_LAST_CALC);
hashGr.setValue("hash", timestamp);
hashGr.insert();
}
return lastCalc;
},
generateAndSendMidListJson: function(agentsPerMid, agentsShouldPerformConnectivityTest) {
for (var mid in agentsPerMid) { //build payload per mid
var resArray = [];
var agents = agentsPerMid[mid]; //get all agents associated to MID
var agentsThatWillPerformConnectivityTest = [];
for (var idx = 0; idx < agents.length; idx++) {
var currAgent = agents[idx];
// run mid selection for this agent
var requiredCapabilities = this.getRequiredCapabilitiesForAgent(currAgent.sysId);
var suitableMids = this.runMidSelection(requiredCapabilities);
var midsInfo = [];
var currentMidIsSuitable = false;
for (var i = 0; i < suitableMids.length; i++) {
var suitableMid = suitableMids[i];
// This limits us to only the domain of agents and MIDs that match
var suitableMidGr = new GlideRecord('ecc_agent');
suitableMidGr.addQuery('sys_domain', currAgent['domain']);
suitableMidGr.addQuery('sys_id', suitableMid);
suitableMidGr.query();
if (!suitableMidGr.next()) {
gs.debug(this.DEBUG_PREFIX + "Could not find the suitable mid with sys_id: " + suitableMid);
continue;
}
// current MID server is in the list of suitable MID servers
if (mid == suitableMid)
currentMidIsSuitable = true;
var midInfo = this.getMidInfo(suitableMidGr);
gs.debug(this.DEBUG_PREFIX + "Mid info: " + JSON.stringify(midInfo));
if (midInfo)
midsInfo.push(midInfo);
}
if (!midsInfo.length) {
// mark agent as no suitable MID
var agentInfoGr = new GlideRecord('sn_agent_ci_extended_info');
agentInfoGr.get(currAgent.sysId);
agentInfoGr.setValue('no_suitable_mids', 'true');
agentInfoGr.update();
gs.error(this.DEBUG_PREFIX + "No suitable mids found for agent " + currAgent.id);
continue;
}
// instruct the agent to perform connectivity test if:
// the call to the function specifies that all agents in the agentsPerMid map should perform connectivity test
// OR if the agent was previously marked as having no suitable MIDs
// OR if the current MID server is not suitable
var shouldPerformConnectivityTest = agentsShouldPerformConnectivityTest || currAgent.hadNoSuitableMids || !currentMidIsSuitable;
if (shouldPerformConnectivityTest) {
agentsThatWillPerformConnectivityTest.push(currAgent.id);
if (currAgent.hadNoSuitableMids) {
// agent now has non empty MID list, unset the no_suitable_mids flag on the agent
gs.debug(this.DEBUG_PREFIX + 'Suitable MIDs have been found for ' + currAgent.id + ', unsetting no_suitable_mids flag on agent record');
var agentInfoGr = new GlideRecord('sn_agent_ci_extended_info');
agentInfoGr.get(currAgent.sysId);
agentInfoGr.setValue('no_suitable_mids', 'false');
agentInfoGr.update();
}
}
resArray.push({
agentId: currAgent.id,
mids: midsInfo,
shouldPerformConnectivityTest: shouldPerformConnectivityTest
});
}
if (Object.keys(resArray).length > 0) {
var json = {
agentToEligibleMids: resArray
};
var currentMidGr = new GlideRecord('ecc_agent');
if (!currentMidGr.get(mid)) {
gs.debug(this.DEBUG_PREFIX + "could not find mid server with sys id: " + mid);
continue;
}
var midName = currentMidGr.getValue('name');
if (!midName) {
gs.debug(this.DEBUG_PREFIX + "could not get name for MID server with sys id: " + mid);
continue;
}
this.sendJsonToMidServer(midName, json);
if (agentsThatWillPerformConnectivityTest.length > 0) {
var gr = new GlideRecord('sn_agent_ci_extended_info');
gr.addQuery('agent_id', agentsThatWillPerformConnectivityTest);
gr.setValue('last_time_auto_mid_select_triggered', new GlideDateTime());
gr.updateMultiple();
gs.info("Auto-MID-Selection was triggered on the following agent IDs: " + JSON.stringify(agentsThatWillPerformConnectivityTest));
}
}
}
},
sendJsonToMidServer: function(midName, json) {
var payload = (new MonitoringSync()).getPayload(JSON.stringify(json)); //json of all agents associated to specific MID
gs.debug(this.DEBUG_PREFIX + "Mid list payload: " + payload);
new MonitoringConfig().sendProbe('auto_mid_selection', 'auto_mid_selection', "mid.server." + midName, payload);
},
getMidInfo: function(midGr) {
var midSysId = midGr.getUniqueValue();
if (this.midInfoMap[midSysId]) {
gs.debug(this.DEBUG_PREFIX + 'getMidInfo: found mid info in map for sys id ' + midSysId);
return this.midInfoMap[midSysId];
}
var midInfo = {};
if (midGr.validated + '' !== 'true') {
gs.debug(this.DEBUG_PREFIX + "Mid not validated: " + midSysId);
return;
}
midInfo["id"] = midGr.getUniqueValue();
midInfo["name"] = midGr.getValue("name");
midInfo["domain"] = midGr.getValue("sys_domain");
// add other data to the additionalInfo
var addInfo = {};
addInfo["host_name"] = midGr.getValue("host_name");
// MID is expecting a string value during JSON unmarshalling, so the value here must be a string
midInfo["additionalInfo"] = JSON.stringify(addInfo);
var webServerGr = new GlideRecord("ecc_agent_ext_context_webserver");
webServerGr.addQuery("mid_server", midSysId);
webServerGr.addQuery("status", "Started");
webServerGr.query();
// mid must have Web Server in 'Started' state to be eligible
if (!webServerGr.next()) {
gs.debug(this.DEBUG_PREFIX + "Mid does not have web server started: " + midSysId);
return;
}
midInfo["port"] = webServerGr.getValue("port_number");
midInfo["secured"] = (webServerGr.getValue("secure_connection") === "1");
var websocketGr = new GlideRecord("sn_agent_ext_context");
websocketGr.addQuery("mid_server", midSysId);
websocketGr.addQuery("status", "Started");
websocketGr.query();
// mid must have Websocket Endpoint in 'Started' state to be eligible
if (!websocketGr.next()) {
gs.debug(this.DEBUG_PREFIX + "Mid does not have websocket endpoint started: " + midSysId);
return;
}
// a) if accessible IP defined for this MID, *ONLY* use it
// b) otherwise try looking up all the MID's IP addresses
// c) last resort use the IP address on mid server record
var accessibleIP = websocketGr.getValue("ip_address");
if (accessibleIP) {
midInfo["ips"] = [accessibleIP];
gs.debug(this.DEBUG_PREFIX + "Using accessible IP address for mid:" + midSysId);
} else {
var midIps = this.getMidIPs(midSysId);
if (midIps.length) {
midInfo["ips"] = midIps;
gs.debug(this.DEBUG_PREFIX + "Using ecc_agent_ip_address for mid: " + midSysId);
} else {
midInfo["ips"] = [midGr.getValue("ip_address")];
gs.debug(this.DEBUG_PREFIX + "Using ecc_agent.ip_address for mid: " + midSysId);
}
}
gs.debug(this.DEBUG_PREFIX + 'getMidInfo: adding mid info to map for sys id ' + midSysId);
this.midInfoMap[midSysId] = midInfo;
return midInfo;
},
getMidIPs: function(midSysId) {
var midIps = [];
var midIpGr = new GlideRecord("ecc_agent_ip_address");
midIpGr.addQuery("mid_server", midSysId);
midIpGr.query();
while (midIpGr.next())
midIps.push(midIpGr.getValue("ip_address") + '');
return midIps;
},
/*
* Runs MID selection based on the passed capabilities
* requiredCapabilities: a list of capability objects that will be used for selecting a MID server
* sample requiredCapabiliites [ { sysId: '1234', name: 'capability 1' }, { sysId: '5678', name: 'capability 2' } ]
*/
runMidSelection: function(requiredCapabilities) {
var capabilityNames = [];
var capabilitySysIds = [];
for (var i = 0; i < requiredCapabilities.length; i++) {
capabilityNames.push(requiredCapabilities[i].name);
capabilitySysIds.push(requiredCapabilities[i].sysId);
}
// create comma separated sorted capabilities map key to check if
// MID selection API has already been called with these capabilities
var sortedCapabilities = capabilityNames.sort();
var capabilitiesKey = sortedCapabilities.join(',');
if (this.capabilityToMidsMap[capabilitiesKey]) {
gs.debug(this.DEBUG_PREFIX + "runMidSelection: found mid sys ids in map for key " + capabilitiesKey);
return this.capabilityToMidsMap[capabilitiesKey];
}
// convert list of capability names to list of objects [ 'Capability 1', 'Capability 2' ] ->
// [ { capability: 'Capability 1' }, { capability: 'Capability 2'} ]
// this is the format that the MID selection API expects for the capabilities
var mappingFunction = function(capability) {
var mappedCapability = {};
mappedCapability.capability = capability;
return mappedCapability;
};
var mappedCapabilities = capabilityNames.map(mappingFunction);
try {
var mids = new sn_automation.AutomationAPI().selectUpMidServers(null, null, mappedCapabilities);
// the MID selection API will return MIDs that match the required capabilities AND any MID server that ALL capability
// these ALL MID servers may not be suitable for use with certain Scoped Apps like ACC-L and ACC-M
// so we will filter the MID servers again, this time returning only MID servers that explicitly have the
// required capabilities
// MID servers that were returned solely because they contained the ALL capability will be filtered out
mids = this.filterMidsByExactCapabilities(mids, capabilitySysIds);
gs.debug(this.DEBUG_PREFIX + "runMidSelection: adding mid sys ids to map for key " + capabilitiesKey);
this.capabilityToMidsMap[capabilitiesKey] = mids;
return mids;
} catch (e) {
//if no matching MID servers found, ignore exception. Accelerator UI
//will show to configure MID server
gs.error(this.DEBUG_PREFIX + e);
return [];
}
},
addAgentNowCapability: function(websocketGr) {
var mids = this.getMidsOfWebsocketExt(websocketGr);
if (mids.length > 0) {
for (var index = 0; index < mids.length; index++) {
var gr = new GlideRecord("ecc_agent_capability_m2m");
gr.addQuery("capability", this.AGENTNOW_CAPABILITY); // if Agent Client Collector capability app is already defined on the MID server - skip it
gr.addQuery("agent", mids[index]);
gr.query();
if (!gr.hasNext()) {
gs.info("Adding Agent Client Collector capability to mid: " + mids[index]);
gr.initialize();
gr.setValue("capability", this.AGENTNOW_CAPABILITY); //Agent Client Collector capability
gr.setValue("agent", mids[index]);
gr.insert();
}
}
}
},
getMidsOfWebsocketExt: function(websocketGr) {
var mids = [];
if (websocketGr.getValue("execute_on") == "Specific MID Server") {
mids.push(websocketGr.getValue("mid_server"));
} else {
var cluster = websocketGr.getValue("mid_server_cluster");
var grCluster = new GlideRecord("ecc_agent_cluster_member_m2m");
grCluster.addQuery("cluster", cluster);
grCluster.query();
while (grCluster.next()) {
mids.push(grCluster.getValue("agent"));
}
}
return mids;
},
getMidAccCapabilities: function(midSysId) {
if (this.midToCapabilityMap[midSysId]) {
gs.debug(this.DEBUG_PREFIX + "found mid sys id " + midSysId + " in capability map");
return this.midToCapabilityMap[midSysId];
}
var accCapabilities = [];
var midCapabilityGr = new GlideRecord('ecc_agent_capability_m2m');
midCapabilityGr.addQuery('agent', midSysId);
midCapabilityGr.query();
while (midCapabilityGr.next()) {
if (midCapabilityGr.capability && midCapabilityGr.capability.is_acc_capability) {
gs.debug(this.DEBUG_PREFIX + 'adding capability ' + midCapabilityGr.capability.capability);
accCapabilities.push(midCapabilityGr.getValue('capability'));
}
}
this.midToCapabilityMap[midSysId] = accCapabilities;
return accCapabilities;
},
getRequiredCapabilitiesForAgent: function(agentInfoSysId) {
var agentToCapabilityGr = new GlideRecord('sn_agent_capability_m2m');
agentToCapabilityGr.addQuery('agent', agentInfoSysId);
agentToCapabilityGr.query();
var capabilities = [];
while (agentToCapabilityGr.next()) {
var sysId = agentToCapabilityGr.required_capability + '';
var name = agentToCapabilityGr.required_capability.capability + '';
capabilities.push({
sysId: sysId,
name: name
});
}
return capabilities;
},
/*
* Maps the specified agent to the passed capabilities in the sn_agent_capability_m2m table
* Will insert any missing capabilities, but not remove any capabilities
*/
ensureCapabilitiesForAgent: function(agentInfoGr, capabilities) {
var agentInfoSysId = agentInfoGr.getUniqueValue();
if (!agentInfoGr.auto_update_capabilities) {
gs.debug(this.DEBUG_PREFIX + "customer has explicitly set capabilities for agent record: " + agentInfoSysId);
return;
}
var existingCapabilities = this.getRequiredCapabilitiesForAgent(agentInfoSysId);
for (var i = 0; i < existingCapabilities.length; i++) {
var existingCapability = existingCapabilities[i];
var index = capabilities.indexOf(existingCapability.sysId);
if (index > -1) {
gs.debug(this.DEBUG_PREFIX + "capability " + existingCapability.name + " already exists, will not add");
capabilities.splice(index, 1);
}
}
gs.debug(this.DEBUG_PREFIX + "adding capabilities: " + capabilities);
for (var i = 0; i < capabilities.length; i++) {
var capability = capabilities[i];
agentToCapabilityGr = new GlideRecord('sn_agent_capability_m2m');
agentToCapabilityGr.setValue('agent', agentInfoSysId);
agentToCapabilityGr.setValue('required_capability', capability);
// set workflow false so that inserting does not trigger the business rule that marks an agent as having its capabilities explicitly set
agentToCapabilityGr.setWorkflow(false);
agentToCapabilityGr.insert();
}
},
/*
* From the list of specified MID servers, returns the MID servers that have the
* specified capabilities
* mids: array of MID server sys ids
* capabilitySysIds: array of ecc_agent_capability sys ids
*/
filterMidsByExactCapabilities: function(mids, capabilitySysIds) {
var midsWithExactCaps = [];
var capabilitiesGa = new GlideAggregate('ecc_agent_capability_m2m');
capabilitiesGa.addQuery('agent', mids);
capabilitiesGa.addQuery('capability', capabilitySysIds);
capabilitiesGa.setGroup(true);
capabilitiesGa.groupBy('agent');
capabilitiesGa.addAggregate('COUNT', 'agent');
capabilitiesGa.query();
while (capabilitiesGa.next()) {
// the MID server should be included in the returned list if
// the number of queried records matches the length of capabilitySysIds,
// meaning that for this MID server, we found all the capabilities we were looking for
if (capabilitiesGa.getAggregate('COUNT', 'agent') == capabilitySysIds.length) {
gs.debug(this.DEBUG_PREFIX + "mid " + capabilitiesGa.agent.name + " has exact capabilities");
midsWithExactCaps.push(capabilitiesGa.getValue('agent'));
}
}
return midsWithExactCaps;
},
type: 'MidSelectionHandler'
};
Sys ID
55a6c2aa5377630034b8ddeeff7b12b6