Name

global.DiscoveryVirshSensor

Description

This is a DiscoverySensor with methods to process output from the virsh utility from libvirt. (see shell scripts on KVM probes)

Script

var DiscoveryVirshSensor = Class.create();

/**
*  To be used with probes running against the virtualization platform CI
*/
DiscoveryVirshSensor.prototype = Object.extendsObject(DiscoverySensor, {
 
  /**
  * Tell the sensor which tables to load the data into:
  * serverTable - subclass of cmdb_ci_vm or cmdb_ci_virtualization_server (the *current* CI in this context)
  *    expected fields: linux_host, details_xml
  * instanceTable - subclass of cmdb_ci_vm_instance, 
  *    expected fields: details_xml
  * deviceTable - holds records for <devices> for a domain
  *    expected fields: vm_instance, device, type, details_xml
  * networkTable - subclass of cmdb_ci_vm_object
  *    expected fields: forward_mode, bridge, details_xml
  * poolTable - subclass of cmdb_ci_vm_object, details_xml
  *    expected fields: capacity, allocation, available, path
  * volumeTable - cmdb_ci_vm_object
  *    expected fields: storage_pool, capacity, allocation, path
  */
  setTables: function(serverTable, instanceTable, deviceTable, networkTable, poolTable, volumeTable) {
  	this.serverTable = serverTable;
  	this.instanceTable = instanceTable;
  	this.deviceTable = deviceTable;
  	this.networkTable = networkTable;
  	this.poolTable = poolTable;
  	this.volumeTable = volumeTable;
  },
  
  getVirtPlatformCi: function() {
  	return this.getCmdbRecord();
  },
  
  /**
  * Parse <domains> xml, containing:
  * 1) <domain_list> containing output from command "virsh list --all"
  * 2) <domain>'s containing output from "virsh dumpxml"
  * 3) <disk_data>'s containing output from "virsh domblkinfo"
  */
  parseDomainListOutput: function(output) {
  	
  	var xmlHelper = new XMLHelper();
  	var xmlResult = xmlHelper.toObject(output);
  	
  	var domainList = g_array_util.ensureArray(xmlResult.domain);
  	var diskDataList = g_array_util.ensureArray(xmlResult.disk_data);
  	
  	//Some data structures for stashing away data for later look-up
  	var domainStates = {};	 //maps vm name -> state (running, stopped, etc)
  	var diskSizes = {};		//maps disk file path -> capacity
  	
  	//Pull out vm states into our domainStates data structure
  	var lines = xmlResult.domain_list.split('\n');
  	for (var i = 0; i < lines.length; i++) {
  		var fields = /^\s*\S+\s+(\S+)\s+(.+)$/.exec(lines[i]);
  		if (fields && fields[1] && fields[2]) 
  			domainStates[fields[1]] = fields[2]; //name -> state
  	}
  	
  	//Pull out the disk capacity stuff into our diskSizes data structure
  	for (var i = 0; i < diskDataList.length; i++) {
  		var file = diskDataList[i]['@file'];
  		var diskData = diskDataList[i]['#text'];
  		var match = /Capacity:\s+([0-9]+)/.exec(diskData);
  		if (match && match[0] && match[1])
  			diskSizes[file] = match[1];
  	}
  	
  	var discoveredVms = [];
  	
  	//Start pulling in each <domain> element
  	for (var i = 0; i < domainList.length; i++) {
  		var domain = domainList[i];
  		var objectId = domain.uuid;
  		
  		var instanceSysId;
  		var doUpdate;
  		
  		var gr = new GlideRecord(this.instanceTable);
  		gr.addQuery("object_id", objectId);
  		gr.query();
  		
  		if (!gr.next()) {
  			doUpdate = false;
  			gr.initialize();
  			gr.object_id = objectId;
  		}
  		else {
  			doUpdate = true;
  			instanceSysId = gr.sys_id;	
  		}
  		
  		var memKB = domain.memory['#text'];
  		var cpus = domain.vcpu['#text'];
  		
  		//Deal with some funky stuff from XMLHelper's toObject 
  		//  ("#text" may or may not be there)
  		if (!memKB)
  			memKB = domain.memory;
  		
  		if (!cpus)
  			cpus = domain.vcpu;
  		
  		gr.name = domain.name;
  		gr.state = this.mapState(domainStates[domain.name]);
  		gr.cpus = cpus;
  		gr.memory = memKB/1024; //KB -> MB
  		
  		gr.details_xml = xmlHelper.toXMLStr({domain:domain});
  		
  		//Process devices/disk[@device='disk'] elements
  		var diskList = g_array_util.ensureArray(domain.devices.disk);
  		var diskCount = 0;
  		var diskSize = 0;
  		for (var j = 0; j < diskList.length; j++) {
  			if (diskList[j]["@device"] == "disk") {
  				diskCount++;
  				var file = diskList[j].source['@file'];
  				diskSize += diskSizes[file];
  			}
  		}
  		
  		gr.disks = diskCount;
  		gr.disks_size = Math.round(diskSize/(1024*1024*1024)) || 0; //Bytes -> GB
  		
  		
  		//Process devices/interface elements
  		var nicList = g_array_util.ensureArray(domain.devices["interface"]);
  		gr.nics = nicList.length;
  		
  		
  		if (doUpdate)
  			gr.update();
  		else
  			instanceSysId = gr.insert();

  		var sourceName = gs.getProperty('glide.discovery.source_name', "ServiceNow");
  		var os = new ObjectSource(sourceName, gr.sys_class_name, gr.sys_id);
  		os.setValue("last_scan", new GlideDateTime().getDisplayValue());
  		os.process();

  		//Keep track of the VM's we see
  		discoveredVms.push(gr.getUniqueValue());
  		
  		//Registered-on relationship between vm instance and virtualization platform 
  		var virtCi = this.getVirtPlatformCi();
  		if (virtCi)
  			g_disco_functions.createRelationshipIfNotExists(gr, virtCi, "Registered on::Has registered");
  		
  		//Sync up the devices list for this domain
  		var macList = []; //hang on to the mac addresses 
  		var networkList = []; // and networks
  		var storageList = []; // and storage volumes
  		
  		var devGr = new GlideRecord(this.deviceTable);
  		devGr.addQuery("vm_instance", instanceSysId);
  		devGr.deleteMultiple();
  		
  		for (var d in domain.devices) {
  			if (d == 'emulator')
  				continue;
  			
  			var devArray = g_array_util.ensureArray(domain.devices[d]); 
  			for (var j=0; j < devArray.length; j++) {
  				devGr.initialize();
  				devGr.vm_instance = instanceSysId;
  				devGr.device = d;
  				devGr.type = devArray[j]['@type'];					
  				
  				var xml = {};
  				xml[d] = devArray[j]; //so we get the root element name correct in toXMLStr below
  				devGr.details_xml = xmlHelper.toXMLStr(xml);
  				
  				
  				var deviceType = ""+devArray[j]['@type'];
  				
  				//Stash mac if this is a network interface
  				if(d == "interface" && (deviceType == "network" || deviceType == "bridge")) {
  					if(devArray[j].mac && devArray[j].mac['@address']) {
  						macList.push(devArray[j].mac['@address']);
  						devGr.id = devArray[j].mac['@address'];
  					}
  					if(devArray[j].source && devArray[j].source["@network"])
  						networkList.push(devArray[j].source["@network"]);
  				}
  				
  				//For disks, grab the source/@file info for the id field
  				if(d == "disk" && deviceType == "file") {
  					if(devArray[j].source && devArray[j].source["@file"]) {
  						storageList.push(devArray[j].source["@file"]);
  						devGr.id = devArray[j].source["@file"];
  					}
  				}
  				
  				devGr.insert();
  			}
  		}
  		
  		//Link up any computer CI's that might be in the database
  		if(macList[0]) {
  			var adapterGr = new GlideRecord("cmdb_ci_network_adapter");
  			var computerCi = new GlideRecord("cmdb_ci_computer");
  			adapterGr.addQuery("mac_address", macList);
  			adapterGr.addQuery("install_status", "!=", 100); //Exclude absent NICs
  			adapterGr.query();
  			
  			if(adapterGr.next()) {
  				g_disco_functions.createRelationshipIfNotExists(gr, adapterGr.cmdb_ci, "Instantiates::Instantiated by");
  				g_disco_functions.createRelationshipIfNotExists(adapterGr.cmdb_ci, virtCi, "Virtualized by::Virtualizes");
  				
  				computerCi.get(adapterGr.cmdb_ci);
  				if(!computerCi.virtual) {
  					computerCi.virtual = true;
  					computerCi.update();
  				}
  					
  			}
  		}
  		
  		//And any "connected-by" action w/ networks
  		var connectedBy = [];
  		if(networkList[0]) {
  			for(var j = 0; j < networkList.length; j++) {
  				var netGr = new GlideRecord(this.networkTable);
  				netGr.addQuery("name", networkList[j]);
  				netGr.query();
  				if(netGr.next()) {
  					//make sure this is w/ the correct kvm server (different kvm's can have networks w/ same name)
  					var relGr = new GlideRecord("cmdb_rel_ci");
  					relGr.addQuery("parent", netGr.sys_id);
  					relGr.addQuery("type", "4afd799338a02000c18673032c71b817"); //Provided by
  					relGr.addQuery("child", virtCi.sys_id);
  					relGr.query();
  					if(relGr.next()) {
  						g_disco_functions.createRelationshipIfNotExists(gr, netGr, "Connected by::Connects");
  						connectedBy.push(""+netGr.sys_id);
  					}
  				}
  			}
  		}
  		
  		//Clean-up connected-by's that are no longer valid.
  		var relGr = new GlideRecord("cmdb_rel_ci");
  		relGr.addQuery("parent", gr.sys_id); //VM instance
  		relGr.addQuery("type", "3deab95338a02000c18673032c71b876"); //Connected by
  		relGr.addQuery("child", "NOT IN", connectedBy); //virtual network
  		relGr.deleteMultiple();
  		
  		
  		//And any storage pool relationships
  		var storedOn = [];
  		for(var j = 0; j < storageList.length; j++) {
  			var volGr = new GlideRecord(this.volumeTable);
  			volGr.addQuery("path",storageList[j]);
  			volGr.query();
  			if(volGr.next()) {
  				//make sure the storage pool is for this KVM 
  				var relGr = new GlideRecord("cmdb_rel_ci");
  				relGr.addQuery("parent", volGr.storage_pool);
  				relGr.addQuery("type", "de5aeb6a0ab3015854626f204fb7b1c0"); //Defines resources for
  				relGr.addQuery("child", virtCi.sys_id);
  				relGr.query();
  				if(relGr.next()) {
  					g_disco_functions.createRelationshipIfNotExists(volGr.storage_pool, gr, "Provides storage for::Stored on");
  					storedOn.push(''+volGr.storage_pool);
  				}
  			}
  		}
  		
  		//clean up any storage pool/vm instance relationships
  		relGr.initialize();
  		relGr.addQuery("parent", "NOT IN", storedOn); //storage pool
  		relGr.addQuery("type", "c6fd799338a02000c18673032c71b81d"); //Provides storage for
  		relGr.addQuery("child", gr.sys_id); //vm instance
  		relGr.deleteMultiple();
  	}
  	
  	
  	//Remove any instances that have been deleted since the last discovery
  	// that is instanceTable records that have registered-on relationship w/ virtCi, 
  	// but are NOT in our discoveredVms list.
  	this.reconcileCiRels(this.instanceTable, discoveredVms, "aa9434870ab301544ce2943bf03fd7a8");
  	
  },

  
  /**
  * Parse <networks> xml, containing:
  * 1) <network_list> containing output from command "virsh net-list --all"
  * 2) <network>'s containing output from "virsh net-dumpxml"
  */
  parseNetworkListOutput: function(output) {
  	
  	var xmlHelper = new XMLHelper();
  	var xmlResult = xmlHelper.toObject(output);
  	
  	var networkStates = {};	 //maps network name -> state (active/inactive)
  		
  	//Pull out network states into our networkStates data structure
  	var lines = xmlResult.network_list.split('\n');
  	for (var i = 0; i < lines.length; i++) {
  		var fields = /^\s*(\S+)\s+(\S+)\s+.*$/.exec(lines[i]);
  		if (fields && fields[1] && fields[2]) 
  			networkStates[fields[1]] = fields[2]; //name -> state
  	}
  	
  	
  	//Pull in each <network> element
  	var discoveredNetworks = [];
  	var networkList = g_array_util.ensureArray(xmlResult.network);
  	for (var i = 0; i < networkList.length; i++) {
  		var networkElem = networkList[i];
  		var objectId = networkElem.uuid;
  		
  		var doUpdate = true;
  		var gr = new GlideRecord(this.networkTable);
  		gr.addQuery("object_id", objectId);
  		gr.query();
  		
  		if (!gr.next()) {
  			doUpdate = false;
  			gr.initialize();
  			gr.object_id = objectId;
  		}
  		
  		gr.name = networkElem.name;			
  		gr.state = networkStates[networkElem.name];
  		
  		if(networkElem.forward)
  			gr.forward_mode = networkElem.forward['@mode'];
  		if(networkElem.bridge)
  			gr.bridge = networkElem.bridge['@name'];
  		
  		gr.details_xml = xmlHelper.toXMLStr({network: networkElem});
  		
  		if (doUpdate)
  			gr.update();
  		else
  			gr.insert();
  		
  		discoveredNetworks.push(gr.getUniqueValue());
  		
  		//Provided-by relationship between network and virtualization platform 
  		var virtCi = this.getVirtPlatformCi();
  		if (virtCi)
  			g_disco_functions.createRelationshipIfNotExists(gr, virtCi, "Provided By::Provides");
  		
  	}
  	
  	// remove networkTable records that have Provided By relationship w/ virtCi, 
  	// but are NOT in our discoveredNetworks list.
  	this.reconcileCiRels(this.networkTable, discoveredNetworks, "4afd799338a02000c18673032c71b817");
  	
  },
  
  /**
  * Parse <storage_pools> xml, containing:
  * 1) <pool_list> containing output from command "virsh pool-list --all"
  * 2) <poolio>'s containing output from "virsh pool-dumpxml" followed by "<volume>" output from respective "virsh vol-dumpxml"
  */
  parseStoragePoolListOutput: function(output) {
  	var xmlHelper = new XMLHelper();
  	var xmlResult = xmlHelper.toObject(output);
  	
  	var poolStates = {};	 //maps pool name -> state (active/inactive)
  		
  	//Pull out network states into our networkStates data structure
  	var lines = xmlResult.network_list.split('\n');
  	for (var i = 0; i < lines.length; i++) {
  		var fields = /^\s*(\S+)\s+(\S+)\s+.*$/.exec(lines[i]);
  		if (fields && fields[1] && fields[2]) 
  			poolStates[fields[1]] = fields[2]; //name -> state
  	}
  	
  	//Pull in each <pool> element
  	var discoveredPools = [];
  	var poolioList = g_array_util.ensureArray(xmlResult.poolio);
  	for (var i = 0; i < poolioList.length; i++) {
  		var poolElem = poolioList[i].pool;
  		var objectId = poolElem.uuid;
  		
  		var doUpdate = true;
  		var poolGr = new GlideRecord(this.poolTable);
  		poolGr.addQuery("object_id", objectId);
  		poolGr.query();
  		
  		if (!poolGr.next()) {
  			doUpdate = false;
  			poolGr.initialize();
  			poolGr.object_id = objectId;
  		}
  		
  		poolGr.name = poolElem.name;
  		poolGr.state = poolStates[poolElem.name];
  		
  		var capacity = this._getText(poolElem.capacity);
  		var allocation = this._getText(poolElem.allocation);
  		var available = this._getText(poolElem.available);
  		
  		if(capacity)
  			poolGr.capacity = capacity/1048576;
  		if(allocation)
  			poolGr.allocation = allocation/1048576;
  		if(available)
  			poolGr.available = available/1048576;
  		if(poolElem.target)
  			poolGr.path = poolElem.target.path;
  		
  		poolGr.details_xml = xmlHelper.toXMLStr({pool: poolElem});
  		
  		if (doUpdate)
  			poolGr.update();
  		else
  			poolGr.insert();
  		
  		discoveredPools.push(poolGr.getUniqueValue());
  		
  		//Defines-resources-for relationship between pool and virtualization platform 
  		var virtCi = this.getVirtPlatformCi();
  		if (virtCi)
  			g_disco_functions.createRelationshipIfNotExists(poolGr, virtCi, "Defines resources for::Gets resources from");
  		
  		//Pull in the volumes for this pool
  		var volList = g_array_util.ensureArray(poolioList[i].volume);
  		var volSysIds = [];
  		for (var j = 0; j < volList.length; j++) {
  			var volElem = volList[j];
  			
  			var volGr = new GlideRecord(this.volumeTable);
  			
  			volGr.addQuery("name", volElem.name);
  			volGr.addQuery("storage_pool", poolGr.sys_id);
  			volGr.query();
  			
  			if (!volGr.next()) {
  				doUpdate = false;
  				volGr.name = volElem.name;
  				volGr.storage_pool = poolGr.sys_id;
  			}
  			else {
  				doUpdate = true;
  				volSysIds.push(volGr.sys_id);
  			}
  			
  			var capacity = this._getText(volElem.capacity);
  			var allocation = this._getText(volElem.allocation);
  			
  			if(capacity)
  				volGr.capacity = capacity/1048576;
  			if(allocation)
  				volGr.allocation = allocation/1048576;
  			if (volElem.target)
  				volGr.path = volElem.target.path;
  			
  			volGr.details_xml = xmlHelper.toXMLStr({volume: volElem});
  			
  			if (doUpdate)
  				volGr.update();
  			else
  				volSysIds.push(volGr.insert());
  			
  			//Provides-storage-for relationships between pool and vm instances
  			var devGr = new GlideRecord(this.deviceTable);
  			devGr.addQuery("id", volGr.path);
  			devGr.query();
  			while(devGr.next()) {
  				g_disco_functions.createRelationshipIfNotExists(poolGr, devGr.vm_instance, "Provides storage for::Stored on");
  			}
  		}
  		
  		//Clean up storage volumes that are no longer present
  		var volGr = new GlideRecord(this.volumeTable);
  		volGr.addQuery("storage_pool", poolGr.sys_id);
  		volGr.addQuery("sys_id", "NOT IN", volSysIds);
  		volGr.deleteMultiple();
  		
  		
  	}
  	
  	// remove poolTable records that have "Defines resources for" relationship w/ virtCi, 
  	// but are NOT in our discoveredPools list.
  	this.reconcileCiRels(this.poolTable, discoveredPools, "de5aeb6a0ab3015854626f204fb7b1c0");
  	
  },
  
  //Deal with some funky stuff from XMLHelper's toObject ("#text" may or may not be there)
  _getText: function(node) {
  	if(!node)
  		return node;
  	
  	var nodeText = node['#text'];
  	if(!nodeText)
  		return node;
  	return nodeText;
  },
  
  /**
  * map domain (VM) state from what virsh gives us to what cmdb_ci_vm_instance.state expects
  */
  mapState: function(s) {
  	return {
  		'running':'on',
  		'idle':'on',
  		'shut off':'off',
  		'paused':'paused',
  		'pmsuspended':'paused',
  		'shutdown':'stopping',
  		'crashed':'error',
  		'dying':'terminating'
  	}[s];
  },
  
  	
  reconcileCiRels: function(parentTable, parentList, relationType) {
  	//Remove any instances that have been deleted since the last discovery
     var relGr = new GlideRecord("cmdb_rel_ci");
     relGr.addQuery("type", relationType); 
     relGr.addQuery("parent", "NOT IN", parentList);
     relGr.addQuery("child", this.getVirtPlatformCi().getUniqueValue());
     relGr.query();
     
     var toDelete = [];
     while(relGr.next()) {
  	  toDelete.push(""+relGr.parent);
     }
     
     var parentGr = new GlideRecord(parentTable);
     parentGr.addQuery("sys_id", toDelete);
     parentGr.query();
     parentGr.deleteMultiple();
  },
  
  type: "DiscoveryVirshSensor"
});

Sys ID

90d66b44c351010031e65ad8cbba8fe9

Offical Documentation

Official Docs: