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

Offical Documentation

Official Docs: