Name

global.Discovery

Description

Various javascripts used in discovery

Script

var Discovery = Class.create();

Discovery.throttlingInfoMessage = function() {
  if (gs.getProperty('glide.discovery.throttling.enabled', 'false') == 'true')
  	gs.addInfoMessage(gs.getMessage('Discovery sensor throttling is currently enabled'));
};

Discovery.TYPE = {
  ConfigurationItems: 'CIs',
  IpAddresses: 'IPs',
  Networks: 'Nets',
  Service: 'Service',
  Serverless: 'Hostless',
  CloudApplication: 'Cloud',
  CloudResources: 'Cloud Resources',
  Certificates: 'Certificates'
};

Discovery.prototype = {
  initialize: function() {},
  
  discoveryStartJob: function() {
  	if (!this.isValidDiscoverySchedule(current))
  		return;
  	
  	// LDCs and Sub Accounts Discovery Patterns are triggered via the Script Include - "CloudWizardDiscovery"
  	// In order to access the APIs of the Cloud Wizard Script Include, the current logged in (or) schedule's run_as user should have either 'mid_server' or 'discovery_admin' role
  	// If none of those roles exists then user is not allowed to access the APIs thus resulting in the error and not creating the Discovery status thereby
  	var hasRequiredRole = gs.getUser().hasRole('mid_server') || gs.getUser().hasRole('discovery_admin');
  	if (current.hasOwnProperty('discover') && (current.discover + '' == 'Cloud Resources') && (current.sys_id + '') && hasRequiredRole)
  		this._syncAndUpdateLDCsAndSubAccountsForSchedule(current);
  	
  	gs.print('**********');
  	gs.log('  STARTING DISCOVERY: ' + current.name);
  	gs.print('**********');
  	this.clearPreviousServiceJobsInStartingState(current, job);
  	var sd = new StartDiscovery();
  	sd.startFromSchedule(current, job);
  	if (current.disco_run_type == "periodically") {
  		this.updateNextRunTimeForPeriodicSchedule(current, job);
  	}
  },
  
  discoverNow: function(scheduleGr) {
  	if (!this.isValidDiscoverySchedule(scheduleGr))
  		return "";

  	// trigger this discovery...
  	var jobSysId = SncTriggerSynchronizer.executeNow(scheduleGr);

  	// Get the scheduler created discovery status, and update it with proper discover now description and source
  	var status = new GlideRecord('discovery_status');
  	status.addQuery('scheduler_job', jobSysId);
  	var timeoutSeconds = gs.getProperty('glide.discovery.discover_now_timeout', 10);
  	// Searches for max of timeoutSeconds seconds.
  	for (var attempts = 0; attempts < timeoutSeconds * 4; attempts++) {
  		status.query();
  		if (status.hasNext())
  			break;
  		gs.sleep(250);
  	}
  	if (!status.next())
  		return null;
  	status.setValue('description', 'Discover Now');
  	status.setValue('source', 'Discover_now_schedule');
  	status.update();
  	
  	var datacenterType = new CloudResourceDiscoveryUtil().fetchDatacenterTypeFromSchedule(scheduleGr.getUniqueValue());
  	var usePatternDiscovery = gs.getProperty('sn_cmp.use_pattern_discovery.' + datacenterType, 'true');
  	if (usePatternDiscovery.equals('true') && !(datacenterType.equals('cmdb_ci_vcenter_datacenter')))
  		this._persistStatusToLDCs(status);

  	return status.sys_id;
  },
  
  
  _persistStatusToLDCs: function(status) {
  	var configData = new DiscoveryCloudConfig().getConfig(status.scheduleID + '');
  	var serviceAccountSysId = configData.service_account.sys_id + '';
  	var dcType = configData.service_account.datacenter_type + '';
  	var ldcSysIds = [];
  	if (configData.all_datacenters) {
  		var hostedOnRelationSysId = new CloudResourceDiscoveryUtil().getRelTypeId('Hosted on::Hosts');

  		var relGR = new GlideRecord('cmdb_rel_ci');
  		relGR.addQuery('child', serviceAccountSysId);
  		relGR.addQuery('type', hostedOnRelationSysId);
  		relGR.query();
  		while (relGR.next()) {
  			var parentClassName = relGR.parent.sys_class_name;
  			if (parentClassName.equals(dcType))
  				ldcSysIds.push(relGR.getValue('parent'));
  		}
  	} else {
  		var datacenterList = configData.datacenters;
  		for (var i in datacenterList)
  			ldcSysIds.push(datacenterList[i].sys_id + '');
  	}

  	for (var j in ldcSysIds) {
  		var ldcGR = new GlideRecord(dcType);
  		ldcGR.get(ldcSysIds[j]);
  		ldcGR.discovery_status = status.sysID;
  		ldcGR.update();
  	}
  },
  
  
  /**
  * API meant to get the latest information about the sub-accounts and the LDC information automatically thereby
  * keeping always keeping the information upto date in Cloud Service Account and Provider-specific Datacenter
  * tables. 
  * Major goals of this API is to
  * #1 Get and create the LDC config if any new Sub-Account is added in the Cloud
  * #2 Get the LDC information as well in order to sync up with missing or unavailale LDCs and update the LDC Config
  * #3 Delete the invalid LDC config entries
  **/
  _syncAndUpdateLDCsAndSubAccountsForSchedule: function(schedule) {
  	var mainServiceAccountSysId, allMembersInformation,
  		memberAccs = [],
  		midServer = null,
  		scheduleName = schedule.name + '',
  		scheduleSysID = schedule.sys_id + '',
  		scheduleType = schedule.discover + '',
  		scheduleAccelratorConfig = schedule.accel_config + '',
  		cloudWizardDiscovery = new CloudWizardDiscovery(),
  		cloudResourceDiscoveryUtil = new CloudResourceDiscoveryUtil(),
  		cloudDiscoveryScheduleConfig = new CloudDiscoveryScheduleConfig();
  	
  	try {
  		/** Returns information about schedule. Example: 
  		*	{ 
  		*		"service_account":{ "sys_id":"a1b12b1707fb0010ce5bf1bb4bd300a3", ........... , "datacenter_type":"cmdb_ci_aws_datacenter"},
  		*		"is_master_account":true,
  		*		"parent_account":null,
  		*		"all_datacenters":false,
  		*		"all_accounts_for_master":false,
  		*		"member_accounts":[
  		*			{ "sys_id":"40c1ab1707fb0010ce5bf1bb4bd3004c", ................ , "parent_account":"a1b12b1707fb0010ce5bf1bb4bd300a3" },
  		*			{ "sys_id":"c8c1ab1707fb0010ce5bf1bb4bd3004d", .................., "parent_account":"a1b12b1707fb0010ce5bf1bb4bd300a3" }],
  		*		"datacenters":[
  		*			{ "sys_id":"47d1a35707fb0010ce5bf1bb4bd300fe", "name":"ap-northeast-1", "region":"ap-northeast-1" },
  		*			{ "sys_id":"4fd1a35707fb0010ce5bf1bb4bd300fd", "name":"ap-northeast-2", "region":"ap-northeast-2" } ]
  		*	}
  		**/
  		var configData = new DiscoveryCloudConfig().getConfig(scheduleSysID);
  	
  		// If no config data is returned
  		if (!configData)
  			return;
  		
  		// If the system property - "glide.discovery.cdu.auto_refresh_sub_accounts_and_ldcs" is not true means
  		// user don't want to auto refresh the sub-accounts and LDCs so we just clean up the improper schedule
  		// configuration and nothing else
  		if (gs.getProperty('glide.discovery.cdu.auto_refresh_sub_accounts_and_ldcs', 'false') != 'true') {
  			cleanUpInvalidLDCConfigData(scheduleSysID, configData);
  			return;
  		}
  	
  		mainServiceAccountSysId = configData.service_account.sys_id + '';
  		
  		// Lets get the existing selected sub-accounts in the system as per schedule configuration to refresh the LDC
  		// information. This way we can take care if user bypasses the LDC discovery for any sub-accounts or new LDCs
  		// are applicable for the account
  		if (configData.all_accounts_for_master)
  			memberAccs = getExistingMemberAccountsInSystem(configData.service_account, scheduleSysID);
  		else
  			memberAccs = configData.hasOwnProperty('member_accounts') ? configData.member_accounts : memberAccs;
  		
  		// We do the LDC refresh for the Master Member Accounts based on the schedule configuration retreived but not
  		// for normal accounts because the LDCs for the normal regular accounts can be retrieved on demand by the user
  		// via the UI action provided by the CDU UI under the Slush Bucket.
  		if (!configData.is_master_account && !configData.all_accounts_for_master && !memberAccs.length)
  			return;
  		
  		// Refresh the LDCs for member accounts or related projects (GCP) so that missing or deleted LDCs are restored
  		// back in the system.
  		midServer = cloudResourceDiscoveryUtil.getSelectedMidFromSchedule(scheduleSysID);
  		// Adding undefined in the parameter because inside Cloud Wizard Discovery (CWD) we have written if else condition based on undefined. 
  		// We can't change both simultaneously as CWD file is in store app and we have to make it backward compatible.
  		var statusId002 = cloudWizardDiscovery.refreshDatacenters(mainServiceAccountSysId, midServer, undefined, scheduleName);
  		if(!statusId002)
  			return;
  		var statusResult002 = waitAndGetDiscoveryResult(String(statusId002));
  		if (!statusResult002.hasOwnProperty('ci_list') || statusResult002.state == 'processing')
  				gs.info('Discovery Status Information for refreshing the LDCs of Existing Accounts via Discovery Status (sys_id) - "' + statusId002 +'" is\t:\t' + JSON.stringify(statusResult002));
  		
  		
  		// As above, we refreshed the datacenters, we fix the current schedule configuration by mapping the missing LDCs
  		// of the service accounts associated to the schedule but we do the check if the schedule is configurated to use
  		// specific LDCs instead of all LDCs and then decide to modify .
  		if (!configData.all_datacenters) {
  			var serviceAccountGR = new GlideAggregate("cmdb_ci_cloud_service_account");
  			var joinGR = serviceAccountGR.addJoinQuery("cmp_discovery_ldc_config", "sys_id", "service_account");
  			joinGR.addCondition('discovery_schedule', scheduleSysID);
  			serviceAccountGR.addQuery('is_master_account', 0);
  			serviceAccountGR.addQuery('exclude_from_discovery', false);
  			
  			// GCP Org Support is a special case because it doesn't align with Master-Member framework available in
  			// Cloud Service Account. Instead of that, it's respective related accounts are grouped via organization_id
  			// so that's the reason we ignore the sys_id of the schedule's main service account and to look for other
  			// GCP service accounts
  			if (configData.service_account.datacenter_type == 'cmdb_ci_google_datacenter')
  				serviceAccountGR.addQuery('sys_id', '!=', configData.service_account.sys_id );
  			serviceAccountGR.query();
  			
  			while (serviceAccountGR.next()) {
  				// we update the LDC config now for each sub account
  				configData.datacenters.forEach(function(ldcObj) {
  					if (!findCloudDiscoveryLDCConfig(serviceAccountGR.getValue('sys_id'), scheduleSysID, ldcObj.name, configData.all_accounts_for_master))
  						saveCloudDiscoveryLDCConfig(serviceAccountGR.getValue('sys_id'), scheduleSysID, ldcObj.name, configData.all_accounts_for_master);
  				});
  			}
  		}
  					
  		// A check to confirm whether we dealing with Non GCP Org specific account (GCP Cloud Service Account whose
  		// Organization_id has some value). The reason why GCP org support is exceptional is cloud service account
  		// doesn't align with is_master_account flag instead of that, it uses the organization_id.
  		var discoverSubAccounts = true;
  		if (!configData.is_master_account && (configData.service_account.datacenter_type !='cmdb_ci_google_datacenter'))
  			discoverSubAccounts = false;
  		
  		// If user has selected all accounts for master then it means if any new sub-account is discovered then we need to
  		// create an LDC config for it also. There by syncing the new accounts automatically for the current discovery
  		// schedule. We support related projects for GCP as well but making sure that current main service account belongs
  		// to an organization.
  		if ((configData.all_accounts_for_master && discoverSubAccounts) || (configData.service_account.datacenter_type =='cmdb_ci_google_datacenter' && configData.all_accounts_for_master && (configData.service_account.organization_id+'' || configData.service_account.is_master_account))) {
  			
  			// Refresh the sub-accounts information to get the latest ones if anything added recently
  			midServer = cloudResourceDiscoveryUtil.getSelectedMidFromSchedule(scheduleSysID);
  			var statusId = cloudWizardDiscovery.refreshMemberAccounts(mainServiceAccountSysId, midServer, scheduleName);
  			if(!statusId)
  				return;
  			var statusResult = waitAndGetDiscoveryResult(String(statusId));
  			
  			// If pattern failed to retrieve sub accounts for some reason then let's not disturb the existing LDC config
  			if (!statusResult.hasOwnProperty('ci_list') || statusResult.state == 'processing' || (statusResult.ci_list).length == 0)
  				return;
  			
  			allMembersInformation = statusResult.ci_list;
  			allMembersInformation = allMembersInformation.filter(function(accObj){ return (accObj.sys_id != mainServiceAccountSysId && !accObj.exclude_from_discovery); });

  			//Mark stale sub-accounts as retired and delete from discovery-schedule by comparing latest sub-accounts and existing ones
  			cloudResourceDiscoveryUtil.deleteStaleAccountRecords(memberAccs, allMembersInformation, false, 'sys_id', scheduleSysID);

  			// Get all member accounts existing in the system and compare with newly discovered sub accounts if any new
  			// member account(s) added
  			var existingSubAccountsSysIDs = [];
  			memberAccs.forEach(function(acc) { existingSubAccountsSysIDs.push(acc.sys_id ? acc.sys_id : acc.sysId); });
  			allMembersInformation = allMembersInformation.filter(function(accObj){ return (existingSubAccountsSysIDs.indexOf(accObj.sys_id) == -1); });
  			
  			allMembersInformation.forEach(function(accObj) {
  				//If old stale account is rediscovered again, first mark them installed
  				cloudResourceDiscoveryUtil.markOldStaleInstalled(accObj, 'sys_id');
  				midServer = cloudResourceDiscoveryUtil.getSelectedMidFromSchedule(scheduleSysID);
  				var dcStatusId = cloudWizardDiscovery.refreshDatacenters(accObj.sys_id, midServer);
  				waitAndGetDiscoveryResult(dcStatusId);
  				
  				// For the newer sub accounts discovered so we need to create the LDC config accordingly
  				if (configData.all_datacenters)
  					saveCloudDiscoveryLDCConfig(accObj.sys_id, scheduleSysID, '', configData.all_accounts_for_master);
  				else {
  					for (var ldc in configData.datacenters)
  						saveCloudDiscoveryLDCConfig(accObj.sys_id, scheduleSysID, configData.datacenters[ldc].name, configData.all_accounts_for_master);
  				}
  			});
  		}
  		
  		// By this point we've upgraded the LDC config for both existing cloud service account and newer accounts as
  		// well, if discovered, so we need to delete the invalid LDC config entries.
  		cleanUpInvalidLDCConfigData(scheduleSysID, configData);
  	} catch (e) {
  		return;
  	}

  	function getExistingMemberAccountsInSystem(serviceAccountInfo, scheduleSysID) {
  		var existingMemberAccountsInSystem = [];
  		
  		// Making join with ldc config table to get the schedule data otherwise if the same account has two schedule it will not update the second schedule 
  		var serviceAccountGR = new GlideRecord('cmp_discovery_ldc_config');
  		serviceAccountGR.addQuery('discovery_schedule', scheduleSysID);
  		
  		var joinGR = serviceAccountGR.addJoinQuery("cmdb_ci_cloud_service_account", "service_account", "sys_id");
  		joinGR.addCondition('is_master_account', 0);
  		joinGR.addCondition('exclude_from_discovery', false);
  					
  		if (serviceAccountInfo.datacenter_type == 'cmdb_ci_google_datacenter' && serviceAccountInfo.organization_id) {
  			joinGR.addCondition('organization_id', serviceAccountInfo.organization_id);
  			joinGR.addCondition('sys_id', '!=', serviceAccountInfo.sys_id + '');
  		} else {
  			joinGR.addCondition('parent_account', serviceAccountInfo.sys_id + '');
  		}
  		serviceAccountGR.query();
  		while (serviceAccountGR.next())
  			existingMemberAccountsInSystem.push({
  				'sys_id': serviceAccountGR.getValue('service_account'),
  				'is_master_account':  JSUtil.getBooleanValue(serviceAccountGR, "is_master_account"),
  				'datacenter_url' :  serviceAccountGR.getValue("datacenter_url"),
  				'datacenter_type' : serviceAccountGR.getValue("datacenter_type"),
  				'parent_account' : serviceAccountGR.getValue("parent_account"),
  				'account_id' : serviceAccountGR.getValue("account_id"),
  				'discovery_credentials' : serviceAccountGR.getValue("discovery_credentials"),
  				'organization_id' : serviceAccountGR.getValue("organization_id"),
  				'exclude_from_discovery' : (serviceAccountGR.getValue("exclude_from_discovery") == 1)
  			});
  		
  		return existingMemberAccountsInSystem;
  	}
  	
  	function waitAndGetDiscoveryResult(statusId) {
  		var retries = 0;
  		var resultObj = cloudWizardDiscovery.getDiscoveryResult(statusId);
  		while (resultObj.state == 'processing' && retries < 30) {
  			gs.sleep(15000); // Sleep 15 Secs
  			resultObj = cloudWizardDiscovery.getDiscoveryResult(statusId);
  			retries++;
  		}
  		return resultObj;
  	}
  	
  	function findCloudDiscoveryLDCConfig(service_account, scheduleId, ldc, all_accounts_for_master) {
  		var ldcId = cloudResourceDiscoveryUtil.getLogicalDatacentersByNameAndServiceAccount(service_account, ldc);
  		var gr = new GlideRecord('cmp_discovery_ldc_config');
  		gr.addQuery('service_account', service_account);
  		gr.addQuery('discovery_schedule', scheduleId);
  		gr.addQuery('ldc', ldcId);
  		gr.addQuery('all_accounts_for_master', all_accounts_for_master);
  		gr.query();
  		return gr.next();
  	}
  	
  	function saveCloudDiscoveryLDCConfig(service_account, scheduleId, ldc, all_accounts_for_master) {
  		var ldcId = "";
  		if(JSUtil.notNil(ldc))
  			ldcId = cloudResourceDiscoveryUtil.getLogicalDatacentersByNameAndServiceAccount(service_account, ldc);
  		var gr = new GlideRecord('cmp_discovery_ldc_config');
  		gr.initialize();
  		gr.setValue('service_account', service_account);
  		gr.setValue('discovery_schedule', scheduleId);
  		if (JSUtil.notNil(ldcId))
  			gr.setValue('ldc', ldcId);
  		if (JSUtil.notNil(all_accounts_for_master))
  			gr.setValue('all_accounts_for_master', all_accounts_for_master);
  		return gr.insert();
  	}
  	
  	function cleanUpInvalidLDCConfigData(scheduleSysID, scheduleConfigData) {
  		var gr = new GlideRecord('cmp_discovery_ldc_config');
  		gr.addQuery('discovery_schedule', scheduleSysID);
  		gr.addQuery('service_account.is_master_account', 0);
  		gr.addQuery('all_accounts_for_master', scheduleConfigData.all_accounts_for_master);
  		(scheduleConfigData.all_datacenters) ? gr.addNotNullQuery('ldc') : gr.addNullQuery('ldc');
  		gr.query();
  		gr.deleteMultiple();
  	}
  },

  isValidDiscoverySchedule: function(schedule) {
  	var title = 'Discovery schedule ' + schedule.name + ' is aborted';

  	//Adding check for VM Schedule. If an IP schedule has a parent cloud schedule, Discover all the IPs of the VM discovery.
  	if (schedule.discover.equals('CIs') && new CloudResourceDiscoveryUtil().hasParentCloudSchedule(schedule.sys_id))
  		return true;

  	if (schedule.discover != 'Web Service') {
  		if (this.isValidRange(schedule))
  			return true;

  		if (this.hasZeroRange) {
  			this.warnMsg(title + ': There is a Discovery IP Range record using an invalid 0 or 0.0.0.0 netmask. Please address before re-running this schedule.');
  			return false;
  		}

  		if (this.isValidServiceDiscovery(schedule) || this.isValidCloudResources() || this.isValidUrlCertificate(schedule))
  			return true;
  	}

  	if (this.isValidWebService(schedule))
  		return true;

  	if (this.isValidPattern(schedule))
  		return true;

  	this.warnMsg(title + ': No active range or account specified for Discovery');
  	return false;
  },
  
  warnMsg: function(msg) {
  	var log = GlideLog;
  	log.warn(msg);
  },
  
  isValidRange: function(schedule) {
  	// If this is a Service schedule, verify it is valid and return true.
  	// This is required in order to trigger "After Discovery" type Service schedules.
  	if(this.isValidServiceDiscovery(schedule))
  		return true;
  	// Checking discovery IP ranges
  	var gr = new GlideRecord('discovery_range_item');
  	gr.addQuery('schedule', schedule.sys_id);
  	gr.addQuery('active', 'true');
  	gr.query();
  	while (gr.next()) {
  		if (!this.checkZeroMask(gr))
  			return false;
  	}
  	
  	// No need to check for range set if schedule type is only Basic
  	if (schedule.discover == 'Devices')
  		return false;
  	
  	//Checking discovery range sets
  	var hasValidRangeSetItem = false;
  	var dsgr = new GlideRecord('discovery_schedule_range');
  	dsgr.addQuery('dscheduler', schedule.sys_id);
  	dsgr.query();
  	while (dsgr.next()) {
  		var drgr = new GlideRecord('discovery_range');
  		if (dsgr.range)
  			drgr.get('sys_id', dsgr.range);
  		if (drgr.active == false)
  			continue;
  		
  		// Look at the range items to see if any of them are active...
  		var drigr = new GlideRecord('discovery_range_item');
  		drigr.addQuery('parent', dsgr.range);
  		drigr.addQuery('active', 'true');
  		drigr.query();
  		while (drigr.next()) {
  			if (!this.checkZeroMask(drigr))
  				return false;
  			hasValidRangeSetItem = true;
  		}
  	}

  	// Check if we have any valid discovery_range_item record for this schedule
  	if (gr.getRowCount() > 0 || hasValidRangeSetItem)
  		return true;
  	return false;
  },
  
  checkZeroMask: function(rangeGr) {
  	if (rangeGr.type == 'IP Network' && (rangeGr.netmask == "0" || rangeGr.netmask == "0.0.0.0")) {
  		this.hasZeroRange = true;
  		return false;
  	}
  	return true;
  },

  isValidServiceDiscovery: function(schedule) {
  	if (schedule.discover !=  'Service')
  		return false;
  	
  	// Either ci_id or ci_type should be populated.
  	if (!schedule.ci_id && !schedule.ci_type)
  		return false;
  	
  	return true;
  },
  
  /*
  *	This function sets all previous jobs of the current schedule that are in Starting state
  *	to Canceled state in order to allow for a new attempt at running this schedule. If left at Starting state,
  *	new job will be canceled, and schedule will not run again.
  *   If status is stuck in Active state, check if no ECC queue records are being processed, otherwise cancel the job.
  *	@schedule: The discovery_schedule that is about to start.
  *	@job:  The sys_trigger record of the current schedule.
  */
  clearPreviousServiceJobsInStartingState: function(schedule, job){
  	if (schedule.discover ==  'Service'){
  		var gr = new GlideRecord('discovery_status');
  		// if this is not a repeat job, then it was started using the "run now" UI action.
  		// in this case we ignore the current job id, which is already in the DB.
  		if(JSUtil.nil(job.repeat))
  			gr.addQuery('scheduler_job', '!=', job.sys_id);
  		gr.addQuery('dscheduler', schedule.sys_id);
  		var qc = gr.addQuery('state', 'Starting');
  		qc.addOrCondition('state', 'Active');
  		gr.query();
  		while(gr.next()){
  			if(gr.getValue('state') == 'Starting'){
  				gr.setValue('state', 'Canceled');
  				gr.update();
  				var msg = 'Canceled previously started schedule ' + gr.getValue('number') + ' stuck in Starting state';
  				gs.addErrorMessage(msg);
  			}
  			// If state is 'Active' check if all of its ECC queue records are in 'processed' or 'error' state
  			// otherwise, cancel the job. we use SncDiscoveryCancel because a business rule on discovery_status prevents updating records directly
  			// under certain conditions.
  			else if(gr.getValue('state') == 'Active'){
  				var eccGr = new GlideRecord('ecc_queue');
  				eccGr.addQuery('agent_correlator', gr.getValue('sys_id'));
  				eccGr.query();
  				var inProgress = 'false';
  				while(eccGr.next()){
  					if(eccGr.getValue('state') != 'processed' && eccGr.getValue('state') != 'error'){							
  						inProgress = 'true';
  						break;
  					}
  				}
  				if(inProgress == 'false'){
  					gs.print('inProgress = ' + inProgress);
  					SncDiscoveryCancel().cancelAll(gr.getUniqueValue());
  					var msg = 'Canceled previously started schedule ' + gr.getValue('number') + ' stuck in Active state';
  					gs.addErrorMessage(msg);						
  				}						
  			}
  		}
  	}
  },
  
  isValidWebService: function(schedule) {
  	var s = new DiscoverySchedule(schedule);
  	return s.valid;
  },
  	
  isValidPattern: function(schedule) {
  	var s = new DiscoverySchedule(schedule);
  	return s.valid;
  },
  
  isValidCloudResources: function(schedule) {
  	var s = new DiscoverySchedule(schedule);
  	return s.valid;
  },

  isValidUrlCertificate: function(schedule) {
      var s = new DiscoverySchedule(schedule);
      return s.valid;
  },

  /*
   * Starts a discovery of the CI with the given sysID, returning the sysID of the status record.  Returns null if the
   * CI could not be discovered.
   * @cmdb_ci: The sysID of the CI to be discovered.
   * @source:  Who calls this function (optional)
   * returns:  The sysID of the status record found or created, or null if the CI could not be discovered.
   */
  discoveryFromCI: function(cmdb_ci, source) {
      var ips = this.getIPsInCI(cmdb_ci);
      if (!ips)
          return null;

      var params = this.getScheduleContainingAnyIP(ips, cmdb_ci);
      if (!params)
          return null;

      var sd = new StartDiscovery(source);
      var statusID = sd.startFromIP(params.schedule, params.ip);

      return statusID;
  },

  /*
   * Starts a discovery based on an IP address. If defaultMID is specified, then use it. Otherwise look for it through MID server finder
   * @ip: a single IP address
   * @midserver: name of the MID server (without the mid.server prefix) (mandatory if source argument is not undefined)
   * @source: who runs discovery (optional)
   */
  discoveryFromIP: function(ip, midserver, source) {
      var midserver = midserver;
      var autoSelectMid = false;

      // We've got a MID server name, but we need to verify it.
      if (midserver) {
          var agentCache = new SNC.ECCAgentCache();
          var gr = agentCache.getByName(midserver);

          if (!gr) {
              gs.log("Invalid MID server sys_id " + midserver);
              return;
          } else
              midserver = gr.sys_id + ''; // in case the midserver argument was the name instead of sys_id
      } else {
          try {
              midserver = new SNC.MidSelector().selectAnyMidServer('Discovery', [ip], null);
          } catch (e) {
              gs.log(e);
              return;
          }

          autoSelectMid = true;
      }

      var globalIPExclusion = new GlobalIPExclusionUtil();
      if (SNC.IPAddress.isV4(ip) && globalIPExclusion.contains(ip)) {
          var msg = 'Cannot Discover IP address <' + ip + '>, it is part of Global IP Exclusion List';
          gs.addErrorMessage(msg);
          return;
      } else {
          var schedule = new DiscoveryQuickSchedule(ip, midserver, autoSelectMid);

          var sd = new StartDiscovery(source);
          var statusID = sd.startFromIP(schedule);

          return statusID;
      }

  },
  
  
  discoveryFromDatacenter: function(datacenterSysId, serviceAccountSysId /* optional */) {
  	var sd = new StartDiscovery();
      
  	return sd.startCloudDiscoveryFromDatacenter(datacenterSysId, serviceAccountSysId);
  },

  discoveryFromDatacenterWithStatus: function(statusId, datacenterSysId, serviceAccountSysId /* optional */) {
  	var sd = new StartDiscovery();
      
  	return sd.startCloudDiscoveryFromDatacenterWithStatus(statusId, datacenterSysId, serviceAccountSysId);
  },

  /*
   * Runs a schedule that refreshes the processes and connections for a specified host.
   */
  refreshADM: function(ciId, osType, ip, source) {
      var recentRefreshAttempt = this.recentRefreshAttempt(ciId);
      if (recentRefreshAttempt == 'not_active') {
          gs.log('Recent ADM attempt was found for ' + ip);
          return null;
      } else if (recentRefreshAttempt == null) {
          var schedule = new ADMRefreshSchedule(ip, ciId);
          gs.log('RefreshADM: ' + schedule.name + ' ' + ciId + ' ' + osType + ' ' + ip + ' ' + source);

          var sd = new StartDiscovery(source);
          var statusID = sd.startAdmFromIP(schedule, ciId, osType, ip);
          return statusID;
      } else
          return recentRefreshAttempt;
  },

  /*
   * checks if a recent ADM refresh attempt was made on this host.
   */
  recentRefreshAttempt: function(ciId) {
      var updatedTime = new GlideDateTime(); // current time
      gs.log('update time = ' + updatedTime);
      var hostConnsRelevanceTime = GlideProperties.get("sa.cmdb_tcp_last_refresh_attempt_time_minutes", 1440);
      var timeToSubtract = hostConnsRelevanceTime * 60 * 1000;
      gs.log('timeToSubtract = ' + timeToSubtract);
      updatedTime.subtract(timeToSubtract);
      gs.log('new updateTime = ' + updatedTime);
      var gr = new GlideRecord('discovery_status');
      gr.addQuery('dscheduler', ciId);
      gr.addQuery('sys_updated_on', '>=', updatedTime.getValue());
      gr.orderByDesc('sys_updated_on');
      gr.setLimit(1);
      gr.query();
      if (gr.next()) {
          var state = gr.getValue('state');
          if (state == 'Active')
              return gr.getValue('sys_id');
          else
              return 'not_active';
      }
      return null;
  },

  updateNextRunTimeForPeriodicSchedule: function(scheduleGR, job) {
  	var gdt = new GlideDateTime(job.next_action);
  	gdt.add(scheduleGR.run_period.dateNumericValue());
  	scheduleGR.run_time = gdt.getValue();
  	scheduleGR.update();
  },

  /*
   * Returns an object with two properties: "schedule" that has a DiscoverySchedule instance for a schedule that contains
   * any IP address in the given array, and "ip" that has the IP address to be discovered. Returns null if no such schedule
   * could be found for any IP.
   */
  getScheduleContainingAnyIP: function(ips, cmdb_ci) {
  	var gr, result, schedule;
  	// try to fetch the schedule details from device history
  	if (JSUtil.notNil(cmdb_ci)) {
  		var dh = new GlideRecord('discovery_device_history');
  		dh.addQuery('cmdb_ci', cmdb_ci);
  		dh.addQuery('status.dscheduler.active', true);
  		dh.orderByDesc('sys_created_on');
  		dh.query();
  		while (dh.next() && SncIPAddress.isValid(dh.source)) {
  			gr = new GlideRecord('discovery_schedule');
  			gr.get('sys_id', dh.status.dscheduler);
  			schedule = new DiscoverySchedule(gr);
  			if (schedule.contains(dh.source) || schedule.containsAnyIp(ips)) {
  				result = {};
  				result['ip'] = dh.source;
  				result['schedule'] = schedule;
  				return result;
  			}
  		}
  	}
  	// see if any schedule contains one of these IP addresses...
  	gr = new GlideRecord('discovery_schedule');
  	gr.addQuery('discover', '!=', 'Networks');
  	gr.orderByDesc('active');
  	gr.orderByDesc('sys_updated_on');
  	gr.query();
  	while (gr.next()) {
  		schedule = new DiscoverySchedule(gr);
  		for (var i = 0; i < ips.length; i++) {
  			var ip = ips[i];
  			if (schedule.contains(ip)) {
  				result = {};
  				result['ip'] = ip;
  				result['schedule'] = schedule;
  				return result;
  			}
  		}
  	}
  	return null;
  },

  /*
   * Returns an array of IP addresses in the CI with the given sysID.  Returns null if the CI doesn't exist.
   */
  getIPsInCI: function(ci_sysID) {
      var gr = new GlideRecord('cmdb_ci');
      if (!ci_sysID || !gr.get('sys_id', ci_sysID))
          return null;

  	var ipDedup = {};
  	var ips = [];
  	var ip;
  	if (JSUtil.notNil(gr.ip_address)) {
  		ip = gr.ip_address +'';
  		ips.push(ip);
  		ipDedup[ip] = true;
  	}

  	var ipgr = new GlideRecord('cmdb_ci_ip_address');
  	ipgr.addQuery('nic.cmdb_ci', ci_sysID);
  	ipgr.addQuery('nic.install_status', "!=", 100);
  	ipgr.addQuery('install_status', '!=', 100);
  	ipgr.query();
  	while (ipgr.next()) {
  		ip = ipgr.getValue('ip_address');
  		if (!ipDedup[ip]) {
  			ips.push(ip);
  			ipDedup[ip] = true;
  		}
  	}

  	return ips;
  },

  // completed gets call when the completed business rule gets called
  // because the discovery_status started == completed
  completed: function() {
  	// Check if "discovery.complete" event has been created for this status before
  	if (this.checkDiscoveryCompleteEvent())
  		return;

  	var rl = new GlideSelfCleaningMutex('discovery_complete' + current.sys_id);
  	if (rl.get()){
  		// Secondary check for "discovery.complete" event in case this mutex was created prior to the first mutex being completed
  		if (this.checkDiscoveryCompleteEvent()) {
  			rl.release();
  			return;
  		}
  		
  		gs.print('Discovery completed');
  	
  		var dl = new DiscoveryLogger(current.sys_id);
  		dl.info('Discovery completed', 'Discovery');
  	
  		// Stop aggregating common error API stats
  		var errorManager = new SNC.DiscoveryErrorManager();
  		errorManager.finishInstance(current.sys_id);
  		
  		if (current.scratchpad && current.scratchpad.oncomplete)
  			eval(current.scratchpad.oncomplete);
  	
  		gs.eventQueue('discovery.complete', current, current.number, current.dscheduler.name);
  		rl.release();
  		
  	} else {
  		gs.log("Unable to lock on to discovery_complete" + current.sys_id);
  	}
  },
  
  _clean: function(table) {
  	var gr = new GlideRecord(table);
  	gr.deleteMultiple();
  },
  
  _cleanUsingMD: function(table) {
  	var md = new GlideMultipleDelete(table);
  	md.execute();
  },
  
  checkDiscoveryCompleteEvent: function() {
  	var gr = new GlideRecord('sysevent');
  	gr.addQuery('instance', current.sys_id);
  	gr.addQuery('sys_created_on', '>=', current.sys_created_on);   // Avoids union of sharded tables
  	gr.addQuery('name', 'discovery.complete');
  	gr.query();
  	return gr.hasNext();
  },

  type: "Discovery"
};

Sys ID

a6cdaf5bc0a802550004f460b6c04967

Offical Documentation

Official Docs: