UNPKG

@stordata/vsphere-soapify

Version:

A NodeJS abstraction layer for the vSphere SOAP API

260 lines (222 loc) 9.72 kB
'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]); } };