Name

global.InstanceCloneScheduler

Description

Utility methods for instance clone scheduling

Script

var InstanceCloneScheduler = Class.create();
InstanceCloneScheduler.prototype = {
  initialize: function() {
  	this.clone_server_url = gs.getProperty("glide.db.clone.instance_clone_server");
  	this.className = "InstanceCloneScheduler";
  },
  
  type: 'InstanceCloneScheduler',

  buildSOAPRequest: function(soapRequest, cloneGr, action)  {
  	if(gs.nil(cloneGr) || gs.nil(action)) 
  		return;

  	if(gs.nil(soapRequest)) 
  		soapRequest = new SOAPMessage('Instance Clone Schedule', 'execute');
  	
  	var endpoint = this.clone_server_url;
  	var primaryIP = GlideUtil.isDeveloperInstance() ? "localhost" : GlideHostUtil.getPublicIPAddress();
  	var standbyInstanceGr = this._getInstanceRecord(cloneGr.getValue('source_instance'));
  	var targetInstanceGr = this._getInstanceRecord(cloneGr.getValue('target_instance'));

  	var sourceInstanceId = gs.getProperty('instance_id');
  	var sourceName = SncCloneUtils.getSourceInstanceName();
  	var sourceUrl = SncCloneUtils.getSourceInstanceUrl();

  	soapRequest.setStringParameter('clone_id', cloneGr.getValue('sys_id'));

  	var password = gs.getProperty('glide.customurl.datacenter.instance.password', null);
  	soapRequest.setStringParameter('quebecPlus', 'true');
  	soapRequest.setStringParameter('user', sourceInstanceId);
  	soapRequest.setStringParameter('password', GlideStringUtil.base64Encode(password));
  	soapRequest.setStringParameter('source_instance_id', sourceInstanceId);

  	if(action.equalsIgnoreCase("rollback")) {
  		soapRequest.setStringParameter('request_for_rollback','true');
  		soapRequest.setStringParameter('action',cloneGr.getValue('state'));
  		soapRequest.setStringParameter('telemetry',cloneGr.getValue('telemetry'));
  	} else if(action.equalsIgnoreCase("cancel")) {
  		soapRequest.setStringParameter('action',cloneGr.getValue('state'));
  		soapRequest.setStringParameter('telemetry',cloneGr.getValue('telemetry'));
  	} else if(action.equalsIgnoreCase("getCloneStatus")) 
  		soapRequest.setStringParameter('request_for_clone_status', 'true');
  	else if(action.equalsIgnoreCase("schedule")) {
  		soapRequest.setStringParameter('parent_clone_id', cloneGr.getValue('parent'));
  		soapRequest.setStringParameter('date', cloneGr.getValue('scheduled') || new GlideDateTime());
  		soapRequest.setStringParameter('state', cloneGr.getValue('state') || 'Draft');
  		soapRequest.setStringParameter('cluster_node', this._getSystemId(cloneGr.getValue('cluster_node')));
  		soapRequest.setStringParameter('user_name', cloneGr.getValue('sys_created_by'));
  		soapRequest.setStringParameter('user_maint', this._isMaint(cloneGr.getValue('sys_created_by')));
  		soapRequest.setStringParameter('user_email', cloneGr.getValue('email'));
  		soapRequest.setStringParameter('security_token', cloneGr.getValue('security_token') || '');

  		// instance details
  		soapRequest.setStringParameter('source_instance_name', gs.getProperty('instance_name') || sourceName);
  		soapRequest.setStringParameter('source_instance_url', sourceUrl);
  		soapRequest.setStringParameter('target_instance_id', targetInstanceGr.instance_id);
  		soapRequest.setStringParameter('target_instance_name', targetInstanceGr.instance_name);
  		soapRequest.setStringParameter('target_instance_url', targetInstanceGr.instance_url);

  		// version details
  		soapRequest.setStringParameter('source_version', this._formatVersion(gs.getProperty('glide.war')));
  		soapRequest.setStringParameter('target_version', this._formatVersion(targetInstanceGr.war_version));

  		// jdbc details
  		var primaryParms = GlideDBUtil.getPrimaryDBConfigurationParms();
  		var sourceJDBCUrl = primaryParms.getURL().replace("localhost", primaryIP);
  		var sourceDBName = primaryParms.getDatabaseName();

  		var targetJDBCUrl = targetInstanceGr.database_url.replace("localhost", primaryIP);
  		var targetDBName = targetInstanceGr.database_name;
  		var standbyJDBCUrl = standbyInstanceGr == null ? "" : standbyInstanceGr.database_url.replace("localhost", primaryIP);
  		var standbyDBName = standbyInstanceGr == null ? "" : standbyInstanceGr.database_name;

  		soapRequest.setStringParameter('source_jdbc_url', sourceJDBCUrl);
  		soapRequest.setStringParameter('source_db_server', this._formatDBServer(sourceJDBCUrl));
  		soapRequest.setStringParameter('source_db_name', sourceDBName);
  		soapRequest.setStringParameter('target_jdbc_url', targetJDBCUrl);
  		soapRequest.setStringParameter('target_db_server', this._formatDBServer(targetJDBCUrl));
  		soapRequest.setStringParameter('target_db_name', targetDBName);
  		soapRequest.setStringParameter('standby_jdbc_url', standbyJDBCUrl);
  		soapRequest.setStringParameter('standby_db_server', this._formatDBServer(standbyJDBCUrl));
  		soapRequest.setStringParameter('standby_db_name', standbyDBName);
  		soapRequest.setStringParameter('validation_error', targetInstanceGr.validation_error);

  		// timing and metric details
  		soapRequest.setStringParameter('mb_to_copy', cloneGr.getValue('megabytes_to_copy'));
  		soapRequest.setStringParameter('mb_copied', cloneGr.getValue('.megabytes_copied'));
  		soapRequest.setStringParameter('kb_per_second', cloneGr.getValue('kilobytes_per_second'));
  		soapRequest.setStringParameter('duration', cloneGr.getValue('duration'));
  		soapRequest.setStringParameter('started', cloneGr.getValue('started'));
  		soapRequest.setStringParameter('completed', cloneGr.getValue('completed'));
  		soapRequest.setStringParameter('cancelled', cloneGr.getValue('canceled')); 
  		
  		if(cloneGr.state != 'Draft'){
  			var telemetry = this._getTelemetryData();
  			soapRequest.setStringParameter('telemetry', telemetry);
  		}
  		//reservation details
  		soapRequest.setStringParameter('check_for_available_reservation','false');
  		
  		// clone options
          soapRequest.setStringParameter('exclude_tables_specified_in_exclusion_list', cloneGr.getValue('exclude_all_from_exclusion_list'));
          soapRequest.setStringParameter('exclude_audit_and_log_data', cloneGr.getValue('exclude_large_data'));
          soapRequest.setStringParameter('exclude_large_attachment_data', cloneGr.getValue('filter_attachment_data'));
          soapRequest.setStringParameter('preserve_in_progress_update_sets', cloneGr.getValue('preserve_in_progress_update_sets'));
          soapRequest.setStringParameter('amount_of_data_copied_from_large_tables', cloneGr.getValue('amount_of_data_copied_from_large_tables'));
          soapRequest.setStringParameter('preserve_theme', cloneGr.getValue('preserve_theme'));
          soapRequest.setStringParameter('clone_frequency', cloneGr.getValue('clone_frequency'));
  		
  		soapRequest.setStringParameter('profile', cloneGr.getDisplayValue('profile'));
          soapRequest.setStringParameter('lock_clone_config', cloneGr.getValue('lock_profile_settings'));
  	}

  	if (endpoint)
  		soapRequest.setSoapEndPoint(endpoint);

  	// use custom authorization
  	soapRequest.setRequestHeader("Authorization", "Clone " + gs.getProperty('instance_id') + ":" + primaryIP);
  },

  _checkForCloneStateChange: function(cloneRecord, doc) {
  	var state = this._getText(doc, 'state') + '';
  	var description = this._getText(doc, 'description') + '';

  	var oldState = cloneRecord.state;
  	var stateChanged = !gs.nil(state) && oldState != state;

  	if ( stateChanged ) {
  		gs.log("Schedule Server changed state from " + oldState + " to " + state + " (" + description + ")", this.className);
  		cloneRecord.state = state;
  		if (state == 'Hold')
  			cloneRecord.message = description;
  	}
  },

  _checkForCloneScheduleChange: function(cloneGr, doc) {

  	var cloneValidationRules = this._getText(doc,'clone_request_validation_rules')+'';
  	cloneGr.setValue('clone_request_validation_rules', cloneValidationRules);
  	var scheduled = cloneGr.scheduled;
  	if (!gs.nil(this._getText(doc, 'date')))
  		scheduled = new GlideDateTime(this._getText(doc, 'date')); // GMT

  	var dateChanged = cloneGr.scheduled.getDisplayValue() != scheduled.getDisplayValue();

  	if ( dateChanged ) {
  		var cloneEstimationMessage = this._getText(doc,'clone_estimation_message')+'';
  		var isReservationAvailableForCloning = this._getText(doc,'is_reservation_available')+'';

  		cloneGr.setValue("scheduled", scheduled); // GMT
  		cloneGr.setValue("clone_estimation_message", cloneEstimationMessage);
  		cloneGr.setValue("is_reservation_available", isReservationAvailableForCloning);
  	}

  },

  _updateCloneRespParams: function(cloneRecord, doc) {

  	var description = this._getText(doc, 'description') + '',
  		primaryIP = GlideUtil.isDeveloperInstance() ? "localhost" : GlideHostUtil.getPublicIPAddress();

  	var standbyInstanceGr = this._getInstanceRecord(cloneRecord.source_instance);
  	var standbyDBName = standbyInstanceGr == null ? "" : standbyInstanceGr.database_name;
  	var standbyJDBCUrl = standbyInstanceGr == null ? "" : standbyInstanceGr.database_url.replace("localhost", primaryIP);

  	var cloneType = this._getText(doc, 'clone_type')+'';

  	var newStandbyJDBCUrl = this._getText(doc, 'standby_jdbc_url')+'';
  	var newStandbyDBName = this._getText(doc, 'standby_db_name')+'';
  	var newClusterNode = this._getText(doc, 'cluster_node')+'';

  	var isCloneTypeChanged = !gs.nil(cloneType) && cloneRecord.clone_type != cloneType;
  	var standbyChanged = !gs.nil(newStandbyJDBCUrl) && (gs.nil(standbyJDBCUrl) || standbyJDBCUrl != newStandbyJDBCUrl);
  	var nodeChanged = !gs.nil(newClusterNode) && (gs.nil(cloneRecord.cluster_node) || cloneRecord.cluster_node != newClusterNode);
  	
  	var oldScheduled = cloneRecord.scheduled;
  	var oldClusterNode = cloneRecord.cluster_node||'';
  	
  	if ( nodeChanged && this._setClusterNode(cloneRecord, newClusterNode) ) 
  		gs.log("Schedule Server changed cluster node from " + oldClusterNode + " to " + newClusterNode + " (" + description + ")", this.className);
  	
  	/*if ( standbyChanged && this._setStandbySource(cloneRecord, newStandbyJDBCUrl, newStandbyDBName) ) 
  		gs.log("Schedule Server changed standby db from " + standbyJDBCUrl + "." + standbyDBName + " to " + newStandbyJDBCUrl + "." + newStandbyDBName + 
  			   " (" + description + ")", this.className);*/

  	if ( isCloneTypeChanged ) {
  		gs.log("Clone Type changed from " + cloneRecord.clone_type + " to " + cloneType, this.className);
  		cloneRecord.setValue("clone_type", cloneType);
  	}

  },

  handleSOAPResponse: function(response, cloneGr) {
  	try {
  		var doc = new GlideXMLDocument();
  		doc.parse(response);
  		gs.log(cloneGr.getValue('clone_id') + " : Schedule Clone Response from server " + doc, this.className);

  		var isError  = this._getText(doc,'isError')+'';
  		if (isError == 'true') {
  			this.logCloneMessage(cloneGr, /*error*/2, this._getText(doc, 'description'), '');
  			cloneGr.setValue('state', 'Error');
  			cloneGr.setWorkflow(false);//Don't trigger any BR
  			cloneGr.update();
  			return;
  		}

  		this._checkForCloneScheduleChange(cloneGr, doc);
  		this._checkForCloneStateChange(cloneGr, doc);
  		this._updateCloneRespParams(cloneGr, doc);

  		var cloudDetails = this._getText(doc,'cloud_details')+'';
  		if(!gs.nil(cloudDetails)) 
  			cloneGr.setValue("cloud_details", cloudDetails);

  		cloneGr.setValue('estimated_clone_duration', this._getText(doc,'estimatedCloneDuration'));
  		cloneGr.setWorkflow(false);
  		cloneGr.update();
  	} catch (e) {
  		throw e;
  	}
  },
  
  /** Notify instance clone schedule server by calling web service - response might cause the record to be updated */
  notifyServer: function(cloneGr) {
  	var notified = false;
  	
  	var targetInstanceGr = this._getInstanceRecord(cloneGr.target_instance);
  	var sourceName = SncCloneUtils.getSourceInstanceName();
  	
  	// developer installs and localhost: source/target should not contact production url clone.service-now.com
  	if (this._shouldSkip(sourceName, targetInstanceGr.instance_name))
  		return notified;
  	
  	try {
  		var soapRequest = new SOAPMessage('Instance Clone Schedule', 'execute');
  		
  		this.buildSOAPRequest(soapRequest, cloneGr, 'schedule');

  		var response = soapRequest.post();
  		if (soapRequest.httpStatus != 200) 
  			this.logCloneMessage(cloneGr, /*error*/2, 'clone.log.error.server.confirmation', '');
  		else {
  			this.handleSOAPResponse(response, cloneGr);
  			notified = true;
  			if(cloneGr.getValue("state") == CloneConstants.status.CLONE_STATUS_REQUESTED) 
  				this.logCloneMessage(cloneGr, /*info*/0, 'clone.log.info.schedule.success', cloneGr.getValue('clone_id'));
  		}
  	} catch (e) {
  		this.logCloneMessage(cloneGr, /*error*/2, 'clone.log.error.schedule.request', e.toString());
  	}

  	return notified;
  },

  logCloneMessage: function(cloneGr, logLevel, msgKey, msgArgs) {
  	var argsArray = [];

  	if(gs.nil(cloneGr)) 
  		return;

  	if(!gs.nil(msgArgs)) 
  		argsArray.push(msgArgs);

  	if(!gs.nil(msgKey)) 
  		SncCloneLogger.log(this.className, cloneGr.getValue('sys_id'), null, logLevel, gs.getMessage(msgKey, argsArray));
  },

  // Piggy Back the same service Call with additional paramter for checking Reservation

  notifySchedulingServerForCheckingReservation: function(cloneGr) {

  	var soapRequest = new SOAPMessage('Instance Clone Schedule', 'execute');
  	soapRequest.setStringParameter('clone_id', cloneGr.getValue('sys_id'));
  	soapRequest.setStringParameter('date', cloneGr.scheduled || new GlideDateTime());
  	soapRequest.setStringParameter('check_for_available_reservation','true');
  	var endpoint = this.clone_server_url;
  	gs.log("Calling instance clone scheduler server: " + endpoint, this.className);
  	var primaryIP = GlideUtil.isDeveloperInstance() ? "localhost" :
  	GlideHostUtil.getPublicIPAddress();
  	if (endpoint)
  		soapRequest.setSoapEndPoint(endpoint);
  	// use custom authorization
  	soapRequest.setRequestHeader("Authorization", "Clone " + 
  					gs.getProperty('instance_id') + ":" + primaryIP);
  	var response = soapRequest.post();
  	if (soapRequest.httpStatus != 200)
  		this.logCloneMessage(cloneGr, /*error*/2, 'clone.log.error.server.confirmation', '');
  	else {
  		var doc = new GlideXMLDocument();
  		doc.parse(response);
  		var isReservationAvailableForCloning = this._getText(doc,'is_reservation_available');
  		var scheduled = new GlideDateTime(this._getText(doc, 'date')); // GMT
  		var cloneEstimationMessage = this._getText(doc,'clone_estimation_message');
  		cloneGr.setValue("scheduled", scheduled); // GMT
  		cloneGr.setValue("clone_estimation_message",cloneEstimationMessage);
  		cloneGr.setValue("is_reservation_available", isReservationAvailableForCloning);
  		cloneGr.setWorkflow(false); //Don't trigger any BR
  		cloneGr.update();
  		
  	}
  	
  },
  notifySchedulingServerForCancel: function(cloneGr) {
  	var schedule = new ScheduleOnce();
  	schedule.setLabel("Schedule Clone Cancel for " + cloneGr.getValue('clone_id'));
  	schedule.script = "new InstanceCloneScheduler()._notifyCloneServerForCancelByCloneId('" + cloneGr.getValue('sys_id') + "');";
  	return schedule.schedule();
  },

  /** Notify Clone Server for Clone Rollback in case of Backup Based Clones */
  notifySchedulingServerForRollback: function(cloneGr){
  	var schedule = new ScheduleOnce();
  	schedule.setLabel("Schedule Clone Rollback for " + cloneGr.getValue('clone_id'));
  	schedule.script = "new InstanceCloneScheduler()._notifyCloneServerForRollbackByCloneId('" + cloneGr.getValue('sys_id') + "');";
  	return schedule.schedule();
  },

  /** Notify instance clone schedule server by calling web service - response might cause the record to be updated */
  scheduleNotifyServer: function(cloneRecord) {
  	var schedule = new ScheduleOnce();
  	schedule.setLabel("Notify instance clone scheduler");
  	schedule.script = "new InstanceCloneScheduler()._notifyServerByCloneId('" +
  		cloneRecord.sys_id + "');";
  	return schedule.schedule();
  },

  /** Notify instance clone schedule server by calling web service - response might cause the record to be updated */
  _notifyServerByCloneId: function(cloneId) {
  	var cloneRecord = new GlideRecord("clone_instance");
  	if (cloneRecord.get(cloneId))
  		this.notifyServer(cloneRecord);

  },
  
  checkCloneStatus: function(cloneSysId) {
  	var cloneGr = new GlideRecord("clone_instance");
  	if (!cloneGr.get(cloneSysId)) 
  		return;

  	var cloneState = cloneGr.getValue('state');
  	if(!cloneState.equalsIgnoreCase('Requested')) 
  		return;

  	var cloneId = cloneGr.getValue('clone_id');
  	try {
  		var soapRequest = new SOAPMessage('Instance Clone Schedule', 'execute');
  		this.buildSOAPRequest(soapRequest, cloneGr, 'getCloneStatus');
  		var response = soapRequest.post();
  		if (soapRequest.httpStatus != 200) {
  			this.logCloneMessage(cloneGr, /*error*/2, 'clone.log.error.status.confirmation', '');
  			gs.logError(cloneId + ": Error in getting status from server : " + response, this.className);
  			cloneGr.setValue('state', 'Error');
  		} else {
  			var doc = new GlideXMLDocument();
  			doc.parse(response);
  			gs.log(cloneId + ": Check Clone Status Response: " + doc, this.className);
  			cloneState = this._getText(doc, 'state');
  			if(!gs.nil(cloneState))
  				cloneGr.setValue('state', cloneState);

  			this.logCloneMessage(cloneGr, /*info*/0, this._getText(doc, 'description'), '');
  		}
  	} catch (e) {
  		this.logCloneMessage(cloneGr, /*error*/2, 'clone.log.error.status.confirmation', '');
  		gs.logError(cloneId + ": Error in getting status from server : " + e.toString(), this.className);
  		cloneGr.setValue('state', 'Error');
  	}
  	cloneGr.setWorkflow(false);
  	cloneGr.update();
  },

  _notifyCloneServerForCancelByCloneId: function(cloneSysId) {
  	var cloneGr = new GlideRecord("clone_instance");
  	if (!cloneGr.get(cloneSysId)) 
  		return;

  	var cloneId = cloneGr.getValue('clone_id');
  	var soapRequest = new SOAPMessage('Instance Clone Schedule', 'execute');

  	this.buildSOAPRequest(soapRequest, cloneGr, 'cancel');

  	var response = soapRequest.post();
  	if (soapRequest.httpStatus != 200){
  		this.logCloneMessage(cloneGr, /*error*/2, 'clone.log.error.server.confirmation', '');
  		gs.logError(cloneId + " : Error in Response from server : " + response, this.className);
  	} else {
  		var doc = new GlideXMLDocument();
  		doc.parse(response);

  		var isError  = this._getText(doc,'isError')+'';
  		if (isError == 'true') {
  			this.logCloneMessage(cloneGr, /*error*/2, this._getText(doc, 'description'), '');
  			return;
  		}

  		gs.log(cloneId + " : Cancel Clone Response from server " + doc, this.className);
  		SncCloneLogger.log(this.className, cloneGr.getValue('sys_id'), null, /*info*/0, this._getText(doc, 'description'));
  	}
  },

  _notifyCloneServerForRollbackByCloneId: function(cloneSysId) {
  	var cloneGr = new GlideRecord("clone_instance");
  	if (!cloneGr.get(cloneSysId)) 
  		return;

  	var cloneId = cloneGr.getValue('clone_id');
  	var soapRequest = new SOAPMessage('Instance Clone Schedule', 'execute');

  	this.buildSOAPRequest(soapRequest, cloneGr, 'rollback');

  	var response = soapRequest.post();
  	if (soapRequest.httpStatus != 200){
  		this.logCloneMessage(cloneGr, /*error*/2, 'clone.log.error.server.confirmation', '');
  		gs.logError(cloneId + " : Error in Response from server : " + response, this.className);
  	} else {
  		var doc = new GlideXMLDocument();
  		doc.parse(response);

  		var isError  = this._getText(doc,'isError')+'';
  		if (isError == 'true') {
  			this.logCloneMessage(cloneGr, /*error*/2, this._getText(doc, 'description'), '');
  			return;
  		}

  		var state = this._getText(doc, 'state')+'';
  		cloneGr.setValue('state', state);
  		cloneGr.setWorkflow(false);//Don't trigger any BR
  		cloneGr.update();

  		gs.log(cloneId + " : Rollback Clone Response from server " + doc, this.className);
  		SncCloneLogger.log("InstanceCancelClone", cloneGr.getValue('sys_id'), null, /*info*/0, this._getText(doc, 'description'));
  	}
  },

  _isMaint: function(user_id) {
  	if (gs.hasRole('maint'))
  		return true;

  	var gr = new GlideRecord("sys_user");
  	gr.addQuery("user_name", user_id);
  	gr.query();
  	if (gr.next()) {
  		var user = GlideUser.getUserByID(gr.sys_id);
  		return user != null ? user.hasRole("maint") : false;
  	}
  	return false;
  },

  _formatVersion: function(version) {
  	if (gs.nil(version))
  		return "";

  	if (version == 'null')
  		return "";

  	if (version.length > 4 && version.indexOf('.') > -1)
  		return version.substring(0, version.length-4);

  	return version;
  },

  _formatDBServer: function(jdbcUrl) {
  	if (gs.nil(jdbcUrl))
  		return "";

  	// jdbc:mysql://servername:port/
  	// jdbc:sqlserver://servername:portnum/
  	var answer = jdbcUrl;
  	var idx = answer.indexOf('://');
  	if (idx > -1) {
  		answer = answer.substring(idx+3).replace('/', '');
  		idx = answer.indexOf(':');
  		if (idx > -1)
  			answer = answer.substring(0, idx);
  	}

  	// jdbc:oracle:thin:@host.com:1521:sidname
  	idx = jdbcUrl.indexOf(':@');
  	if (idx > -1) {
  		answer = answer.substring(idx+2).replace('/', '');

  		idx = answer.indexOf(':');
  		if (idx > -1)
  			answer = answer.substring(0, idx);
  	}

  	return answer;
  },

  _getText: function(doc, tag) {
  	var el = doc.getElementByTagName(tag);
  	if (el && el != null)
  		return el.getTextContent();

  	return "";
  },

  _getSystemId: function(clusterStateSysId) {
  	if (gs.nil(clusterStateSysId))
  		return "";

  	var gr = new GlideRecord("sys_cluster_state");
  	if (gr.get(clusterStateSysId))
  		return gr.system_id;

  	return "";
  },

  _setClusterNode: function(cloneRecord, clusterNode) {
  	if (gs.nil(clusterNode))
  		return false;

  	var gr = new GlideRecord("sys_cluster_state");
  	gr.addQuery("status", "online"); // only allow change to online nodes
  	// we can receive cluster node input in three variations
  	var qc = gr.addQuery("sys_id", clusterNode);
  	qc.addOrCondition("node_id", clusterNode);
  	qc.addOrCondition("system_id", clusterNode);
  	gr.query();
  	if ( gr.next() && (gs.nil(cloneRecord.cluster_node) || 
  					cloneRecord.cluster_node != gr.sys_id) ) {
  		cloneRecord.cluster_node = gr.sys_id;
  		return true;
  	}
  	
  	return false;
  },

  _getInstanceRecord: function(instanceSysId) {
  	if (gs.nil(instanceSysId))
  		return null;

  	var gr = new GlideRecord('instance');
  	if (gr.get(instanceSysId) && gr.primary == false) // don't return the primary setup record
  		return gr;

  	return null;
  },

  _setStandbySource: function(cloneRecord, jdbcUrl, dbName) {
  	if (gs.nil(jdbcUrl) || gs.nil(dbName))
  		return false;

  	// First validate db connection
  	var primaryParms = GlideDBUtil.getPrimaryDBConfigurationParms();
  	var user = primaryParms.getUser();
  	var clearPassword = primaryParms.getPassword();
  	var encryptedPassword = this._encrypt(clearPassword);
  	var rdbms = primaryParms.getRDBMS();
  	var tablespace = null;
  	try {
  		var instance = new SncCloneInstance(dbName, user, encryptedPassword,
  											jdbcUrl, rdbms, tablespace);
  		// verify that the database exists
  		if (!instance.instanceDatabaseExists()) {
  			SncCloneLogger.log("InstanceClone", cloneRecord.sys_id,
  							   null,
  							   /*error*/2,
  							   "Source cannot be cloned from: database does not "+
  							   "exist");
  			return false;
  		}

  		// verify that the alternative source can be cloned from
  		if (!instance.canCloneFrom()) {
  			SncCloneLogger.log("InstanceClone", 
  							   cloneRecord.sys_id, null, 
  							   /*error*/2, 
  							   "Source cannot be cloned from: instance_id "+
  							   "does not match primary database "+
  							   "(it appears to be a different database)");
  			return false;
  		}
  	} catch(e) {
  		SncCloneLogger.log("InstanceClone", 
  						   cloneRecord.sys_id, 
  						   null, 
  						   /*error*/2, 
  						   "Source cannot be cloned from: unable "+
  						   "to connect to database and verify instance_id: "+
  						   e.toString());
  		return false;
  	}

  	var isNew = false;
  	var gr = new GlideRecord("instance");
  	gr.addQuery("source", true);
  	gr.addQuery("primary", false);
  	gr.query();
  	if (!gr.next()) {
  		isNew = true;
  		gr.initialize();
  		gr.setValue("source", true);
  		gr.setValue("primary", false);
  	}

  	gr.database_url = jdbcUrl;
  	gr.database_name = dbName;
  	gr.database_tablespace = tablespace;
  	gr.database_type = rdbms;
  	gr.database_user = user;
  	gr.database_password = encryptedPassword;

  	gr.instance_id = gs.getProperty("instance_id")+"";
  	if (gs.nil(gr.instance_name))
  		gr.instance_name = "Standby Database";
  	if (gs.nil(gr.instance_url))
  		gr.instance_url = gs.getProperty("glide.servlet.uri")+"";
  	gr.production = gs.getProperty("glide.installation.production");
  	gr.war_version = gs.getProperty("glide.war")+"";

  	if (isNew)
  		gr.insert();
  	else
  		gr.update();

  	cloneRecord.source_instance = gr.sys_id;
  	return true;
  },

  /* developer installs and localhost: source/target should not contact production url clone.service-now.com */
  _shouldSkip: function(sourceName, targetName) {
  	var answer = false;
  	var url = this.clone_server_url; // eg https://clone.service-now.com/InstanceCloneSchedule.do?SOAP
  	if (gs.nil(url))
  		return answer;

  	var productionUrl = "https://clone.service-now.com/";
  	var isDeveloper = GlideUtil.isDeveloperInstance() &&
  		!GlideUtil.isProductionInstance();
  	var isLocalHost = sourceName.indexOf("localhost") > -1 ||
  		targetName.indexOf("localhost") > -1;
  	// if we're trying to contact the production url and we're a local installation, do not pass go
  	if (url.indexOf(productionUrl) > -1 && (isDeveloper || isLocalHost)) {
  		answer = true;
  		gs.print("InstanceCloneScheduler - this is a developer "+
  				 "or localhost instance, production scheduling service is not "+
  				 "supported: to resolve, change property "+
  				 "glide.db.clone_server_url");
  	}

  	return answer;
  },
  
  _getTelemetryData: function(){
  	var gr = new GlideRecord("clone_instance");
  	gr.orderByDesc('sys_created_on');
  	//Each clone request sends three SOAP requests with states - Draft,Draft,Requested
  	gr.setLimit(3);
  	gr.query();
  	var telemetry = {};
  	while (gr.next()) {
  		// security_token value is null for first Draft request in which we store the telemetry data
  		if (!gr.getValue('security_token')) {
  			telemetry = gr.getValue('telemetry');
  		}
  	}
  	return telemetry;
  },

  _encrypt: function(unencryptedString) {
  	if (gs.nil(unencryptedString))
  		return "";

  	var encryptor =  new GlideEncrypter();
  	return encryptor.encrypt(unencryptedString);
  }
};

Sys ID

9141db8e1b10200081599e3bcc07135b

Offical Documentation

Official Docs: