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