Name

global.DiscoverySchedule

Description

Encapsulates the notion of a Discovery Schedule.

Script

// Discovery class

/**
* Encapsulates the notion of a Discovery Schedule.  Instances where isValid() returns true have the following
* properties initialized:
*
* name:            name of this Discovery schedule
* sysID:           sys_id of this Discovery schedule record
* midSelMode:      how to select the MID Servers: 'auto_select', 'specific_mid', 'specific_cluster', or 'behavior'
* midServerID:     sys_id of associated MID Server
* midClusterID:    sys_id of associate MID Server cluster record
* behaviorID:      sys_id of associated behavior
* discover:        what to discover: 'CIs', 'IPs', or 'Nets'
* active:          true if this Discovery schedule is active
* midServer:       the MIDServer instance for the associated MID server
* ranges:          a JavaScript array of IPIncludeExcludeCollection instances, one per range
* location:        the location reference in the schedule, or null if none
* includeAlive:    boolean, true if include alive was checked
* logStateChanges: boolean, true to log state changes
* maxRun:          GlideElement of max_run parameter
*
* Tom Dilatush tom.dilatush@service-now.com
*/
var DiscoverySchedule = Class.create();

DiscoverySchedule.prototype = Object.extend(new AbstractDBObject(), {
  /*
   * Retrieves or creates the schedule record, using the given source, and initializes this instance with the
   * information in that record. If the source is a GlideRecord instance, then this method assumes it's a Discovery
   * schedule record and uses it to intialize this instance. Otherwise the source is treated as a string instance
   * containing a sysID for the desired Discovery schedule record.
   */
  initialize: function(source) {
  	this.valid = false;
  	var gr = this._getRecord(source, 'discovery_schedule');
  	if (!gr)
  		return;

  	// initialize this instance...
  	this.name                   = gr.getValue('name'            );
  	this.sysID                  = gr.getValue('sys_id'          );
  	this.midSelMethod           = gr.getValue('mid_select_method');
  	this.midServerID            = gr.getValue('mid_server'      );
  	this.midClusterID           = gr.getValue('mid_cluster'     );
  	this.behaviorID             = gr.getValue('behavior'        );
  	this.discover               = gr.getValue('discover'        );
  	this.active                 = gr.getValue('active'          );
  	this.location               = gr.getValue('location'        );
  	this.maxRun                 = gr.getElement('max_run'       );
  	this.midServer              = new MIDServer(this.midServerID);
  	this.useSnmpVersion         = gr.getValue('use_snmp_version');
  	this.shazzamBatchSize       = gr.getElement('shazzam_batch_size'     );
  	this.shazzamClusterSupport  = gr.getElement('shazzam_cluster_support');
  	this.includeAlive           = JSUtil.getBooleanValue(gr, 'include_alive'    );
  	this.logStateChanges        = JSUtil.getBooleanValue(gr, 'log_state_changes');
  	this.ranges                 = [];
  	this.excludes               = new SncIPList();
  	this.rangesDB               = null;
  	this.webServices            = [];
  	this.certDiscoveryType      = gr.isValidField('cert_discovery_type') ? gr.getValue('cert_discovery_type') : null;
  	this.exclusionRangeTooLarge = false;
  	this.rangesSet              = false;
  	this.readCache              = false;

  	if (this.discoverWebService()) {
  		this._getWebServiceAccounts();
  	} else if (this.discoverCloudResources()) {
  		var ldcGr = new GlideAggregate('cmp_discovery_ldc_config');
  		ldcGr.addAggregate('COUNT', 'sys_id');
  		ldcGr.addActiveQuery();
  		ldcGr.setLimit(1);
  		ldcGr.query();
  		if (ldcGr.hasNext()) {
  			this.valid = true;
  		}
  	} else if (this.discoverUrlCertificates()) {
  		//check if atleast 1 url is mapped for the schedule.
  		var scheduledUrls = new GlideRecord('sn_disco_certmgmt_cert_url_sched_m2m');
  		scheduledUrls.addQuery('schedule', this.sysID);
  		scheduledUrls.query();
  		if (scheduledUrls.next())
  			this.valid = true;
  		else {
  			var isHTTPEndpointURLEnabled = gr.isValidField('include_endpoint_https_urls') ? gr.getValue('include_endpoint_https_urls') : null;

  			if (isHTTPEndpointURLEnabled == 1) {
  				//check if we have atleast one entry in cmdb_ci_endpoint_http table with valid url field.
  				var httpEndpointGR = new GlideRecord('cmdb_ci_endpoint_http');
  				httpEndpointGR.addQuery('sys_domain', gr.getValue('sys_domain'));
  				httpEndpointGR.addNotNullQuery('url');
  				httpEndpointGR.query();

  				if (httpEndpointGR.getRowCount() > 0)
  					this.valid = true;
  				else
  					gs.warn('Please make sure there are valid records in cmdb_ci_endpoint_http with proper values in URL field in order to discover certificates for the URLs from HTTP Endpoint table.');
  			}
  		}
  	} else {
  		if (this.discoverCloud() ||
  			this.discoverHostless() ||
  			this.discoverCaTrustCertificates() ||
  			this.importCertificates()) {

  			var lchrGr = new GlideRecord('discovery_pattern_launcher');
  			lchrGr.addQuery('schedule', this.sysID );
  			lchrGr.query();
  			if (lchrGr.hasNext())
  				this.valid = true;
  			else
  				this.valid = false;
  		} else if (gr.discover.equals('CIs') && new CloudResourceDiscoveryUtil().hasParentCloudSchedule(this.sysID)) {
  			//For VM Schedules which runs after Cloud Schedules, we will not be having the IP ranges populated in the schedule table.
  			//So, Bypassing the range check for VM schedule if it has a parent cloud schedule.
  			var ipList = new CloudDiscoveryScheduleConfig().getListofIPAddressesForVMSchedule(this.sysID);
  			this.setRanges(ipList);
  		} else {
  			if (new Discovery().isValidRange(gr)) {
  				this.valid = true;
  			}
  		}
  	}
  },

  errorsInSchedule: function() {
  	if (!this.sysID)
  		return false;

  	var excludedIpCount = countExcludedIPs(this.sysID);
  	if (excludedIpCount >= 500000)
  		return 'Too Many IPs Excluded: ' + excludedIpCount;

  	var ipInListCount = countIPs(this, function(range) { return range.hasV6(); });
  	if (ipInListCount > 5000)
  		return 'IP lists with IPv6 addresses can only contain 5,000 IPs but have ' + ipInListCount;

  	var ipTotalCount = countIPs(this);
  	if (ipTotalCount > 1048576)  // This is the size of a /12 network
  		return 'Schedule can only contain 1,048,576 IPs but has ' + ipTotalCount;

  	function countIPs(_this, filter) {
  		var total = 0,
  			ranges = SNC.DiscoveryRanges.getByScheduleID(_this.sysID, null),
  			cit = ranges.rangeIterator();

  		while (cit.hasNext()) {
  			var range = cit.next();
  			if (!filter || filter(range))
  				total += range.size;
  		}

  		return total;
  	}

  	function countExcludedIPs(scheduleId) {
  		return checkRangeSets(scheduleId) + checkRangeItems(scheduleId);

  		// Inner functions to calculate # of Excluded IPs
  		function IPNetworkSize(netmask) {
  			return Math.pow(2, (32 - netmask)) - 2;
  		}

  		function IPAddressRangeSize(ip_range) {
  			return new SncIPRangeV4().getIPRangeV4Instance(ip_range).size();
  		}

  		function IPAddressListSize(parent_sys_id) {
  			var count = new GlideAggregate('discovery_range_item_ip');
  			count.addQuery('exclude_parent', parent_sys_id);
  			count.addAggregate('COUNT');
  			count.query();
  			var size = 0;
  			if (count.next())
  				size = count.getAggregate('COUNT');
  			return size;
  		}

  		function checkRangeSets(scheduleId) {
  			var excluded_ip_count = 0;
  			var scheduleRangeGr = new GlideRecord('discovery_schedule_range');
  			scheduleRangeGr.addQuery('dscheduler', scheduleId);
  			scheduleRangeGr.query();
  			while (scheduleRangeGr.next()) {
  				var scheduleRangeId = scheduleRangeGr.getValue('range');
  				var rangeGr = new GlideRecord('discovery_range');
  				rangeGr.addQuery('sys_id', scheduleRangeId);
  				rangeGr.addActiveQuery();
  				rangeGr.query();
  				if (rangeGr.next()) {
  					var rangeItemGr = new GlideRecord('discovery_range_item');
  					rangeItemGr.addQuery('parent', scheduleRangeId);
  					rangeItemGr.addActiveQuery();
  					rangeItemGr.query();
  					while (rangeItemGr.next())
  						excluded_ip_count += getExcludeIpCount(rangeItemGr);
  				}
  			}
  			return excluded_ip_count;
  		}

  		function checkRangeItems(scheduleId) {
  			var excluded_ip_count = 0;
  			var rangeItemGr = new GlideRecord('discovery_range_item');
  			rangeItemGr.addQuery('schedule', scheduleId);
  			rangeItemGr.addActiveQuery();
  			rangeItemGr.query();
  			while (rangeItemGr.next())
  				excluded_ip_count += getExcludeIpCount(rangeItemGr);
  			return excluded_ip_count;
  		}

  		function getExcludeIpCount(rangeItemGr) {
  			var count = 0;
  			var included_sys_id = rangeItemGr.getValue('sys_id');
  			var excludedGr = new GlideRecord('discovery_range_item_exclude');
  			excludedGr.addQuery('parent', included_sys_id);
  			excludedGr.query();
  			while (excludedGr.next()) {
  				var type = excludedGr.getValue('type');
  				if (type == 'IP Network')
  					count += IPNetworkSize(parseInt(excludedGr.getValue('netmask')));
  				else if (type == 'IP Address Range')
  					count += parseInt(IPAddressRangeSize(excludedGr.getValue('summary')));
  				else if (type == 'IP Address List')
  					count += parseInt(IPAddressListSize(excludedGr.getValue('sys_id')));
  			}
  			return count;
  		}
  	}
  },

  setRanges: function(ipList) {
  	if (this.rangesSet)
  		return;

  	this.rangesSet = true;
  	var scheduleId = this.sysID;
  	var gr = this._getRecord(scheduleId, 'discovery_schedule');
  	if (!gr)
  		return;

  	// get our ranges...
  	var dr = SNC.DiscoveryRanges.getByScheduleID(scheduleId, ipList || null);
  	this._setScheduleRanges(dr);
  },

  _setScheduleRanges: function(ranges) {
  	var cit, collection, rangeWithNoBehavior, behavior,
  		optimize = GlideProperties.getBoolean('glide.discovery.shazzam_simplify_ranges', true);

  	this.rangesDB = ranges;
  	cit = ranges.rangeIterator();
  	while (optimize && cit.hasNext()) {
  		collection = cit.next().asIPCollection();
  		// When the collection doesn't have a behavior collection.behavior will be null, but
  		// it's some weird kind of null.  typeof returns "object", but
  		//    behavior == null
  		// returns false.  Loose comparison to a string was the only way I found to tell
  		// when behavior is null.
  		if (collection.behavior && (collection.behavior != "null")) {
  			if (rangeWithNoBehavior)
  				optimize = false;
  			else if (!behavior)
  				behavior = collection.behavior;
  			else if (behavior + '' != collection.behavior + '')
  				optimize = false;
  		} else {
  			if (behavior && (behavior != 'null'))
  				optimize = false;
  			rangeWithNoBehavior = true;
  		}
  	}

  	if (optimize) {
  		try {
  			var hosts = SNC.IPCollectionUtil.getListOfHostnames(this.rangesDB.asIPCollection());
  			if (hosts.size() != 0)
  				this.ranges.push(new SNC.DiscoveryRanges(hosts).asIPCollection());
  			// Passing 20 million as shazzam batch size.  I just want the ranges to be simplified, not split for Shazzam
  			var optimals = SNC.IPCollectionUtil.getOptimalRanges(this.getAppName(), this.rangesDB.asIPCollection(), 20000000);
  			var _this = this;
  			optimals.forEach(function(optimal) {
  				optimal.getRanges().forEach(function(range) {
  					var collection = new SNC.DiscoveryRanges(range.getRange()).asIPCollection();

  					// Getting optimal ranges doesn't preserve range behavior.  This will set the schedule's
  					// behavior (if there is one) on all ranges.  Behavior on individual ranges will be lost.
  					if (_this.behaviorID)
  						collection.behavior = behavior || _this.behaviorID;

  					_this.ranges.push(collection);
  				});
  			});

  			return;
  		} catch (e) {
  			// Something went wrong with getOptimalRanges.  Fall back to the old behavior.
  			// I'd like to log something here but the discovery_status hasn't been created yet
  			// so I can't write a log message where the user would see it.
  			gs.warn('Failed to calculate optimal ranges: ' + e.toString() + '. Using non-optimized ranges.');
  		}
  	}

  	cit = ranges.rangeIterator();
  	while (cit.hasNext())
  		this.ranges.push(cit.next().asIPCollection());
  },

  _getWebServiceAccounts: function() {
  	var m2mGr = new GlideRecord('dscy_sch_wb_srvc_acct_m2m');
  	m2mGr.addQuery('schedule', this.sysID);
  	m2mGr.query();

  	if (m2mGr.hasNext()) {
  		while (m2mGr.next())
  			this.webServices.push(String(m2mGr.account.sys_id));
  		this.valid = true;
  	}
  },

  contains: function(ip) {
  	// PRB1454263: If this is called from a CI schedule, then we may not have set rangesDB yet
  	// so we need to call setRanges to update rangesDB before doing this contains check
  	this.setRanges();
  	return JSUtil.notNil(this.rangesDB) && this.rangesDB.contains(SNC.IPAddress.get(ip));
  },

  /*
   * Takes a list of ips and checks if one of the ips is part of the schedule
   *
   * ips: Array
   */
  containsAnyIp: function(ips) {
  	var _this = this;
  	return ips.some(function(ip) {
  		return _this.contains(ip);
  	});
  },

  /*
   * Get the correct behavior for the given IP address, or null if none can be found.
   *
   * ip: The string dotted-form IP address to get a behavior for.
   */
  getBehaviorForIP: function(ip) {
  	this.setRanges();
  	var jip = new SNC.IPAddress(ip);
  	for (var i = 0; i < this.ranges.length; i++) {
  		var ipiec = this.ranges[i];
  		if (ipiec.contains(jip))
  			return this.getBehaviorForRange(ipiec);
  	}
  	return null;
  },

  /*
   * Get the DiscoveryBehavior instance for the given range.
   *
   * ipiec: the IPIncludeExcludeCollection for the range
   */
  getBehaviorForRange: function(ipiec) {
  	var behaviorSource = null;
  	if (this.discoverIPs() || this.discoverNets() || !this.midSelectBehavior())
  		behaviorSource = this.getMIDServer(ipiec);
  	else
  		behaviorSource = ipiec.behavior;
  	return new DiscoveryBehaviorRecord(behaviorSource, this.discover);
  },

  /*
   * Adds the given IP address (in dotted form) to the list of IP addresses excluded from the ranges in this schedule.
   * This is used by DiscoveryPhaser.
   *
   * ip: the IP address (in dotted form) to exclude.
   */
  exclude: function(ip) {
  	var ipa = SNC.IPAddress.get(ip);
  	if (!ipa)
  		return;

  	this.excludes.add(ipa.getIP());
  },

  /*
   * Modifies the schedule's ranges to exclude the accumulated list of IP addresses.
   */
  finalizeExcludes: function() {
  	this.setRanges();
  	for (var i = 0; i < this.ranges.length; i++) {
  		var meta = new SncIPMetaCollection();
  		meta.add(this.excludes);
  		var oldExcludes = this.ranges[i].getExclude();
  		if (oldExcludes)
  			meta.add(oldExcludes);
  		this.ranges[i].setExclude(meta);
  	}
  },

  /*
   * Get a MIDServer instance for a discovery without behaviors, first from the given range (if it is specified),
   * then from the schedule (if it is specified), and finally the default MID server if all else fails.
   *
   * ipiec: the IPIncludeExcludeCollection for the range
   */
  getMIDServer: function(ipiec) {
  	// get the mid server that's in the range, if there is any...
  	var midServer = null;
  	var midServerName = ipiec.midServer;

  	if (midServerName)
  		midServer = MIDServer.getByName(midServerName);

  	// if no mid server in the range, try getting the schedule's mid server...
  	if (!midServer)
  		midServer = this.midServer;

  	// no mid server in either the range or the schedule, use the default mid server...
  	if (!midServer)
  		midServer = MIDServer.getDefault();

  	return midServer;
  },

  discoverCIs: function() {
  	return this.discover == 'CIs';
  },

  discoverIPs: function() {
  	return this.discover == 'IPs';
  },

  discoverNets: function() {
  	return this.discover == 'Nets';
  },

  discoverWebService: function() {
  	return this.discover == 'Web Service';
  },

  discoverService: function() {
  	return this.discover == 'Service';
  },

  discoverCloudResources: function() {
  	return this.discover == 'Cloud Resources';
  },

  discoverCloud: function() {
  	return this.discover == 'Cloud';
  },

  discoverHostless: function() {
  	return this.discover == 'Hostless';
  },

  discoverUrlCertificates: function(gr) {
  	return (this.discover == 'Certificates' && this.certDiscoveryType == 'url');
  },

  discoverCaTrustCertificates: function() {
  	return (this.discover == 'Certificates' && this.certDiscoveryType == 'trust');
  },

  importCertificates: function() {
  	return (this.discover == 'Certificates' && this.certDiscoveryType == 'import');
  },

  refreshAdm: function(){
  	return this.discover == 'Processes/Connections';
  },

  midSelectAuto: function() {
  	return this.midSelMethod == 'auto_select';
  },

  midSelectSpecificMid: function() {
  	return this.midSelMethod == 'specific_mid';
  },

  midSelectSpecificCluster: function() {
  	return this.midSelMethod == 'specific_cluster';
  },

  midSelectBehavior: function() {
  	return this.midSelMethod == 'behavior';
  },

  getAppName: function() {
  	return this.discoverService() ? 'ServiceMapping' : 'Discovery';
  },

  type: 'DiscoverySchedule'
});

Sys ID

092329440ab30150009e4c93176ef3d0

Offical Documentation

Official Docs: