Name

global.VmwareVmCorrelator

Description

Finds VMware instance or template records. Manage relationships from instance to guest, and relationships from instance to guest s NICs

Script

var VmwareVmCorrelator;

(function() {
  _debug('BEGIN VmwareVmCorrelator outer wrapper');
  var g_disco_functions = new DiscoveryFunctions();
  var debugLog = false;
  var instantiatedByRelationshipTypeId = g_disco_functions.findCIRelationshipType('cmdb_rel_type', 'Instantiates::Instantiated by');

  // public functions
  VmwareVmCorrelator = {
  	processVmInstanceOrTemplate: processVmInstanceOrTemplate,
  	getVmInstance: getVmInstance,
  	getVmInstances: getVmInstances
};
  

// Called by vCenter sensor.
// Input:
//   columns -- props where key = VM instance table column name:
//     bios_uuid
//     vm_instance_uuid
//     vcenter_uuid
//     object_id (moRef)
//   macs -- array of MAC address strings.  Must have at least one element.  <<<< ADD CHECK FOR THIS
//   isVmTemplateRec -- false if VM instance rec
//   processGlideRec -- callback for additional processing on glide record
//   esx -- optional sys_id of the ESX server the VM runs on.  We'll create a Virtualized by::
//          Virtualizes relationship between the VM record and the ESX record.
// Output: VM instance gliderec
// Throws MultVMFoundNewStyle, MultVMFoundOldStyle, duplicateGuestsFound
//
// The only OOTB use of this function is from VCenterVMsSensor.preWriteVm.  That code has already found or created a
// VM/template record based on the index defined there.  We don't ever want to create a record here - doing so guarantees
// that we'll get a duplicate record.  I would remove the call to _getVmInstanceOrTemplate (and any related code) but
// it's possible that someone has non-OOTB code which uses this function.
function processVmInstanceOrTemplate(columns, macs, isVmTemplateRec, processGlideRec, esx, vmGr) {
  try {
  	_debug_refresh();
  	_debug('BEGIN processVmInstanceOrTemplate');
  	var guestGr;
  	var addVmInstanceRec;   // bool
  	var guestCiSysId;
  	var tableName = isVmTemplateRec ? 'cmdb_ci_vmware_template' : 'cmdb_ci_vmware_instance';
  	var correlation_id = VMUtils.turnUuidToCorrelationId(columns.bios_uuid);

  	vmGr = vmGr || _getVmInstanceOrTemplate(tableName, columns.vm_instance_uuid, columns.vcenter_uuid, correlation_id, columns.object_id);
  	// The caller may pass in a new GlideRecord (without a sys_id) in vmGr.  In that
  	// case the call to newRecord() below won't get executed and the new record won't
  	// have a sys_id and we won't be able to create relationships to it.  Make sure
  	// this doesn't happen by assigning a sys_id here.
  	if (!vmGr.sys_id)
  		vmGr.setNewGuid();

  	addVmInstanceRec = !vmGr;
  	if (addVmInstanceRec) {
  		vmGr = new GlideRecord(tableName);

  		// newRecord() sets gr.sys_id so can add relationship recs to Vm guest recs below *before*
  		// doing gr.insert(), because vmGr.guest is also used as a flag below to decide
  		// if need to check/update guest relationship.
  		vmGr.newRecord();
  		vmGr.correlation_id = correlation_id;
  	}

  	if (processGlideRec)
  		processGlideRec(vmGr);

  	// merge in column values to vmGr
  	for (var prop in columns) {
  		if (prop != 'sys_id')
  			vmGr[prop] = columns[prop];
  	}

  	// If a VM Instance rec, and rec not related to guest (or existing relationship rec needs update),
  	// verify/resolve relationship to guest and resolve NIC's on guest.
  	if (!isVmTemplateRec) {
  		_debug("processVmInstanceOrTemplate  vmGr.guest_reconciled: "+  vmGr.guest_reconciled);
  		if (!vmGr.guest_reconciled)
  			guestCiSysId = _resolveVmGuest(vmGr, macs, esx);

  		updateVirtualizesRelationship(vmGr, esx, guestCiSysId);
  	}
  	if (addVmInstanceRec) {
  		_debug('processVmInstanceOrTemplate -- added new rec');
  		vmGr.insert();
  	} else {
  		_debug('processVmInstanceOrTemplate -- updated existing rec');
  		vmGr.update();
  	}
  	return vmGr;
  } finally {
  	_debug('END processVmInstanceOrTemplate');
  }
}
  
// In:
//   4 IDs: must pass 1) vmInstanceUuid, vCenterInstanceUuid  OR 2) vCenterInstanceUuid, correlationId, vmObjectId.
//   Ok to pass in all.
// 
// Returns:
//   vmGr if rec found, else return undefined
// Throws MultVMFoundNewStyle, MultVMFoundOldStyle, duplicateGuestsFound
function getVmInstance(vmInstanceUuid, vCenterInstanceUuid, correlationId, vmObjectId) {
  _debug_refresh();
  return _getVmInstanceOrTemplate('cmdb_ci_vmware_instance', vmInstanceUuid, vCenterInstanceUuid, correlationId, vmObjectId);
}

// In:
//   vCenterInstanceUuid, vmObjectId
//
// Returns:
//   Array of sys_ids of matching records.  Usually 1 item in array, but it may be
//   more if MOR IDs have been recycled.
function getVmInstances(vCenterInstanceUuid, vmObjectId) {
  _debug_refresh();
  _debug('BEGIN getVmInstances: vCenterInstanceUuid: ' + vCenterInstanceUuid + ', vmObjectId: ' + vmObjectId);

  var sysIds = [ ];

  var vmGr = new GlideRecord('cmdb_ci_vmware_instance');
  vmGr.addQuery('vcenter_uuid', vCenterInstanceUuid);
  vmGr.addQuery('object_id', vmObjectId);
  vmGr.query();

  while (vmGr.next())
  	sysIds.push('' + vmGr.sys_id);

  _debug('Found VM instances: ' + sysIds.join(', '));
  _debug('END getVmInstances');

  return sysIds;
}

// In:
//   4 IDs: must pass 1) vmInstanceUuid, vCenterInstanceUuid OR 2) vCenterInstanceUuid, correlationId, vmObjectId
//   Ok to pass in all.
// 
// Returns:
//   vmGr if rec found, else return undefined
// Throws MultVMFoundNewStyle, MultVMFoundOldStyle, duplicateGuestsFound
function _getVmInstanceOrTemplate(tableName, vmInstanceUuid, vCenterInstanceUuid, correlationId, vmObjectId) {
  try {
  	_debug('BEGIN _getVmInstanceOrTemplate: ' + correlationId + ", vCenterInstanceUuid: " + vCenterInstanceUuid + ", vmObjectId: " + vmObjectId + ", vmInstanceUuid: " + vmInstanceUuid);
  	var vmGr;
  	
  	// if have non-blank mvInstanceUuid and vCenterInstanceUuid, try to find match to DB new-style rec
  	// include object_id in the query to avoid flip-flop of object_id when vm_instance_uuid not unique
  	if (!JSUtil.nil(vmInstanceUuid) && !JSUtil.nil(vCenterInstanceUuid)) {
  		vmGr = new GlideRecord(tableName);
  		vmGr.addQuery('vm_instance_uuid', vmInstanceUuid);
  		vmGr.addQuery('vcenter_uuid', vCenterInstanceUuid);
  		vmGr.addQuery('object_id', vmObjectId);
  		vmGr.query();
  		if (vmGr.getRowCount() > 1)
  			throw  {
  				error: "VmwareVmCorrelator.MultVMFoundNewStyle",
  				msg: "Multiple VM " + tableTypeName(tableName) + " CIs found.  VM Instance UUID: "
  						+ vmInstanceUuid + ", vCenter UUID: " + vCenterInstanceUuid
  			};

  		if (vmGr.next()) {
  			_debug('new-style match for ' + vmGr.name);
  			return vmGr;
  		}
  	}

  	// handle VM migration between vCenters
  	if (!JSUtil.nil(vmInstanceUuid)) {
  		vmGr = new GlideRecord(tableName);
  		vmGr.addQuery('vm_instance_uuid', vmInstanceUuid);
  		vmGr.addQuery('vcenter_uuid', '!=', vCenterInstanceUuid);
  		vmGr.query();
  		if (vmGr.getRowCount() > 1)
  			throw  {
  				error: "VmwareVmCorrelator.MultVMFoundNewStyle",
  				msg: "Multiple VM " + tableTypeName(tableName) + " CIs found.  VM Instance UUID: "
  						+ vmInstanceUuid
  			};

  		if (vmGr.next()) {
  			_debug('new-style match for migrated vm ' + vmGr.name);
  			return vmGr;
  		}
  	}

  	// No match, may be old style rec.  Try match by vCenter UUID, vmObjectId, and BIOS UUID
  	if (findByObjectId(correlationId))
  		return vmGr;

  	// See if we have a record that doesn't have a correlation ID.  This checks for both
  	// erroneously inserted records (without a correlation_id) and records created by the
  	// event collector which don't yet have a correlation_id.
  	if (findByObjectId())
  		return vmGr;

  	// Last try.  Try again with correlation ID.  The event collector will create VMs with
  	// vCenter UUID and VM object ID but no correlation ID.  The findObjectId(correlationId)
  	// call above may fail because the collector created a record without a correlation ID.
  	// It's possible for the record to be updated on another thread before the 2nd call to
  	// findObjectById() gets made.  That call will then fail because the record now has a
  	// correlation ID, so we have to check one more time with correlation ID to make sure
  	// that hasn't happened.
  	if (findByObjectId(correlationId))
  		return vmGr;

  	_debug('no match for correlationId: ' + correlationId + ", vCenterInstanceUuid: " + vCenterInstanceUuid + ", vmObjectId: " + vmObjectId + ", vmInstanceUuid: " + vmInstanceUuid);
  } finally {
  	_debug('END _getVmInstanceOrTemplate');
  }

  // This function uses the outer function's variables for everything except correlationId.  This allows
  // us to (1) not pass in a bunch of parameters, and (2) return the value of vmGr that gets set by this
  // function.
  function findByObjectId(correlationId) {
  	if (!JSUtil.nil(vCenterInstanceUuid) && !JSUtil.nil(vmObjectId)) {
  		vmGr = new GlideRecord(tableName);
  		if (correlationId)
  			vmGr.addQuery('correlation_id', correlationId);
  		else
  			vmGr.addNullQuery('correlation_id');
  		vmGr.addQuery('vcenter_uuid', vCenterInstanceUuid);
  		vmGr.addQuery('object_id', vmObjectId);
  		vmGr.query();
  		if (vmGr.getRowCount() > 1)
  			throw  {
  				error: "VmwareVmCorrelator.MultVMFoundOldStyle",
  				msg: "Multiple VM " + tableTypeName(tableName) + " CIs found.  Correlation ID (BIOS UUID): " + correlationId
  						+ ", vCenter UUID: " + vCenterInstanceUuid + ", Object Id (VMWare MoRef): " + vmObjectId
  			};

  		if (vmGr.next()) {
  			_debug('old-style match for ' + vmGr.name);
  			return vmGr;
  		}
  	}
  }
}

// Create vm instance <-> guest relationship and reference, cleanup bad ones.
// Also create and cleanup vmGr <-> nic relationship.
// Input
//   vmGr - glide rec for cmdb_ci_vmware_instance
//   macs - array of MAC address strings
//   esx -- optional sys_id of the ESX server the VM runs on.  We'll create a Virtualized by::
//          Virtualizes relationship between the VM record and the ESX record.
// Output: none, may fill in vmGr.guest
// Throws duplicateGuestsFound
function _resolveVmGuest(vmGr, macs, esx) {
  try {
  	_debug("BEGIN _resolveVmGuest");

  	var nics;
  	var relGr;
  	var serial = '' + vmGr.correlation_id;
  	var excludedStatus = [ '7', '8', '100' ]; // RETIRED, STOLEN, ABSENT
  	var guestGr = new GlideRecord('cmdb_ci_computer');
  	guestGr.addQuery('serial_number', ['zone-' + serial, 'vmware-' + serial]);
  	guestGr.addQuery('install_status', 'NOT IN', excludedStatus);
  	guestGr.query();

  	var guestCiSysId;
  	var duplicateCIs = [];
  	// find the guest CI that contains a mac address belonging to this vm
  	while (guestGr.next()) {
  		_debug("_resolveVmGuest  guestGr.name: " + guestGr.name);
  		// get the NIC's associated with this guest
  		nics = new GlideRecord('cmdb_ci_network_adapter');
  		nics.addQuery('cmdb_ci', guestGr.sys_id);
  		nics.addQuery('mac_address', 'IN', macs);
  		nics.query();
  		if (nics.getRowCount() > 0) {
  			duplicateCIs.push(guestGr.sys_id);
  			if (JSUtil.nil(guestCiSysId))
  				guestCiSysId = guestGr.getValue('sys_id');
  		} else if (guestGr.getRowCount() == 1) {
  			// We couldn't find a matching NIC, but we only have a single
  			// possible guest.  If we can't rule out this guest based on
  			// NICs then go ahead and use it.
  			nics = new GlideRecord('cmdb_ci_network_adapter');
  			nics.addQuery('cmdb_ci', guestGr.sys_id);
  			nics.query();

  			var macLessNics = new GlideRecord('cmdb_ci_network_adapter');
  			macLessNics.addQuery('cmdb_ci', guestGr.sys_id);
  			macLessNics.addNullQuery('mac_address');
  			macLessNics.query();

  			// If we have no NICs for the guest or if all NICs for the guest
  			// have empty MAC address then use the guest.
  			if (macLessNics.getRowCount() == nics.getRowCount())
  				guestCiSysId = guestGr.getValue('sys_id');
  		}
  	}

  	if (JSUtil.nil(guestCiSysId))
  		guestCiSysId = _resolveCredentiallessGuest(vmGr);

  	if (JSUtil.nil(guestCiSysId))
  		return;

  	if (duplicateCIs.length > 1)
  		throw {
  			"error": "VMCorrelator.DuplicateGuestsFound",
  			"msg": "Multiple Guest CI's existing on this VM instance.  serial_number " + serial + ", Mac addresses: " + macs,
  			"duplicateCis": duplicateCIs
  		};

  	// if any existing guestCi<->vmGr rel recs for grVm points to wrong guest
  	// remove these relationships
  	var instantiatedByRelationshipTypeId = g_disco_functions.findCIRelationshipType('cmdb_rel_type', 'Instantiates::Instantiated by');
  	var gr = new GlideRecord('cmdb_rel_ci');
  	gr.addQuery('child', vmGr.sys_id);
  	gr.addQuery('type', instantiatedByRelationshipTypeId);
  	gr.addQuery('parent', '!=', guestCiSysId);
  	gr.deleteMultiple();

  	// add guestCi<->vmGr rel recs if not present
  	var instantiatedRelCreated = g_disco_functions.createRelationshipIfNotExists(guestCiSysId, vmGr.sys_id, 'Instantiates::Instantiated by');

  	_debug("_resolveVmGuest now reconciled to guest sys_id " + guestCiSysId);
  	if(! JSUtil.nil(instantiatedRelCreated))
  		vmGr.guest_reconciled = true;
  	
  	return guestCiSysId;
  } finally {
  	_debug('END _resolveVmGuest');
  }

  function _resolveCredentiallessGuest(vmGr) {
  	try {
  		_debug("BEGIN _resolveCredentiallessGuest");

  		// Avoid making query when the VM doesn't have an IP address
  		if (!vmGr.ip_address)
  			return;

  		// If the guest was created by credentialless discovery pretty much the only information
  		// it's going to have is IP address.  If we coudn't reconcile the VM using BIOS UUID & NICs
  		// we'll check for a credentialless record.
  		var guestGr = new GlideRecord('cmdb_ci_computer');
  		guestGr.addQuery('discovery_source', 'CredentiallessDiscovery');
  		guestGr.addQuery('ip_address', vmGr.ip_address);
  		guestGr.setLimit(2);
  		guestGr.query();
  		// We only want to reconcile by IP if the match is unambiguous
  		if (guestGr.getRowCount() == 1) {
  			guestGr.next();
  			return guestGr.getValue('sys_id');
  		}
  	} finally {
  		_debug('END _resolveCredentiallessGuest');
  	}
  }
}

// Fix guestCi<->esx relationships
function updateVirtualizesRelationship(vmGr, esx, guestCiSysId) {

  if (!esx)
  	return;

  // We usually won't get a guest sys_id.  Try to look it up based on the Instantiates relationship
  if (!guestCiSysId) {
  	var instantiatedByRelationshipTypeId = g_disco_functions.findCIRelationshipType('cmdb_rel_type', 'Instantiates::Instantiated by');
  	var gr = new GlideRecord('cmdb_rel_ci');
  	gr.addQuery('child', vmGr.sys_id);
  	gr.addQuery('type', instantiatedByRelationshipTypeId);
  	gr.query();

  	if (!gr.next())
  		return;

  	guestCiSysId = gr.parent;
  }

  // Delete any existing incorrect relationships
  var virtualizedByRelationshipTypeId = g_disco_functions.findCIRelationshipType('cmdb_rel_type', 'Virtualized by::Virtualizes');
  var gr = new GlideRecord('cmdb_rel_ci');
  gr.addQuery('child', '!=', esx);
  gr.addQuery('type', virtualizedByRelationshipTypeId);
  gr.addQuery('parent', guestCiSysId);
  gr.deleteMultiple();

  // Add guestCi<->ESX server if not present
  g_disco_functions.createRelationshipIfNotExists(guestCiSysId, esx, 'Virtualized by::Virtualizes');
}

function tableTypeName(tableName) {
  return tableName == 'cmdb_ci_vmware_template' ? 'Template' : 'Instance';
}
  
function _debug_refresh() {
  debugLog = JSUtil.toBoolean(gs.getProperty("vmware_vm_correlator.debug", "false"));
}
  
function _debug(msg) {
  if (debugLog)
  	gs.log("*** VmwareVmCorrelator *** " + msg);
}
  
})();

Sys ID

8dddde336730120072b9f06943415ad8

Offical Documentation

Official Docs: