Name

sn_itom_pattern.GoogleEventHandler

Description

Handling the events from GCP and invoking discovery pattern

Script

var GoogleEventHandler = Class.create();
GoogleEventHandler.prototype = Object.extendsObject(sn_cmp.CloudEventHandler, {
  initialize: function() {
  },

  processEvent : function(headersStr, bodyStr, queryParamsStr, eventId) {
  	var response = {};

  	var jsonBody = JSON.parse(bodyStr);

  	var ciType;
  	var objectID = this.getObjectID(jsonBody);
  	try {
  		ciType = this.extractCMDBCIType(jsonBody);
  	}
  	catch(e){
  		gs.error("Failed to get cmdb type for object ID "+objectID);
  	}

  	if(!ciType){
  		gs.warn("GoogleEventHandler: Not supported type "+this.getResourceType(jsonBody)+" with object ID "+objectID);
  	}else if(this.shouldInvokeDiscovery(jsonBody)){
  		gs.info("GoogleEventHandler: invoking discovery on event ID "+eventId+" with object ID "+objectID);
  		response = this.invokeDiscovery(jsonBody,eventId,objectID);
  	}else{
  		gs.info("GoogleEventHandler: handing in server event ID "+eventId+" with object ID "+objectID);
  		response.ci = this.handleAlertInServer(jsonBody,objectID,ciType);

  	}
  	response.state = "processed";

  	// getting the account Domain
  	var serviceAccountId = this.getProjectID(jsonBody);
  	var serviceAccountGR = this._verifyServiceAccount(serviceAccountId);
      response.sys_domain = serviceAccountGR.sys_domain;

      if (this.getActionType(jsonBody))
          response.subject = this.getActionType(jsonBody);
  	if (jsonBody.timestamp)
  		response.event_time = this.getEventTime(jsonBody.timestamp);

      return response;

  },

  /***
   * returns true if the alert should trigger a discovery
   * @param jsonBody
   */
  shouldInvokeDiscovery : function(jsonBody) {

  	// for now the only logic is that we need to check if its a delete event
  	// the shouldInvokeDiscovery is still here to support future cases of handling events in the server

  	// TODO: the problem in deleting CI from server is that the 'absent' and 'terminated' is not aggregating to the
  	// children
  	return !this.isDeleteEvent(jsonBody);


  },

  /***
   * handle the alert without running a discovery
   * Alerts that will be handled here are
   *  - delete
   * @param jsonBody - the event json body
   * @param objectId - the CI object ID
   */
  handleAlertInServer : function(jsonBody,objectId,cmdbCIType) {
  	var resCI;
  	if(this.isDeleteEvent(jsonBody)){
  		gs.info("GoogleEventHandler: CI was marked as absent with object ID "+objectId);
  		resCI = this.markCIAbsent(this.getObjectID(jsonBody),cmdbCIType);
  	}else{
  		gs.warn("GoogleEventHandler: No Action was set for CI with object ID "+objectId);
  	}
  	return resCI;

  },

  /***
   * Mark the CI from type ciClass with object ID objectID as absent
   * @param objectID - the object ID attribute
   * @param ciClass - the cmdb class
   * return: glide record of the CI that is marked as absent
   *
   */
  markCIAbsent : function(objectID, ciClass) {
  	var resultVal;

  	if (ciClass) {
  		var ciGR = new GlideRecord(ciClass);
  		if (ciGR.get('object_id', objectID)) {
  			gs.info("GoogleEventHandler: marking object "+objectID+" for absent");
  			new sn_cmp.CMPReconciler().markCIAbsent(ciGR);
  			resultVal = ciGR.getUniqueValue();
  		}
  	}else{
  		gs.error("GoogleEventHandler: failed to terminate CI "+objectID+", no cmdb_ci table was found");
  	}
  	return resultVal;
  },

  /***
   * invoke the discovery based on alert
   * @param jsonBody
   * @param eventID - event ID
   * @param objectID - ci object ID
   */
  invokeDiscovery : function(jsonBody,eventID,objectID) {
  	var response = {};

  	var configObj = this.prepareConfigObj(jsonBody,eventID,objectID);
  	response.state = 'processing';
  	response.discovery_status = null;
  	response.error_message = "";

  	gs.info("GoogleEventHandler: Invoking discovery from event ID "+eventID+" on object id "+ objectID + " with pattern "+configObj.patternId);
  	var cloudPatternInvocation = new sn_itom_pattern.CloudPatternInvocation();
  	var result = cloudPatternInvocation.eventBasedDiscovery(configObj, response);

  	if (result){
  		//For sucess invocation the state | error_mesaage will be update in HorizontalDiscoveryResultHandler.finalizeCloudChangeEvent()
  		//Will be determine if the pattern is success or not
  		gs.info("GoogleEventHandler: Finished Event based discovery on object ID "+objectID);
  	}else {
  		gs.error("GoogleEventHandler: Failed Event based discovery on object ID "+objectID);
  		response.state = (response.state) ? response.state : 'error';
  		response.error_message =  (response.error_message) ? response.error_message : gs.getMessage('Cannot invoke pattern discovery for: {0}', [JSON.stringify(configObj , null, 2)]);
  	}

  	response.resource_id = objectID;

  	response.resource_type = configObj.resource_type;
  	// response.resource_block = this._getResourceTypeGr(this.resourceType);

  	return response;
  },

  /***
   * prepare the config type for invoking the discovery
   * @param jsonBody
   * @param eventID
   * @param objectID
   * @returns {{changeType: *|string, classType: *, accountId: *|string, ldc: *|string, inputObjectId: *, cloudId: *, patternId: *, eventId: *, eventType: string, eventData: string}}
   */
  prepareConfigObj : function(jsonBody,eventID,objectID){
  	var resourceType  = this.getResourceType(jsonBody);
  	var invocationPatternId =  this._getPatternId(resourceType);
  	var ciClassType = this._getClassTypeId(resourceType);
  	var actionType =  this.getActionType(jsonBody);
  	var projectID = this.getProjectID(jsonBody);
  	var context = this.extractContext(jsonBody);
  	var ldc = this.extractLDC(jsonBody);

  	//Prepare the mandatory parameters to pass to the pattern context
  	var configObj = {
  		changeType : actionType,
  		classType: ciClassType,
  		accountId: projectID,
  		ldc: ldc,
  		inputObjectId: objectID,
  		cloudId: objectID,
  		patternId: invocationPatternId,
  		eventId: eventID,
  		eventType: "GCP"
  	};

  	return configObj;
  },


  getResourceType : function(jsonBody) {
  	return jsonBody.resource.type;
  },

  getActionType : function(jsonBody) {
  	return jsonBody.protoPayload.response.operationType;
  },

  getProjectID : function(jsonBody){
  	return jsonBody.resource.labels.project_id;

  },

  /***
   * it will parse event timestamp like 2020-01-28T18:27:46.171277Z to the readable format[yyyy-mm-dd hr:min:sec]
   * @param eventTime
   * @returns {eventTime in format yyyy-mm-dd hr:min:sec}
   */

  getEventTime: function(eventTime) {
      var re = /^(\d\d\d\d-\d\d-\d\d)T(\d\d:\d\d:\d\d)\.\d\d\d\d\d\dZ$/g;
      var arr = re.exec(eventTime);
      if (arr && arr.length == 3)
          return new GlideDateTime(arr[1] + ' ' + arr[2]);
      return '';
  },

  getObjectID : function(jsonBody){
  	return jsonBody.protoPayload.response.targetId;

  },
  extractContext : function(jsonBody){
  	return jsonBody.protoPayload;
  },
  extractLDC : function(jsonBody) {
  	var ldcRes = "us-east1" ; //TODO: today we dont have support for global resources. it needs to be added in future version. currently the pattern is ignoring this attribute for global CIs
  	
  	var region = jsonBody.resource.labels.region;
  	var location = jsonBody.resource.labels.location;
  	var findLdc = false;
  	var zone = '';
  	zone = jsonBody.resource.labels.zone;
      if (!zone) { // For subnetworks	
          zone = jsonBody.resource.labels.location;	
      }
  	// the assumption here is that i can ignore the service account context since all Availability zones with the name XXX will always be connected to the LDC with name YYY.
  	
  	if (region) {
  		ldcRes = region;
  		findLdc = true;
  		return ldcRes;
  	}
  	// List of location taken from https://cloud.google.com/spanner/docs/instance-configurations#available-configurations-multi-region
  	if(location) {
  		switch (location) 
  			{   case "asia1" : ldcRes = "asia-northeast1"; break;    
  				case "eur3" : ldcRes = "europe-west1"; break; 
  				case "eur5" : ldcRes = "europe-west2"; break; 
  				case "eur6" : ldcRes = "europe-west4"; break; 
  				case "nam3" : ldcRes = "us-east4"; break;
  				case "nam6" : ldcRes = "us-central1"; break;
  				case "nam7" : ldcRes = "us-central1"; break; 
  				case "nam8" : ldcRes = "us-west2"; break; 
  				case "nam9" : ldcRes = "us-east4"; break; 
  				case "nam10" : ldcRes = "us-central1"; break; 
  				case "nam11" : ldcRes = "us-central1"; break; 
  				case "nam12" : ldcRes = "us-central1"; break; 
  				case "nam-eur-asia1" : ldcRes = "us-central1"; break;
  				default: ldcRes = location;
  			}
  		findLdc = true;
  		return ldcRes;		
  	}
  	
  	if(zone) {
  		var azGR = new GlideRecord('cmdb_ci_availability_zone');
  		azGR.addQuery('name', zone);
  		azGR.query();

          if (azGR.next()) { // For Zones
  			var relGR = new GlideRecord('cmdb_rel_ci');
  			relGR.addQuery("child", azGR.sys_id);
  			relGR.addQuery("parent.sys_class_name",'cmdb_ci_google_datacenter');
  			relGR.query();

  			if (relGR.next()) {
  				if(relGR.parent.name) {
  					ldcRes = relGR.parent.name;
                      findLdc = true;
  				}
  				gs.info("GoogleEventHandler: Found LDC with name " + relGR.parent.name + " for availability zone " + zone);
  			}else{
  				gs.error("GoogleEventHandler: Failed to find datacenter to availability zone " + azGR.name);
  			}


          } else { // For TCP LB,Region name comming as zone.
              var ldcGr = new GlideRecord('cmdb_ci_google_datacenter');
              ldcGr.addQuery('name', zone);
              ldcGr.query();
              if (ldcGr.next()) {
                  ldcRes = ldcGr.name;
                  findLdc = true;
                  gs.info("GoogleEventHandler: Found LDC with name " + ldcRes + " for Region " + zone);
  		} else {
                  gs.error("GoogleEventHandler: Failed to find datacenter to Region " + zone);
  		}
          }
      }
      if (!findLdc) {
          gs.info("GoogleEventHandler: failed to find Availability zone/ Region for name " + zone + ", assuming global");
  	}

  	return ldcRes;
  },

  isDeleteEvent : function(jsonBody) {
  	// check if the type of the discovery is delete, otherwise run discovery
  	var res = false;
  	var action = this.getActionType(jsonBody);
  	if(action.includes("delete")){
  		res = true;
  	}
  	return res;

  },

  extractCMDBCIType : function(jsonBody) {
  	var resouceType = this.getResourceType(jsonBody);
  	gs.info("GoogleEventHandler: extracting class type for "+resouceType);
  	var ciType;
  	if(resouceType){
  		try {
  			ciType = this._getClassTypeId(resouceType);
  		}catch (e){
  			gs.warn("Failed to get cmdb type for object ID "+this.getObjectID(jsonBody)+" and type "+this.getResourceType(jsonBody));
  		}
  	}

  	return ciType;
  },

  type: 'GoogleEventHandler'
});

Sys ID

472489db730133005ad2db37aef6a763

Offical Documentation

Official Docs: