@stordata/vsphere-soapify
Version:
A NodeJS abstraction layer for the vSphere SOAP API
260 lines (222 loc) • 9.72 kB
JavaScript
'use strict';
const soap = require('soap'),
_ = require('lodash'),
Parser = require('./parser'),
{ ServiceInstance, HostSystem, VirtualMachine, Datacenter, Datastore } = require('./sdk').managed,
{ PerfQuerySpec, PerfMetricId } = require('./sdk').data,
DEFAULT_FEATURES = require('./features');
module.exports = class Client {
/**
* Creates a new Client to access a vSphere server.
*
* @param {String} url The URL to the vSphere endpoint.
* Use the base URL only, the library manages the actual SDK endpoints automatically
* @param {Object} [options] The client options, passed to the `soap` library directly
* @param {Object} [features] An optional list of feature flags to control advanced settings of the created Client
* @param {boolean} [features.alwaysUseMaxSampleInQuerySpec=false] Whether to force the use of `maxSample` in QuerySpec objects,
* even though this might form invalid calls. Use with caution !
* @param {Number} [features.maxQuerySpecSize=64] The maximum size of a QueryPerf call. See https://kb.vmware.com/s/article/2107096
*
* @constructor
*/
constructor(url, options, features) {
this.url = url;
this.options = options;
this.features = _.defaults(features, DEFAULT_FEATURES);
this.parser = new Parser(this);
}
/**
* Creates a node-soap Client instance.
* This call will try to connect to the vSphere API, fetch the WSDL files and initialize the client instance.
*
* Normally you wouldn't use this method directly, but rather one of the getter for core managed objects, then
* call managed methods directly on the managed object. This method is however exposed for convenience, or if
* the call you're interested in isn't modeled by the library yet.
*
* @returns {Promise<soap.Client>} A Promise resolving with the node-soap Client.
*/
getClient() {
const wsdl = `${__dirname}/wsdl/6.7/vimService.wsdl`;
return this._cache('client', () => soap.createClientAsync(wsdl, { endpoint: this.url, ...this.options }));
}
/**
* Gets the {@link Parser} instance used to parse XML data structures.
*
* @returns {Parser} The Parser instance.
*/
getParser() {
return this.parser;
}
/**
* Retrieves the singleton {@link ServiceInstance}.
*
* @returns {Promise<ServiceInstance>} A Promise resolving with the ServiceInstance singleton
*
* @see https://pubs.vmware.com/vsphere-50/topic/com.vmware.wssdk.apiref.doc_50/vim.ServiceInstance.html
*/
getServiceInstance() {
return this._cache('serviceInstance', () => Promise.resolve(new ServiceInstance(this)));
}
/**
* Retrieves the singleton {@link ServiceContent} from the {@link ServiceInstance}.
*
* @returns {Promise<ServiceContent>} A Promise resolving to the ServiceContent singleton
*
* @see https://pubs.vmware.com/vsphere-50/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc_50%2Fvim.ServiceInstanceContent.html
*/
getServiceContent() {
return this._cache('serviceContent', () => this.getServiceInstance().then(serviceInstance => serviceInstance.retrieveServiceContent()));
}
getSessionManager() {
return this.getServiceContent().then(serviceContent => serviceContent.sessionManager);
}
getViewManager() {
return this.getServiceContent().then(serviceContent => serviceContent.viewManager);
}
getRootFolder() {
return this.getServiceContent().then(serviceContent => serviceContent.rootFolder);
}
getPropertyCollector() {
return this.getServiceContent().then(serviceContent => serviceContent.propertyCollector);
}
getPerformanceManager() {
return this.getServiceContent().then(serviceContent => serviceContent.perfManager);
}
getEventManager() {
return this.getServiceContent().then(serviceContent => serviceContent.eventManager);
}
/**
* Retrieves the list of {@link HostSystem} instances in this vSphere system.
*
* @param {Array<String>} [properties] The list of properties to retrieve for each host.
* Pass nothing or an empty array to fetch all properties
*
* @returns {Promise<Array<HostSystem>>} A Promise resolving to an array of {@link HostSystem} instances
*/
async getHosts(properties) {
return this.getInventoryItems(HostSystem, properties);
}
/**
* Retrieves the list of {@link VirtualMachine} instances in this vSphere system.
*
* @param {Array<String>} [properties] The list of properties to retrieve for each virtual machine.
* Pass nothing or an empty array to fetch all properties
*
* @returns {Promise<Array<VirtualMachine>>} A Promise resolving to an array of {@link VirtualMachine} instances
*/
async getVirtualMachines(properties) {
return this.getInventoryItems(VirtualMachine, properties);
}
/**
* Retrieves the list of {@link Datacenter} instances in this vSphere system.
*
* @param {Array<String>} [properties] The list of properties to retrieve for each datacenter.
* Pass nothing or an empty array to fetch all properties
*
* @returns {Promise<Array<Datacenter>>} A Promise resolving to an array of {@link Datacenter} instances
*/
async getDatacenters(properties) {
return this.getInventoryItems(Datacenter, properties);
}
/**
* Retrieves the list of {@link Datastore} instances in this vSphere system.
*
* @param {Array<String>} [properties] The list of properties to retrieve for each datastore.
* Pass nothing or an empty array to fetch all properties
*
* @returns {Promise<Array<Datastore>>} A Promise resolving to an array of {@link Datastore} instances
*/
async getDatastores(properties) {
return this.getInventoryItems(Datastore, properties);
}
/**
* Retrieves a list of inventory items from this vSphere system.
* This is a generic method that can be used when no more specific method exists, such as {@link getHosts}.
*
* @param {Class} type The type of entity to retrieve
* @param {Array<String>} [properties] The list of properties to retrieve for each instance.
* Pass nothing or an empty array to fetch all properties
*
* @returns {Promise<Array<ManagedEntity>>} A Promise resolving to an array of {@ ManagedEntity} (or subclass) instances
*/
async getInventoryItems(type, properties) {
return this.getRootFolder().then(folder => folder.getChildren(type, true, properties));
}
/**
* Retrieves a list of counter and their id from this vSphere system.
* This queries the "Past Day" historical interval to get points with 5 minutes precision (if using defaults)
*
* @param {Class} type The type of entity to retrieve
* @param {Object<String, String>} counters The map of counter names <> instance type to retrieve.
* Instance type can be an asterisk (*) to specify all instances or empty string ("") to specify aggregated statistics
* @param {Array<String>} [properties=[]] The optional list of entity properties
*
* @returns {Promise<object[]>} A Promise resolving to an array of Objects { metric string and key to call }
*/
async getPerfCounters(type, counters, properties = []) {
const perfMng = await this.getPerformanceManager(),
entities = await this.getInventoryItems(type, properties);
if (!entities || entities.length === 0) {
return [];
}
const { counterToId, idToCounter, historicalInterval } = await perfMng.preparePerfCounters(counters),
pastDayInterval = _.find(historicalInterval, { key: 1 }),
period = pastDayInterval ? pastDayInterval.samplingPeriod : 300,
twoPointsAgo = new Date(Date.now() - (2 * period * 1000)), // We need to span two intervals to get one value
specs = entities.map((entity) => {
const spec = new PerfQuerySpec();
spec.entity = entity;
spec.startTime = twoPointsAgo;
spec.metricId = _.map(counterToId, ({ counterId, instance }) => new PerfMetricId(this, { counterId, instance }));
spec.intervalId = period;
// https://github.com/vmware/govmomi/issues/1608
spec.maxSample = this.features.alwaysUseMaxSampleInQuerySpec ? 1 : undefined;
return spec;
}),
chunks = _.chunk(specs, this.features.maxQuerySpecSize / _.size(counterToId));
function toValidMetric(acc, metric) {
if (Array.isArray(metric.value)) {
acc.push({
value: metric.value,
id: {
counterId: metric.id.counterId,
counterName: idToCounter[metric.id.counterId],
instance: metric.id.instance
}
});
}
return acc;
}
function toValidPerfSample(acc, perf) {
if (perf.value) {
acc.push({
entity: entities.find(entity => entity.ref === perf.entity.ref),
sampleInfo: perf.sampleInfo,
value: perf.value.reduce(toValidMetric, [])
});
}
return acc;
}
function concatValidPerfResults(acc, result) {
if (result) {
return acc.concat(result.reduce(toValidPerfSample, []));
}
return acc;
}
return Promise.all(chunks.map(chunk => perfMng.queryPerf(chunk))).then(results => results.reduce(concatValidPerfResults, []));
}
login(username, password) {
return this.getSessionManager().then(sessionManager => sessionManager.login(username, password));
}
logout() {
return this.getSessionManager().then(sessionManager => sessionManager.logout());
}
_cache(key, get) {
if (this[key]) {
return Promise.resolve(this[key]);
}
return get()
.then((value) => { this[key] = value; })
.then(() => this[key]);
}
};