Name

sn_agent.PolicyClientsGeneratorNG

Description

Calculate the correlation between a policy, agents and monitored CIs

Script

var PolicyClientsGeneratorNG = Class.create();
PolicyClientsGeneratorNG.prototype = {

  DOCKER_PARENT_TABLE: 'cmdb_ci_docker_container',
  HOST_PARENT_TABLE: 'cmdb_ci_computer',
  APP_PARENT_TABLE: 'cmdb_ci_appl',
  AGENT_PARENT_TABLE: 'sn_agent_cmdb_ci_agent',
  DEFAULT_MAX_NUMBER_OF_MONITORED_CIS: 400000,
  MAX_DB_VIEW_RECORDS: 1000000,

  AGENT_DB_VIEW_PREFIX: 'agent',
  CI_PARAM_KEY: '{{.labels.params_ci_',
  CI_PARAMS_TO_CHECK_PLACE_HOLDER: 'CI_PARAMS_TO_CHECK_PLACE_HOLDER',

  AGENT_POLICY_DB_VIEW_PREFIX: "sn_agent_agents_connected_to_",

  SERVICE_INFO_PREFIX: 'rel_service_',
  ENTRYPOINT_DB_VIEW: 'sn_itmon_http_entrypoint',

  currentPolicy: {},

  initialize: function(policyStateUtil) {
      this.base64CheckParamsUtil = new Base64CheckParamsUtil();
      this.MAX_NUMBER_OF_MONITORED_CIS_TO_PROCESS = Number(gs.getProperty('sn_agent.max_number_of_monitored_cis_per_policy', this.DEFAULT_MAX_NUMBER_OF_MONITORED_CIS));
      if (!this.MAX_NUMBER_OF_MONITORED_CIS_TO_PROCESS)
          this.MAX_NUMBER_OF_MONITORED_CIS_TO_PROCESS = this.DEFAULT_MAX_NUMBER_OF_MONITORED_CIS;

      // if policyStateUtil is passed in, then use it.
      // passing in policyStateUtil allows for reuse of policy state record cache.
      // otherwise, the PolicyStateUtil will be created with an empty cache.
      this.policyStateUtil = policyStateUtil ? policyStateUtil : new PolicyStateUtil();
      this.policyUpdateDomainSeparation = new PolicyUpdateDomainSeparation();
      this.domainInfo = new DomainInfo();
  },

  loadCiParamsForCurrentPolicy: function(policyGr) {
      var grChecks = new GlideRecord('sn_agent_check');
      grChecks.addQuery('monitoring_policy', policyGr.getUniqueValue());
      grChecks.addActiveQuery();
      grChecks.query();

      this.currentPolicy.ci_params = {};

      while (grChecks.next())
          this.currentPolicy.ci_params = this.getCiParamsFromCheck(grChecks);
  },

  resetServiceParamsForCurrentPolicy: function() {
      // An array of all service specific parameters needs for all checks.
      this.currentPolicy.service_params = [];
      // A map between a specific service to a param key to value:
      // this.currentPolicy.service_params_values[<SERVICE ID>][<PARAM NAME>] = <PARAM VALUE>
      this.currentPolicy.service_params_values = {};
      // A map that maps a CI to a service ID (a 1 to 1 mapping).
      // If we used a map between service ID to CIs, we would need to go over all serivces to find a CI.
      // This might be "wasteful" of memory, but we expect service CIs to be in the thousands (or less), not anything bigger.
      this.ciToServiceId = {};
  },

  loadServiceParamsForCurrentPolicy: function(policyGr) {
      var grChecks = new GlideRecord('sn_agent_check');
      grChecks.addQuery('monitoring_policy', policyGr.getUniqueValue());
      grChecks.addActiveQuery();
      grChecks.query();

      this.resetServiceParamsForCurrentPolicy();

      while (grChecks.next()) {
          var command = grChecks.getValue('command');
          // if no CI params - go to the next check
          if (command.indexOf(this.SERVICE_INFO_PREFIX) < -1)
              continue;

          // Get all service info parameters
          var myRegexp = new RegExp(this.SERVICE_INFO_PREFIX + '(.*?)}}', 'gm');
          var match = myRegexp.exec(command);
          while (match != null) {
              if (match[1]) {
                  this.currentPolicy.service_params.push(match[1]);
              }
              match = myRegexp.exec(command);
          }
      }
  },

  getCiParamsFromCheck: function(checkGr, command) {
      var ciParams = {};
      if (!command)
          command = checkGr.getValue('command');
      // if no CI params - go to the next check
      if (command.indexOf(this.CI_PARAM_KEY) < -1)
          return ciParams;

      var checkDef = checkGr.getValue('check_def');
      if (!checkDef)
          checkDef = checkGr.getValue('sys_id'); // assuming this is a a check definition
      // Get the param key and the default value using regex
      var myRegexp = new RegExp(this.CI_PARAM_KEY + '(.*?)(?:\\|default\\s+(.*?))?}}', 'gm');
      var match = myRegexp.exec(command);
      while (match != null) {
          if (match[1]) {
              var paramKey = match[1];

              // Service related params are gathered on the service, not CI.
              if (paramKey.startsWith(this.SERVICE_INFO_PREFIX)) {
                  match = myRegexp.exec(command);
                  continue;
              }

              var shouldEncodeParam = (this.base64CheckParamsUtil.shouldEncodeCheckParam(checkDef, paramKey));
              ciParams[paramKey] = {};
              ciParams[paramKey].encode = shouldEncodeParam;

              var paramDefaultValue = "";
              if (match[2]) {
                  paramDefaultValue = match[2];
                  if ((paramDefaultValue.startsWith('"') && paramDefaultValue.endsWith('"')) ||
                      (paramDefaultValue.startsWith("'") && paramDefaultValue.endsWith("'")))
                      paramDefaultValue = paramDefaultValue.substring(1, paramDefaultValue.length - 1); // remove any quotes
              }
              ciParams[paramKey].defaultValue = paramDefaultValue; // we keep it on the policy so we can query the CI once and populate all attribute.
          }
          match = myRegexp.exec(command);
      }

      return ciParams;
  },

  loadCiParamsValuesForCurrentPolicyAndCi: function(gr) {
      var ciParams = [];
      if (!this.currentPolicy.ci_params)
          return [];
      for (var ciParamKey in this.currentPolicy.ci_params) {
          var ciParamValue = gr.getValue(ciParamKey);
          if (!ciParamValue)
              ciParamValue = this.currentPolicy.ci_params[ciParamKey].defaultValue; // get default value
          if (ciParamValue && this.currentPolicy.ci_params[ciParamKey].encode) // check if we should encode
              ciParamValue = gs.base64Encode(ciParamValue);
          if (ciParamValue)
              ciParams.push(ciParamKey + "=" + ciParamValue);
      }

      ciParams.sort(); // Makes sure we always have the same value
      return ciParams;
  },

  /*
  	Gets a list of comma seperated sys ids of cron expression (sn_agent_cron_expression) table and a map between the cron expression sys id and its value
  	Returns a list of the expression values replaced by the OR (|) operator.
  	Example:
  		input: 1644f943b7d76010ff78dc55ce11a9cb,39cc3d0fb7d76010ff78dc55ce11a97e,6a60c747b7a62010ff78dc55ce11a9b8
  		output: 0 0 0,1,2,3,4,5,6,7,17,18,19,20,21,22,23 ? * *|0 * 8-16 ? * *|0 * 8-16 ? * MON,TUE,WED,THU,FRI
  */
  replaceCronReferenceWithExpression: function(reference, expMap) {
      if (!reference || !expMap)
          return "";

      var arrExpValues = [];

      var refArray = reference.split(',');
      for (var i = 0; i < refArray.length; i++) {
          var ref = refArray[i].trim();
          if (expMap[ref])
              arrExpValues.push(expMap[ref]);
      }

      return arrExpValues.join('|');
  },

  syncClients: function() {
      // ensure we are in global domain before getting the leaf domains
      var currentDomainId = this.domainInfo.getCurrentDomain();
      if (this.policyUpdateDomainSeparation.isDomainSeparationPluginActive && currentDomainId != 'global')
          this.policyUpdateDomainSeparation.changeSessionDomain('global');

      try {
          var leafDomainIds = this.policyUpdateDomainSeparation.getAgentLeafDomains();
          for (var i = 0; i < leafDomainIds.length; i++) {
              var leafDomainId = leafDomainIds[i];
              var currentDomainId = this.domainInfo.getCurrentDomain();
              if (this.policyUpdateDomainSeparation.isDomainSeparationPluginActive && leafDomainId != currentDomainId)
                  this.policyUpdateDomainSeparation.changeSessionDomain(leafDomainId);

              gs.info("syncClients processing domain: " + leafDomainId);
              var shouldSendUpdateToMIDs = false;

              // First: process full policies that are either requested are never been processed before.
              var processedPolicies = this.processRequestedPolicies();
              shouldSendUpdateToMIDs = shouldSendUpdateToMIDs || (processedPolicies.length > 0);

              if (this.shouldDetectCmdbChanges()) {
                  // When detecing changes do the following:
                  // 1. Go over deleted Monitored CIs and update the table
                  shouldSendUpdateToMIDs = this.updatePolicyMonitoredCiTableWithCmdbDeletions() || shouldSendUpdateToMIDs;
                  /* 2. Detect CMDB Changes: this includes:
                  	2.1: Newly discovered monitored CIs that have an associated agent
                  	2.2: New agents that were installed on hosts that already had the monitored CIs (and they passed the filter)
                  	2.3: An update to the CMDB casued existing (updated) montiored CIs to pass the filter
                  	2.4: An update to the CMDB casued existing (updated) montiored CIs that used to pass the filter to NOT pass the filter
                  	2.5: Agents that were deleted from the system and their monitored CIs
                  */
                  shouldSendUpdateToMIDs = this.detectCmdbChanges(processedPolicies) || shouldSendUpdateToMIDs;
                  this.markFlagsAsDone();
              }

              // Make sure we updated all policies publish status that are still processing to error
              this.setPublishProcessingPoliciesToError();
          }

          if (this.policyUpdateDomainSeparation.isDomainSeparationPluginActive)
              this.policyUpdateDomainSeparation.updatePolicies();
      } finally {
          var currentDomainId = this.domainInfo.getCurrentDomain();
          if (this.policyUpdateDomainSeparation.isDomainSeparationPluginActive && currentDomainId != 'global')
              this.policyUpdateDomainSeparation.changeSessionDomain('global');
      }

      return shouldSendUpdateToMIDs;
  },

  /*
  	This method returns true if we need to detect CMDB changes again for ALL policies
  */
  shouldDetectCmdbChanges: function() {
      var shouldDetect = false;

      var hashGr = new GlideRecord("sn_agent_flags");
      hashGr.addQuery("name", "mon_filters_re_calc").addOrCondition("name", "mon_filters_last_sync_time");
      var currentDomain = new sn_agent.DomainInfo().getCurrentDomain();
      hashGr.addQuery("sys_domain", currentDomain);
      var now = new GlideDateTime();
      hashGr.query();

      var mon_filters_re_calc = '';
      var mon_filters_last_sync_time = '';

      while (hashGr.next()) {
          if (hashGr.getValue('name') == 'mon_filters_re_calc')
              mon_filters_re_calc = hashGr.getValue('hash');
          if (hashGr.getValue('name') == 'mon_filters_last_sync_time')
              mon_filters_last_sync_time = hashGr.getValue('hash');
      }

      if (mon_filters_last_sync_time) {
          var lastRun = new GlideDateTime(mon_filters_last_sync_time);
          shouldDetect = (now.getNumericValue() - lastRun.getNumericValue()) > gs.getProperty("sn_agent.sync_filters_interval_min", 15) * 60000;
      }

      if (mon_filters_re_calc == 'false' && !shouldDetect)
          return false;

      return true;
  },

  /*
  	This method marks the sn_agent flags to indicate we have finished processing all policies.
  	mon_filters_re_calc will have the value of false which means no need to force re-calculate
  	mon_filters_last_sync_time will have the value of the current time
  */
  markFlagsAsDone: function() {
      var hashGr = new GlideRecord("sn_agent_flags");
      hashGr.addQuery("name", "mon_filters_re_calc").addOrCondition("name", "mon_filters_last_sync_time");
      var currentDomain = new sn_agent.DomainInfo().getCurrentDomain();
      hashGr.addQuery("sys_domain", currentDomain);
      hashGr.query();

      var create_mon_filters_re_calc = true;
      var create_mon_filters_last_sync_time = true;

      // If flags exist, then update.
      while (hashGr.next()) {
          if (hashGr.getValue('name') == 'mon_filters_re_calc') {
              create_mon_filters_re_calc = false;
              hashGr.setValue('hash', 'false');
              hashGr.update();
          }
          if (hashGr.getValue('name') == 'mon_filters_last_sync_time') {
              create_mon_filters_last_sync_time = false;
              hashGr.setValue('hash', new GlideDateTime());
              hashGr.update();
          }
      }

      if (!create_mon_filters_re_calc && !create_mon_filters_last_sync_time)
          return;

      // If does not exist - create
      if (create_mon_filters_re_calc) {
          hashGr.initialize();
          hashGr.setValue('name', 'mon_filters_re_calc');
          hashGr.setValue('hash', 'false');
          hashGr.insert();
      }
      if (create_mon_filters_last_sync_time) {
          hashGr.initialize();
          hashGr.setValue('name', 'mon_filters_last_sync_time');
          hashGr.setValue('hash', new GlideDateTime());
          hashGr.insert();
      }
  },

  isProxyPolicy: function(policyGr) {
      return policyGr.single_proxy_agent || policyGr.proxy_advanced || policyGr.proxy_script_advanced || policyGr.proxy_cluster;
  },

  /*
  	This method calculate policy when the CI Type is sn_agent_cmdb_ci_agent.
  	No database view is needed as this is the only CI we need.
  	Input:
  		agentCisGr: a glide record with an already defined filtered (but not queried), which we will add more filters to populate sn_agent_policy_monitored_cis table
  		detectCmdbChanges: boolean, should we only detect CMDB change or do a full policy recalculation. Even if this parameter is true,
  			if there is no last_calc_time for a policy, we must do a full policy recalculation
  */
  policyCalculationForAgentsCisAsMonitoredCis: function(policyGr, agentCisGr, detectCmdbChanges) {
      var fullPolicyUpdate = true;
      var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
      if (detectCmdbChanges && lastCalcTime) {
          agentCisGr.addQuery("sys_updated_on", ">", lastCalcTime).addOrCondition("agent_extended_info.sys_updated_on", ">", lastCalcTime);
          fullPolicyUpdate = false;
      }
      var monitoredCisToValues = {};
      this.loadCiParamsForCurrentPolicy(policyGr);
      var currentCalcTime = new GlideDateTime().toString();
      agentCisGr.setCategory('acc-policy-calc');
      agentCisGr.query();

      while (agentCisGr.next()) {
          var monitoredCiSysId = agentCisGr.getUniqueValue();
          var agentId = agentCisGr.getValue('agent_id');

          var ciParams = this.loadCiParamsValuesForCurrentPolicyAndCi(agentCisGr);
          // Build a chache per monitored CI with the agents and CI Params needed.
          if (!monitoredCisToValues[monitoredCiSysId])
              monitoredCisToValues[monitoredCiSysId] = {};
          if (!monitoredCisToValues[monitoredCiSysId].agents)
              monitoredCisToValues[monitoredCiSysId].agents = {};
          // No need to save the policy ID as we work policy by policy
          monitoredCisToValues[monitoredCiSysId].agents[agentId] = "";
          monitoredCisToValues[monitoredCiSysId].ci_params = ciParams.join(',');

      }

      var rtnObj = this.updateAndInsertIntoPolicyToMonitoredCis(policyGr, monitoredCisToValues, currentCalcTime, fullPolicyUpdate, false);
      // Empty cache
      monitoredCisToValues = undefined;

      rtnObj.fullPolicyUpdate = fullPolicyUpdate;
      rtnObj.updateTime = currentCalcTime;
      rtnObj.monitoredCiType = this.AGENT_PARENT_TABLE;

      return rtnObj;
  },

  policyCalculationForMonitoredCisWithProxyAgents: function(policyGr, monitoredCisGr, detectCmdbChanges) {
      var fullPolicyUpdate = true;
      var tableName = monitoredCisGr.getTableName();
      var isEntryPointView = (tableName == this.ENTRYPOINT_DB_VIEW);
      var dbViewPrefix = '';
      if (isEntryPointView)
          dbViewPrefix = 'endpoint_';

      // Build the query to detect changes since last calculation
      var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
      if (detectCmdbChanges && lastCalcTime) {
          if (this.doesTableContainSysUpdatedOn(monitoredCisGr, dbViewPrefix)) {
              monitoredCisGr.addQuery(dbViewPrefix + 'sys_updated_on', ">", lastCalcTime);
              fullPolicyUpdate = false;
          } else {
              gs.info(gs.getMessage("PolicyClientsGeneratorNG: table {0} does not contain sys_updated_on, will do full calc instead of diff calc", monitoredCisGr.getTableName()));
          }
      }
      var runChecksOnAllAgents = policyGr.run_checks_on_all_proxyagents;
      if (policyGr.proxy_script_advanced) {
          return this.policyCalculationForMonitoredCisWithScriptedProxyAgents(policyGr, monitoredCisGr, fullPolicyUpdate);
      }

      // Either cluster or advance or single proxy
      var proxyAgents = this.getProxyAgentPerPolicy(policyGr, detectCmdbChanges);

      if (!proxyAgents) {
          return {
              update: false
          };
      } else if (proxyAgents.length < 1) {
          gs.warn(gs.getMessage("PolicyClientsGeneratorNG: no live proxy_agents found for policy={0}. Cannot cotninue", policyGr.name));
          return {
              update: false
          };
      }

      var numOfProxyAgents = proxyAgents.length;

      var monitoredCisToValues = {};
      this.loadCiParamsForCurrentPolicy(policyGr);
      var currentCalcTime = new GlideDateTime().toString();
      monitoredCisGr.setCategory('acc-policy-calc');
      monitoredCisGr.query();

      //Using this variable to use % operator to assign monitoredCI to agents in roundrobin fashion
      var monitoredCisIndex = 0;
      while (monitoredCisGr.next()) {
          var monitoredCiSysId = monitoredCisGr.getValue(dbViewPrefix + 'sys_id');
          var proxyAgentId;

          // Build a chache per monitored CI with the agents and CI Params needed.
          if (!monitoredCisToValues[monitoredCiSysId])
              monitoredCisToValues[monitoredCiSysId] = {};
          if (!monitoredCisToValues[monitoredCiSysId].agents)
              monitoredCisToValues[monitoredCiSysId].agents = {};
          // Add all agents to the monitored CI as requested
          if (runChecksOnAllAgents) {
              for (var i = 0; i < numOfProxyAgents; i++) {
                  proxyAgentId = proxyAgents[i];
                  monitoredCisToValues[monitoredCiSysId].agents[proxyAgentId] = true;
              }
          } else {
              // Will assign monitoredCIs on round robin fashion to proxy agents
              // Load balance the monitoredCIs among proxy agents, ratherthan running all monitoredCI checks on all agents
              proxyAgentId = proxyAgents[monitoredCisIndex++ % numOfProxyAgents];
              monitoredCisToValues[monitoredCiSysId].agents[proxyAgentId] = true;
          }

          var ciParams = this.getCiParamsForProxyPolicy(monitoredCisGr, isEntryPointView);
          monitoredCisToValues[monitoredCiSysId].ci_params = ciParams.join(',');
      }

      var rtnObj = this.updateAndInsertIntoPolicyToMonitoredCis(policyGr, monitoredCisToValues, currentCalcTime, fullPolicyUpdate, runChecksOnAllAgents);
      // Empty the cache
      monitoredCisToValues = undefined;

      rtnObj.fullPolicyUpdate = fullPolicyUpdate;
      rtnObj.updateTime = currentCalcTime;
      rtnObj.monitoredCiType = monitoredCisGr.getTableName();

      return rtnObj;
  },

  policyCalculationForMonitoredCisWithScriptedProxyAgents: function(policyGr, monitoredCisGr, fullPolicyUpdate) {
      var runChecksOnAllAgents = policyGr.run_checks_on_all_proxyagents;
      var monitoredCisToValues = {};
      this.loadCiParamsForCurrentPolicy(policyGr);
      var currentCalcTime = new GlideDateTime().toString();
      monitoredCisGr.setCategory('acc-policy-calc');
      monitoredCisGr.query();

      var tableName = monitoredCisGr.getTableName();
      var isEntryPointView = (tableName == this.ENTRYPOINT_DB_VIEW);
      var fieldToGetSysId = 'sys_id';
      if (isEntryPointView)
          fieldToGetSysId = 'endpoint_sys_id';
      //Using this variable to use % operator to assign monitoredCI to agents in roundrobin fashion
      var monitoredCisIndex = 0;
      while (monitoredCisGr.next()) {
          var monitoredCiSysId = monitoredCisGr.getValue(fieldToGetSysId);

          // Perform the script associated with the proxy policy per monitored CI (this is how this feature was designed)
          var evaluator = new GlideScopedEvaluator();
          evaluator.putVariable('answer', null);
          evaluator.putVariable('currentCiResult', monitoredCisGr); //pass current host record as param
          var proxy_agents = evaluator.evaluateScript(policyGr, 'proxy_script');
          if (!proxy_agents)
              proxy_agents = evaluator.getVariable('answer');

          // Validate that proxy_agents var is of type string
          if (typeof proxy_agents != 'string') {
              gs.warn(gs.getMessage("PolicyClientsGeneratorNG: proxy_agents did not evaluate to type string, cannot process proxy for CI {0} on policy {1}. proxy_agents type: {2}",
                  [monitoredCiSysId, policyGr.getValue('name'), typeof proxy_agents]));
              continue;
          }

          var proxyAgents = proxy_agents.split(/\s*,\s*/); // Split, ignore spaces.
          if (!proxyAgents || proxyAgents.length < 1) {
              gs.warn(gs.getMessage("PolicyClientsGeneratorNG: did not get any proxy agents for CI {0} on policy {1}. Cannot contine",
                  [monitoredCiSysId, policyGr.getValue('name')]));
              return {
                  update: false
              };
          }

          var numOfProxyAgents = proxyAgents.length;
          var proxyAgentId;
          // Build a chache per monitored CI with the agents and CI Params needed.
          if (!monitoredCisToValues[monitoredCiSysId])
              monitoredCisToValues[monitoredCiSysId] = {};
          if (!monitoredCisToValues[monitoredCiSysId].agents)
              monitoredCisToValues[monitoredCiSysId].agents = {};
          // Add all agents to the monitored CI as requested
          if (runChecksOnAllAgents) {
              for (var i = 0; i < numOfProxyAgents; i++) {
                  proxyAgentId = proxyAgents[i];
                  monitoredCisToValues[monitoredCiSysId].agents[proxyAgentId] = true;
              }
          } else {
              // Will assign monitoredCIs on round robin fashion to proxy agents
              // Load balance the monitoredCIs among proxy agents, ratherthan running all monitoredCI checks on all agents
              // So just 1 agent is needed
              proxyAgentId = proxyAgents[monitoredCisIndex++ % numOfProxyAgents];
              monitoredCisToValues[monitoredCiSysId].agents[proxyAgentId] = true;
          }

          var ciParams = this.getCiParamsForProxyPolicy(monitoredCisGr, isEntryPointView);
          monitoredCisToValues[monitoredCiSysId].ci_params = ciParams.join(',');
      }

      var rtnObj = this.updateAndInsertIntoPolicyToMonitoredCis(policyGr, monitoredCisToValues, currentCalcTime, fullPolicyUpdate, runChecksOnAllAgents);
      // Empty the cache
      monitoredCisToValues = undefined;

      rtnObj.fullPolicyUpdate = fullPolicyUpdate;
      rtnObj.updateTime = currentCalcTime;
      rtnObj.monitoredCiType = monitoredCisGr.getTableName();

      return rtnObj;
  },

  getProxyAgentPerPolicy: function(policyGr, detectCmdbChanges) {
      var proxyAgents = [];
      if (policyGr.proxy_advanced) {
          var proxyRecord = new GlideRecord('sn_agent_cmdb_ci_agent');
          if (policyGr.proxy_filter) {
              proxyRecord.addEncodedQuery(policyGr.proxy_filter);
          }
          //take only proxy that is up and not silent
          proxyRecord.addEncodedQuery("agent_extended_info.statusIN0,1^agent_extended_info.data_collection=0");
          proxyRecord.orderBy('name');
          proxyRecord.query();
          while (proxyRecord.next()) {
              proxyAgents.push('' + proxyRecord.agent_id);
          }
      } else if (policyGr.proxy_cluster) {
          //get the agents present in that cluster and used them for client json calculation
          var agentGR = new GlideRecord('sn_agent_cmdb_ci_agent');
          var clusterGR = agentGR.addJoinQuery('sn_agent_in_agentcluster', 'sys_id', 'agent');
          agentGR.addEncodedQuery("agent_extended_info.statusIN0,1^agent_extended_info.data_collection=0");
          clusterGR.addCondition("cluster", policyGr.agent_cluster_name);
          agentGR.orderBy('name');
          agentGR.query();

          while (agentGR.next()) {
              proxyAgents.push('' + agentGR.agent_id);
          }
      } else if (policyGr.proxy_agent) {
          var extendedInfoRecord = policyGr.proxy_agent.agent_extended_info;
          //take only proxy that is up and not silent
          if (extendedInfoRecord.status != 2 && extendedInfoRecord.data_collection == 0)
              proxyAgents.push('' + extendedInfoRecord.agent_id);
      } else {
          gs.error(gs.getMessage('PolicyClientsGeneratorNG: did not find supported proxy setting for proxy policy {0}. Cannot continue,', policyGr.getValue('name')));
          return undefined;
      }

      // No need for sorting if the number is small or if this is not a request for changes
      if (!detectCmdbChanges || !proxyAgents || proxyAgents.length < 2)
          return proxyAgents;

      // Get aggregated data
      var monitoredCisPerAgent = {};
      var agg = new GlideAggregate('sn_agent_policy_monitored_cis');
      // Cound how many times does our agent is shown per policy = how many monitored CIs per agent
      agg.addAggregate('COUNT', 'agent_id');
      agg.addQuery('agent_id', proxyAgents);
      agg.addQuery('policy', policyGr.getValue('sys_id'));

      // Cannot sort by GlideAggregate, will sort manually.
      agg.query();

      if (!agg.hasNext())
          return proxyAgents;

      // Build a map of agent id to number of monitored CIs.
      while (agg.next()) {
          monitoredCisPerAgent[agg.getValue('agent_id')] = agg.getAggregate('COUNT', 'agent_id');
      }

      // For new proxy agents, put 0 monitored CIs.
      for (var i = 0; i < proxyAgents.length; i++) {
          if (!monitoredCisPerAgent[proxyAgents[i]])
              monitoredCisPerAgent[proxyAgents[i]] = 0;
      }

      var sortedMonitoredCisPerAgent = [];
      for (var agentId in monitoredCisPerAgent) {
          sortedMonitoredCisPerAgent.push([agentId, monitoredCisPerAgent[agentId]]);
      }

      // End results is a sorted map.
      sortedMonitoredCisPerAgent.sort(function(a, b) {
          return a[1] - b[1];
      });

      var sortedProxyAgents = [];
      for (i in sortedMonitoredCisPerAgent) {
          sortedProxyAgents.push(sortedMonitoredCisPerAgent[i][0]); // take only the agent ID from the map
      }

      // Returning a sorted array of proxy agents ids by the number of monitored CIs associated with them.
      // This helps re-distribute the proxy agents by selecting the less loaded agents first.
      return sortedProxyAgents;
  },

  getCiParamsForProxyPolicy: function(monitoredCisGr, isEntryPointView) {
      var ciParams = this.loadCiParamsValuesForCurrentPolicyAndCi(monitoredCisGr);
      if (isEntryPointView)
          ciParams.push('ep_service_name=' + monitoredCisGr.getValue('service_name'));
      var targetIp = monitoredCisGr.getValue('ip_address');
      if (targetIp && targetIp.length > 0)
          ciParams.push('target_ip=' + targetIp);

      ciParams.sort(); // Makes sure we always have the same value
      return ciParams;
  },

  /*
  	This method detects CMDB changes on a Policy with filtered CIs and makes sure that the
  	sn_agent_policy_monitored_cis table is up to date
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  					updatedMonitoredCis: [] - array of all sys ids that were updated
  				}
  */
  detectCmdbChangesForMonitoredCisByFilter: function(policyGr) {
      return this.policyCalculationForMonitoredCisByFilter(policyGr, true);
  },

  /*
  	Performs a full policy calculation for filter based policy without additional conditions other than the policy filter
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  				}
  */
  fullPolicyCalculationForMonitoredCisByFilter: function(policyGr) {
      return this.policyCalculationForMonitoredCisByFilter(policyGr, false);
  },

  /*
  	This method calcualte a Policy with filtered CIs and makes sure that the
  	sn_agent_policy_monitored_cis table is up to date.
  	It can get a flag to limit the results to only changed cmdb changes
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updatedMonitoredCis: [] - array of all sys ids that were updated; empty if performed full policy calculation.
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  					fullPolicyUpdate : true/fasle - true if a full policy calculation was performed, otherwise false
  				}
  */
  policyCalculationForMonitoredCisByFilter: function(policyGr, detectCmdbChanges) {
      var monitoredCiType = policyGr.getValue('table');
      var filter = policyGr.getValue('filter');
      if (!filter)
          filter = "";

      var cisFromServices = undefined;
      // If service query, add the syd ids of the CIs in the service.
      // Assumption here is that the service CIs count is a relativley low number
      // so it is good practice to add it to the query at this point
      if (policyGr.is_service_filter) {
          cisFromServices = this.getCisForServiceByServiceQuery(policyGr, monitoredCiType);
          if (!cisFromServices || cisFromServices.length < 1) {
              // Clear all cis from the policy:
              // The application service may change to contain 0 CIs for the policy CI type and we need to reflect that.
              var didDelete = this.clearAllMonitoredCisForPolicy(policyGr);
              gs.info(gs.getMessage("PolicyClientsGeneratorNG: Policy {0} filters based on application service which returned 0 CIs for the CI type {1}. Policy has no agents", [policyGr.name, monitoredCiType]));
              return {
                  update: didDelete,
                  updateTime: currentCalcTime
              };
          }
      }

      // If the table used is the agents ci type (sn_agent_cmdb_ci_agent) - no need for a db view. Calculate the policy and return.
      if (monitoredCiType == this.AGENT_PARENT_TABLE) {
          var agentCisGr = new GlideRecord(this.AGENT_PARENT_TABLE);
          agentCisGr.addEncodedQuery(filter);
          if (cisFromServices)
              agentCisGr.addQuery("sys_id", cisFromServices);
          return this.policyCalculationForAgentsCisAsMonitoredCis(policyGr, agentCisGr, detectCmdbChanges);
      }

      // This is a proxy policy (the agents are being selected by a different mechanism, and no need for a db view table)
      if (this.isProxyPolicy(policyGr)) {
          var monitoredCisGr = new GlideRecord(monitoredCiType);
          monitoredCisGr.addEncodedQuery(filter);
          if (cisFromServices)
              monitoredCisGr.addQuery("sys_id", cisFromServices);
          return this.policyCalculationForMonitoredCisWithProxyAgents(policyGr, monitoredCisGr, detectCmdbChanges);
      }

      var monitoredCiTypeNoUnderscore = monitoredCiType.replace(/_/g, ''); // DB View do not support underscore on the variable side
      var agentsToMonitoredCisDbViewName = this.AGENT_POLICY_DB_VIEW_PREFIX + monitoredCiType;

      // If the database view does not exist - revet to legacy calculation
      if (!gs.tableExists(agentsToMonitoredCisDbViewName)) {
          gs.debug(gs.getMessage("PolicyClientsGeneratorNG: database view {0} does not exist, reverting to legacy calculation.", agentsToMonitoredCisDbViewName));

          return this.legacyPolicyCalculation(policyGr, monitoredCiType);
      }

      if (!policyGr.getUniqueValue()) {
          gs.error(gs.getMessage("PolicyClientsGeneratorNG: in policyCalculationForMonitoredCisByFilter(): could find monitored CIs as there was a problem getting values from the policy glide record."));
          return {
              update: false
          };
      }

      var fullPolicyUpdate = true;

      /* As we are using a db view, we need to add the db view prefix to the table fileds to fit the db view conditions.
      	input: nameLIKEMSAN^nameNOT LIKE1234^ORnameNOT LIKE4567^NQshort_description=desc^ORshort_description!=333
      	output: <monitoredCiType>_nameLIKEMSAN^<monitoredCiType>_nameNOT LIKE1234^OR<monitoredCiType>_nameNOT LIKE4567^NQ<monitoredCiType>_short_description=desc^OR<monitoredCiType>_short_description!=333
      */
      filter = filter.replace(/(NQ|OR|AND)?(.*?)(LIKE|NOT LIKE|=|!=|>=|<=|ANYTHING|IN|STARTSWITH|ENDSWITH|ISEMPTY|SAMEAS|ISNOTEMPTY|BETWEEN)(.*?)(\^|$)/g, "$1" + monitoredCiTypeNoUnderscore + "_$2$3$4$5");

      var agentsToMonitoredCisDbView = new GlideRecord(agentsToMonitoredCisDbViewName);
      if (filter)
          agentsToMonitoredCisDbView.addEncodedQuery(filter);

      // If rquested by the caller
      // Set to only get CIs that were changed since the last time we detectCmdbChangesFord the policy.
      var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
      if (detectCmdbChanges && lastCalcTime) {
          agentsToMonitoredCisDbView.addQuery(monitoredCiTypeNoUnderscore + "_sys_updated_on", ">",
              lastCalcTime).addOrCondition(this.AGENT_DB_VIEW_PREFIX + "_sys_created_on", ">", lastCalcTime);
          fullPolicyUpdate = false;
      }

      var currentCalcTime = new GlideDateTime().toString();
      if (cisFromServices)
          agentsToMonitoredCisDbView.addQuery(monitoredCiTypeNoUnderscore + "_sys_id", cisFromServices);

      agentsToMonitoredCisDbView.addQuery(this.AGENT_DB_VIEW_PREFIX + "_is_duplicate", "0");
      // By default database views are restricted to 1,000 records; this helps to fetch up to MAX_DB_VIEW_RECORDS records.
      // We can make this better by iterating over the database view couple of hundreds of thousands of monitored CIs at a time.
      agentsToMonitoredCisDbView.chooseWindow(0, this.MAX_DB_VIEW_RECORDS);
      agentsToMonitoredCisDbView.setCategory('acc-policy-calc');
      agentsToMonitoredCisDbView.query();

      cisFromServices = undefined;

      var rtnObj = this.calculateMonitoredCisForQueryWithDbView(policyGr, agentsToMonitoredCisDbView, monitoredCiType, currentCalcTime, fullPolicyUpdate);
      rtnObj.fullPolicyUpdate = fullPolicyUpdate;
      rtnObj.updateTime = currentCalcTime;
      rtnObj.monitoredCiType = monitoredCiType;

      return rtnObj;
  },

  /*
  	This method detects CMDB changes on a Policy with CI group and makes sure that the
  	sn_agent_policy_monitored_cis table is up to date
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  					updatedMonitoredCis: [] - array of all sys ids that were updated
  				}
  */
  detectCmdbChangesForMonitoredCisByGroup: function(policyGr) {
      return this.policyCalculationForMonitoredCisByGroup(policyGr, true);
  },

  /*
  	Performs a full policy calculation for group based policy without additional conditions other than the policy filter
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  				}
  */
  fullPolicyCalculationForMonitoredCisByGroup: function(policyGr) {
      return this.policyCalculationForMonitoredCisByGroup(policyGr, false);
  },

  /*
  	This method calculate a policy with CI group and makes sure that the
  	sn_agent_policy_monitored_cis table is up to date.
  	It can receive a flag to indicate should we limit the results to CMDB changes only
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updatedMonitoredCis: [] - array of all sys ids that were updated; empty if performed full policy calculation.
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  					fullPolicyUpdate : true/fasle - true if a full policy calculation was performed, otherwise false
  				}
  */
  policyCalculationForMonitoredCisByGroup: function(policyGr, detectCmdbChanges) {
      var groupName = policyGr.monitored_ci_group;
      var groupRecord = new GlideRecord("cmdb_group");
      groupRecord.addQuery("group_name", groupName);
      groupRecord.query();
      if (!groupRecord.next()) {
          gs.error(gs.getMessage("PolicyClientsGeneratorNG: in policyCalculationForMonitoredCisByGroup(), could not find any group named {0}", groupName));
          return {
              error: gs.getMessage("Could not find CMDB group {0}", groupName),
              update: false
          };
      }

      var fullPolicyUpdate = true;
      var groupId = groupRecord.getValue("sys_id");
      var results = JSON.parse(sn_cmdbgroup.CMDBGroupAPI.getAllCI(groupId));
      if (!results || !results.idList || results.idList.length < 1) {
          return {
              update: false
          };
      }

      var ciRecord = new GlideRecord("cmdb_ci");
      if (!ciRecord.get('sys_id', results.idList[0])) {
          gs.error(gs.getMessage('PolicyClientsGeneratorNG: For policy {1}, something went wrong when trying to get data for {1}', [policyGr.name, ciRecord]));
          return {
              error: gs.getMessage("For CMDB group, could not locaet any CIs", groupName),
              update: false
          };
      }
      var className = ciRecord.getValue("sys_class_name");

      //find the most generic type that suits this class type
      var commonTable = this.getParentTable(className);
      var commonTableNoUnderscore = commonTable.replace(/_/g, '');

      var cisFromService = undefined;
      // If service query, add the syd ids of the CIs in the service.
      // Assumption here is that the service CIs count is a relativley low number
      // so it is good practice to add it to the query at this point
      if (policyGr.is_service_filter) {
          cisFromService = this.getCisForServiceByServiceQuery(policyGr, commonTable);
          if (!cisFromService || cisFromService.length < 1) {
              // Clear all cis from the policy:
              // The application service may change to contain 0 CIs for the policy CI type and we need to reflect that.
              var didDelete = this.clearAllMonitoredCisForPolicy(policyGr);
              gs.info(gs.getMessage("PolicyClientsGeneratorNG: Policy {0} filters based on application service which returned 0 CIs for the CI type {1}. Policy has no agents", [policyGr.name, commonTable]));
              return {
                  update: didDelete,
                  updateTime: currentCalcTime
              };
          }
      }

      // If the common table is the agents ci type (sn_agent_cmdb_ci_agent) - no need for a db view. Calculate the policy and return.
      if (commonTable == this.AGENT_PARENT_TABLE) {
          var agentCisGr = new GlideRecord(commonTable);
          agentCisGr.addQuery("sys_id", "IN", results.idList); // NEED TO REPLACE WITH CHOOSE WINDOW AND CHUNKS
          if (cisFromService)
              agentCisGr.addQuery("sys_id", cisFromService);
          return this.policyCalculationForAgentsCisAsMonitoredCis(policyGr, agentCisGr, detectCmdbChanges);
      }
      // This is a proxy policy (the agents are being selected by a different mechanism, and no need for a db view table)
      if (this.isProxyPolicy(policyGr)) {
          var monitoredCisGr = new GlideRecord(commonTable);
          monitoredCisGr.addQuery("sys_id", "IN", results.idList); // NEED TO REPLACE WITH CHOOSE WINDOW AND CHUNKS
          if (cisFromService)
              monitoredCisGr.addQuery("sys_id", cisFromService);
          return this.policyCalculationForMonitoredCisWithProxyAgents(policyGr, monitoredCisGr, detectCmdbChanges);
      }

      var agentsToMonitoredCisDbViewName = this.AGENT_POLICY_DB_VIEW_PREFIX + commonTable;

      // If the database view does not exist - revet to legacy calculation
      if (!gs.tableExists(agentsToMonitoredCisDbViewName)) {
          gs.debug(gs.getMessage("PolicyClientsGeneratorNG: database view {0} does not exist, reverting to legacy calculation.", agentsToMonitoredCisDbViewName));

          return this.legacyPolicyCalculation(policyGr, commonTable);
      }

      var agentsToMonitoredCisDbView = new GlideRecord(agentsToMonitoredCisDbViewName);
      agentsToMonitoredCisDbView.addQuery(commonTableNoUnderscore + "_sys_id", "IN", results.idList); // NEED TO REPLACE WITH CHOOSE WINDOW AND CHUNKS

      // Set to only get CIs that were changed since the last time we detectCmdbChangesFord the policy.
      var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
      if (detectCmdbChanges && lastCalcTime) {
          agentsToMonitoredCisDbView.addQuery(commonTableNoUnderscore + "_sys_updated_on", ">", lastCalcTime).addOrCondition(this.AGENT_DB_VIEW_PREFIX + "_sys_created_on", ">", lastCalcTime);
          fullPolicyUpdate = false;
      }

      var currentCalcTime = new GlideDateTime().toString();

      if (cisFromService)
          agentsToMonitoredCisDbView.addQuery(commonTableNoUnderscore + "_sys_id", cisFromService);

      agentsToMonitoredCisDbView.addQuery(this.AGENT_DB_VIEW_PREFIX + "_is_duplicate", "0");
      agentsToMonitoredCisDbView.chooseWindow(0, this.MAX_DB_VIEW_RECORDS);
      agentsToMonitoredCisDbView.setCategory('acc-policy-calc');
      agentsToMonitoredCisDbView.query();
      // Free memory
      results = undefined;
      cisFromServices = undefined;

      var rtnObj = this.calculateMonitoredCisForQueryWithDbView(policyGr, agentsToMonitoredCisDbView, commonTable, currentCalcTime, fullPolicyUpdate);
      rtnObj.fullPolicyUpdate = fullPolicyUpdate;
      rtnObj.updateTime = currentCalcTime;
      rtnObj.monitoredCiType = commonTable;
      return rtnObj;
  },

  /*
  	This method detects CMDB changes on a Policy with a script to get the monitored CIs and makes sure that the
  	sn_agent_policy_monitored_cis table is up to date
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  					updatedMonitoredCis: [] - array of all sys ids that were updated
  					fullPolicyUpdate : fasle - a full policy calculation was NOT performed
  				}
  */
  detectCmdbChangesForMonitoredCisByScript: function(policyGr) {
      return this.policyCalculationForMonitoredCisByScript(policyGr, true);
  },

  /*
  	Performs a full policy calculation for a policy with a script to get the monitored CIs without additional conditions other than
  	the policy filter.
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  					fullPolicyUpdate : true - full policy calculation was performed
  				}
  */
  fullPolicyCalculationForMonitoredCisByScript: function(policyGr) {
      return this.policyCalculationForMonitoredCisByScript(policyGr, false);
  },

  /*
  	This method calcualte a Policy with a script to get the monitored CIs and makes sure that the
  	sn_agent_policy_monitored_cis table is up to date.
  	It can get a flag to limit the results to only changed cmdb changes
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updatedMonitoredCis: [] - array of all sys ids that were updated; empty if performed full policy calculation.
  					updateTime - the time this policy was calculated (just before we got all the data from the db)
  					monitoredCiType - the most common CI type of the policy
  					fullPolicyUpdate : true/fasle - true if a full policy calculation was performed, otherwise false
  				}
  */
  policyCalculationForMonitoredCisByScript: function(policyGr, detectCmdbChanges) {
      var evaluator = new GlideScopedEvaluator();
      var monitoredCisGr = evaluator.evaluateScript(policyGr, 'monitored_ci_script');
      if (!monitoredCisGr) {
          var msg = gs.getMessage("PolicyClientsGeneratorNG: Policy {0}, the monitored CI type based on the script has failed. Check your script and try again.", policyGr.name);
          gs.error(msg);
          return {
              error: msg,
              update: false
          };
      }

      try {
          // We are setting a limit to make sure the script did not go more than we want it too.
          monitoredCisGr.setLimit(this.MAX_NUMBER_OF_MONITORED_CIS_TO_PROCESS);
          monitoredCisGr.setCategory('acc-policy-calc');
          monitoredCisGr.query();
      } catch (e) {
          gs.error(gs.getMessage("PolicyClientsGeneratorNG: For policy {0}, the script to get the monitored CIs had failed. Try fixing it. Error={1}", [policyGr.name, e]));
          return {
              error: gs.getMessage('Getting Cis from script has failed. Error={0}', e),
              update: false
          };
      }

      var monitoredCiType = monitoredCisGr.getTableName();
      var cisFromService = undefined;
      // If service query, add the syd ids of the CIs in the service.
      // Assumption here is that the service CIs count is a relativley low number
      // so it is good practice to add it to the query at this point
      if (policyGr.is_service_filter) {
          cisFromService = this.getCisForServiceByServiceQuery(policyGr, monitoredCiType);
          if (!cisFromService || cisFromService.length < 1) {
              // Clear all cis from the policy:
              // The application service may change to contain 0 CIs for the policy CI type and we need to reflect that.
              var didDelete = this.clearAllMonitoredCisForPolicy(policyGr);
              gs.info(gs.getMessage("PolicyClientsGeneratorNG: Policy {0} filters based on application service which returned 0 CIs for the CI type {1}. Policy has no agents", [policyGr.name, monitoredCiType]));
              return {
                  update: didDelete,
                  updateTime: currentCalcTime
              };
          }
      }

      // // If the script table is the agents ci type (sn_agent_cmdb_ci_agent) - no need for a db view. Calculate the policy and return.
      if (monitoredCiType == this.AGENT_PARENT_TABLE) {
          if (cisFromService)
              monitoredCisGr.addQuery("sys_id", cisFromService);
          return this.policyCalculationForAgentsCisAsMonitoredCis(policyGr, monitoredCisGr, detectCmdbChanges);
      }
      // This is a proxy policy (the agents are being selected by a different mechanism, and no need for a db view table)
      if (this.isProxyPolicy(policyGr)) {
          if (cisFromService)
              monitoredCisGr.addQuery("sys_id", cisFromService);
          return this.policyCalculationForMonitoredCisWithProxyAgents(policyGr, monitoredCisGr, detectCmdbChanges);
      }

      var monitoredCis = [];

      while (monitoredCisGr.next()) {
          monitoredCis.push(monitoredCisGr.getValue('sys_id'));
      }

      if (monitoredCis.length < 1)
          return {
              update: false
          };

      var monitoredCiTypeNoUnderscore = monitoredCiType.replace(/_/g, '');

      var agentsToMonitoredCisDbViewName = this.AGENT_POLICY_DB_VIEW_PREFIX + monitoredCiType;

      // If the database view does not exist - revet to legacy calculation
      if (!gs.tableExists(agentsToMonitoredCisDbViewName)) {
          gs.debug(gs.getMessage("PolicyClientsGeneratorNG: database view {0} does not exist, reverting to legacy calculation.", agentsToMonitoredCisDbViewName));

          return this.legacyPolicyCalculation(policyGr, monitoredCiType);
      }

      var fullPolicyUpdate = true;
      var agentsToMonitoredCisDbView = new GlideRecord(agentsToMonitoredCisDbViewName);
      agentsToMonitoredCisDbView.addQuery(monitoredCiTypeNoUnderscore + '_sys_id', monitoredCis); // NEED TO REPLACE WITH CHOOSE WINDOW AND CHUNKS

      // If rquested by the caller
      // Set to only get CIs that were changed since the last time we detectCmdbChangesFord the policy.
      var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
      if (detectCmdbChanges && lastCalcTime) {
          agentsToMonitoredCisDbView.addQuery(monitoredCiTypeNoUnderscore + "_" + "sys_updated_on", ">", lastCalcTime)
              .addOrCondition(this.AGENT_DB_VIEW_PREFIX + "_sys_created_on", ">", lastCalcTime);
          fullPolicyUpdate = false;
      }

      var currentCalcTime = new GlideDateTime().toString();
      if (cisFromService)
          agentsToMonitoredCisDbView.addQuery(monitoredCiTypeNoUnderscore + "_sys_id", cisFromService);

      agentsToMonitoredCisDbView.addQuery(this.AGENT_DB_VIEW_PREFIX + "_is_duplicate", "0");
      agentsToMonitoredCisDbView.chooseWindow(0, this.MAX_DB_VIEW_RECORDS);
      agentsToMonitoredCisDbView.setCategory('acc-policy-calc');
      agentsToMonitoredCisDbView.query();
      // Free memory
      monitoredCis = undefined;
      cisFromServices = undefined;

      var rtnObj = this.calculateMonitoredCisForQueryWithDbView(policyGr, agentsToMonitoredCisDbView, monitoredCiType, currentCalcTime, fullPolicyUpdate);
      rtnObj.fullPolicyUpdate = fullPolicyUpdate;
      rtnObj.updateTime = currentCalcTime;
      rtnObj.monitoredCiType = monitoredCiType;
      return rtnObj;
  },

  /*
  	This method gets a policy GR pointing to the current policy and an agentsToMonitoredCisGr which was already queried earlier.
  	Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updatedMonitoredCis: [] - array of all sys ids that were updated; empty if performed full policy calculation.
  				}
  */
  calculateMonitoredCisForQueryWithDbView: function(policyGr, agentsToMonitoredCisGr, monitoredCiType, currentCalcTime, fullPolicyCalc) {
      var monitoredCisToValues = {};
      var monitoredCiSysId = "";
      this.loadCiParamsForCurrentPolicy(policyGr);

      // First: cahce in the data that we would like to update the table with.
      while (agentsToMonitoredCisGr.next()) {
          monitoredCiSysId = this.getValueFromDbView(agentsToMonitoredCisGr, monitoredCiType, 'sys_id', true);
          var agentId = this.getValueFromDbView(agentsToMonitoredCisGr, this.AGENT_DB_VIEW_PREFIX, 'agent_id');

          if (!monitoredCiSysId || !agentId) {
              gs.warn(gs.getMessage('PolicyClientsGeneratorNG: could not find monitored CI sys id or agent id on table {0} for policy {1}', [agentsToMonitoredCisGr.getTableName(), policyGr.name]));
              continue;
          }

          // Can take the CI Params right now
          var ciParams = [];
          if (this.currentPolicy.ci_params) {
              for (var ciParamKey in this.currentPolicy.ci_params) {
                  var ciParamValue = this.getValueFromDbView(agentsToMonitoredCisGr, monitoredCiType, ciParamKey, true);
                  if (!ciParamValue)
                      ciParamValue = this.currentPolicy.ci_params[ciParamKey].defaultValue; // get default value
                  if (ciParamValue && this.currentPolicy.ci_params[ciParamKey].encode)
                      ciParamValue = gs.base64Encode(ciParamValue);
                  if (ciParamValue)
                      ciParams.push(ciParamKey + "=" + ciParamValue);
              }
          }

          if (this.currentPolicy.service_params_values && this.ciToServiceId && this.ciToServiceId[monitoredCiSysId]) {
              var thisCiServiceId = this.ciToServiceId[monitoredCiSysId];
              for (var serviceParam in this.currentPolicy.service_params_values[thisCiServiceId]) {
                  var value = this.currentPolicy.service_params_values[thisCiServiceId][serviceParam];
                  if (value)
                      ciParams.push(serviceParam + "=" + value);
              }
              // We are done with this CI.
              delete this.ciToServiceId[monitoredCiSysId];
          }

          ciParams.sort(); // Makes sure we always have the same value

          /* Data we cache is the monitored CI sys id + agent id + CI Params
          	Example: 967d9158539151108ec4ddeeff7b12a9|1234567890123456|NAME=Myname,Port=123
          	Each record is: 32 + 16 + 20 = 68
          	Most policies will be around 25,000 monitored CIs which will give us 68 * 25,000 = 1,700,000 characters.
          	As each character is 2 bytes we will get: 1,700,000 * 2 = 3,400,000 bytes => 3.24MB
          	Assuming a stretch policy with 1,000,000 monitored CIs, we will get 68,000,000 characters
          	68,000,000 * 2 = 136,000,000 bytes (2 bytes per character) => 129.7MB memory to cache the old data
          	This is acceptable.
          	The data structure allows more agents to be used as there is the use case of proxy agents which may have multiple agents per monitored CI
          */
          if (!monitoredCisToValues[monitoredCiSysId])
              monitoredCisToValues[monitoredCiSysId] = {};
          if (!monitoredCisToValues[monitoredCiSysId].agents)
              monitoredCisToValues[monitoredCiSysId].agents = {};
          // No need to save the policy ID as we work policy by policy
          var agentStatus = this.getValueFromDbView(agentsToMonitoredCisGr, this.AGENT_DB_VIEW_PREFIX, 'status');
          if (!agentStatus)
              agentStatus = 999;
          monitoredCisToValues[monitoredCiSysId].agents[agentId] = agentStatus;
          monitoredCisToValues[monitoredCiSysId].ci_params = ciParams.join(',');
      }

      // Clear some memory, and make sure these mapping are cleared for the next policy
      this.resetServiceParamsForCurrentPolicy();

      if (gs.isDebugging()) {
          var cacheSize = Object.keys(monitoredCisToValues).length;
          gs.debug("Cahce size for policy " + policyGr.name + " has:" + cacheSize + " items which is about: " + (cacheSize * 68 * 2) / 1048576 + "MB");
      }

      var rtnObj = this.updateAndInsertIntoPolicyToMonitoredCis(policyGr, monitoredCisToValues, currentCalcTime, fullPolicyCalc);
      // Empty the cache
      monitoredCisToValues = undefined;

      return rtnObj;
  },

  /*
  	This method performs insers and updates to the sn_agent_policy_monitored_cis table.
  	
  	Input:
  		policyGr - the GR that points to the current policy
  		monitoredCisToValues - The following structure: 
  											monitoredCisToValues = {
  																		"<monitored CI sys id" : {
  																			"agents": {
  																				"<agent1_id>": "",
  																				"<agent1_id>": "",
  																				...
  																			},
  																			"ci_params": {
  																				"<CI PARAM 1 Key>": "<CI PARAM 1 value>", 
  																				"<CI PARAM 2 Key>": "<CI PARAM 2 value>",
  																				...
  																			}
  																		}
  		currentCalcTime - the timestamp to put on the sn_agent_policy_monitored_cis table and the policy table
  		fullPolicyCalc  - true if a full policy recalculation is needed otherwise false.
  		multipleAgentsPerMonitoredCi - true if a monitored CI can have multiple agents monitoring it (like proxy agents),
  			false otherwise: this will create the policy + monitored CI as a unique sequenece and the agent id may update.
  		Returns an object:
  				{
  					update: true/fasle - true if there was at least 1 isert or 1 update otherwise false
  					updatedMonitoredCis: [] - array of all sys ids that were updated; empty if performed full policy calculation.
  				}
  */
  updateAndInsertIntoPolicyToMonitoredCis: function(policyGr, monitoredCisToValues, currentCalcTime, fullPolicyCalc, multipleAgentsPerMonitoredCi) {
      var arrMonitoredCiSysIds = Object.keys(monitoredCisToValues);
      if (!arrMonitoredCiSysIds || arrMonitoredCiSysIds.length < 1) {
          var update = false
          // For full policy calculation - remove all old records.
          if (fullPolicyCalc)
              update = this.clearAllMonitoredCisForPolicy(policyGr);
          // Nothing to update/insert
          return {
              update: update,
              updateTime: currentCalcTime
          };
      }

      // For hierarchical policies, makes sure we do not have the same monitored CIs as higher priority policies (sibling or children).
      if (this.removedMonitoredCisAlreadyCalculatedByHigherPriorityPolicies(policyGr, monitoredCisToValues))
          arrMonitoredCiSysIds = Object.keys(monitoredCisToValues); // if monitoredCisToValues was update, reflect this in the array
      var monitoredCiSysId = "";
      var numOfUpdates = 0;
      var numOfInserts = 0;
      var totalMonitoredCisTriggered = arrMonitoredCiSysIds.length;
      var updatedMonitoredCis = [];
      var didUpdate = false;

      // If this is not a full policy recalc - check if we need to update records.
      if (!fullPolicyCalc) {
          didUpdate = this.updatePolicyToMonitoredCis(policyGr, monitoredCisToValues, arrMonitoredCiSysIds, updatedMonitoredCis, multipleAgentsPerMonitoredCi, numOfUpdates);
      } else {
          // This is a full policy recalculation.
          // Clear old data as we are about to build new data
          didUpdate = this.clearAllMonitoredCisForPolicy(policyGr, monitoredCisToValues);
      }

      var policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      // Process inserts
      for (monitoredCiSysId in monitoredCisToValues) {
          // If already processed by an update, skip this monitored CI
          if (monitoredCisToValues[monitoredCiSysId].processed)
              continue;

          var policyId = policyGr.getValue('sys_id');
          // Make sure we log the monitored CI sys id we updated.
          if (!fullPolicyCalc)
              updatedMonitoredCis.push(monitoredCiSysId);

          var ciParams = monitoredCisToValues[monitoredCiSysId].ci_params;
          for (var agentId in monitoredCisToValues[monitoredCiSysId].agents) {
              // If already processed by an update, skip this agent id
              if (monitoredCisToValues[monitoredCiSysId].agents[agentId] == "processed")
                  continue;
              policyMonitoredCIsGr.initialize();
              policyMonitoredCIsGr.setValue('policy', policyId);
              policyMonitoredCIsGr.setValue('monitored_ci', monitoredCiSysId);
              policyMonitoredCIsGr.setValue('agent_id', agentId);
              policyMonitoredCIsGr.setValue('ci_params', ciParams);
              policyMonitoredCIsGr.insert();
              if (!didUpdate)
                  didUpdate = true;
              numOfInserts++;
          }
      }

      if (gs.isDebugging()) {
          numOfUpdates = updatedMonitoredCis.length;
          var numIgnoredCis = totalMonitoredCisTriggered - (numOfUpdates + numOfInserts);
          gs.debug(gs.getMessage("PolicyClientsGeneratorNG: performed {0} updates and {1} insert out of {2} triggered monitored CIs ({3} were ignored.)", [numOfUpdates, numOfInserts, totalMonitoredCisTriggered, numIgnoredCis]));
      }

      return {
          update: didUpdate,
          updateTime: currentCalcTime,
          updatedMonitoredCis: updatedMonitoredCis
      };
  },

  /*
  	This method tries to update every record that exist in the database with monitoredCisToValues new values.
  	It will marked processed records as processed and will add to updatedMonitoredCis array.
  */
  updatePolicyToMonitoredCis: function(policyGr, monitoredCisToValues, arrMonitoredCiSysIds, updatedMonitoredCis, multipleAgentsPerMonitoredCi, numOfUpdates) {
      var policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      // First, work on updates: check if the monitored ci for the policy already exist and if it needs an update.
      policyMonitoredCIsGr.addQuery('monitored_ci', arrMonitoredCiSysIds);
      policyMonitoredCIsGr.addQuery('policy', policyGr.getValue('sys_id'));
      var didUpdate = false;
      var recordsToDelete = [];

      policyMonitoredCIsGr.query();

      while (policyMonitoredCIsGr.next()) {
          monitoredCiSysId = policyMonitoredCIsGr.getValue('monitored_ci');
          if (!monitoredCisToValues[monitoredCiSysId] || !monitoredCisToValues[monitoredCiSysId].agents) {
              if (monitoredCisToValues[monitoredCiSysId])
                  monitoredCisToValues[monitoredCiSysId].processed = true;
              gs.warn(gs.getMessage("PolicyClientsGeneratorNG: Something went wrong. Monitored CI sys id={0} for policy {1}. Cannot continue.", [monitoredCiSysId, policyGr.name]));
              continue;
          }
          var dbAgentId = policyMonitoredCIsGr.getValue('agent_id');

          var numberOfAgentsPerMonitoredCi = Object.keys(monitoredCisToValues[monitoredCiSysId].agents).length;
          if (numberOfAgentsPerMonitoredCi < 1) {
              monitoredCisToValues[monitoredCiSysId].processed = true;
              gs.warn(gs.getMessage("PolicyClientsGeneratorNG: Got no agents per monitored CI. Monitored CI sys id={0} for policy {1}. Cannot continue.", [monitoredCiSysId, policyGr.name]));
              continue;
          }

          /* If we can have multiple agents per monitored CI AND we couldn't locate the existin agent id in the cache
          	// This means we have an old record in the db that the current calculation did not find - we will delete it.
          		A use case that may happen: a monitored CI had changed value AND a proxy agent is down at the moment.
          		But the proxy agents calculation did not run yet: we will get old records for the updated CI we have detected with the old proxy agent
          	*/
          if (multipleAgentsPerMonitoredCi && !monitoredCisToValues[monitoredCiSysId].agents[dbAgentId]) {
              recordsToDelete.push(policyMonitoredCIsGr.getUniqueValue());
              continue;
          }

          var agentId = agentId = Object.keys(monitoredCisToValues[monitoredCiSysId].agents)[0];
          /*
              For unique agent per monitored CI - make sure it is unique.
              If not unique, for example: multiple agents were installed on the same host, gives many agents to host w/ different agenti id
              We will try and select the first agent with an up status.
          */
          if (!multipleAgentsPerMonitoredCi && numberOfAgentsPerMonitoredCi > 1) {
              var agentStatus = 9999;
              for (var agent in monitoredCisToValues[monitoredCiSysId].agents) {
                  // monitoredCisToValues[monitoredCiSysId].agents[agent] = agent status as string
                  var statusAsNumber = Number(monitoredCisToValues[monitoredCiSysId].agents[agent])
                  if (typeof statusAsNumber === 'number' && statusAsNumber < agentStatus) {
                      agentStatus = statusAsNumber;
                      agentId = agent;
                  }
              }
          }
          var ciParams = monitoredCisToValues[monitoredCiSysId].ci_params;

          // We know the policy, the monitored CI and the agent id are all the same.
          // Just need to check that the CI params are the same - if so: no update required.
          if ((ciParams == policyMonitoredCIsGr.getValue('ci_params') && !multipleAgentsPerMonitoredCi) ||
              (ciParams == policyMonitoredCIsGr.getValue('ci_params') && agentId == dbAgentId)) {
              // Remove the monitored ci - as we already processed it and there was nothing to update.
              updatedMonitoredCis.push(monitoredCiSysId);
              if (monitoredCisToValues[monitoredCiSysId])
                  monitoredCisToValues[monitoredCiSysId].processed = true;
              continue;
          }

          // If something had changed - update it
          // Policy id and monitored CI id are known on this record.
          // If unique agent per monitored ci is required - we need to update this record
          if (!multipleAgentsPerMonitoredCi) {
              monitoredCisToValues[monitoredCiSysId].processed = true;
              policyMonitoredCIsGr.setValue('agent_id', agentId);
          } else if (monitoredCisToValues[monitoredCiSysId].agents[dbAgentId]) {
              monitoredCisToValues[monitoredCiSysId].agents[dbAgentId] = "processed";
          }
          policyMonitoredCIsGr.setValue('ci_params', ciParams);
          policyMonitoredCIsGr.update(); // Update happens if ci params had changed, otherwise if unique agent per monitored CI had changed, otherwise no update needed 

          // Remove the monitored ci - as we already processed it by updating the record.
          updatedMonitoredCis.push(monitoredCiSysId);
          numOfUpdates++;
          if (!didUpdate)
              didUpdate = true;
      }

      // Cleaning up old records
      if (recordsToDelete.length > 0) {
          policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
          policyMonitoredCIsGr.addQuery('sys_id', recordsToDelete);
          policyMonitoredCIsGr.setWorkflow(false); // do not trigger BRs
          policyMonitoredCIsGr.deleteMultiple();
      }

      return didUpdate;
  },

  updatePolicy: function(policyId, detectCmdbChanges, isManual) {
      this.isManual = isManual;
      var policyGr = new GlideRecord('sn_agent_policy');
      if (!policyGr.get('sys_id', policyId)) {
          gs.error(gs.getMessage("PolicyClientsGeneratorNG: Could not find policy with sys id={0}", policyId));
          return;
      }

      if (!detectCmdbChanges || isManual) {
          this.fullyUpdateSinglePolicy(policyGr);
      } else {
          this.updateSinglePolicyWithCmdbChanges(policyGr);
      }
  },

  /**
   * Determines which status to set the policy status to after processing a policy.
   * During policy processing, the status of a policy is set to 4 to indicate processing.
   * After processing is complete, this function is called to determine what the new status should be.
   * 
   * Parameters:
   *     prevStatus - the policy status before it is set to processing
   * 
   * Return:
   *     the status that the policy should be set to after processing
   */
  getValidPrevStatus: function(prevStatus) {
      // if we had an error, but now everything is well, set status to ready to publish to make sure the MID cache has the latest
      // if prevStatus is 4, that means that the policy is being reactivated, so set status to ready to publish
      if (prevStatus == '6' || prevStatus == '4')
          return '5';

      // If nothing changed and status was Queued (3) or Not processed (7)
      // set to Publish (as there is no update to the MID)
      if (prevStatus == '3' || prevStatus == '7')
          return '1';

      // pass through, no change needed for the status
      return prevStatus;
  },

  fullyUpdateSinglePolicy: function(policyGr) {
      try {
          var prevPublishStatus = this.policyStateUtil.getPolicyStatePublishStatus(policyGr);
          this.setPolicyPublishProcessing(policyGr, true);
          var detectionResults = undefined;
          if (policyGr.monitored_ci_type_script) {
              detectionResults = this.fullPolicyCalculationForMonitoredCisByScript(policyGr);
          } else if (policyGr.monitored_ci_type_group) {
              detectionResults = this.fullPolicyCalculationForMonitoredCisByGroup(policyGr);
          } else {
              detectionResults = this.fullPolicyCalculationForMonitoredCisByFilter(policyGr);
          }

          if (!detectionResults || (detectionResults.update && !detectionResults.updateTime) || detectionResults.error) {
              var err;
              if (detectionResults && detectionResults.error)
                  err = detectionResults.error;
              else
                  err = gs.getMessage('Something went wrong. Did not get the expected results')
              this.setPolicyPublishError(policyGr, err);
              return;
          }

          // No update was done, no need to update policy
          if (!detectionResults.update) {
              prevPublishStatus = this.getValidPrevStatus(prevPublishStatus);
              var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
              if (!lastCalcTime)
                  this.setPolicyPublishStatus(policyGr, prevPublishStatus, undefined, new GlideDateTime(), detectionResults.fullPolicyUpdate);
              else
                  this.setPolicyPublishStatus(policyGr, prevPublishStatus, undefined, detectionResults.updateTime, detectionResults.fullPolicyUpdate, true);
              return;
          }

          var clearError = detectionResults.error ? false : true;
          this.setPolicyReadyToPublish(policyGr, clearError, detectionResults.updateTime, true);

      } catch (e) {
          this.setPolicyPublishError(policyGr, gs.getMessage('Something went wrong. Error={0}', e));
      }
  },

  updateSinglePolicyWithCmdbChanges: function(policyGr) {
      try {
          if (global.JSUtil.getBooleanValue(policyGr, 'proxy_script_advanced')) {
              // DEF0390003: proxy policies do not get recalculated
              // so this is a temporary fix to recalculate the proxy policy every time cmdb diff calculation is performed
              // (every 15 minutes)
              // will need a more performative permanent fix in the future
              this.fullyUpdateSinglePolicy(policyGr);
              return;
          }
          var prevPublishStatus = this.policyStateUtil.getPolicyStatePublishStatus(policyGr);
          this.setPolicyPublishProcessing(policyGr, true);

          var currentCalcTime = undefined;
          var detectionResults = undefined;

          if (policyGr.monitored_ci_type_script) {
              detectionResults = this.detectCmdbChangesForMonitoredCisByScript(policyGr);
          } else if (policyGr.monitored_ci_type_group) {
              detectionResults = this.detectCmdbChangesForMonitoredCisByGroup(policyGr);
          } else {
              detectionResults = this.detectCmdbChangesForMonitoredCisByFilter(policyGr);
          }

          /*
          	expecting to get detectionResults = {
          			update: true/fasle - true if there was at least 1 insert or 1 update otherwise false
          			updatedMonitoredCis: [] - array of all sys ids that were updated; empty if performed full policy calculation.
          			updateTime - the time this policy was calculated (just before we got all the data from the db)
          			monitoredCiType - the most common CI type of the policy
          			fullPolicyUpdate : true/fasle - true if a full policy calculation was performed, otherwise false
          		}
          */
          if (!detectionResults || (detectionResults.update && !detectionResults.updateTime) || detectionResults.error) {
              var err;
              if (detectionResults && detectionResults.error)
                  err = detectionResults.error
              else
                  err = gs.getMessage('Something went wrong. Did not get the expected results');
              this.setPolicyPublishError(policyGr, err);
              return;
          }

          currentCalcTime = detectionResults.updateTime;

          // Detect old monitored CIs that use to pass the policy's filter and a CMDB update made them to not pass the filter.
          // If a full policy recalc was done, all records are new and up to date - nothing to remove.
          if (!detectionResults.fullPolicyUpdate && detectionResults.updateTime && detectionResults.monitoredCiType) {
              var didDelete = this.removeMonitoredCisThatDoNotPassPolicyFilter(policyGr, detectionResults.monitoredCiType, detectionResults.updateTime, detectionResults.updatedMonitoredCis);
              detectionResults.update = detectionResults.update || didDelete;
          }

          // No update was done - finished with policy
          if (!detectionResults.update) {
              prevPublishStatus = this.getValidPrevStatus(prevPublishStatus);
              var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
              if (!lastCalcTime)
                  this.setPolicyPublishStatus(policyGr, prevPublishStatus, undefined, new GlideDateTime(), detectionResults.fullPolicyUpdate);
              else
                  this.setPolicyPublishStatus(policyGr, prevPublishStatus, undefined, detectionResults.updateTime, detectionResults.fullPolicyUpdate, true);

              return;
          }

          if (gs.isDebugging()) {
              if (!detectionResults.update)
                  gs.debug("PolicyClientsGeneratorNG: policy {0} did not had any updates, skipping for calculation.", policyGr.name);
              else
                  gs.debug(gs.getMessage("PolicyClientsGeneratorNG: setting policy {0} last calc time to {1}", [policyGr.name, currentCalcTime]));
          }

          var clearError = detectionResults.error ? false : true;
          this.setPolicyReadyToPublish(policyGr, clearError, currentCalcTime, detectionResults.fullPolicyUpdate);

          return detectionResults.update;
      } catch (e) {
          this.setPolicyPublishError(policyGr, gs.getMessage('Something went wrong. Error={0}', e));
      }

      return false;
  },

  detectCmdbChanges: function(arrExcluePolicies) {
      var policyGr = new GlideRecord('sn_agent_policy');
      policyGr.addActiveQuery();
      policyGr.addQuery('is_draft', '0');
      if (arrExcluePolicies && arrExcluePolicies.length > 0)
          policyGr.addQuery('sys_id', 'NOT IN', arrExcluePolicies);
      policyGr.orderByDesc("published_parent");
      policyGr.orderBy("order");
      policyGr.query();

      var didUpdatePolicies = false;
      while (policyGr.next()) {
          didUpdatePolicies = this.updateSinglePolicyWithCmdbChanges(policyGr) || didUpdatePolicies;
      }
  },

  removeMonitoredCisThatDoNotPassPolicyFilter: function(policyGr, monitoredCiType, updateTime, updatedMonitoredCis) {
      var didDelete = false;

      if (!monitoredCiType || !updateTime) {
          return didDelete;
      }

      /*
      	We essentially reverse the policy condition to get all CI Types that that were updated that DO NOT pass the filter.
      	We will use the sys ids from this query - to find out if we had a CI that used to pass the filter and now it does not.
      	Example: suppose we have Policy for Tomcat with the filter: name starts with the letter 'a'.
      				Up to a specific point, we had 1,100 tomcats matching this criteria.
      				Suppose Discovery (agentless or agent based) updated 9 of the Tomcats.
      				3 tomcats that DO NOT start with the letter 'a' got an update on their description.
      				3 tomcats that DO start with the letter 'a' got an update on their description. (for example, names: a, aa, aaa)
      				and 3 Tomcats that their name used to starts with the letter 'a' and now changed to the letter 'x' (names befoer: a1, a2, a3 and now: x1, x2, x3)
      				in updatedMonitoredCis array we will get the 3 changed Tomcat sys ids for [a, aa, aaa] - as they still comply with the policy's filter and got changed.
      				Next, we will query the Tomcat CI Type but without these 3 Tomcat which returns: 6 Tomcats including x1,x2,x3 and the 3 that thier name never started with the letter 'a'.
      				Then we will go to the sn_agent_policy_monitored_cis and perform a delete with the condition: policy=current policy, sys id is one of the 6.
      				As only 3 sys ids were found (x1, x2, x3) - they will get removed.
      */
      var updatedMonitoredCisWithoutPolicyFilter = [];
      var monitoredCiGr = new GlideRecord(monitoredCiType);
      monitoredCiGr.addQuery('sys_updated_on', '<=', updateTime); // In order not to include any changes that happened since we build the updatedMonitoredCis array
      var lastCalcTime = this.policyStateUtil.getPolicyStateLastCalcTime(policyGr);
      monitoredCiGr.addQuery('sys_updated_on', '>', lastCalcTime);
      if (updatedMonitoredCis && updatedMonitoredCis.length > 0)
          monitoredCiGr.addQuery('sys_id', 'NOT IN', updatedMonitoredCis);
      monitoredCiGr.setCategory('acc-policy-calc');
      monitoredCiGr.query();

      while (monitoredCiGr.next())
          updatedMonitoredCisWithoutPolicyFilter.push(monitoredCiGr.getUniqueValue());

      if (updatedMonitoredCisWithoutPolicyFilter.length < 1) {
          return didDelete;
      }

      var policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCIsGr.addQuery('policy', policyGr.getValue('sys_id'));
      policyMonitoredCIsGr.addQuery('monitored_ci', updatedMonitoredCisWithoutPolicyFilter);
      policyMonitoredCIsGr.setLimit(1);
      policyMonitoredCIsGr.query();

      didDelete = policyMonitoredCIsGr.hasNext();

      if (!didDelete) {
          return didDelete;
      }

      this.closeAlertsForRemovedCis(policyGr, updatedMonitoredCisWithoutPolicyFilter);

      policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCIsGr.addQuery('policy', policyGr.getValue('sys_id'));
      policyMonitoredCIsGr.addQuery('monitored_ci', updatedMonitoredCisWithoutPolicyFilter);
      policyMonitoredCIsGr.query();
      policyMonitoredCIsGr.setWorkflow(false); // we do not want to trigger the business rules on the table
      policyMonitoredCIsGr.deleteMultiple();

      return didDelete;
  },

  getPolicyFullCalcCycleAsGlideTime: function() {
      var DEFAULT_FULL_POLICY_RECALC_CYCLE = 7; // A week (7 days * 24 hours: will convert to seconds in a few lines)
      var policyFullCalcCycleSec = Number(gs.getProperty('sn_agent.full_policy_calculation_cycle_days', DEFAULT_FULL_POLICY_RECALC_CYCLE));
      if (!policyFullCalcCycleSec || policyFullCalcCycleSec <= 0)
          policyFullCalcCycleSec = DEFAULT_FULL_POLICY_RECALC_CYCLE; //in case we got NAN or 0 or a negative number, set the default

      policyFullCalcCycleSec = policyFullCalcCycleSec * 24 * 60 * 60; // Translate days to seconds
      var policyFullCalcCycleTime = new GlideDateTime();
      policyFullCalcCycleTime.addSeconds(policyFullCalcCycleSec * -1);

      return policyFullCalcCycleTime;
  },

  /**
   * Queries for policy records that should be fully calculated.
   * The criteria are:
   * 1. the policy is active
   * 2. the policy is nondraft
   * 3. the policy either
   *     a. does not have a last full calc time
   *     b. is in "Queued" publish state
   *     c. is in "Not processed" publish status
   *     d. has a last full calc time which is less than the full calc cycle time
   * 
   * Parameters:
   *     fullCalcCycleTime - the timestamp for the full calc cycle, if a policy has a full calc time less than this
   *                         then the policy will be included in the resulting query
   * 
   * Return:
   *     a queried GlideRecord on sn_agent_policy that contains all the records that should be fully calculated
   */
  queryPoliciesToFullyCalculate: function(fullCalcCycleTime) {
      var policyGr = new GlideRecord('sn_agent_policy');
      // Get all policies that
      // 1) Were never been calculated
      // 2) Policies that the user had requested to process.
      // 3) Policies full calculation cycle had reached
      var policyStateQuery = policyGr.addJoinQuery('sn_agent_policy_state', 'sys_id', 'policy');
      policyStateQuery.addCondition('last_full_calc_time', 'NULL')
          .addOrCondition('publish_status', '3') // publish status=3 is a Queued, when a user request to calculate a policy when clicking publish/re-publish policy.
          .addOrCondition('publish_status', '7') // publish status=7 is Not Processed
          .addOrCondition('last_full_calc_time', '<', fullCalcCycleTime);
      policyGr.addActiveQuery();
      policyGr.addQuery('is_draft', '0');
      policyGr.orderByDesc("published_parent");
      policyGr.orderBy("order");
      policyGr.query();
      return policyGr;
  },

  processRequestedPolicies: function() {
      // for each active non-draft policy, create a policy state record for it if one does not exist
      // each new policy state record has the publish_status set to "Queued" so that the corresponding
      // policy will be fully updated
      this.createStateRecordsForNewPolicies();

      var processedPolicies = [];
      var policyFullCalcCycleTime = this.getPolicyFullCalcCycleAsGlideTime();
      var policyGr = this.queryPoliciesToFullyCalculate(policyFullCalcCycleTime);
      while (policyGr.next()) {
          this.fullyUpdateSinglePolicy(policyGr);
          processedPolicies.push(policyGr.getUniqueValue());
      }

      return processedPolicies;
  },

  /**
   * Creates policy state records for any active nondraft policies that do not have
   * a policy state record created already. The newly created policy state records will
   * have their publish statuses set to "Queued" so that the policy will be fully processed.
   * Any policy state records found or created will be added the policy state record cache.
   */
  createStateRecordsForNewPolicies: function() {
      var policyGr = new GlideRecord('sn_agent_policy');
      policyGr.addActiveQuery();
      policyGr.addQuery('is_draft', '0');
      policyGr.query();
      if (!policyGr.hasNext())
          return;

      while (policyGr.next()) {
          var policyStateGr = this.policyStateUtil.getExistingPolicyStateRecord(policyGr);
          if (policyStateGr)
              // policy state record already exists for this policy
              continue;

          var policySysId = policyGr.getUniqueValue();
          // create policy state record in "Queued" state
          if (!this.policyStateUtil.createPolicyStateRecord(policySysId, '3'))
              gs.warn("PolicyStateUtil createStateRecordsForNewPolicies: Could not create policy state record for policy " + policySysId);

          var overriddenPolicySysId = policyGr.getValue('sys_overrides');
          if (!overriddenPolicySysId)
              continue;

          // delete the overridden policy
          this.policyStateUtil.deletePolicyStateRecord(overriddenPolicySysId);
          var policyMonitoredCisGr = new GlideRecord('sn_agent_policy_monitored_cis');
          policyMonitoredCisGr.addQuery('policy', overriddenPolicySysId);
          policyMonitoredCisGr.query();
          if (!policyMonitoredCisGr.hasNext())
              continue;

          policyMonitoredCisGr.deleteMultiple();
          new MonitoringConfig().sendProbe('policy_deleted', overriddenPolicySysId);
      }
  },

  getCisForServiceByServiceQuery: function(policyGr, monitoredCiType) {
      var serviceQuery = policyGr.service_filter;
      this.loadServiceParamsForCurrentPolicy(policyGr);
      var cis = [];

      var serviceGr = new GlideRecord("cmdb_ci_service");
      if (serviceQuery)
          serviceGr.addEncodedQuery(serviceQuery);
      //serviceGr.addJoinQuery("svc_ci_assoc", "sys_id", "service_id"); // we want to get the cis
      serviceGr.query();
      var servicesIds = [];

      while (serviceGr.next()) {
          servicesIds.push(serviceGr.getUniqueValue());
          for (var i = 0; i < this.currentPolicy.service_params.length; i++) {
              var serviceParam = this.currentPolicy.service_params[i];
              if (!serviceParam)
                  continue;
              var value = serviceGr.getValue(serviceParam);
              if (value) {
                  var serviceId = serviceGr.getUniqueValue();
                  if (!this.currentPolicy.service_params_values[serviceId])
                      this.currentPolicy.service_params_values[serviceId] = {};
                  this.currentPolicy.service_params_values[serviceId][this.SERVICE_INFO_PREFIX + serviceParam] = value;
              }
          }
      }

      // We are done with this mapping
      this.currentPolicy.service_params = [];

      if (servicesIds.length < 1)
          return cis;

      var assocCiGr = new GlideRecord("svc_ci_assoc");
      // If we got a type, filter based on the type or its children
      if (monitoredCiType) {
          var q = assocCiGr.addQuery("ci_id.sys_class_name", monitoredCiType); // allowed on CIs
          var extendedTables = new GlideTableHierarchy(monitoredCiType).getTableExtensions();
          if (extendedTables) {
              for (var i = 0; i < extendedTables.length; i++) {
                  q = q.addOrCondition("ci_id.sys_class_name", extendedTables[i]); // allowed on CIs
              }
          }
      }
      assocCiGr.addQuery("service_id", servicesIds);
      assocCiGr.setCategory('acc-policy-calc');
      assocCiGr.query();

      while (assocCiGr.next()) {
          // This can result in more CIs that we need as some CIs might not have an agent associate to them - we will clean this later.
          this.ciToServiceId[assocCiGr.getValue('ci_id')] = assocCiGr.getValue('service_id');
          cis.push(assocCiGr.getValue('ci_id'));
      }

      return cis;
  },

  clearAllMonitoredCisForPolicy: function(policyGr, monitoredCisToValues) {
      var didDelete = false;
      gs.debug(gs.getMessage('PolicyClientsGeneratorNG: About to delete all associations of CIs for policy {1}', policyGr.name));
      // First check if there is anything to delete;
      var policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCIsGr.addQuery('policy', policyGr.getValue('sys_id'));
      policyMonitoredCIsGr.setLimit(1);
      policyMonitoredCIsGr.query();
      didDelete = policyMonitoredCIsGr.hasNext();
      if (!didDelete)
          return didDelete;

      // Close alerts for CIs that are not longer part of the policy (before deleting)
      this.closeAlertsForOtherCis(policyGr, monitoredCisToValues);

      // Now delete if we need to.
      policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCIsGr.addQuery('policy', policyGr.getValue('sys_id'));
      policyMonitoredCIsGr.setWorkflow(false); // do not trigger BRs
      policyMonitoredCIsGr.deleteMultiple();
      gs.debug(gs.getMessage('PolicyClientsGeneratorNG: Deleted all associations of CIs for policy {1}', policyGr.name));

      return didDelete;
  },

  getParentTable: function(tablename) {
      //now check if its in hardware hierarchy OR docker container
      var baseTables = new GlideTableHierarchy(tablename).getTables();

      for (var i = 0; i < baseTables.length; i++) {
          var currTable = baseTables[i];
          if (currTable == this.HOST_PARENT_TABLE || currTable == this.DOCKER_PARENT_TABLE ||
              currTable == this.APP_PARENT_TABLE || currTable == this.AGENT_PARENT_TABLE) {
              return currTable;
          }
      }

      return tablename;
  },

  /*
      This method goes over all deleted monitored CIs, agents and policies and makes sure that the
      MID servers associated with these deleted records are getting an update.
      return true if at least one MID server needs to send updates otherwise false.
      Upon CI, policy or agent deletion the cascade rule is to delete the reference (have null in the field value).
      This is the most efficient way to clear records from a (potentially) a huge table.
      Agents, CIs and policies can sometimes get deleted with workflow set to false.
      This may cause the refrence field to have a value, but it will show up as <empty> as there is no refrence
      to the table.
      The following will find all the agents, CIs and policies that shows up as <empty> on the table
      and will delete them (we do that by dot walking to get the name: monitored_ci.name)
  */
  updatePolicyMonitoredCiTableWithCmdbDeletions: function() {
      var forcedSyncMids = {};
      var policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCIsGr.addNullQuery('monitored_ci.name')
          .addOrCondition('policy.name', 'NULL')
          .addOrCondition('agent_id.name', 'NULL')
          .addOrCondition('monitored_ci', 'NULL')
          .addOrCondition('policy', 'NULL')
          .addOrCondition('agent_id', 'NULL')
      policyMonitoredCIsGr.query();
      while (policyMonitoredCIsGr.next()) {
          if (policyMonitoredCIsGr.agent_id && policyMonitoredCIsGr.agent_id.mid)
              forcedSyncMids[policyMonitoredCIsGr.agent_id.mid];
      }

      // Delete anything that is not valid
      policyMonitoredCIsGr.setWorkflow(false);
      policyMonitoredCIsGr.deleteMultiple();

      // Since we force the MID to get the updates - no need to update the policy timestamp
      var forcedSyncMidsysIds = Object.keys(forcedSyncMids);
      if (forcedSyncMidsysIds < 1)
          return false;

      var midInfoGr = new GlideRecord('sn_agent_mid_info');
      midInfoGr.addQuery('mid', forcedSyncMidsysIds);
      midInfoGr.setValue('force_config_sync', '1');
      midInfoGr.setWorkflow(false);
      midInfoGr.updateMultiple();

      return true;
  },

  // This method returns a policy hierarchy: parent and sibling
  getPolicyHierarchy: function(policyGr) {
      var policyId = policyGr.getValue('sys_id');
      // No hierarchy
      if (policyGr.getValue('relationship_state') == '0')
          return {
              isHierarchy: false
          };

      // This is a parent, get the children
      if (policyGr.getValue('relationship_state') == '1')
          return this.getParentChildren(policyId, policyId, policyGr.getValue('order'));

      // This is a child, get the parent and the children
      if (policyGr.getValue('relationship_state') == '2')
          return this.getParentChildren(policyGr.getValue('published_parent'), policyId, policyGr.getValue('order'));
  },

  getParentChildren: function(parentPolicyId, currentPolicyId, currentPolicyOrder) {
      var policyHierarchy = {};
      policyHierarchy.isHierarchy = true;

      currentPolicyOrder = Number(currentPolicyOrder);

      if (currentPolicyId) {
          policyHierarchy.higherPriorityPolicies = [];
          policyHierarchy.current = {};
          policyHierarchy.current.id = currentPolicyId;
          policyHierarchy.current.order = currentPolicyOrder;
          if (parentPolicyId == currentPolicyId)
              policyHierarchy.current.isParent = true;
          else
              policyHierarchy.current.isParent = false;

          policyHierarchy.parent = parentPolicyId;
      }

      var familyPointer;
      if (policyHierarchy.current.isParent) {
          policyHierarchy.children = {};
          familyPointer = policyHierarchy.children;
      } else {
          policyHierarchy.sibling = {};
          familyPointer = policyHierarchy.sibling;
      }
      var childPolicies = new GlideRecord('sn_agent_policy');
      childPolicies.addQuery('published_parent', parentPolicyId);
      childPolicies.addActiveQuery();
      childPolicies.addQuery('is_draft', '0');
      childPolicies.orderBy('order');
      childPolicies.query();
      while (childPolicies.next()) {
          var childPolicyId = childPolicies.getUniqueValue();
          var childPolicyOrder = Number(childPolicies.getValue('order'));
          if (!childPolicyOrder)
              childPolicyOrder = 999;
          if (policyHierarchy.current.isParent || childPolicyId != currentPolicyId) {
              if (policyHierarchy.current.isParent || currentPolicyOrder > childPolicyOrder)
                  policyHierarchy.higherPriorityPolicies.push(childPolicyId);
              familyPointer[childPolicyId] = childPolicyOrder;
          }
      }

      return policyHierarchy;
  },

  /*
  	This method takes the monitored CIs that are part of the current policy calculation and removes any monitored ci
  	that belongs to a higher priority policy.
  	A higher prioirty policy are all the children of a parent policy
  	or
  	a lower order sibling policy for a child policy.
  	We process higher prioirty policy first, so we know these monitored CIs are up to date.
  */
  removedMonitoredCisAlreadyCalculatedByHigherPriorityPolicies: function(policyGr, monitoredCisValues) {
      var policyHierarchy = this.getPolicyHierarchy(policyGr);
      // Got no data to work with.
      if (!policyHierarchy || !policyHierarchy.higherPriorityPolicies || policyHierarchy.higherPriorityPolicies.length < 1 ||
          !policyHierarchy.isHierarchy)
          return false;

      var monitoredCisToBeAdd = Object.keys(monitoredCisValues);

      var policyMonitoredCiGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCiGr.addQuery('policy', policyHierarchy.higherPriorityPolicies);
      policyMonitoredCiGr.addQuery('monitored_ci', monitoredCisToBeAdd);
      policyMonitoredCiGr.setCategory('acc-policy-calc');
      policyMonitoredCiGr.query();

      var didDelete = false;
      while (policyMonitoredCiGr.next()) {
          var monitoredCiSysId = policyMonitoredCiGr.getValue('monitored_ci');
          if (monitoredCisValues[monitoredCiSysId]) {
              delete monitoredCisValues[monitoredCiSysId];
              didDelete = true;
          }
      }

      return didDelete;
  },

  // In case user changed the policy nad published it
  // send event to notify about changes, only handled if monitoring is installed
  // CIRTICAL: This should only run for full policy calcualtion
  closeAlertsForOtherCis: function(policyGr, monitoredCisToValues) {
      if (!GlidePluginManager.isActive('com.itom-monitoring') ||
          gs.getProperty("sn_itmon.enable_auto_alert_closing", "false") == 'false')
          return;

      // Find the differece between the new monitored CIs and the monitored CIs in the database
      // CIRTICAL: This should only run for full policy calcualtion
      var arrCurrentMonitoredCisSysIds = undefined;
      if (monitoredCisToValues)
          arrCurrentMonitoredCisSysIds = Object.keys(monitoredCisToValues);
      var arrRemainingMonitoredCis = [];
      var policyMonitoredCiGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCiGr.addQuery('policy', policyGr.getValue('sys_id'));
      if (arrCurrentMonitoredCisSysIds)
          policyMonitoredCiGr.addQuery('monitored_ci', 'NOT IN', arrCurrentMonitoredCisSysIds);
      policyMonitoredCiGr.setCategory('acc-policy-calc');
      policyMonitoredCiGr.query();
      while (policyMonitoredCiGr.next())
          arrRemainingMonitoredCis.push(policyMonitoredCiGr.getValue('monitored_ci'));

      this.closeAlertsForRemovedCis(policyGr, arrRemainingMonitoredCis);
  },

  closeAlertsForRemovedCis: function(policyGr, arrMonitoredCisToCloseAlerts) {
      // Nothing to update
      if (arrMonitoredCisToCloseAlerts.length < 1)
          return;
      gs.eventQueue("sn_agent.notify_about_filter_changes", policyGr, arrMonitoredCisToCloseAlerts);
  },

  // This is a fast way to get row count. To get row count of 1,183,031 took 0.361 seconds vs. GlideRecord getRowCount() which took 3.8 seconds
  rapidRowCount: function(tableName, tableFilter) {
      var ga = new GlideAggregate(tableName);
      ga.addEncodedQuery(tableFilter);
      ga.addAggregate('COUNT');
      ga.query();
      if (ga.next())
          return ga.getAggregate('COUNT');

      return 0;
  },

  getValueFromDbView: function(agentsToMonitoredCisDbView, prefix, fieldName, removeUnderscore) {
      if (removeUnderscore)
          prefix = prefix.replace(/_/g, '');
      return agentsToMonitoredCisDbView.getValue(prefix + "_" + fieldName);
  },

  setPolicyPublishQueued: function(policyGr, clearError) {
      var err = undefined;
      if (clearError)
          err = '';
      this.setPolicyPublishStatus(policyGr, '3', err);
  },

  setPolicyPublishProcessing: function(policyGr, clearError) {
      var err = undefined;
      if (clearError)
          err = '';
      this.setPolicyPublishStatus(policyGr, '4', err);
  },

  setPolicyReadyToPublish: function(policyGr, clearError, currenctCalcTime, fullPolicyCalc) {
      var err = undefined;
      if (clearError)
          err = '';
      this.setPolicyPublishStatus(policyGr, '5', err, currenctCalcTime, fullPolicyCalc);
  },

  setPolicyPublishPublished: function(policyGr, clearError) {
      var err = undefined;
      if (clearError)
          err = '';
      this.setPolicyPublishStatus(policyGr, '1', err); // 1 == published
  },

  setPolicyPublishError: function(policyGr, err) {
      this.setPolicyPublishStatus(policyGr, '6', err);
  },

  setPolicyPublishStatus: function(policyGr, status, err, currenctCalcTime, fullPolicyCalc, skipLastCalc) {
      var policyStateGr = this.policyStateUtil.getPolicyStateRecord(policyGr);
      var isLastCalcTime = policyStateGr.getValue('last_full_calc_time') ? true : false;

      policyStateGr.setValue('publish_status', status);
      if (this.policyUpdateDomainSeparation.isDomainSeparationPluginActive) {
          // track the publish_status update for this policy record for the current domain
          this.policyUpdateDomainSeparation.updatePolicyStatusMap(policyGr, status);
      } else {
          // set the publish_status on the policy record
          policyGr.setValue('publish_status', status);
          policyGr.update();
      }

      if (err != undefined)
          policyStateGr.setValue('publish_error', err);

      if (currenctCalcTime) {
          if (!skipLastCalc)
              policyStateGr.setValue('last_calc_time', currenctCalcTime);
          if (fullPolicyCalc || !isLastCalcTime)
              // Set last_full_calc_time if was not set before, even if not requested.
              policyStateGr.setValue('last_full_calc_time', currenctCalcTime);
      } else if (!isLastCalcTime)
          // this will prevent the policy from re-calculating over and over every minute.
          // We can get here if we had an error, or if the policy returns 0 CIs
          policyStateGr.setValue('last_full_calc_time', new GlideDateTime());

      policyStateGr.update();
  },

  setPublishProcessingPoliciesToError: function() {
      var policyStateGr = new GlideRecord('sn_agent_policy_state');
      policyStateGr.addQuery('policy.active', true);
      policyStateGr.addQuery('policy.is_draft', '0');
      policyStateGr.addQuery('publish_status', '4'); // 4 == processing
      policyStateGr.query();
      while (policyStateGr.next()) {
          var policyId = policyStateGr.getValue('policy');
          policyStateGr.setValue('publish_status', '6'); // 6 == error
          policyStateGr.setValue('publish_error', gs.getMessage('Something went wrong. Read system logs for more information.'));
          policyStateGr.update();

          // invalidate the cache entry for each policy that has had its policy state record modified by the batch update
          this.policyStateUtil.invalidateCacheEntry(policyId);

          if (!this.policyUpdateDomainSeparation.isDomainSeparationPluginActive)
              continue;

          // for domain separation, we also need to map the {domain, policy} to the new status
          // so that we can update the policy record publish_status after iterating over all domains
          var domainId = policyStateGr.policy.sys_domain;
          this.policyUpdateDomainSeparation.updatePolicyStatusMapFromId(domainId, policyId, '6');
      }

      if (this.policyUpdateDomainSeparation.isDomainSeparationPluginActive)
          // for domain separation, we already processed the publish_status for the policy records in the loop above
          // so we are done
          return;

      // but if domain separation is not active, we know we can update the policy record publish_status right here
      // since there are no domains to iterate over
      var policyGr = new GlideRecord('sn_agent_policy');
      policyGr.addActiveQuery();
      policyGr.addQuery('is_draft', '0');
      policyGr.addQuery('publish_status', '4'); // 4 == processing
      policyGr.setValue('publish_status', '6'); // 6 == error
      policyGr.setValue('publish_error', gs.getMessage('Something went wrong. Read system logs for more information.'));
      policyGr.setWorkflow(false);
      policyGr.updateMultiple();
  },

  resetPoliciesToClientsForMidSync: function(policiesSysIds) {
      // Mark the policy to ready to publish - this will cause the policies to sync down to the MID servers
      var filterResultGr = new GlideRecord('sn_agent_policy_clients');
      filterResultGr.addQuery("policy", policiesSysIds);
      filterResultGr.setValue("publish_status", '5'); // 5 = ready to publish.
      filterResultGr.updateMultiple();
  },

  getCisFromGroup: function(groupName, isProxyPolicy) {
      var groupRecord = new GlideRecord("cmdb_group");
      groupRecord.addQuery("group_name", groupName);
      groupRecord.query();
      if (!groupRecord.next()) {
          return undefined;
      }

      var groupId = groupRecord.getValue("sys_id");
      var results = JSON.parse(sn_cmdbgroup.CMDBGroupAPI.getAllCI(groupId));
      if (!results || !results.idList || results.idList.length < 1) {
          return undefined;
      }

      var ciRecord = new GlideRecord("cmdb_ci");
      if (!ciRecord.get('sys_id', results.idList[0]))
          return undefined;

      var className = ciRecord.getValue("sys_class_name");

      //find the most generic type that suits this class type
      var commonTable = this.getParentTable(className);
      var commonTableNoUnderscore = commonTable.replace(/_/g, '');

      // If the common table is the agents ci type (sn_agent_cmdb_ci_agent) - no need for a db view. Calculate the policy and return.
      if (commonTable == this.AGENT_PARENT_TABLE) {
          var agentCisGr = new GlideRecord(commonTable);
          agentCisGr.addQuery(commonTableNoUnderscore + "sys_id", "IN", results.idList); // NEED TO REPLACE WITH CHOOSE WINDOW AND CHUNKS
          return agentCisGr;
      }
      // This is a proxy policy (the agents are being selected by a different mechanism, and no need for a db view table)
      if (isProxyPolicy) {
          var monitoredCisGr = new GlideRecord(commonTable);
          monitoredCisGr.addQuery(commonTableNoUnderscore + "sys_id", "IN", results.idList); // NEED TO REPLACE WITH CHOOSE WINDOW AND CHUNKS
          return monitoredCisGr;
      }

      try {
          var agentsToMonitoredCisDbViewName = this.AGENT_POLICY_DB_VIEW_PREFIX + commonTable;
          if (!gs.tableExists(agentsToMonitoredCisDbViewName)) {
              var legacyPolicyCalc = new PolicyClientsGenerator();
              return legacyPolicyCalc.getCisFromGroup(groupName);
          }
          var agentsToMonitoredCisDbView = new GlideRecord(agentsToMonitoredCisDbViewName);
          agentsToMonitoredCisDbView.addQuery(commonTableNoUnderscore + "_sys_id", "IN", results.idList); // NEED TO REPLACE WITH CHOOSE WINDOW AND CHUNKS
          return agentsToMonitoredCisDbView;
      } catch (e) {
          return undefined;
      }
  },

  getClientsForProxyAgentsByCisForCheck: function(policyGr, monitoredCisGr, tmpPolicy, proxyAgentSysId, checkId) {
      var proxyGr = new GlideRecord("sn_agent_ci_extended_info");
      if (!proxyGr.get(proxyAgentSysId)) {
          gs.warn('PolicyClientsGeneratorNG: Got no proxy agent id');
          return {};
      }

      var proxyAgentId = proxyGr.getValue('agent_id');
      tmpPolicy[proxyAgentId] = {};

      // No need to auto bind the cis.
      if (!policyGr.auto_binding)
          return tmpPolicy;

      var tableName = this.getPolicyTableName(policyGr);

      //Using this variable to use % operator to assign monitoredCI to agents in roundrobin fashion
      var m = new MonitoringConfig();
      var isEntryPointView = (tableName == this.ENTRYPOINT_DB_VIEW);
      var dbViewPrefix = '';
      if (isEntryPointView)
          dbViewPrefix = 'endpoint_';

      while (monitoredCisGr.next()) {
          var monitoredCiSysId = monitoredCisGr.getValue(dbViewPrefix + 'sys_id');
          var values = {};
          this.populateCiParamsOnPolicy(tmpPolicy, proxyAgentId, monitoredCiSysId, checkId, monitoredCisGr);
          if (!tmpPolicy[proxyAgentId][monitoredCiSysId][checkId])
              tmpPolicy[proxyAgentId][monitoredCiSysId][checkId] = {};
          if (isEntryPointView) {
              var serviceName = monitoredCisGr.getValue('service_name');
              tmpPolicy[proxyAgentId][monitoredCiSysId][checkId]['ep_service_name'] = serviceName;
              values['ep_service_name'] = serviceName;
          }
          var targetIp = monitoredCisGr.getValue('ip_address');
          if (targetIp && targetIp.length > 0) {
              tmpPolicy[proxyAgentId][monitoredCiSysId][checkId]['target_ip'] = targetIp;
              values['target_ip'] = targetIp;
          }
          m.addProxyCheckCiParamsValues(tmpPolicy[proxyAgentId][monitoredCiSysId][checkId], values, proxyAgentId);
      }
  },

  getClientsByCisForCheckNoPolicy: function(monitoredCisGr, checkId, command, isTestCheckRun) {
      return this.getClientsByCisForCheck(undefined, monitoredCisGr, checkId, command, isTestCheckRun);
  },

  getClientsByCisForCheck: function(policyGr, monitoredCisGr, checkId, command, isTestCheckRun, proxyAgentSysId) {
      var tmpPolicy = {};
      tmpPolicy.clients_cis = {};
      monitoredCisGr.setCategory('acc-policy-calc');

      // Populate a fake agent to hold the ci Params - this way we can cache it.
      if (command && command.indexOf(this.CI_PARAM_KEY) > -1) {
          /* Structure will be
  			tmpPolicy.clients_cis.CI_PARAMS_TO_CHECK_PLACE_HOLDER[<check sys id>] = array of ci param keys
          */
          var checkGr = new GlideRecord('sn_agent_check');
          if (!checkGr.get('sys_id', checkId)) {
              checkGr = new GlideRecord('sn_agent_check_def')
              if (!checkGr.get('sys_id', checkId)) {
                  gs.error(gs.getMessage('Could not locate check with check id={0}', checkId))
                  return;
              }
          }
          tmpPolicy.clients_cis[this.CI_PARAMS_TO_CHECK_PLACE_HOLDER] = {};
          tmpPolicy.clients_cis[this.CI_PARAMS_TO_CHECK_PLACE_HOLDER][checkId] = this.getCiParamsFromCheck(checkGr, command);
      }

      var monitoredCiType = monitoredCisGr.getTableName();
      if (policyGr) {
          if (policyGr.is_service_filter) {
              var cis = this.getCisForServiceByServiceQuery(policyGr, monitoredCiType);
              if (!cis || cis.length < 1) {
                  return {}; // nothing.
              }
              monitoredCisGr.addQuery('sys_id', cis);
              monitoredCisGr.query(); // we can query a 2nd time while adding a filter.
          }

          if (this.isProxyPolicy(policyGr)) {
              if (!proxyAgentSysId) {
                  gs.warn('PolicyClientsGeneratorNG: getClientsByCisForCheck requested for proxy policy without a proxy agent')
                  return {}; // nothing
              }
              this.getClientsForProxyAgentsByCisForCheck(policyGr, monitoredCisGr, tmpPolicy, proxyAgentSysId, checkId);
              if (tmpPolicy.clients_cis)
                  delete tmpPolicy.clients_cis;

              return tmpPolicy;
          }
      }

      if (monitoredCiType == this.AGENT_PARENT_TABLE) {
          while (monitoredCisGr.next()) {
              var agentId = monitoredCisGr.getValue('agent_id');
              var monitoredCiSysId = monitoredCisGr.getValue('sys_id');
              this.populateCiParamsOnPolicy(tmpPolicy, agentId, monitoredCiSysId, checkId, monitoredCisGr);
          }

          if (tmpPolicy.clients_cis)
              delete tmpPolicy.clients_cis;

          return tmpPolicy;
      }

      var agentsToMonitoredCisDbViewName = this.AGENT_POLICY_DB_VIEW_PREFIX + monitoredCiType;
      // If no DB View - support legacy.
      if (!gs.tableExists(agentsToMonitoredCisDbViewName)) {
          var legacyPolicyCalc = new PolicyClientsGenerator();
          var checkToCiParams = {};
          var serviceFilter = "";
          var useServiceFilter = false;
          if (policyGr && policyGr.next()) {
              serviceFilter = policyGr.service_filter;
              useServiceFilter = policyGr.is_service_filter;
          }
          //check if we have refference to ci properties from the command
          if (command) {
              var ciParamsStart = command.indexOf(this.CI_PARAM_KEY);
              if (ciParamsStart > -1 && checkId)
                  checkToCiParams[checkId] = legacyPolicyCalc.parseCommandToCiParms(command, ciParamsStart);
          }
          return legacyPolicyCalc.getClientsByCis(monitoredCisGr, checkToCiParams, true, "", isTestCheckRun, serviceFilter, useServiceFilter);
      }

      var monitoredCisSysIdsArr = [];
      while (monitoredCisGr.next())
          monitoredCisSysIdsArr.push(monitoredCisGr.getValue('sys_id'));

      var monitoredCiTypeNoUnderscore = monitoredCiType.replace(/_/g, ''); // DB View do not support underscore on the variable side
      var agentsToMonitoredCisDbView = new GlideRecord(agentsToMonitoredCisDbViewName);
      agentsToMonitoredCisDbView.addQuery(monitoredCiTypeNoUnderscore + '_sys_id', monitoredCisSysIdsArr);
      agentsToMonitoredCisDbView.chooseWindow(0, this.MAX_DB_VIEW_RECORDS);
      agentsToMonitoredCisDbView.setCategory('acc-policy-calc');
      agentsToMonitoredCisDbView.query();

      while (agentsToMonitoredCisDbView.next()) {
          var agentId = this.getValueFromDbView(agentsToMonitoredCisDbView, this.AGENT_DB_VIEW_PREFIX, 'agent_id');
          var monitoredCiSysId = this.getValueFromDbView(agentsToMonitoredCisDbView, monitoredCiTypeNoUnderscore, 'sys_id');

          this.populateCiParamsOnPolicy(tmpPolicy, agentId, monitoredCiSysId, checkId, agentsToMonitoredCisDbView, monitoredCiTypeNoUnderscore);
      }

      if (tmpPolicy.clients_cis[this.CI_PARAMS_TO_CHECK_PLACE_HOLDER])
          delete tmpPolicy.clients_cis[this.CI_PARAMS_TO_CHECK_PLACE_HOLDER];

      if (tmpPolicy.clients_cis && Object.keys(tmpPolicy.clients_cis) < 1)
          delete tmpPolicy.clients_cis;

      return tmpPolicy;
  },

  populateCiParamsOnPolicy: function(tmpPolicy, agentId, monitoredCiSysId, checkId, monitoredCisGr, fieldPrefix) {
      if (!tmpPolicy[agentId])
          tmpPolicy[agentId] = {};
      if (!tmpPolicy[agentId][monitoredCiSysId])
          tmpPolicy[agentId][monitoredCiSysId] = {};

      if (!tmpPolicy.clients_cis[this.CI_PARAMS_TO_CHECK_PLACE_HOLDER] || !tmpPolicy.clients_cis[this.CI_PARAMS_TO_CHECK_PLACE_HOLDER][checkId])
          return;

      tmpPolicy[agentId][monitoredCiSysId][checkId] = {};
      tmpPolicy[agentId][monitoredCiSysId][checkId]["ci"] = {};
      var ciParams = tmpPolicy.clients_cis[this.CI_PARAMS_TO_CHECK_PLACE_HOLDER][checkId];
      for (var ciParmKey in ciParams) {
          var fieldToQueryFromDb = ciParmKey;
          if (fieldPrefix)
              fieldToQueryFromDb = fieldPrefix + '_' + ciParmKey;
          var ciParmValue = monitoredCisGr.getValue(fieldToQueryFromDb);
          if (!ciParmValue)
              ciParmValue = ciParams[ciParmKey].defaultValue;
          if (ciParmValue && ciParams[ciParmKey].encode) // we should encode the value
              ciParmValue = gs.base64Encode(ciParmValue);
          tmpPolicy[agentId][monitoredCiSysId][checkId]["ci"][ciParmKey] = ciParmValue;
      }
  },

  getAndUpdateReCalcFilterFlag: function(newFlagValue, domainId) {
      var hashGr = new GlideRecord("sn_agent_flags");
      hashGr.addQuery("name", "mon_filters_re_calc");
      if (!domainId)
          domainId = new sn_agent.DomainInfo().getCurrentDomain();

      hashGr.addQuery("sys_domain", domainId);
      hashGr.query();
      if (hashGr.next()) {
          var prevFlag = hashGr.getValue("hash");
          //don't update the db if flag wasn't changed
          if (prevFlag != newFlagValue) {
              hashGr.setValue("hash", newFlagValue);
              hashGr.update();
          }
          return prevFlag;

      } else {
          hashGr.setValue("name", "mon_filters_re_calc");
          hashGr.setValue("hash", newFlagValue);
          hashGr.insert();
          //if value doesn't exist, we should recalc
          return "true";
      }
  },

  legacyPolicyCalculation: function(policyGr, monitoredCiType) {
      var legacyCalc = new PolicyClientsGenerator();
      var currentCalcTime = new GlideDateTime().toString();
      var clientsJson = legacyCalc.getClientsJsonForSinglePolicy(policyGr);

      if (!clientsJson || Object.keys(clientsJson) < 1) {
          gs.info(gs.getMessage("PolicyClientsGeneratorNG: For policy {0}: Legacy calculatation results in empty results", policyGr.name));
          return {
              update: true,
              fullPolicyUpdate: true, // This will make sure that the last_full_calc_time is set correctly.
              updateTime: currentCalcTime,
              monitoredCiType: monitoredCiType,
          };
      }

      var monitoredCisToValues = {};

      // JSON structure is: clientsJson[agentId][monitoredCiId][checkId]["ci"][paramKey] = paramVal
      for (var agentId in clientsJson) {
          for (var monitoredCiSysId in clientsJson[agentId]) {
              var ciParams = [];
              // Check if we need to construct ci params.
              var numberOfChecksWithCiParms = Object.keys(clientsJson[agentId][monitoredCiSysId]).length;
              if (numberOfChecksWithCiParms > 0) {
                  for (var check in clientsJson[agentId][monitoredCiSysId]) {
                      if (clientsJson[agentId][monitoredCiSysId][check]["ci"]) {
                          for (var paramKey in clientsJson[agentId][monitoredCiSysId][check]["ci"]) {
                              var val = clientsJson[agentId][monitoredCiSysId][check]["ci"][paramKey];
                              if (val)
                                  ciParams.push(paramKey + "=" + val);
                          }
                      }
                  }
              }

              ciParams.sort();

              if (!monitoredCisToValues[monitoredCiSysId])
                  monitoredCisToValues[monitoredCiSysId] = {};
              if (!monitoredCisToValues[monitoredCiSysId].agents)
                  monitoredCisToValues[monitoredCiSysId].agents = {};
              // No need to save the policy ID as we work policy by policy
              monitoredCisToValues[monitoredCiSysId].agents[agentId] = "";
              monitoredCisToValues[monitoredCiSysId].ci_params = ciParams.join(',');
          }
      }

      // We are sending full policy calculation as false, even though we do full calc because this is how we will compare 
      // the current state of the db and the calculated values to know if there was an update or not.
      // if no update, no need to send config_publish
      var rtnObj = this.updateAndInsertIntoPolicyToMonitoredCis(policyGr, monitoredCisToValues, currentCalcTime, false, false);
      rtnObj.fullPolicyUpdate = true; // This will make sure that the last_full_calc_time is set correctly.
      rtnObj.updateTime = currentCalcTime;
      rtnObj.monitoredCiType = monitoredCiType;

      // Find records that needs to be deleted as they are not in the current json (current json is full calc always)
      var policyMonitoredCIsGr = new GlideRecord('sn_agent_policy_monitored_cis');
      policyMonitoredCIsGr.setWorkflow(false);
      policyMonitoredCIsGr.addQuery('monitored_ci', 'NOT IN', Object.keys(monitoredCisToValues));
      policyMonitoredCIsGr.addQuery('policy', policyGr.getValue('sys_id'));
      policyMonitoredCIsGr.query();

      // If we have anything to delete - this is an update
      rtnObj.update = rtnObj.update || policyMonitoredCIsGr.hasNext();
      policyMonitoredCIsGr.deleteMultiple();

      return rtnObj;
  },

  doesTableContainSysUpdatedOn: function(gr, dbViewPrefix) {
      var tableName = gr.getTableName();
      if (tableName.startsWith('cmdb_ci_') || tableName == 'sn_agent_cmdb_ci_agent')
          return true;

      var fieldNameNeeded = dbViewPrefix + 'sys_updated_on'
      var field = gr.getElement(fieldNameNeeded);
      if (field != null && field.getName() == fieldNameNeeded)
          return true;

      return false;
  },

  getPolicyTableName: function(policyGr) {
      var tableName;

      if (policyGr.monitored_ci_type_script) {
          var evaluator = new GlideScopedEvaluator();
          var scriptGr = evaluator.evaluateScript(policyGr, 'monitored_ci_script');
          if (scriptGr == null) {
              gs.error("The scriptGr is invalid");
          } else {
              tableName = scriptGr.getTableName();
          }
      } else if (policyGr.monitored_ci_type_group) {
          tableName = this.getPolicyTableNameFromGroup(policyGr);
      } else {
          tableName = policyGr.getValue('table');
      }

      return tableName;
  },

  getPolicyTableNameFromGroup: function(policyGr) {
      var groupName = policyGr.monitored_ci_group;
      var groupRecord = new GlideRecord("cmdb_group");
      groupRecord.addQuery("group_name", groupName);
      groupRecord.query();
      if (!groupRecord.next()) {
          gs.error(gs.getMessage("PolicyClientsGeneratorNG: in policyCalculationForMonitoredCisByGroup(), could not find any group named {0}", groupName));
          return;
      }

      var groupId = groupRecord.getValue("sys_id");
      var results = JSON.parse(sn_cmdbgroup.CMDBGroupAPI.getAllCI(groupId));
      if (!results || !results.idList || results.idList.length < 1) {
          return;
      }

      var ciRecord = new GlideRecord("cmdb_ci");
      if (!ciRecord.get('sys_id', results.idList[0])) {
          gs.error(gs.getMessage('PolicyClientsGeneratorNG: For policy {1}, something went wrong when trying to get data for {1}', [policyGr.name, ciRecord]));
          return;
      }
      var className = ciRecord.getValue("sys_class_name");

      return this.getParentTable(className);
  },

  type: 'PolicyClientsGeneratorNG'
};

Sys ID

97c925e4539551108ec4ddeeff7b12c1

Offical Documentation

Official Docs: