Name

global.CreateOrUpdateITService

Description

this script main function accepts a JSON describing an IT service and creates or updates it in the CMDB. This script is currently limited to services which do not contain discovered elements

Script

var CreateOrUpdateITService = Class.create();

CreateOrUpdateITService.prototype = {

  initialize: function() {
  	this.msg="";
  },

  /**
  * iterate over the list of key of manual endpoints in the before, and check if they exist now.
  * if not, delete those manual endpoints
  */
  removeOutdatedManualEndpoints : function(manualEpBefore, manualEpAfter) {
  	var outdatedManualEndpoints = []; var outdatedDirectRelations = [];

  	for (var manualEp in manualEpBefore) {
  		if(!manualEpAfter[manualEp] && manualEpBefore[manualEp]) {
  			outdatedManualEndpoints.push(manualEpBefore[manualEp].sys_id);
  			//do we need to remove direct relation?
  			if(GlideProperties.get("sa.it_service.manual_ci_rel_type") && manualEpBefore[manualEp].direct_relation){
  				outdatedDirectRelations.push(manualEpBefore[manualEp].direct_relation);
  			}
  		}
  	}

  	while(outdatedDirectRelations.length){
  		outdatedDirectRelationsSubList = outdatedDirectRelations.splice(0,500);
  		var dirRelGr = new GlideRecord("cmdb_rel_ci");
  		dirRelGr.addQuery("sys_id", "IN", outdatedDirectRelationsSubList);
  		dirRelGr.deleteMultiple();
  	}

  	while(outdatedManualEndpoints.length){
  		outdatedManualEndpointSubList = outdatedManualEndpoints.splice(0,500);
  		var manualEpGr = new GlideRecord('cmdb_ci_endpoint_manual');
  		manualEpGr.addQuery("sys_id", "IN", outdatedManualEndpointSubList);
  		manualEpGr.deleteMultiple();
  	}
  },
  
  /*
  * create map containing all the manual endpoints in the service. the key is parent-child
  */
  getManualEndpointsInService : function(bsId) {
  	var gr = new GlideRecord('cmdb_ci_service_discovered');
  	gr.get(bsId);
  	var layerId= gr.layer;
  	var layerGr= new GlideRecord('svc_layer');
  	var manualEpMap = {};
  	layerGr.get(layerId);
  	var  allCis = sn_svcmod.ServiceContainerFactory.queryCIsAssociatedWithEnvironment(layerGr.environment);
  	for (var i = 0; i<allCis.length ; i++) {
  		var ciId = allCis[i];
  		var manualEpGr = new GlideRecord('cmdb_ci_endpoint_manual');
  		if (manualEpGr.get(ciId)) {
  			var parent = manualEpGr.source_ci;
  			var child = manualEpGr.target_ci;
  			if (!parent)
  				parent = '';
  			var key = parent + '-' + child;
  			var sysIdDirectRelation = {};
  			sysIdDirectRelation["sys_id"] = manualEpGr.getUniqueValue();
  			sysIdDirectRelation["direct_relation"] = manualEpGr.getValue("direct_relation");
  			manualEpMap[key] = sysIdDirectRelation;
  		}
  	}
  	return manualEpMap;
  },
  /**
  * fill the gliderecord with relevant fields from JSON
  */
  populateServiceFields : function(gr, jsonObj) {
  	// Iterate over the fields in the upper level of the JSON. ignore items and relations
  	for (var fieldName in jsonObj) {
  		if (fieldName == 'relations' || fieldName == 'items')
  			continue;
  		gr.setValue(fieldName, jsonObj[fieldName]);
  	}
  },

  /*
  * populagte the msg member and throw exception
  */
  throwError : function (err) {
  	this.msg = err;
  	gs.log("CreateOrUpdateITService exception msg : = " + this.msg);
  	throw this.msg;
  },

  /*
  * create a key to a relation based on parent and child
  */
  createRelationKey : function (rel) {
  	var parent = rel['parent'];
  	if (!parent)
  		parent = '';
  	var child = rel['child'];
  	if (!child) {
  		this.throwError(gs.getMessage("Invalid relation in input. The child attribute must not be empty"));
  	}
  	var relKey = parent + '-' + child;
  	return relKey;
  },

  /*
  * process the input request. Create or udpate a service and populate it with CIs
  */
  process : function(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
  	var body = "";

  	response.setStatus(200);
  	response.setContentType('application/json');

  	try {
  		body = GlideStringUtil.getStringFromStream(request.body.dataStream);

  		if (GlideStringUtil.nil(body)) {
  			throw new sn_ws_err.BadRequestError( gs.getMessage("Wrong input for creation or update of a service. input must not be empty") );
  		} else {
  			var responseObj = this.processJSON(body);
  			response.setBody(responseObj);
  		}
  	}catch (e){
  		response.setStatus= 400;
  		this.msg = gs.getMessage("Add or create service failed: {0}", this.msg);
  		throw new sn_ws_err.BadRequestError(this.msg);
  	}

  },


  /*
  * process the input JSON. Create or udpate a service and populate it with CIs
  */
  processJSON : function(jsonBody) {
  	this.msg="";
  	var json = "";
  	var bsId = "";
  	var errorId = "";
  	var infoMessage = "";
  	var responseObj = {};


  			try {
  				json = JSON.parse(jsonBody);
  			}
  			catch (eJson) {
  				this.msg = gs.getMessage("Wrong input for creation or update of a service, input must be JSON. {0}", eJson);
  			}

  			var bsName = json.name;
  			if (bsName == '') {
  				this.msg = gs.getMessage("Wrong input for creation or update of a service, The business service name must not be empty");
  			}

  			// Insert or update the business service record
  			if (this.msg !== "") {
  				this.throwError(this.msg);
  			}else if (this.msg === "") {
  				var bsGr = new GlideRecord('cmdb_ci_service_discovered');
  				bsGr.addQuery('name', bsName);
  				bsGr.query();
  				if (bsGr.next()) {
  					// Validate the service_type is manual or empty
  					if (bsGr.type == '2') {
  						this.throwError(gs.getMessage("This API is allowed to operate only on empty or manual service. This service contains discovered elements"));
  					}
  					this.populateServiceFields(bsGr, json);
  					bsGr.update();
  					bsId = bsGr.getUniqueValue();
  				} else {
  					this.populateServiceFields(bsGr, json);
  					bsId = bsGr.insert();
  					if (!bsId) {
  						this.throwError(gs.getMessage("Failed to insert a new business service. Insertion was probably blocked by business rule"));
  					}
  				}

  				var responseString = '/api/now/table/cmdb_ci_service_discovered/' + bsId;
  				responseObj['url'] = responseString;
  				responseObj['getContentUrl'] = '/api/now/cmdb/app_service/' + bsId + "/getContent";

  			}
              var rels = json['service_relations'];
  			var cisAdded = {};
  			if(this.shouldPopulateServiceBatch()) {
  				cisAdded = this.populateDiscoveredServiceBatch(bsId,rels);
  			} else {
  				cisAdded = this.populateDiscoveredService(bsId,rels);
  			}

  			// Report on the number of CIs added to the service
  			var ciCount = 0;
  			for (var ci in cisAdded) ciCount++;
  			infoMessage = gs.getMessage('{0} CIs added to business service', ciCount + '');
  			responseObj['info'] = infoMessage;

  	if (this.msg !== "") {
  		this.throwError(this.msg);
  	}

  	return responseObj;
  },

  shouldPopulateServiceBatch: function() {
      return GlideProperties.getBoolean('sa.service.batch_create_update_it_service_population', true) && !GlideProperties.getBoolean('sa.it_service.skip_domain_verification', false) && !GlideProperties.getBoolean('sa.it_service.verify_hierarchical_domain', false);
  },

  populateDiscoveredService: function(bsId, rels) {
  	this.msg="";
  	var bsm = new SNC.BusinessServiceManager();
      var somethingChanged = true;
      var cisAdded = {};
      var relationsAdded = {};
      var manualEpBeforeChange;
  	manualEpBeforeChange = this.getManualEndpointsInService(bsId);
      if (rels) {
          for (var level = 0; level < 100 && somethingChanged; level++) {
              somethingChanged = false;
              for (var i = 0; i < rels.length; i++) {
                  var rel = rels[i];
                  var parent = rel['parent'];
                  if (!parent)
                      parent = '';
                  var child = rel['child'];
                  var relKey = this.createRelationKey(rel);
                  if (relationsAdded[relKey]) // we already handled this relation
                      continue;
                  if (!parent) { // this is an entry point
                      this.msg = bsm.addCI(bsId, parent, child);
                      if (this.msg)
                          this.throwError(this.msg);
                      somethingChanged = true;
                      cisAdded[child] = true;
                      relationsAdded[relKey] = true;
                      continue;
                  }
                  // If the parent was already added, we can add the child
                  if (cisAdded[parent]) {
                      this.msg = bsm.addCI(bsId, parent, child);
                      if (this.msg)
                          this.throwError(this.msg);
                      cisAdded[child] = true;
                      relationsAdded[relKey] = true;
                      continue;
                  }
              } // end of loop on relations
          } // end of loop over levels
      }

      this.removeOutdatedManualEndpoints(manualEpBeforeChange, relationsAdded);

      // Check for dangling relations
      for (var j = 0; j < rels.length; j++) {
          var rel1 = rels[j];
          var relKey1 = this.createRelationKey(rel1);
          if (!relationsAdded[relKey1])
              this.throwError(gs.getMessage('Relation from parent {0} to child {1} is dangling. The parent is not part of the service', [rel1['parent'], rel1['child']]));
      }

      return cisAdded;
  },

  populateDiscoveredServiceBatch: function(bsId, rels) {
  	this.msg="";
  	var bsm = new SNC.BusinessServiceManager();
      var somethingChanged = true;
      var cisAdded = {};
      var relationsAdded = {};
  	var uniqueRelations = [];
      var manualEpBeforeChange;
  	manualEpBeforeChange = this.getManualEndpointsInService(bsId);
      if (rels) {
          for (var level = 0; level < 100 && somethingChanged; level++) {
              somethingChanged = false;
              for (var i = 0; i < rels.length; i++) {
                  var rel = rels[i];
                  var parent = rel['parent'];
                  if (!parent)
                      parent = '';
                  var child = rel['child'];
                  var relKey = this.createRelationKey(rel);
                  if (relationsAdded[relKey]) // we already handled this relation
                      continue;
  				if(parent == '' || cisAdded[parent]) {
  					var relationMap = {};
  					relationMap["parent"] = parent;
  					relationMap["child"] = child;
  					uniqueRelations.push(relationMap);
  					cisAdded[child] = true;
  					relationsAdded[relKey] = true;
  				}
                  if (parent == '') {
                      somethingChanged = true;
                  }
                  continue;
              } // end of loop on relations
          } // end of loop over levels
          this.msg = bsm.addBulkCIs(bsId, JSON.stringify(uniqueRelations), true);
          if (this.msg)
              this.throwError(this.msg);
      }

      this.removeOutdatedManualEndpoints(manualEpBeforeChange, relationsAdded);

  	for (var j = 0; j < rels.length; j++) {
          var rel1 = rels[j];
          var relKey1 = this.createRelationKey(rel1);
          if (!relationsAdded[relKey1])
              this.throwError(gs.getMessage('Relation from parent {0} to child {1} is dangling. The parent is not part of the service', [rel1['parent'], rel1['child']]));
      }

      return cisAdded;
  },
  
  checkLevels : function(levels) {
  	var checkedLevels = levels;
  	//Check the levels
  	if (levels == null){
  		checkedLevels = gs.getProperty('svc.manual.convert.levels.default_value', 3); //default value
  	}
  	
  	if (parseInt(levels) <= 0){
  		this.msg = gs.getMessage("The number of levels specified must be a positive integer.");
  	}
  	
  	return checkedLevels;
  
  },
  
  createDynamicService : function(name, levels, payload){
  	this.msg="";
  	
  	//Check the name
  	if (name =="" ){
  		this.msg = gs.getMessage("Wrong input for creation or update of a service, The business service name must not be empty");	
  	}
  	//Check the levels
  	var checkedLevels = this.checkLevels(levels);
  	
  	if (this.msg ===""){
  		try{
  			var gr = new GlideRecord("cmdb_ci_service_calculated");
  			gr.initialize();

  			//Creation of the empty service
  			gr.setValue("name", name);
  			gr.setValue("populator_status", 1);
  			gr.setValue("service_populator", "11f01e3dc3f23300daa79624a1d3ae32"); //service populator
  			gr.setValue("metadata", JSON.stringify({"levels":parseInt(checkedLevels)}));
  			gr.setValue("type", 5); //dynamic
  			
  			//With payload 
  			if (payload){
  				var attributes = Object.keys(payload);
  				for (i=0 ; i<attributes.length; i++){
  					gr.setValue(attributes[i], payload[attributes[i]]);
  				}	
  			}
  			
  			return gr.insert();			
  		} catch (err){
  			this.msg = gs.getMessage("An error occured during the creation of the empty dynamic service. {0}", err);
  		}
  	}	
  	if (this.msg !== "") {
  		this.throwError(this.msg);
  	}	
  },
  
  convertManualToDynamicService : function(serviceId, levels){
  	this.msg="";
  	
  	//Check the levels
  	var correctLevels = this.checkLevels(levels);
  	
  	var gr = new GlideRecord("cmdb_ci_service_discovered");
  	
  	if (!gr.get(serviceId)){
  		this.msg = gs.getMessage("We couldn't find the service {0} in the database.", serviceId);
  	}
  	
  	
  	if (this.msg !== ""){
  		this.throwError(this.msg);
  	} else {
  		//Reclassification
  		gr.setValue("sys_class_name", "cmdb_ci_service_calculated");
  		gr.update();
  		
  		//Add the service populator 
  		var gr2 = new GlideRecord("cmdb_ci_service_calculated");

  		gr2.get(serviceId);
  		gr2.setValue("populator_status", 1);
  		gr2.setValue("service_populator", "11f01e3dc3f23300daa79624a1d3ae32"); //service populator
  		gr2.setValue("metadata", JSON.stringify({"levels":correctLevels}));
  		gr2.setValue("type", 5); //dynamic

  		gr2.update();	
  	}
  },
  
  updateDynamicNumberOfLevels : function(serviceId, levels){
  	this.msg="";
  	//Check the levels
  	var checkedLevels = this.checkLevels(levels);
  	
  	var gr = new GlideRecord("cmdb_ci_service_calculated");
  	
  	if (!gr.get(serviceId)){
  		this.msg=gs.getMessage("We couldn't find the service {O} in the cmdb_ci_service_calculated table", serviceId);
  	}
  	
  	if (this.msg !==""){
  		this.throwError(this.msg);
  	} else {
  		gr.setValue("metadata", JSON.stringify({"levels":checkedLevels}));
  		gr.update();
  	}	
  },
  
  convertDynamicToManual : function(serviceId) {
  	this.msg="";
  	var gr = new GlideRecord('cmdb_ci_service_calculated');
  	gr.get(serviceId);
  	gr.setValue("sys_class_name", "cmdb_ci_service_discovered");
  	
  	// checks if service is of 'empty' or 'manual' type
  	var ga = new GlideAggregate('sa_m2m_service_entry_point');
  	ga.addAggregate('count');
  	ga.addQuery('cmdb_ci_service',serviceId);
  	ga.query();
  	ga.next();
  	var numOfEp = ga.getAggregate('count');
  	if(numOfEp === '0'){
  		gr.setValue("type", 0);
  	}
  	else{
  		gr.setValue("type", 1);
  	}
  	gr.update();
  },
  
  addCI : function(bsID, sourceCiId , targetCIId) {
  	var bsm = new SNC.BusinessServiceManager();
  	return bsm.addCI(bsID, sourceCiId, targetCIId);
  },
  
  removeCI : function(bsID, ciSysID) {
  	var bsm = new SNC.BusinessServiceManager();
  	return bsm.removeManualCi(ciSysID, bsID);
  },
  
  retrieveSystemExcludeList : function() {
  	var blackWhiteListManager = new SNC.ManualCiBlackWhiteListManager();
  	return blackWhiteListManager.retrieveSystemExcludeList() ;
  },
  
  getExcludedCiClasses : function() {
  	var blackWhiteListManager = new SNC.ManualCiBlackWhiteListManager();
  	return blackWhiteListManager.getExcludedCiClasses();
  },

  getIncludedCiClasses : function() {
  	var blackWhiteListManager = new SNC.ManualCiBlackWhiteListManager();
  	return blackWhiteListManager.getIncludedCiClasses();
  },

  type: 'CreateOrUpdateITService'
};

Sys ID

1f39af45c31303003e76741e81d3aee9

Offical Documentation

Official Docs: