Name

sn_agent.MainDiscoveryHandler

Description

No description available

Script

var MainDiscoveryHandler = Class.create();
var LOG_ID = 'Agent Client Collector Framework []:';
MainDiscoveryHandler.prototype = {
  // Public properties
  sourceAgentClientCollector: 'AgentClientCollector',
  agentId: '',
  ipAddress: '',
  ip6Address: '',
  ireOutput: '',

  initialize: function() {},

  /**
   * This method is empty for the base object and is design to be overridden by object children.
   * This method is called after the CI have been created and we would like to add more data to it.
   **/
  postIreEnrichDiscovery: function(ciSysId, data) {},

  /**
   * This method is empty for the base object and is design to be overridden by object children
   * This method executed before we send the payload to the IRE
   **/
  preIreAddDataToPayload: function(data, ciJsonPayload, basicInventoryJson) {},

  /**
   * This method is empty for the base object and is design to be overridden by object children
   * This method is called after IRE to build references between Host CI and referenced CIs or non-CIs
   **/
  postIreHandleReferences: function(data, ciJsonPayload) {},

  /**
   * This method is empty for the base object and is design to be overridden by object children
   * This method is for attribute that needs further translation (converting form string to sysID or match with a specific String)
   * This method is called before the IRE
   **/
  advanceAttributesToIREParsable: function(columnName, value, valuesJson) {},

  // JS does not support "protected" attribute - so we use a getter
  getAgentId: function() {
      return this.agentId;
  },

  setLog: function() {},

  /**
   *
   * Entry point for this script include
   * input : JSON Object returned by ACC-F discovery
   *
   **/
  handleDiscovery: function(checkResults) {
      var startOfProcessing = new GlideDateTime();
      var tenMinutesBeforeProcessingTime = new GlideDateTime(startOfProcessing);
      tenMinutesBeforeProcessingTime.addSeconds(-600);
      for (var index = 0; index < checkResults.length; index++) {
          var checkResult = checkResults[index];
          var checkStatus = checkResult["check"]["status"];
          this.agentId = checkResult["agent_id"];
          LOG_ID = LOG_ID.replace(/\[.*\]/, "[" + this.agentId + "]");
          this.setLog(); // Sets logs for child as well
          // if agent CI record does not exist, ignore the discovery data
          var accAPI = new AccAgentsAPI();
          if (!accAPI.agentCIExists(this.agentId)) {
              gs.warn(LOG_ID + " In MainDiscoveryHandler.handleDiscovery() ignoring data since no agent exists with agent_id=" + this.agentId);
              continue;
          }
          if (checkStatus != "0") {
              AgentDiscoverySharedUtils.setAgentsToHostDataCollectionFailedStatus(this.agentId);
              gs.error(LOG_ID + " In MainDiscoveryHandler.handleDiscovery() bad status response for discovery check, ignoring: " + JSON.stringify(checkResult));
              continue;
          }

          var checkOutput = checkResult["check"]["output"];
          var checkResultOutput = this.extractOutput(checkOutput);
          this.addDataIntoCMDB(checkResultOutput, tenMinutesBeforeProcessingTime);
          // clear variables
          checkResultOutput = 0;
          checkOutput = 0;

      }

  },

  // extract output per a single Check Result
  extractOutput: function(checkResult) {
      var beginIndex = checkResult.indexOf('{');
      var endIndex = checkResult.lastIndexOf('}');
      if (beginIndex < 0 || endIndex <= 0) {
          gs.error("Error parsing discovery output");
          return "{}";
      }
      gs.debug(LOG_ID + " In MainDiscoveryHandler.extractOutput() returning extracted output from check result");
      return checkResult.substring(beginIndex, endIndex + 1);
  },

  // Add data to CMDB per single a single check output result
  addDataIntoCMDB: function(result, tenMinutesBeforeProcessingTime) {
      LOG_ID = LOG_ID.replace(/\[.*\]/, "[" + this.agentId + "]");
      if (!result || result == "{}") {
          AgentDiscoverySharedUtils.setAgentsToHostDataCollectionFailedStatus(this.agentId);
          gs.error(LOG_ID + " In MainDiscoveryHandler.addDataIntoCMDB() Payload is empty!");
          return;
      }

      var ciInfo;

      try {
          var data = JSON.parse(result);

          if (this.hasFatalErrorInCheck(data)) {
              AgentDiscoverySharedUtils.setAgentsToHostDataCollectionFailedStatus(this.agentId);
              gs.error(LOG_ID + " In MainDiscoveryHandler.addDataIntoCMDB() fatal error in check, cannot process.");
              return;
          }

          // Check if agent is running in contianer and if property is not defined or set to true
          var disableContainerCollection = gs.getProperty('sn_agent.host_data_collection.disable_when_container', 'true');
          if (data.basic_inventory.containerized_acc && disableContainerCollection == 'true') {
              gs.debug(LOG_ID + " In MainDiscoveryHandler.addDataIntoCMDB() data collection is disabled for containers");
              this.setAgentIsContainerized();
              AgentDiscoverySharedUtils.setAgentsToHostDataCollectionDisabledStatus(this.agentId);
              return;
          }

          ciInfo = this.addComputerAndRelatedListCisIntoCmdb(data);
          AgentDiscoverySharedUtils.setAgentsToHostDataCollectedStatus(this.agentId);
      } catch (e) {
          gs.error(LOG_ID + " In MainDiscoveryHandler.addDataIntoCMDB() IRE failed. e=" + e.message);
          AgentDiscoverySharedUtils.setAgentsToHostDataCollectionFailedStatus(this.agentId);
      }

      if ((typeof ciInfo === 'object') && ciInfo.didnt_clobber == true) {
          gs.debug(LOG_ID + " In MainDiscoveryHandler.addDataIntoCMDB() decision to not clobber existing CI");
          return;
      }

      this.postIreEnrichDiscovery(ciInfo, data);
      return ciInfo;
  },

  /**
   *
   * Add basic inventory data into CMDB
   *
   **/
  addComputerAndRelatedListCisIntoCmdb: function(data) {
      var ciJsonPayload = {
          items: []
      };
      ciJsonPayload.relations = [];
      ciJsonPayload.references = [];

      // If there is no basic Inventory information return
      if (!data.basic_inventory) {
          AgentDiscoverySharedUtils.setAgentsToHostDataCollectionFailedStatus(this.agentId);
          gs.error(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() Payload missing basic inventory information to insert CI");
          return;
      }

      // construct a IRE parsable payload for basic inventory and add it to CI payload
      var basicInventoryJson = this.formatBasicInventoryJsonToIREParsable(data.basic_inventory);
      basicInventoryJson = this.splitAndSetObjectId(basicInventoryJson);
      ciJsonPayload.items.push(basicInventoryJson);

      // handle cmdb_serial_number table
      this.handleCMDBSerialNumberTablePopulation(data, ciJsonPayload);

      // handle CI serial number preference
      // do this after cmdb_serial_number table so that the data is clean
      this.handleCiSerialNumberPreference(ciJsonPayload);

      this.preIreAddDataToPayload(data, ciJsonPayload, basicInventoryJson);

      // save serial numbers for after to handle deletion
      var serialNumbers;
      if (ciJsonPayload.items && ciJsonPayload.items[0] && ciJsonPayload.items[0].lookup)
          serialNumbers = ciJsonPayload.items[0].lookup;

      if (ciJsonPayload.items.length) { //create a relationship only when the current CiJsonpayload is non empty and has basicinfo loaded
          //add Agent and Host relationship
          ciJsonPayload = this.addDefaultRelationshipForAgentAndHostCI(this.agentId, ciJsonPayload);
      } else {
          gs.warn(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() " +
              "No basic inventory info hence aborting creating relationship between Host and agent!");
      }

      //JSON stringify before sending it to IRE
      ciJsonPayload = JSON.stringify(ciJsonPayload);

      if (!ciJsonPayload) {
          gs.error(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() something went wrong while adding CI into CMDB!");
          AgentDiscoverySharedUtils.setAgentsToHostDataCollectionFailedStatus(this.agentId);
          return null;
      }

      var idOutput = sn_cmdb.IdentificationEngine.identifyCI(ciJsonPayload);
      var parsedIdOutput = JSON.parse(idOutput);
      var host = this.getFirstComputerFromIrePayload(parsedIdOutput);
      // Validate that we have both host fields available from the IRE payload
      // Not checking sys ID because that is empty for new CIs
      if (!host || !host.className) {
          gs.error(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() unable to identify the CI and extract Info");
          AgentDiscoverySharedUtils.setAgentsToHostDataCollectionFailedStatus(this.agentId);
          return null;
      }
      var hostCiSysId = host.sysId; // parsedIdOutput.items[0].sysId;
      var hostCiSysClassName = host.className; // parsedIdOutput.items[0].className;

      var compatibilityUtil = new AgentDiscoveryAgentlessCompatibilityUtil();

      // Defer Agent Discovery only if the CI was discovered recently (sn_agent.disco_ci_clobber_of_agentless_disco_threshold_days) by Agentless Discovery (ServiceNow, ServiceWatch)
      if (!compatibilityUtil.shouldProcessAgentDiscoveryPayload(hostCiSysId, hostCiSysClassName)) {
          this.linkHostCiAndAgent(hostCiSysId, data.basic_inventory.isSystemd);
          this.createHostCiAgentCiRelation(hostCiSysId);
          return {
              didnt_clobber: true
          };
      }

      // Should update CMDB
      var output = sn_cmdb.IdentificationEngine.createOrUpdateCI(this.sourceAgentClientCollector, ciJsonPayload);
      gs.info(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() CI insert " +
          "output from Identification Engine:  " + output);
      //We only have all the CIs added. We need to handle references to these CIs
      this.postIreHandleReferences(ciJsonPayload, output);

      var parsedOutput = JSON.parse(output);
      // retrieve agent record that may or may not be linked to agent

      var ciSysId = this.connectAgentAndHost(parsedOutput, data.basic_inventory.isSystemd);

      // Handle serial number cleanup
      this.postIreSerialNumberCleanup(hostCiSysId, serialNumbers);

      // If we do not have Discovery plugin - do not attempt to work on TCP ports and processes
      if (!GlidePluginManager.isActive('com.snc.discovery.ip_based')) {
          gs.warn(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb(): Could not continue to TCP ports and running processes as com.snc.discovery.ip_based plugin is not activated.");
          return ciSysId;
      }

      // Need the CI sysID to add the Application CI into CMDB
      // check if tcpConnections and runningProcess exists before accessing any of its child JSON nodes.
      var agentGr = this.getAgentRecord(this.agentId);
      if ((data.tcp_connections || data.running_processes) && ciSysId) {
          if (data.tcp_connections.connections || data.running_processes.processes) {
              gs.debug(LOG_ID + " In MainDiscoveryHandler.addDataIntoCMDB() adding data for tcp connections and installed software");
              this.insertTcpConnectionsAndRunningProcessInfoIntoCMDB(data.tcp_connections, data.running_processes, basicInventoryJson, ciSysId, agentGr);
          }
          if (data.tcp_connections.error)
              gs.error(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() tcp_connection error: " + data.tcp_connections.error);
          else if (data.running_processes.error)
              gs.error(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() running processes error: " + data.tcp_connections.error);
      } else {
          gs.debug(LOG_ID + " In MainDiscoveryHandler.addComputerAndRelatedListCisIntoCmdb() missing data for tcp_connection/Running process or ciSysId");
      }

      this.ireOutput = parsedOutput;
      return ciSysId;
  },

  handleCMDBSerialNumberTablePopulation: function(data, ciJsonPayload) {
      if (data.serial_numbers) {
          gs.debug(LOG_ID + ' In MainDiscoveryHandler.handleCMDBSerialNumberTablePopulation() processing additional serial numbers');
          var serialNumbersJson = this.formatSerialNumbersJsonForIre(data.serial_numbers);
          if (serialNumbersJson.length != 0)
              ciJsonPayload.items[0].lookup = serialNumbersJson; // CI basic inventory is the first item in the IRE payload
          else
              gs.debug(LOG_ID + ' In MainDiscoveryHandler.handleCMDBSerialNumberTablePopulation() no additional serial numbers were present');
      } else {
          gs.debug(LOG_ID + ' In MainDiscoveryHandler.handleCMDBSerialNumberTablePopulation() no additional serial numbers to process');
      }
  },

  // DEF0260576: handle the cmdb_ci.serial_number based on a configurable preference
  handleCiSerialNumberPreference: function(ciJsonPayload) {
      // use the ciJsonPayload since the serial numbers should be cleaned up
      if (!ciJsonPayload || !ciJsonPayload.items[0] || !ciJsonPayload.items[0].lookup)
          return;

      var serials = ciJsonPayload.items[0].lookup;

      // build map of known serial types
      var knownSerials = {};
      var serialNumber, serialType, serialValue;
      for (var s = 0; s < serials.length; s++) {
          serialNumber = serials[s].values;
          serialType = serialNumber.serial_number_type;
          serialValue = serialNumber.serial_number;
          knownSerials[serialType] = serialValue;
      }

      var ciSerialPrefProp = gs.getProperty('sn_agent.ci_serial_number.pref_order', 'bios,system,uuid,baseboard,chassis');
      var ciSerialPref = ciSerialPrefProp.split(',');
      var prefType, value;
      // iterate through the configurable preferences and use the first one found from known serials
      for (var i = 0; i < ciSerialPref.length; i++) {
          prefType = (ciSerialPref[i] + '').trim().toLowerCase();
          if (!knownSerials[prefType])
              continue;

          value = knownSerials[prefType];

          gs.debug(LOG_ID + ' In MainDiscoveryHandler.handleCiSerialNumberPreference() setting to preferred serial number ' + prefType);
          ciJsonPayload.items[0].values.serial_number = value;
          return;
      }
  },

  postIreSerialNumberCleanup: function(ciSysId, serialNumbers) {
      // check for null values
      if (!ciSysId || !serialNumbers) {
          gs.debug(LOG_ID + ' In MainDiscoveryHandler.postIreSerialNumberCleanup() CI sys_id or serial numbers data not present, abandoning cleanup.');
          return;
      }

      // Get cmdbGr from sys_id
      var cmdbGr = new GlideRecord("cmdb_ci_hardware");
      cmdbGr.get('sys_id', ciSysId);
      var serialNumberArr = [];
      // build serial array
      for (var s = 0; s < serialNumbers.length; s++) {
          serialNumberArr.push(serialNumbers[s].values);
      }
      // build data object for reconciler
      var serialNumberDataObj = {};
      serialNumberDataObj['cmdb_serial_number'] = {
          data: serialNumberArr,
          refName: 'cmdb_ci',
          keyName: 'serial_number_type,serial_number,valid',
          deleteGr: true
      };

      new global.DiscoveryReconciler(cmdbGr, serialNumberDataObj).process();
  },

  getAgentRecord: function(agentId) {
      var agentGr = new GlideRecord('sn_agent_ci_extended_info');
      agentGr.addQuery('agent_id', agentId);
      agentGr.addQuery('is_duplicate', 'false'); // Not a dup
      agentGr.query();
      return agentGr;
  },

  getHostSysIdOnAgentRecord: function() {
      var agentGr = this.getAgentRecord(this.agentId);
      if (agentGr.next()) {
          return agentGr.getValue('cmdb_ci');
      }

      return "";
  },

  linkHostCiAndAgent: function(hostCiSysId, isSystemd) {
      var agentGr = this.getAgentRecord(this.agentId);
      if (agentGr.next()) {
          agentGr.setValue('cmdb_ci', hostCiSysId);
          if (isSystemd)
              agentGr.setValue('is_using_systemd', isSystemd);
          agentGr.update();
      }
  },

  createHostCiAgentCiRelation: function(hostCiSysId) {
      var agentCiGr = new GlideRecord("sn_agent_cmdb_ci_agent");
      agentCiGr.get("agent_id", this.getAgentId());
      var agentCiSysId = agentCiGr.getValue("sys_id");

      var runsOnRelationTypeSysId = '60bc4e22c0a8010e01f074cbe6bd73c3';
      var relation = new GlideRecord('cmdb_rel_ci');
      relation.addQuery("parent", agentCiSysId);
      relation.addQuery("child", hostCiSysId);
      relation.addQuery("type", runsOnRelationTypeSysId);
      relation.query();

      if (!relation.next()) {
          relation.initialize();
          relation.setValue("parent", agentCiSysId);
          relation.setValue("child", hostCiSysId);
          relation.setValue("type", runsOnRelationTypeSysId);
          relation.insert();
      }
  },

  getFirstComputerFromIrePayload: function(parsedOutput) {
      if (parsedOutput.items && parsedOutput.items.length > 0) {
          // Changed to cmdb_ci_hardware as part of DEF0364430 to allow for better reclassification of CIs
          var hostCiTypes = new GlideTableHierarchy('cmdb_ci_hardware').getAllExtensions();
          for (var i = 0; i < parsedOutput.items.length; i++) {
              if ('Unknown' != parsedOutput.items[i].sysId && hostCiTypes.indexOf(parsedOutput.items[i].className) >= 0) {
                  return parsedOutput.items[i];
              }
          }
      }
  },

  connectAgentAndHost: function(parsedOutput, isSystemd) {
      var host = this.getFirstComputerFromIrePayload(parsedOutput);
      if (host) {
          this.linkHostCiAndAgent(host.sysId, isSystemd);
          return host.sysId;
      }
  },

  /**
   *
   * Sample Input basic inventory Json:
   * 	"basic_inventory": {
   * 		"name": "dhcp-10-11-128-190",
   * 		"manufacturer": "VMware, Inc.",
   * 		"serial_number": "VMware-42 01 35 23 01 2b 50 eb-85 ea 59 d0 81 80 2d de",
   * 		"operating_system_domain": "lab3.service-now.com",
   * 		"operating_system": "CentOS Linux",
   * 		"operating_system_version": "7.3.1611",
   * 		"operating_system_service_pack": "",
   * 		"ram": "1824",
   * 		"cpu_manufacturer": "GenuineIntel",
   * 		"cpu_speed": "1799",
   * 		"cpu_count": "1",
   * 		"cpu_core_count": "1",
   * 		"ip_address": "10.11.128.190",
   * 		"is_virtual": "true",
   * 		"fully_qualified_domain_name": "dhcp-10-11-128-190.lab3.service-now.com",
   * 		"start_date": "1602805664000"
   *     }
   *
   * Sample Json Ouput basic inventory Json for Identification Engine to insert the CI:
   *     {
   *       "className": "cmdb_ci_linux_server",
   *       "values": {
   *         "name": "dhcp-10-11-199-199",
   *         "host_name": "dhcp-10-11-199-199",
   *         "manufacturer": "VMware, Inc.",
   *         "serial_number": "VMware-42 01 35 23 01 2b 50 eb-ki sh or re dd y0 2d ab",
   *         "dns_domain": "lab3.service-now.com",
   *         "os": "CentOSLinux",
   *         "os_version": "7.3.1611",
   *         "operating_system_service_pack": "",
   *         "ram": "1824",
   *         "cpu_manufacturer": "GenuineIntel",
   *         "cpu_speed": "1999",
   *         "cpu_count": "9",
   *         "cpu_core_count": "9",
   *         "ip_address": "10.11.199.199",
   *         "virtual": "true",
   *         "fqdn": "dhcp-10-11-199-199.lab3.service-now.com",
   *         "start_date": "2020-10-15 16:47:33"
   *       }
   *     }
   *
   */
  formatBasicInventoryJsonToIREParsable: function(basicInventoryJson) {
      var accVAtts = ['cpu_manufacturer', 'start_date', 'model_id', 'manufacturer'];

      //Ignoring field platform since this is used to only identify platform in this script_include
      //and cannot be part of the IRE payload
      var ignoreFieldsInBasicInv = ['platform', 'dns_name', 'hostname', 'computername', 'local_hostname'];

      //create a Map to list all the payload cols thats need replace to match CMDB cols.
      var fieldMap = {};
      fieldMap.operating_system_domain = "os_domain";
      fieldMap.operating_system = "os";
      fieldMap.operating_system_version = "os_version";
      fieldMap.fully_qualified_domain_name = "fqdn";
      fieldMap.is_virtual = "virtual";

      // save the IP Addresses for later
      if (basicInventoryJson.ip_address)
          this.ipAddress = basicInventoryJson['ip_address'];

      if (basicInventoryJson.ip_address_v6)
          this.ip6Address = basicInventoryJson['ip_address_v6'];

      // handle preference of IPv4 or IPv6 for ip_address and default_gateway fields.
      // if IPv6 values exist and should prefer IPv6 OR only have IPv6 value
      if ((basicInventoryJson.ip_address_v6 && AgentDiscoverySharedUtils.getPreferredIPVersion() == 6) ||
          (basicInventoryJson.ip_address_v6 && global.JSUtil.nil(basicInventoryJson.ip_address))) {
          fieldMap.ip_address_v6 = "ip_address";
          fieldMap.default_gateway_v6 = "default_gateway";

          // San Diego release added support to validate IPv6. For backwards compatibility, if ScopedIpUtils is not avail, don't use it
          if (typeof global.ScopedIpUtils == 'function') {
              // ensure the values are in canonical form
              basicInventoryJson['ip_address_v6'] = global.ScopedIpUtils.canonical(basicInventoryJson['ip_address_v6']);
              basicInventoryJson['default_gateway_v6'] = global.ScopedIpUtils.canonical(basicInventoryJson['default_gateway_v6']);
          }

          delete basicInventoryJson['ip_address'];
          delete basicInventoryJson['default_gateway'];
      } else {
          // ignore the IPv6 fields and use the original IPv4 fields
          delete basicInventoryJson['ip_address_v6'];
          delete basicInventoryJson['default_gateway_v6'];
      }

      var basicInventoryJsonData = {};
      var valuesJson = {};

      gs.info(LOG_ID + " In MainDiscoveryHandler.formatBasicInventoryJsonToIREParsable() iterate through" +
          " basic inventory keys and change to IRE compatible structure");
      /**
       *
       * Since the basic invertory payload dosent match the format Identification engine expects
       * below we give the payload the right structure.
       *
       **/
      for (key in basicInventoryJson) {

          if (!basicInventoryJson.hasOwnProperty(key) || ignoreFieldsInBasicInv.indexOf(key) >= 0)
              continue;

          var columnName = key;
          var value = basicInventoryJson[key];

          // don't save serial numbers values that are recognized as invalid.
          // required here in case additional serial numbers cannot be collected
          if ((key == 'serial_number') && (!this.isValidSerialNumber(value))) {
              gs.warn(LOG_ID + " In MainDiscoveryHandler.formatBasicInventoryJsonToIREParsable() ignoring invalid serial number value " + value);
              delete basicInventoryJson[key];
              continue;
          }

          //The columns in the payload have no one-to-one matching with the columns in CMDB
          //And these columns names have to absolutely match the actual columns in CMDB for a successful insert through IRE
          if (fieldMap[key])
              columnName = fieldMap[key];

          if (accVAtts.indexOf(columnName) > -1) { // This is for ACC-V to handle
              this.advanceAttributesToIREParsable(columnName, value, valuesJson);
              continue;
          }

          valuesJson[columnName] = value;
      }

      if (valuesJson.hasOwnProperty("os") && valuesJson.hasOwnProperty("os_version")) {
          var osNameAndVersion = new AgentOSHelper(LOG_ID).getOsNameAndVersion(valuesJson["os"], valuesJson["os_version"]);
          valuesJson["os"] = osNameAndVersion[0];
          valuesJson["os_version"] = osNameAndVersion[1];
      }

      // set name, host_name and fqdn just like patterns
      if (basicInventoryJson.hasOwnProperty('platform') && basicInventoryJson.platform.toLowerCase() == 'mac')
          this.processMacNamePreference(valuesJson, basicInventoryJson);
      else
          this.processNamesAttributes(valuesJson, basicInventoryJson.platform, basicInventoryJson.fully_qualified_domain_name, basicInventoryJson.dns_name);

      //Get the sys_class_name or the table name based on the Operating system
      basicInventoryJsonData.className = this.getOsClassName(basicInventoryJson);
      basicInventoryJsonData.values = valuesJson;

      return basicInventoryJsonData;
  },


  isValidSerialNumber: function(value) {
      // Serial Number is validated by referring to the 'dscy_invalid_serial_list' table and If the serial number turned out
      // to be invalid then we do not tamper the existing data in the respective CI's field.
      // For more information, refer to -
      // https://docs.servicenow.com/bundle/paris-it-operations-management/page/product/discovery/reference/r_SerialNumberTypes.html?cshalt=yes
      //
      // Please note, the condition "(value.toLowerCase() == 'default string')" is added explicitly because the "Default string"
      // isn't added in the Invalid Serial Numbers (dscy_invalid_serial_list) table OOTB until "San Diego" via PRB1497563.
      return (new global.SerialNumberManager().isValid(value)) && !(value.toLowerCase() == 'default string');
  },

  /**
   *
   * Input:
   * {
   *	  "serial_numbers": [
   *		{
   *		  "serial_number": "VMware-42 1d 5b 5f 32 e7 ba 9b-f2 76 db 63 f1 56 30 8b",
   *		  "serial_number_type": "system"
   *		},
   *		{
   *		  "serial_number": "421D5B5F-32E7-BA9B-F276-DB63F156308B",
   *		  "serial_number_type": "uuid"
   *		}
   *	  ]
   * }
   *
   *
   * Output:
   * [
   *    {
   *      "className": "cmdb_serial_number",
   *      "values": {
   *        "valid": "true",
   *        "serial_number": "VMware-42 1d 5b 5f 32 e7 ba 9b-f2 76 db 63 f1 56 30 8b",
   *        "serial_number_type": "system"
   *      }
   *    },
   *    {
   *      "className": "cmdb_serial_number",
   *      "values": {
   *        "valid": "true",
   *        "serial_number": "421D5B5F-32E7-BA9B-F276-DB63F156308B",
   *        "serial_number_type": "uuid"
   *      }
   *    }
   *  ]
   *
   **/
  formatSerialNumbersJsonForIre: function(originalSerialNumJson) {
      var serialNumberJsonArray = [];

      var valueJson, serialNumberJson, value;
      for (var index = 0; index < originalSerialNumJson.length; index++) {
          value = originalSerialNumJson[index].serial_number;

          if (gs.nil(value) || !this.isValidSerialNumber(value)) {
              gs.debug(LOG_ID + ' In MainDiscoveryHandler.formatSerialNumbersJsonForIre skipping invalid serial number value ' + value);
              continue;
          }

          valueJson = {
              valid: "true",
              serial_number: value,
              serial_number_type: originalSerialNumJson[index].serial_number_type
          };

          serialNumberJson = {
              className: "cmdb_serial_number",
              values: valueJson
          };

          serialNumberJsonArray.push(serialNumberJson);
      }

      return serialNumberJsonArray;
  },

  /**
  	This method handles a host CI 3 naming attributes: name, host_name and fqdn
  	The main goal is to copy what Patterns are doing.
  	Input:
  		valuesJson: the json representing the data to be used in IRE
  		platofrm: either windows, linux or mac
  		fqdn:
  			Windows: hostname + "." + domain (from Registry or Win32_ComputerSystem)
  			Linux/Mac: from OSquery
  		dnsName: only valid for windows. dnsName = nslookup result for he host's IP Address

  	Output:
  		no output, modifing valuesJson attributes: name, fqdn and host_name if needed
  **/
  processNamesAttributes: function(valuesJson, platform, fqdn, dnsName) {
      if (platform && platform.toLowerCase() == 'windows') {
          var isWmiTrusted = (gs.getProperty('glide.discovery.hostname.wmi_trusted', 'false') == 'true');
          var isDnsTrusted = (gs.getProperty('glide.discovery.hostname.dns_nbt_trusted', 'false') == 'true');

          if (isDnsTrusted && dnsName)
              valuesJson.fqdn = dnsName;

          if (isWmiTrusted || (!valuesJson.fqdn && fqdn)) // we prefer to trust WMI if it is on
              valuesJson.fqdn = fqdn;

      } else if (fqdn) {
          valuesJson.fqdn = fqdn;
      }

      // According to the Patterns, we split the data from WMI - not from dns
      if (fqdn) {
          var arr = fqdn.split('.');
          if (arr)
              valuesJson.host_name = arr[0]; // always get the hostname without the domain
          else
              valuesJson.host_name = fqdn;
      }

      var hostNameFormatter = new global.HostnameJS();
      var formattedName = hostNameFormatter.format(valuesJson.fqdn);
      if (formattedName)
          valuesJson.name = formattedName;
  },

  processMacNamePreference: function(valuesJson, basicInventoryJson) {
      var macNamePrefProp = gs.getProperty('sn_agent.mac_ci_name_pref_order', 'name,hostname,computer_name,local_hostname');
      var macNamePref = macNamePrefProp.split(',');
      var pref, value;
      // iterate through the configurable preferences and use the first one found from known serials
      for (var i = 0; i < macNamePref.length; i++) {
          pref = (macNamePref[i] + '').trim().toLowerCase();
          if (!basicInventoryJson.hasOwnProperty(pref) || basicInventoryJson[pref].trim() == '')
              continue;

          // format the hostname based on Discovery properties
          var hn = new global.HostnameJS();
          value = hn.format(basicInventoryJson[pref]);
          if (value == null) {
              gs.debug(LOG_ID + ' In MainDiscoveryHandler.processMacNamePreference() attempt to format hostname for ' + pref + ' was unsuccessful ');
              continue;
          }

          gs.debug(LOG_ID + ' In MainDiscoveryHandler.processMacNamePreference() setting to preferred CI name to ' + value);
          valuesJson.name = value;
          return;
      }
  },

  /**
   *
   * Returns the CI Type based on Discovery Classifications Per platform (windows, unix).
   * The default is based on the operating_system
   *
   * Note: This will be enhanced in a future story
   *
   **/
  getOsClassName: function(inventory) {
      try {
          var platform = inventory.platform;
          var dc = new sn_agent.AgentDiscoveryClassification();
          var hostMap = {};
          hostMap.version = inventory.operating_system_version;
          hostMap.osArchitecture = inventory.operating_system_architecture;
          hostMap.ip = inventory.ip_address;
          hostMap.osFullName = inventory.operating_system;
          hostMap.short_description = inventory.short_description ? inventory.short_description : inventory.operating_system;

          if (platform == 'windows') {
              dc.setType(platform);
              var osName = inventory.operating_system;

              // Windows computer must starts with "Windows", for Windows 10 we remove Microsoft as prefix
              if (osName && osName.startsWith('Microsoft'))
                  osName = osName.replaceAll('Microsoft', '').trim();

              // The build is the last digit of the version
              // 10.0.123 - the build = 123
              var build = inventory.operating_system_version;
              if (build) {
                  var arr = build.split('.');
                  build = arr[arr.length - 1];
              }

              hostMap.isVIP = 'false'; // Discovery Classification mandatory
              hostMap.name = osName;
              hostMap.buildNumber = build;
          } else if (platform == 'linux') {
              dc.setType('unix');
              hostMap.name = inventory.short_description;
          }

          var rtn = dc.findCiType(hostMap);
          if (rtn)
              return rtn;
      } catch (e) {
          gs.error(LOG_ID + ". Could not classify device using Discovery Classification, reverting to old behavior. inventory=" + JSON.stringify(inventory) + ". Original excption: " + e);
      }

      // If could not use Discovery Classifeir - revernt to old behavior
      var operatingSystem = inventory.operating_system ? inventory.operating_system.toLowerCase() : '';
      var short_description = inventory.short_description ? inventory.short_description.toLowerCase() : '';
      if (this._contains(operatingSystem, 'linux') || this._contains(short_description, 'linux')) {
          return "cmdb_ci_linux_server";
      } else if (this._startsWith(operatingSystem, 'windows')) {
          if (this._contains(operatingSystem, 'server')) {
              return "cmdb_ci_win_server";
          }
          return "cmdb_ci_computer"; // Should be cmdb_ci_windows_pc once we have the CMDB store app update with the new CMDB class model for ACC-V
      }
      return "cmdb_ci_computer"; //Needs to be defaulted to cmdb_ci_pc_hardware
  },


  /**
   *
   * Javascript startswith() implementation
   *
   **/
  _startsWith: function(originalString, checkString) {
      return originalString.substring(0, checkString.length) === checkString;
  },


  /**
   *
   * Util method to check for Substr
   *
   * Checks if the string is contained in the original/given String.
   *
   **/
  _contains: function(osName, actualOsName) {
      if (osName.toLowerCase().includes(actualOsName.toLowerCase()))
          return true;
      return false;
  },

  /**
   * Inputs:
   *      - tcp connections [JSON]
   *      - running processes [JSON]
   *      - basicInvenoryData [JSON]
   *      - current CI SYS_ID
   *      - agent GlideRecord (sn_agent_ci_extended_info)
   *
   *  Adding TCP Connections and Running process infomation into CMDB
   *  Adds relationship between TCP pid with the appropriate running process in CMDB_TCP table
   *  Adds Application Shell CIs for any software which matches the discovery classifiers
   *  Adds a default application shell for the current agent which is already running on the current host
   *
   **/
  insertTcpConnectionsAndRunningProcessInfoIntoCMDB: function(tcpConnections, runningProcesses, basicInventoryJson, ciSysId, agentGr) {
      var applicationShellCIPayload = {
          items: []
      };
      applicationShellCIPayload.relations = [];
      applicationShellCIPayload.items.push(basicInventoryJson);

      // Discovery ADM will replace ip = 0.0.0.0 or * with the CI IP address.  In the case of an IPv6 only host, there is no IPv4 to replace
      // so we need to remove any tcp listening on IPv4 so that a record would not be created for it.
      if (this.ip6Address && !this.ipAddress) {
          var cleanTCP = [];
          var tcp;
          for (var i = 0; i < tcpConnections.connections.length; i++) {
              tcp = tcpConnections.connections[i];
              if (tcp.type == 'on' && ((tcp.ip == '0.0.0.0') || (tcp.ip == '*')))
                  continue;

              cleanTCP.push(tcp);
          }
          tcpConnections.connections = cleanTCP;
          cleanTCP = []; // free up memory
      }

      // Add data into tcp connection and running process tables
      var admHandler = new AgentDiscoveryHandlerADMHelper(tcpConnections.connections, runningProcesses.processes, ciSysId);
      var admResultData = admHandler.addConnectionsAndRelationshipsToCMDB(applicationShellCIPayload.items.length, basicInventoryJson.values.host_name, agentGr);

      if (!admResultData) {
          gs.info(LOG_ID + " In MainDiscoveryHandler.insertTcpConnectionsAndRunningProcessInfoIntoCMDB, skipping Application Shell CI creation");
          return;
      }

      applicationShellCIPayload.items = applicationShellCIPayload.items.concat(admResultData[0]);
      applicationShellCIPayload.relations = applicationShellCIPayload.relations.concat(admResultData[1]);

      //Insert data into CMDB
      applicationShellCIPayload = JSON.stringify(applicationShellCIPayload);
      gs.info(applicationShellCIPayload);

      var applicationCIOutput = sn_cmdb.IdentificationEngine.createOrUpdateCI(this.sourceAgentClientCollector, applicationShellCIPayload);
      gs.info(LOG_ID + " In MainDiscoveryHandler.insertTcpConnectionsAndRunningProcessInfoIntoCMDB() CI insert " +
          "output from Identification Engine:  " + applicationCIOutput);
  },


  /**
   *
   * Construct default relationship for agent running on the host
   *
   **/
  addDefaultRelationshipForAgentAndHostCI: function(agentId, ciJson) {
      if (agentId.startsWith("ASI_"))
          return ciJson;

      var currentCiPositionInpayload = this.getFirstCiPositionFromCiJsonPayload(ciJson);

      if (currentCiPositionInpayload == -1) {
          gs.warn(LOG_ID + " In MainDiscoveryHandler.addAgentDefaultRelationshipForAgentWithHostCI() could not find suitable CI to associate the Agent!");
          return ciJson;
      }

      var agentCi = {};
      var agentValues = {};
      agentCi['className'] = 'sn_agent_cmdb_ci_agent';
      agentCi['values'] = agentValues;
      agentValues['agent_id'] = agentId;
      ciJson.items.push(agentCi);

      var currentAgentCiPositionInPayload = ciJson.items.length - 1; //agent CI position after push

      var agentRelationShip = {};
      agentRelationShip['parent'] = currentAgentCiPositionInPayload;
      agentRelationShip['child'] = currentCiPositionInpayload;
      agentRelationShip['type'] = 'Runs on::Runs';
      ciJson.relations.push(agentRelationShip);

      return ciJson;
  },

  /**
   *
   * Get CI position from CIJson Payload based on className
   * return -1 if not found
   *
   **/
  getFirstCiPositionFromCiJsonPayload: function(ciJson) {
      var hostCiTypes = new GlideTableHierarchy('cmdb_ci_computer').getAllExtensions();

      for (var i = 0; i < ciJson.items.length; i++) {
          if (hostCiTypes.indexOf(ciJson.items[i].className + '') >= 0)
              return i;
      }
      return -1;
  },

  /**
   *
   * Take the object_id as it is returned from agent and set to be the last part
   * of the string after splitting by '$'. Will need whole string for relationships to cloud resource.
   * This function is derived from Patterns post processing script.
   * Azure Ex: azure$12345678-1234-1234-1234-123412341234
   *
   * AWS Ex: amazon$123412341234$us-east2$12345678990
   *
   **/
  splitAndSetObjectId: function(inventory) {
      var rawId = inventory.values.object_id;
      if (!rawId)
          return inventory;

      var objectIdParts = rawId.split("$");
      if (objectIdParts.length == 4)
          inventory.values.object_id = objectIdParts[3];
      else if (objectIdParts.length == 2)
          inventory.values.object_id = objectIdParts[1];
      return inventory;
  },

  /**
   *
   * This function will set that the agent is containerized
   *
   */
  setAgentIsContainerized: function() {
      var agentExInfo = this.getAgentRecord(this.agentId);
      if (agentExInfo.next()) {
          agentExInfo.setValue('is_containerized', 'true');
          agentExInfo.update();
      }
  },

  hasFatalErrorInCheck: function(outputJSON) {
      var errorKeys = ["error_msg", "error_backtrace", "failed_cmd"];
      var result = outputJSON["basic_inventory"] || outputJSON["data_collection"];

      // This shouldn't happen, but if it does, it shouldn't be processed
      if (!result)
          return true;

      for (var key in result) {
          if (errorKeys.indexOf(key) < 0) {
              return false;
          }
      }

      return true;
  },

  type: 'MainDiscoveryHandler'
};

Sys ID

b26c4d92ff4120108ec45897d53bf116

Offical Documentation

Official Docs: