UNPKG

vsphere-infra

Version:

361 lines (337 loc) 14.5 kB
/* * Copyright 2014-2021 Firestack, all rights reserved. */ import * as soap from 'soap'; import { getMoTypeByName, iidParse } from './managed.objects'; import { VsphereDatacenter } from './vsphere'; import { ref, xsi, paramFilter, parseObjects } from './soap.parser'; import { ix, promise } from 'ts-basis'; export class VsphereCaller extends ix.Entity { $dc: VsphereDatacenter; topError: any = null; topErrorBody: string = ''; topErrorMessage: string = ''; topErrorDetail: string = ''; errors: {data: any, t: number}[] = []; client: soap.Client; serviceContent: any; containerView: any; inventoryData: any; inventoryRaw: any; inventoryStubsData: any; inventoryStubsRaw: any; options = { forever: true, timeout: 30000 }; constructor($dc: VsphereDatacenter, client) { super('vcenter-api'); this.$dc = $dc; this.client = client; $dc.lifecycle.manage(this); } resetData() { this.serviceContent = null; this.containerView = null; this.inventoryData = null; this.inventoryRaw = null; this.inventoryStubsData = null; this.inventoryStubsRaw = null; } errorCodeHandle(message: string) { const dc = this.$dc; const has = part => message.indexOf(part) >= 0; if (has('ManagedObjectNotFoundFault')) { if (has('vim.view.ContainerView:session') && has('has already been deleted or has not been completely created')) { // refetch default container view this.getDefaultContainerView(true); dc.emitEvent(dc.sessionNotValid$, { type: 'not_found' }); return; } else { const moValue = message.split('</obj>')[0].split('>').pop(); const iid = iidParse(moValue); dc.emitEvent(dc.entryNotFound$, { iid, value: moValue }); return; } } else if (has('Unknown mo value session[')) { this.getDefaultContainerView(true); dc.emitEvent(dc.sessionNotValid$, { type: 'unknown_mo' }); return; } return 'YES_DEBUG_OUTPUT'; } resolveError(error, resolve) { this.topError = error; if (error.body) { this.topErrorBody = error.body; try { if (error.body.indexOf('<faultstring>') >= 0) { this.topErrorMessage = error.body.split('<faultstring>')[1].split('</faultstring>')[0]; this.topErrorDetail = error.body.split('<detail>')[1].split('</detail>')[0]; let doOutput = 'YES_DEBUG_OUTPUT'; if (this.topErrorDetail) { doOutput = this.errorCodeHandle(this.topErrorDetail); } if (doOutput) { this.$dc.debugOutput(new Error(this.topErrorDetail)); } } else { let handled = false; if (error instanceof Error) { if (error.message === 'ESOCKETTIMEDOUT') { handled = true; this.$dc.debugOutput('request.js error code: ESOCKETTIMEDOUT'); } } else { } if (!handled) { this.$dc.ix.pushError(new Error(`Unknown error: ${JSON.stringify(error)}`), 2); } } } catch (e) { this.$dc.ix.pushError(e); } } else { if (error instanceof Error) { this.$dc.ix.pushError(error, 2); } else { this.$dc.ix.pushError(new Error(`Unknown error: ${JSON.stringify(error)}`), 2); } } resolve(null); } async getServiceContent(): Promise<any> { if (!this.serviceContent) { this.serviceContent = promise<any>(async resolve => { this.client.VimService.VimPort.RetrieveServiceContent(paramFilter({ _this: 'ServiceInstance' }), async (e, r) => { if (e) { return this.resolveError(e, resolve); } if (!r) { return this.resolveError(new Error('Emtpy response'), resolve); } this.serviceContent = r.returnval; this.$dc.debugOutput(`fetched service content`); return resolve(r.returnval); }, this.options); }); } return await Promise.resolve(this.serviceContent); } async getDefaultContainerView(noCache = false) { if (!this.containerView || noCache) { this.containerView = promise(async resolve => { if (!await this.getServiceContent()) { return resolve(null); } this.client.VimService.VimPort.CreateContainerView(paramFilter({ _this: ref(this.serviceContent.viewManager), container: ref(this.serviceContent.rootFolder), recursive: true }), async (e, r) => { if (e) { return this.resolveError(e, resolve); } if (!r) { return this.resolveError(new Error('Emtpy response'), resolve); } this.containerView = r.returnval; this.$dc.debugOutput(`fetched default container view (${this.containerView.$value})`); return resolve(r.returnval); }, this.options); }); } return await Promise.resolve(this.containerView); } async getInventoryStubs(noCache = false) { if (!this.inventoryStubsData || noCache) { this.inventoryStubsData = promise(async resolve => { if (!await this.getDefaultContainerView()) { return resolve(null); } this.client.VimService.VimPort.RetrievePropertiesEx(paramFilter({ _this: ref(this.serviceContent.propertyCollector), specSet: [ xsi('PropertyFilterSpec', { propSet: [ xsi('PropertySpec', { type: 'Folder', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'HostSystem', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'Datacenter', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'ComputeResource', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'Network', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'Datastore', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'VirtualMachine', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'ResourcePool', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'VirtualApp', all: false, pathSet: ['value'] }), xsi('PropertySpec', { type: 'DistributedVirtualSwitch', all: false, pathSet: ['value'] }), ], objectSet: [ xsi('ObjectSpec', { obj: ref(this.containerView), skip: true, selectSet: [ xsi('TraversalSpec', { type: 'ContainerView', path: 'view', skip: false }) ] }) ], }) ], options: { maxObjects: 1000000 }, }), async (e, r) => { if (e) { return this.resolveError(e, resolve); } if (!r) { return this.resolveError(new Error('Emtpy response'), resolve); } const pages = await this.getAllPages(r.returnval); if (!pages) { return resolve(null); } this.inventoryStubsRaw = pages.rawData; this.inventoryStubsData = pages.data; return resolve(this.inventoryStubsData); }, this.options); }); } return await Promise.resolve(this.inventoryStubsData); } async getInventoryData(noCache = false) { if (!this.inventoryData || noCache) { this.inventoryData = promise(async resolve => { if (!await this.getDefaultContainerView()) { return resolve(null); } this.client.VimService.VimPort.RetrievePropertiesEx(paramFilter({ _this: ref(this.serviceContent.propertyCollector), specSet: [ xsi('PropertyFilterSpec', { propSet: [ xsi('PropertySpec', { type: 'Folder', all: true }), xsi('PropertySpec', { type: 'HostSystem', all: true }), xsi('PropertySpec', { type: 'Datacenter', all: true }), xsi('PropertySpec', { type: 'ComputeResource', all: true }), xsi('PropertySpec', { type: 'Network', all: true }), xsi('PropertySpec', { type: 'Datastore', all: true }), xsi('PropertySpec', { type: 'VirtualMachine', all: true }), xsi('PropertySpec', { type: 'ResourcePool', all: true }), xsi('PropertySpec', { type: 'VirtualApp', all: true }), xsi('PropertySpec', { type: 'DistributedVirtualSwitch', all: true }), ], objectSet: [ xsi('ObjectSpec', { obj: ref(this.containerView), skip: true, selectSet: [ xsi('TraversalSpec', { type: 'ContainerView', path: 'view', skip: false }) ] }) ], }) ], options: { maxObjects: 1000000 }, }), async (e, r) => { if (e) { return this.resolveError(e, resolve); } if (!r) { return this.resolveError(new Error('Emtpy response'), resolve); } const pages = await this.getAllPages(r.returnval); if (!pages) { return resolve(null); } this.inventoryRaw = pages.rawData; this.inventoryData = pages.data; this.$dc.lockStubs(); const allProms = []; for (const moData of this.inventoryData) { const moTypeName = moData.iid.split(':')[1]; const moClass = getMoTypeByName(moTypeName); const mo = new moClass(this.$dc, moData); if (mo) { allProms.push(mo.$initPending); } } this.$dc.unlockStubs(); this.$dc.inventoryPending = promise<boolean>(async invResolve => { await Promise.all(allProms); await this.$dc.postInventoryDataFetch(); this.$dc.inventoryPending = false; invResolve(false); }); return resolve(r.returnval.objects); }, this.options); }); } return await Promise.resolve(this.inventoryData); } async getInventory(noCache = false) { this.$dc.debugOutput(`mapping inventory...`); await this.getInventoryData(noCache); await Promise.resolve(this.$dc.inventoryPending); const hostCount = Object.keys(this.$dc.inventory.hostSystem).length; const foldersCount = Object.keys(this.$dc.inventory.folder).length; const vmCount = Object.keys(this.$dc.inventory.virtualMachine).length; this.$dc.debugOutput(`finished mapping inventory (VMs=${vmCount}, Folders=${foldersCount}, Hosts=${hostCount})`); this.$dc.inventoryChangedLast = Date.now(); this.$dc.preserializeInventory(); return this.$dc; } async getManagedObjectData(target: any) { const stub = ref(target); const moType = stub.attributes.type; return await promise(async resolve => { if (!await this.getDefaultContainerView()) { return resolve(null); } this.client.VimService.VimPort.RetrievePropertiesEx(paramFilter({ _this: ref(this.serviceContent.propertyCollector), specSet: [ xsi('PropertyFilterSpec', { propSet: [ xsi('PropertySpec', { type: moType, all: true }), ], objectSet: [ xsi('ObjectSpec', { obj: stub }) ], }) ], options: { maxObjects: 1 }, }), async (e, r) => { if (e) { return this.resolveError(e, resolve); } if (!r) { return this.resolveError(new Error('Emtpy response'), resolve); } const targetData = (await parseObjects(r.returnval.objects))[0]; return resolve(targetData); }, this.options); }); } async prop(target, prop) { const targetStub = ref(target); let targetType = targetStub.attributes.type; if (!targetType) { targetType = target._type; } if (!targetType) { this.$dc.debugOutput(`unknown target type: ${JSON.stringify(targetStub)}`); return null; } return await promise(async resolve => { if (!await this.getDefaultContainerView()) { return resolve(null); } this.client.VimService.VimPort.RetrievePropertiesEx(paramFilter({ _this: ref(this.serviceContent.propertyCollector), specSet: [ xsi('PropertyFilterSpec', { propSet: [ xsi('PropertySpec', { type: targetType, all: [ prop ] }), ], objectSet: [ xsi('ObjectSpec', { obj: targetStub }), ], }) ], options: { maxObjects: 1 }, }), async (e, r) => { if (e) { return this.resolveError(e, resolve); } if (!r) { return this.resolveError(new Error('Emtpy response'), resolve); } const list = r.returnval.objects; if (!list || list.length === 0) { return null; } return resolve(r.returnval.objects); }, this.options); }); } async propChain(target, ...props: string[]) { let prevTarget = target; for (const prop of props) { target = await this.prop(target, prop); if (target === null) { this.$dc.debugOutput(`Cannot fetch '${prop}' of prop chain '${props.join('.')}' on target ${JSON.stringify(ref(prevTarget))}`); return null; } prevTarget = target; } return target; } async getAllPages(returnVal: any, stepDelayMs: number = 100) { const objectsPagesRaw = [returnVal.objects]; const objectsPages = [await parseObjects(returnVal.objects)]; let token = returnVal.token; while (token) { await sleepMs(stepDelayMs); const page = await this.furtherRetrievePages(token); if (!page) { return null; } objectsPagesRaw.push(page.objects); objectsPages.push(await parseObjects(page.objects)); if (page.token === token) { return null; } token = page.token; } const result = { rawData: [].concat(...objectsPagesRaw), data: [].concat(...objectsPages), }; return result; } async furtherRetrievePages(furtherToken: string): Promise<{objects: any[]; token?: string}> { return await promise(async resolve => { if (!await this.getDefaultContainerView()) { return resolve(null); } this.client.VimService.VimPort.ContinueRetrievePropertiesEx(paramFilter({ _this: ref(this.serviceContent.propertyCollector), token: furtherToken }), async (e, r) => { if (e) { return this.resolveError(e, resolve); } if (!r) { return this.resolveError(new Error('Emtpy response'), resolve); } resolve({ objects: r.returnval.objects as any[], token: r.returnval.token }); }, this.options); }); } } async function sleepMs(ms: number) { return promise<void>(resolve => setTimeout(resolve, ms)); }