Name

global.VCenterDatacentersSensor

Description

Implementation of the VMWare - vCenter Datacenters sensor. Create or update vCenter and datacenter records. Create records for folders, trigger probes for datastores, clusters & resource pools, networks and VMs. Create relationships between root folders and the datacenter, and between non-root folders and their parents.

Script

/* jshint -W030 */

var VCenterDatacentersSensor;

// DiscoveryCMPUtils won't be available if cloud management isn't active.  Declaring
// this ensures that we won't get an exception when we check to see if it's active.
var DiscoveryCMPUtils;

var g_device;

(function() {

var vCenterSysId, vCenterUuid, _this, locationID, statusID,
  allFolders = [ ],
  allVms = [ ],
  allDatastores = [ ],
  allNetworks = [ ],
  allClusters = [ ],
  allHosts = [ ],
  allPools = [ ],
  allDatacenters = [ ],
  folderSchema = {
  	cmdb_ci_vcenter_folder: {
  		index: [ 'morid', 'vcenter_uuid' ],
  		fixup: fixupFolder,
  		parentOf: {
  			cmdb_ci_vcenter_folder: 'Contains::Contained by'
  		},
  		childOf: {
  			cmdb_ci_vcenter_datacenter: 'Contains::Contained by'
  		}
  	}
  };

VCenterDatacentersSensor = {
  process: process,
  handleError: handleError,
  type: "DiscoverySensor"
};

if (DiscoveryCMPUtils.isCmpActive())
  folderSchema.cmdb_ci_vcenter_folder.parentOf.hostedOn = 'Hosted on::Hosts';

/*
Sample data:
{
"vcenter": {
  "type": "ServiceInstance",
  "morid": "ServiceInstance",
  "name": "VMware vCenter Server",
  "version": "5.5.0",
  "fullname": "VMware vCenter Server 5.5.0 build-3252642 (Sim)",
  "api_version": "5.5",
  "instance_uuid": "D877AC10-0803-40C2-AE74-F66F99671D64",
  "url": "https:\/\/10.11.128.135\/sdk"
},
"cmdb_ci_vcenter_datacenter": [
  {
    "type": "Datacenter",
    "morid": "datacenter-2",
    "name": "DC0",
    "folder_morid": "group-v3",
    "host_morid": "group-h4",
    "folders": {
      "type": "Datacenter",
      "morid": "datacenter-2",
      "name": "DC0",
      "datastoreFolder": {
        "type": "Folder",
        "morid": "group-s5",
        "name": "datastore",
        "childEntity": [
          {
            "type": "Datastore",
            "morid": "datastore-170",
            "name": "SANLAB1DS_DC0_C0_0"
          }
        ]
      },
      "hostFolder": {
        "type": "Folder",
        "morid": "group-h4",
        "name": "host",
        "childEntity": [
          {
            "type": "ClusterComputeResource",
            "morid": "domain-c105",
            "name": "DC0_C14"
          }
        ]
      },
      "networkFolder": {
        "type": "Folder",
        "morid": "group-n6",
        "name": "network",
        "childEntity": [
          {
            "type": "Network",
            "morid": "network-7",
            "name": "VM Network"
          }
        ]
      },
      "vmFolder": {
        "type": "Folder",
        "morid": "group-v3",
        "name": "vm",
        "childEntity": [
          {
            "type": "VirtualMachine",
            "morid": "vm-4373",
            "name": "DC0_C1_RP2_VM11"
          }
        ]
      }
    }
  }
],
"hosts": [
  "host-1025"
],
"pools": [
  "resgroup-106"
]
}
*/
//////////////////////////////////////////////////////////////////////////
function process(result) {

  var vCenter, vcGr, serviceAccount,
  	args = {
  		leaveCurrent: 1,
  		schema: {
  			cmdb_ci_vcenter_datacenter: {
  				index: [ 'morid', 'vcenter_uuid' ],
  				fixup: fixupDatacenter,
  				childOf: {
  					managedBy: 'Manages::Managed by'
  				},
  				parentOf: {
  					hostedBy: 'Hosted on::Hosts'
  				}
  			},
  			cmdb_ci_cloud_service_account: {
  				index: [ 'account_id' ],
  			}
  		}
  	};

  if (!g_device)
  	g_device = DeviceHistory.createDeviceHistory(source, null, this.getEccQueueId());

  // This disables the triggering of the associated probes.  Triggering
  // of probes will be handled dynamically below in processDatacenter
  this.setTriggerProbes(false);

  _this = this;
  locationID = this.getLocationID();
  statusID = new DiscoveryStatus(g_probe.getParameter('agent_correlator')+'');

  // During normal discovery g_probe_parameters should always be defined.
  // It's only undefined during test execution.
  if (typeof g_probe_parameters != 'undefined') {
  	g_probe_parameters.cidata = this.getParameter('cidata');
  	g_probe_parameters.source = this.getParameter('source');
  }

  output = JSON.parse(output);

  args.results = output;
  args.location = locationID;
  args.statusId = statusID;

  vCenter = output.vcenter;
  vCenterUuid = vCenter.instance_uuid;

  // The CM API requires an association between the datacenter and a
  // service account.  Create it if CMP has been activated.
  if (DiscoveryCMPUtils.isCmpActive()) {
  	var accountName = "";
  	var ip = '' + g_probe.getSource();

  	if (ip)
  		accountName = 'vCenter ServiceAccount@' + ip;

  	accountName = accountName || vCenter.name || vCenter.fullName || vCenter.url || "vCenter Auto Generated Account (" + vCenterUuid + ")";
  	
  	serviceAccount = {
  		datacenter_type: "cmdb_ci_vcenter_datacenter",
  		name: accountName,
  		account_id: vCenterUuid,
  		object_id: vCenterUuid
  	};
  	
  	// Cloud service account created for the vCenter can have the datacenter_url field with the IP or FQDN or no value
  	// We update the datacenter_url field only if there is no value at all inorder to preserve the FQDN/IP information, if exists
  	var cloudServiceAccountGR = new GlideRecord('cmdb_ci_cloud_service_account');
  	if (!cloudServiceAccountGR.get('account_id', vCenterUuid) || !cloudServiceAccountGR.getValue('datacenter_url'))
  		serviceAccount.datacenter_url = vCenter.url || "";
  	
  	args.results.cmdb_ci_cloud_service_account = [ serviceAccount ];

  	args.results.cmdb_ci_vcenter_datacenter.forEach(
  		function(dc) {
  			dc.hostedBy = serviceAccount;
  			dc.region = dc.name;
  		});
  }

  if (current) {
  	current.url = vCenter.url;
  	current.fullname = vCenter.fullname;
  	current.api_version = vCenter.api_version;
  	current.instance_uuid = vCenterUuid;
  	this.addDiscoveryCiStuff(current);
  }

  // Check for the vCenter CI
  var ip = '' + g_probe.getSource();
  var thisCmdbRecord = this.getCmdbRecord();

  // The probe may have been triggered via process classifier or port probe.  If it was triggered
  // by the process classifier then we expect to have thisCmdbRecord and we can use the sys_id
  // to get the vCenter record.  Unfortunately we sometimes get thisCmdbRecord even when triggered
  // by the port probe, but in this case the sys_id is wrong.  I'm not sure what causes this to happen
  // but we need to guard against it.

  vcGr = new GlideRecord('cmdb_ci_vcenter');
  if (thisCmdbRecord)   // This should mean that the probe was triggered via process classifier
  	vcGr.addQuery('sys_id', thisCmdbRecord.sys_id);
  else  // This should mean the probe was triggered via port probe
  	vcGr.addQuery('instance_uuid', vCenterUuid);

  vcGr.query();
  if (!vcGr.next()) {
  	// There's no existing vcenter record for this sys_id/uuid... check ip address
  	vcGr.initialize();
  	vcGr.addQuery('ip_address', ip);
  	vcGr.query();

  	if(!vcGr.next()) {
  		vcGr.initialize();
  		vcGr.ip_address = ip;
  		vcGr.instance_uuid = current.instance_uuid;
  		vcGr.name = 'vCenter@' + ip;
  	}
  }
  else {
  	vcGr.ip_address = ip;
  	if (/^vCenter@(?:\d{1,3}.){3}\d{1,3}$/.test(vcGr.name))
  		vcGr.name = "vCenter@" + ip;
  }

  if(this.ciData && this.ciData.data && this.ciData.data.dns_name)
  	vcGr.fqdn = this.ciData.data.dns_name;

  this.addDiscoveryCiStuff(vcGr);
  vcGr.update();

  vCenterSysId = '' + vcGr.getUniqueValue();
  this.updateObjectSource(vcGr);

  // vcGr now points to the correct vCenter record.  If we were triggered via process classifier
  // this is the record created by the process classifier, otherwise it's the record with the right
  // UUID.

  if (JSUtil.notNil(g_device)) {
  	g_device.setCISysID(vCenterSysId);
  	g_device.setClassifiedAs('cmdb_ci_vcenter');
  }

  // If we were triggered by a process classifier then we may have a duplicate record.  Since we may not
  // know how we were triggered we will always check and remove any extra vCenter records.
  vcGr.initialize();
  var qc = vcGr.addQuery('instance_uuid', current.instance_uuid);
  qc.addOrCondition('ip_address', ip);
  vcGr.query();
  while (vcGr.next()) {
  	if (vcGr.sys_id + '' != vCenterSysId)
  		vcGr.deleteRecord();
  }

      //  PRB1388892 : Adding a server record if it's a vcenter appliance and creating runs on relationship with vCenter
      //If os_type is linux, then it's a vcenter appliance

      if (vCenter.os_type.indexOf('linux') != -1 && output.appliance && !output.appliance.error) {
       		updatevCenterApplianceInfo(output, ip, vCenter.fullname, vCenterSysId);
      }

  // Create a Runs on::Runs relationship if necessary
  runsOnRelForCredentialless(vCenterSysId, ip);

  // This writes only the datacenter records.
  JsonCi.prepare(args);
  JsonCi.writeJsObject(args);
  JsonCi.writeRelationships(args);

  if (('' + g_probe.getParameter('datacenters_only')) != 'true') {
  	JsonCi.iterate(processDatacenter, args);
  	updateStaleness();
  }
}
//////////////////////////////////////////////////////////////////////////
function updatevCenterApplianceInfo(output, ip, vCenterName, vCenterSysId) {
  	var interfaces = '',
  	hostname = '',
  	macOfAppliance = '';
  	if(output.appliance.response && output.appliance.response.interfaces )
  		interfaces = output.appliance.response.interfaces;
  	if(output.appliance.response && output.appliance.response.dns && output.appliance.response.dns.hostname)
  		hostname = output.appliance.response.dns.hostname;

          	for (var idx = 0; idx < interfaces.length; idx++) {
              	var obj = interfaces[idx];
              	if (obj.value.ipv4.address == ip) {
                  		macOfAppliance = obj.value.mac;
                  		break;
              	}
          	};
          	if(!macOfAppliance)
            		return;

          	var serverGR = new GlideRecord('cmdb_ci_linux_server');
          	serverGR.addQuery('mac_address', macOfAppliance);
          	serverGR.query();
          	if (!serverGR.next()) {
              	serverGR.initialize();
              	serverGR.name = hostname || vCenterName;
              	serverGR.ip_address = ip;
              	serverGR.mac_address = macOfAppliance;
  		serverGR.discovery_source = "ServiceNow";
              	serverGR.insert();
          	}
          	runsOnRelForvCenterAppliance(vCenterSysId, macOfAppliance);
}

//////////////////////////////////////////////////////////////////////////
function handleError(errors) {
  // Only do this if we were triggered by port probe NOT process classifier
  if (!this.getCmdbRecord()) {
  	DiscoverySensor.prototype.handleError.call(this, errors, {
  		sourceName: 'VMWare - vCenter Datacenters',
  		lastState: DiscoverySensor.HistoryStates.ACTIVE_COULDNT_CLASSIFY,
  		deviceState: DiscoverySensor.DeviceStates.REJECTED,
  		fireClassifiers: true
  	});
  } else {
  	DiscoverySensor.prototype.handleError.call(this, errors);
  }
}

//////////////////////////////////////////////////////////////////////////
function runsOnRelForCredentialless(vCenterSysId, ip_address) {
  var newGr,
  	runsOnRelSysId = '60bc4e22c0a8010e01f074cbe6bd73c3',    // Runs on::Runs
  	gr = new GlideRecord('cmdb_rel_ci');

  // 1st check to see if there's already a Runs on::Runs relationship.
  gr.addQuery('parent', vCenterSysId);
  gr.addQuery('type', runsOnRelSysId);
  gr.query();
  if (gr.getRowCount() > 0)
  	return;

  // No Runs on::Runs relationship.  Check for a host discovered by credentialless
  gr = new GlideRecord('cmdb_ci_computer');
  gr.addQuery('discovery_source', 'CredentiallessDiscovery');
  gr.addQuery('ip_address', ip_address);
  gr.setLimit(2);
  gr.query();
  // We only want to reconcile by IP if the match is unambiguous
  if (gr.getRowCount() == 1) {
  	gr.next();
  	newGr = new GlideRecord('cmdb_rel_ci');
  	newGr.parent = vCenterSysId;
  	newGr.type = runsOnRelSysId;
  	newGr.child = gr.sys_id;
  	newGr.insert();
  }
}
  //////////////////////////////////////////////////////////////////////////

  function runsOnRelForvCenterAppliance(vCenterSysId, mac_address) {
      if (!mac_address)
          return;
      var newGr,
          runsOnRelSysId = '60bc4e22c0a8010e01f074cbe6bd73c3', // Runs on::Runs
          gr = new GlideRecord('cmdb_rel_ci');
  
      // 1st check to see if there's already a Runs on::Runs relationship.
      gr.addQuery('parent', vCenterSysId);
      gr.addQuery('type', runsOnRelSysId);
      gr.query();
      if (gr.getRowCount() > 0)
          return;
      // No Runs on::Runs relationship.  Check for a server CI for this appliance
      gr = new GlideRecord('cmdb_ci_linux_server');
      gr.addQuery('mac_address', mac_address);
      gr.setLimit(1);
      gr.query();
      // We only want to reconcile by IP if the match is unambiguous
      if (gr.getRowCount() == 1) {
          gr.next();
          newGr = new GlideRecord('cmdb_rel_ci');
          newGr.parent = vCenterSysId;
          newGr.type = runsOnRelSysId;
          newGr.child = gr.sys_id;
          newGr.insert();
      }

  }

//////////////////////////////////////////////////////////////////////////
function updateStaleness() {

  var vmObjIds, gr, esxGr,
  	isBatchUpdateRequired = g_probe.getParameter('batch_update_vm_state') == 'true' ? true : false;

  markStale(allFolders, 'cmdb_ci_vcenter_folder', vCenterSysId);

  markStale(allDatacenters, 'cmdb_ci_vcenter_datacenter', vCenterSysId, 'morid');
  markStale(allVms, 'cmdb_ci_vmware_template', vCenterSysId);
  markStale(allVms, 'cmdb_ci_vmware_instance', vCenterSysId);

  markStale(allDatastores, 'cmdb_ci_vcenter_datastore', vCenterSysId);
  markStale(allNetworks, 'cmdb_ci_vcenter_network', vCenterSysId);
  markStale(allNetworks, 'cmdb_ci_vcenter_dv_port_group', vCenterSysId);
  markStale(allNetworks, 'cmdb_ci_vcenter_dvs', vCenterSysId);
  markStale(allClusters, 'cmdb_ci_vcenter_cluster', vCenterSysId);

  markStale(allFolders, 'cmdb_ci_vcenter_folder', vCenterSysId);
  markStale(output.hosts, 'cmdb_ci_esx_server', vCenterSysId, 'morid');
  markStale(output.pools, 'cmdb_ci_esx_resource_pool', vCenterSysId);

  vmObjIds = allVms.map(function(vm) { return vm.morid; });

  // Mark stale VMs as 'retired'
  gr = isBatchUpdateRequired ? new GlideMultipleUpdate('cmdb_ci_vmware_instance') : new GlideRecord('cmdb_ci_vmware_instance');
  gr.addQuery('vcenter_ref', vCenterSysId);
  gr.addQuery('object_id', 'NOT IN', vmObjIds);
  gr.addQuery('install_status', '!=', '7');
  gr.setValue('install_status', '7');
  gr.setValue('operational_status', '2');
  gr.setValue('state', 'terminated');
  isBatchUpdateRequired && gr.setValue('sys_updated_on', gs.nowNoTZ());
  isBatchUpdateRequired ? gr.execute() : gr.updateMultiple();

  // VMs may need to be un-retired (usually because of VM migration).
  // We have to handle that in the VMs sensor because an event won't
  // go through this code
  var datacenters = [];
  var esxArray = [];
  //1. Find the datacenters contained in the payload.
  datacenters = VMUtils.lookupSysIds(output.cmdb_ci_vcenter_datacenter, 'cmdb_ci_vcenter_datacenter', vCenterSysId);

  //2.From the Direct RelationShip (L1) ESX server -> Hosted on :: Hosts -> Datacenter, get ESX Servers in the datacenter
  var rel = new GlideRecord('cmdb_rel_ci');
  rel.addQuery("parent.sys_class_name", 'cmdb_ci_esx_server');
  rel.addQuery('child', 'IN', datacenters);
  rel.addQuery('type', '5f985e0ec0a8010e00a9714f2a172815');
  rel.query();
  while(rel.next())
  	esxArray.push(rel.parent.toString());

  var absentProp = gs.getProperty('glide.discovery.mark_esx_servers_as_absent');
  var markAbsent = (!gs.nil(absentProp)) ? absentProp : 'false';
  var INSTALL_STATUS = 'install_status';
  // Mark stale ESX servers as 'retired' or 'absent'
  esxGr = new GlideRecord('cmdb_ci_esx_server');
  esxGr.addQuery('vcenter_ref', vCenterSysId);
  esxGr.addQuery('object_id', 'NOT IN', output.hosts);
  esxGr.addQuery('sys_id', 'IN', esxArray); //3. Filter out ESX Server's by datacenter to avoid flip-flop
  if(markAbsent == 'false') {
  	esxGr.addQuery(INSTALL_STATUS, '!=', '7');
  	esxGr.setValue(INSTALL_STATUS, '7'); //Retired = 7
  } else {
  	esxGr.addQuery(INSTALL_STATUS, '!=', '100');
  	esxGr.setValue(INSTALL_STATUS, '100'); //Absent = 100
  }
  esxGr.updateMultiple();
}

//////////////////////////////////////////////////////////////////////////
function markStale(mors, table, vcenter, morColumn) {
  var sysIds,
  	map = { },
  	gr = new GlideRecord(table);

  gr.addQuery('vcenter_ref', vcenter);
  gr.query();

  while (gr.next())
  	map['' + gr.sys_id] = true;

  sysIds = VMUtils.lookupSysIds(mors, table, vCenterSysId, morColumn);
  sysIds.forEach(function(sysId) {
  	map[sysId] = false;
  });

  SNC.DiscoveryCIReconciler.updateStaleness(JSON.stringify(map), table);
}

//////////////////////////////////////////////////////////////////////////
function fixupDatacenter(dc) {
  dc.managedBy = { sys_id: vCenterSysId };
  dc.object_id = dc.morid;
  dc.vcenter_uuid = vCenterUuid;
  dc.vcenter_ref = vCenterSysId;
}

//////////////////////////////////////////////////////////////////////////
function fixupFolder(folder) {
  folder.vcenter_ref = vCenterSysId;
  folder.vcenter_uuid = vCenterUuid;
  folder.object_id = folder.morid;
}

//////////////////////////////////////////////////////////////////////////
function processDatacenter(dc) {
  var datastores, clusters, networks, vms,
  	folders = [ ],
  	dcFolders = dc.folders,
  	args = {
  		leaveCurrent: 1,
  		location: locationID,
  		statusId: statusID,
  		mutexPrefix: dc.morid,
  		schema: folderSchema,
  		results: { cmdb_ci_vcenter_folder: folders }
  	};

  allHosts = allHosts.concat(dc.hosts);
  allPools = allPools.concat(dc.pools);
  allDatacenters.push(dc.morid);

  datastores = extractFoldersAndChildren(dcFolders.datastoreFolder, folders, dc);
  clusters = extractFoldersAndChildren(dcFolders.hostFolder, folders, dc);
  networks = extractFoldersAndChildren(dcFolders.networkFolder, folders, dc);
  vms = extractFoldersAndChildren(dcFolders.vmFolder, folders, dc);

  // Prepare & write the folders
  JsonCi.prepare(args);
  JsonCi.writeJsObject(args);

  allVms = allVms.concat(vms);
  allDatastores = allDatastores.concat(datastores);
  allNetworks = allNetworks.concat(networks);
  allClusters = allClusters.concat(clusters);
  allFolders = allFolders.concat(folders);

  // Trigger probes configured to run after the datacenters sensor.
  triggerProbes(dc, datastores, clusters, networks, vms);

  JsonCi.updateRelationships(args);
}

//////////////////////////////////////////////////////////////////////////
function triggerProbes(dc, stores, clusters, networks, vms) {
  var objects = {
  	datastore: stores,
  	cluster: clusters,
  	network: networks,
  	vm: vms
  },
  	parms = {
  		vcenter_sys_id: vCenterSysId,
  		vcenter_uuid: vCenterUuid,
  		datacenter_mor_id: dc.morid,
  		datacenter_sys_id: dc.sys_id,
  	};

  VMUtils.triggerProbes(_this, objects, parms);
}

//////////////////////////////////////////////////////////////////////////
function extractFoldersAndChildren(root, folders, dc) {
  var children = [ ];

  extractChildrenFromFolder(root, dc.name);

  root.cmdb_ci_vcenter_folder.forEach(function(c) { c.cmdb_ci_vcenter_datacenter = dc.sys_id; });
  delete root.cmdb_ci_vcenter_folder;

  return children;

  function extractChildrenFromFolder(parent, fullpath) {
  	parent.cmdb_ci_vcenter_folder = [ ];
  	parent.hostedOn = dc.sys_id;
  	parent.childEntity.forEach(
  		function(child) {
  			if (child.type == 'Folder') {
  				parent.cmdb_ci_vcenter_folder.push(child);
  				child.fullpath = fullpath + ' | ' + child.name;
  				folders.push(child);
  				extractChildrenFromFolder(child, child.fullpath);
  			} else if (child.type == 'VirtualApp')
  				extractChildrenFromFolder(child, child.fullpath);
  			else if (child.type == 'StoragePod') {
  				children.push(child);
  				extractChildrenFromFolder(child, child.fullpath);
  			}
  			else
  				children.push(child);
  		});
  	delete parent.childEntity;
  }
}

})();

Sys ID

8da918db93831200c2fe705bb47ffb8e

Offical Documentation

Official Docs: