vsphere-infra
Version:
809 lines (755 loc) • 30.7 kB
text/typescript
/*
* Copyright 2014-2021 Firestack, all rights reserved.
*/
import { VsphereConnection } from './connection';
import { ref } from './soap.parser';
import { getMoTypeByName, iidParse, Mo } from './managed.objects';
import { moref } from '../../models/mo.props';
import { default as axios } from 'axios';
import { SSLTrust } from 'ssl-trust';
import { ix, promise, Unit, PromUtil, Tasks } from 'ts-basis';
import { Observable } from 'rxjs';
import {
VsphereDatacenterEvent, VsphereDatacenterUtilizationSnapshot,
VsphereDatacenterUtilizationSnapshotData,
VsphereDatacenterUtilizationSummary, VsphereEventWebhook, VsphereTarget
} from '../../models/portable';
export class VsphereDatacenter extends ix.ReconnEntity {
static keptData: {type: string, data: any}[] = [];
static registry: {[key: string]: VsphereDatacenter} = {};
static async fromTargetInfo(target: VsphereTarget, vinfra: VsphereInfra): Promise<VsphereDatacenter> {
if (!target.key) { return null; }
let dc: VsphereDatacenter;
const datacenterData = vinfra.datacenterData;
const behavior = vinfra.behavior;
if (!datacenterData[target.key]) {
dc = datacenterData[target.key] = new VsphereDatacenter(target.key, vinfra);
dc.verbose = behavior.verbose;
dc.readonlyMode = behavior.readonlyMode;
dc.webhooks = behavior.webhooks;
dc.jsonLogs = behavior.jsonLogs;
dc.sourceTarget = target;
if (target.cert) {
if (target.cert.trim().startsWith('-----BEGIN CERTIFICATE-----')) {
await SSLTrust.trustCert(target.cert);
} else {
await SSLTrust.trustCertFile(target.cert);
}
}
} else {
dc.sourceTarget = target;
}
return dc;
}
key: string;
sourceTarget: VsphereTarget;
vinfra: VsphereInfra;
ready = false;
readyResolver: (...a: any[]) => any = null;
readyPromise = null as Promise<boolean>;
connections: VsphereConnection[] = [];
connectionPickIndex = 0;
disconnected = false;
inventoryPending: boolean | Promise<boolean> = true;
inventoryStubPendingResolver: any;
inventoryStubPending: Promise<boolean>;
inventoryStubHunts: {[iid: string]: boolean | Promise<boolean>} = {};
inventoryInProgress = false;
inventoryCheckInProgress = 0;
inventoryCheckInterval = 30;
inventoryCheckLast = 0;
inventoryChangedLast = 0;
inventoryWatchStarted = false;
inventoryWatchChecker: ix.Timer = null;
inventorySerialized: string;
inventory = {
byIID: {} as {[key: string]: Mo.Base },
byGUID: {} as {[key: string]: Mo.Base },
folder: {} as {[key: string]: Mo.Folder },
hostSystem: {} as {[key: string]: Mo.HostSystem },
datacenter: {} as {[key: string]: Mo.Datacenter },
network: {} as {[key: string]: Mo.Network },
datastore: {} as {[key: string]: Mo.Datastore },
computeResource: {} as {[key: string]: Mo.ComputeResource },
resourcePool: {} as { [key: string]: Mo.ResourcePool },
virtualApp: {} as {[key: string]: Mo.VirtualApp },
virtualMachine: {} as {[key: string]: Mo.VirtualMachine },
virtualSwitch: {} as {[key: string]: Mo.VirtualSwitch },
deleted: [] as Mo.Base[],
};
index = {
byName: {} as {[key: string]: moref[] },
byIP: {} as {[key: string]: moref[] },
};
utilization: VsphereDatacenterUtilizationSummary = null;
u9nSerialized: string;
u9nCheckInterval = 60;
u9nCheckLast = 0;
u9nHistory: VsphereDatacenterUtilizationSnapshotData[] = [];
u9nHistoryMaxKept = 1440;
u9nWithHistory = true;
u9nWatchStarted = false;
u9nWatchChecker: ix.Timer = null;
readonlyMode = false;
queriedIIDs: string[] = [];
queriedStubs: {iid: moref, name: string}[] = [];
verbose = false;
jsonLogs = false;
webhooks: VsphereEventWebhook[] = [];
constructor(key: string, vinfra: VsphereInfra) {
super(`vsphere-${key}`);
this.key = key;
this.vinfra = vinfra;
this.ix.reconn.on.defunct = () => {
this.debugOutput(`This vCenter (${this.key}) class is going defunct after too many errors.`);
};
this.ix.reconn.on.restore = () => {
this.debugOutput(`This vCenter (${this.key}) has come back from defunct state.`);
};
this.ix.reconn.actions.reset = async () => {
this.debugOutput(`Errors heating up above ${this.ix.reconn.config.resetThreshold}; `
+ `resetting main connection... (${this.ix.reconn.defunctHeat.value.toPrecision(2)})`);
await this.resetConnection(true);
};
this.ix.reconn.actions.attemptToRestore = async () => {
return await this.resetConnection() ? true : false;
};
if (this.vinfra?.behavior?.reconnect) {
this.ix.reconn.setConfigSource(this.vinfra.behavior.reconnect);
}
vinfra.lifecycle.manage(this);
this.readyResetState();
}
get datacenterGeneric$() { return this.rx<VsphereDatacenterEvent>('datacenter_generic').obs(); }
get connectionReset$() { return this.rx<VsphereDatacenterEvent>('connection:reset').obs(); }
get connectionDefunct$() { return this.rx<VsphereDatacenterEvent>('connection:defunct').obs(); }
get connectionRecover$() { return this.rx<VsphereDatacenterEvent>('connection:recover').obs(); }
get sessionNotValid$() { return this.rx<VsphereDatacenterEvent>('session:not_valid').obs(); }
get entryRegister$() { return this.rx<VsphereDatacenterEvent>('entry:register').obs(); }
get entryRefresh$() { return this.rx<VsphereDatacenterEvent>('entry:refresh').obs(); }
get entryMove$() { return this.rx<VsphereDatacenterEvent>('entry:move').obs(); }
get entryNew$() { return this.rx<VsphereDatacenterEvent>('entry:new').obs(); }
get entryDelete$() { return this.rx<VsphereDatacenterEvent>('entry:delete').obs(); }
get entryNotFound$() { return this.rx<VsphereDatacenterEvent>('entry:not_found').obs(); }
get u9nUpdate$() { return this.rx<VsphereDatacenterEvent<VsphereDatacenterUtilizationSummary>>('u9n:update').obs(); }
get u9n() { return this.utilization; }
async procureConnection(timeout = this.vinfra.behavior.connection.timeout, reuseClient = false) {
if (!timeout && timeout !== 0) { timeout = this.vinfra.behavior.connection.timeout; }
let connection: VsphereConnection;
if (reuseClient && this.connections[0]) {
connection = this.connections[0];
const loggedIn = await retry(timeout,
async () => await connection.login(this.sourceTarget.username, this.sourceTarget.password));
if (!loggedIn) {
connection = await this.procureConnection(timeout, false);
} else {
connection.api.resetData();
}
} else {
connection = new VsphereConnection(this, this.sourceTarget.url, {} as any, VsphereInfra.granted);
try {
const connected = await retry(timeout,
async () => await connection.connect());
if (!connected) {
this.debugOutput(`unable to connect to ${this.sourceTarget.url}`);
return null;
}
const loggedIn = await retry(timeout,
async () => await connection.login(this.sourceTarget.username, this.sourceTarget.password));
if (!loggedIn) { return null; }
} catch (e) {
this.debugOutput(e);
return null;
}
this.debugOutput(`successfully logged in as ${this.sourceTarget.username}`);
}
return connection;
}
async resetConnection(reuseClient = false) {
this.readyResetState();
const conn = await this.procureConnection(null, reuseClient);
if (!conn) {
this.ix.pushError(new Error(`unable to procure authenticated connection`), 10);
this.readyFulfill(false);
return null;
}
this.connections.length = 0;
this.connections.push(conn);
this.readyFulfill(true);
return conn;
}
async initializeConnection() {
return await this.resetConnection();
}
pickConnection() {
if (this.connections.length === 0) { return null; }
const idx = (++this.connectionPickIndex) % this.connections.length;
return this.connections[idx];
}
lockStubs() {
this.unlockStubs();
this.inventoryStubPending = new Promise(resolve => { this.inventoryStubPendingResolver = resolve; });
}
unlockStubs() {
if (this.inventoryStubPendingResolver) { this.inventoryStubPendingResolver(false); }
this.inventoryStubPendingResolver = null;
}
async getInventory() {
if (this.inventoryInProgress) { return this; }
try {
this.inventoryInProgress = true;
const conn = this.pickConnection();
if (!conn) { return this.errorNoConnection(); }
await conn.api.getInventory(true);
} catch (e) { this.ix.pushError(e); }
this.inventoryInProgress = false;
return this;
}
trimDeleted() {
this.inventory.deleted = this.inventory.deleted.filter(mo => {
return (Date.now() - mo.$deletedTime < 3600000); // purge all deleted mo older than 1 hour
});
}
async cleanInventory() {
this.debugOutput(`cleaning inventory...`);
for (const iid of Object.keys(this.inventory.byIID)) {
this.inventory.byIID[iid].unregister(); // remove and mark defunct
}
const deletedListSaved = this.inventory.deleted;
this.inventoryPending = true;
this.inventoryStubPendingResolver = null;
this.inventoryStubPending = null;
this.inventoryStubHunts = {};
this.inventoryCheckInProgress = 0;
this.inventory = {
byIID: {} as {[key: string]: Mo.Base },
byGUID: {} as {[key: string]: Mo.Base },
folder: {} as {[key: string]: Mo.Folder },
hostSystem: {} as {[key: string]: Mo.HostSystem },
datacenter: {} as {[key: string]: Mo.Datacenter },
network: {} as {[key: string]: Mo.Network },
datastore: {} as {[key: string]: Mo.Datastore },
computeResource: {} as {[key: string]: Mo.ComputeResource },
resourcePool: {} as { [key: string]: Mo.ResourcePool },
virtualApp: {} as {[key: string]: Mo.VirtualApp },
virtualMachine: {} as {[key: string]: Mo.VirtualMachine },
virtualSwitch: {} as {[key: string]: Mo.VirtualSwitch },
deleted: deletedListSaved
};
this.index = {
byName: {} as {[key: string]: moref[] },
byIP: {} as {[key: string]: moref[] },
};
this.debugOutput(`unregistered all inventory entries.`);
}
async getInventoryIIDs() {
try {
const conn = this.pickConnection();
if (!conn) { return this.errorNoConnection(); }
const moStubs = await conn.api.getInventoryStubs(true);
if (!moStubs) { return null; }
this.queriedStubs = moStubs;
this.queriedIIDs = this.queriedStubs.map(item => item.iid);
} catch (e) { this.ix.pushError(e); }
return this.queriedIIDs;
}
async checkInventory() {
const conn = this.pickConnection();
if (!conn) { return this.errorNoConnection(); }
if (this.inventoryCheckInProgress) {
if (Date.now() - this.inventoryCheckInProgress > 30000) {
this.ix.pushError(new Error(`inventory check was taking too long; attempting a retry.`));
} else {
this.ix.pushError(new Error(`inventory check is already in progress.`), 0.5);
return;
}
}
this.inventoryCheckInProgress = Date.now();
const iids = await this.getInventoryIIDs();
if (!iids) {
this.inventoryCheckInProgress = 0;
this.ix.pushError(new Error(`inventory check failed; entities stub list fetch failed.`), 0.5);
return;
}
const newEntries = [];
const deletedEntries = [];
const iidsMap = {};
for (const iid of iids) {
iidsMap[iid] = true;
if (!this.inventory.byIID[iid]) { newEntries.push(iid); }
}
for (const iid of Object.keys(this.inventory.byIID)) {
if (iid === '$mo:Folder:group-d1') { continue; }
if (!iidsMap[iid]) { deletedEntries.push(iid); }
}
if (newEntries.length > 0 || deletedEntries.length > 0) {
this.debugOutput(`inventory change: ${newEntries.length} new entries and ${deletedEntries.length} deleted entries.`);
if (deletedEntries.length > 0) {
for (const iid of deletedEntries) {
const mo = this.inventory.byIID[iid];
if (!mo) { continue; }
mo.unregister();
this.emitEvent(this.entryDelete$, { target: mo.entitySummaryHeaders() });
}
}
if (newEntries.length > 0) {
const entries = await this.getMany(newEntries);
for (const mo of entries) { this.emitEvent(this.entryNew$, { target: mo.entitySummaryHeaders() }); }
}
this.inventoryChangedLast = Date.now();
this.preserializeInventory();
} else {
// nothing changed
}
this.trimDeleted();
this.inventoryCheckInProgress = 0;
}
preserializeInventory() {
const serializedInv: {[key: string]: string} = {};
for (const guid of Object.keys(this.inventory.byGUID)) {
const mo = this.inventory.byGUID[guid];
if (!mo) { continue; }
serializedInv[guid] = mo.entitySummaryHeaders();
}
this.inventorySerialized = JSON.stringify(serializedInv);
}
async postInventoryDataFetch() {
const dsList = Object.keys(this.inventory.datastore).map(key => this.inventory.datastore[key]);
for (const ds of dsList) {
if (ds.info.aliasOf === ds.info.containerId) { continue; }
ds.aliasOf = dsList.filter(ds2 => {
if (ds2.info.containerId === ds.info.aliasOf) {
if (ds2.aliases.indexOf(ds) === -1) { ds2.aliases.push(ds); }
return true;
}
})[0];
}
const resPoolList = Object.keys(this.inventory.resourcePool).map(key => this.inventory.resourcePool[key]);
for (const resPool of resPoolList) {
if (resPool.parent) { continue; }
resPool.parent = resPool.owner;
}
const vAppList = Object.keys(this.inventory.virtualApp).map(key => this.inventory.virtualApp[key]);
for (const vApp of vAppList) {
if (vApp.parent) { continue; }
vApp.parent = vApp.owner;
}
}
async fetchNewMo(iid, moTypeClass?, stubLock = true, ignoreError = false) {
iid = iidParse(iid);
if (this.inventoryStubHunts[iid]) { return this.inventoryStubHunts[iid]; }
this.inventoryStubHunts[iid] = promise<boolean>(async resolve => {
try {
const conn = this.pickConnection();
if (!conn) { return this.errorNoConnection(); }
const data = await conn.api.getManagedObjectData(iid);
if (!data) {
delete this.inventoryStubHunts[iid];
return resolve(false);
}
if (stubLock) { this.lockStubs(); }
if (!moTypeClass) { moTypeClass = getMoTypeByName(ref(iid).attributes.type); }
const mo = new moTypeClass(this, data);
if (stubLock) { this.unlockStubs(); }
delete this.inventoryStubHunts[iid];
resolve(false);
} catch (e) {
if (ignoreError) { resolve(false); }
this.ix.pushError(e);
}
});
return this.inventoryStubHunts[iid];
}
async get(iid: string, moTypeClass?) {
iid = iidParse(iid);
if (this.inventory.byIID[iid]) { return this.inventory.byIID[iid]; }
let mo = null;
const moResolver = this.fetchNewMo(iid, moTypeClass);
await Promise.resolve(moResolver);
mo = this.inventory.byIID[iid];
if (!mo) { return null; }
return mo;
}
async getMany(iids: string[]): Promise<Mo.Base[]> {
const newMos: Mo.Base[] = [];
const allProms = [];
this.lockStubs();
for (let iid of iids) {
iid = iidParse(iid);
if (this.inventory.byIID[iid]) {
newMos.push(this.inventory.byIID[iid]);
continue;
}
const moResolver = this.fetchNewMo(iid, getMoTypeByName(ref(iid).attributes.type), false, true);
allProms.push(moResolver);
}
await Promise.all(allProms);
this.unlockStubs();
return newMos;
}
getByName(name: string) {
const list = this.index.byName[name];
if (!list || !list[0]) { return null; }
return this.inventory.byIID[list[0]];
}
getManyByName(name: string) {
const list = this.index.byName[name];
return list ? list : [];
}
updateInventoryInterval(seconds: number) {
this.inventoryCheckInterval = seconds;
if (this.inventoryCheckInterval < 5) { this.inventoryCheckInterval = 5; }
return this.inventoryCheckInterval;
}
startInventoryWatch(checkIntervalSeconds: number = 10) {
this.updateInventoryInterval(checkIntervalSeconds);
if (this.inventoryWatchStarted) { return; }
this.inventoryWatchStarted = true;
const checker = async () => {
if (this.destroyed) { return; }
if (!this.inventoryWatchStarted) { return; }
if (this.ix.reconn.defunctState) { return; }
if (Date.now() - this.inventoryCheckLast > this.inventoryCheckInterval * 1000) {
const pendingKeys = Object.keys(this.inventoryStubHunts);
if (pendingKeys.length > 0) {
const proms = [];
for (const pendingKey of pendingKeys) { proms.push(this.inventoryStubHunts[pendingKey]); }
await PromUtil.allSettled(proms);
}
this.inventoryCheckLast = Date.now();
try { this.checkInventory(); } catch (e) { this.ix.pushError(e); }
}
};
Tasks.addForeground(this, `inventory-watch`, checker, 1000);
// this.inventoryWatchChecker = interval(1000, checker);
// this.inventoryWatchChecker.lifecycle.managedBy(this);
}
stopInventoryWatch() {
if (!this.inventoryWatchStarted) { return; }
this.inventoryWatchStarted = false;
this.inventoryWatchChecker.destroy();
this.inventoryWatchChecker = null;
}
async refreshComputeResourceInfo() {
const allProms = [];
for (const clusterId of Object.keys(this.inventory.computeResource)) {
allProms.push(this.inventory.computeResource[clusterId].refresh());
break;
}
for (const hostId of Object.keys(this.inventory.hostSystem)) {
allProms.push(this.inventory.hostSystem[hostId].refresh());
}
const results = await PromUtil.allSettled(allProms);
return results.filter(r => r).length === results.length;
}
async recalculateUtilization(noRefresh = false): Promise<VsphereDatacenterUtilizationSummary> {
if (!noRefresh) {
const refreshAllSuccess = await this.refreshComputeResourceInfo();
if (!refreshAllSuccess) { return null; }
}
const hostCount = Object.keys(this.inventory.hostSystem).length;
const vmCount = Object.keys(this.inventory.virtualMachine).length;
const u9n: VsphereDatacenterUtilizationSummary = {
t: Date.now(), hostCount, vmCount,
percent: { cpu: 0, mem: 0, disk: 0 },
overview: {
totalCpu: 0, consumedCpu: 0, percentCpu: 0,
totalMem: 0, consumedMem: 0, percentMem: 0,
totalStorage: 0, consumedStorage: 0, percentStorage: 0,
},
clusterSummary: null,
hostStats: [],
storageStats: [],
history: this.u9nWithHistory ? this.u9nHistory : null
};
// for (const clusterId of Object.keys(this.inventory.computeResource)) {
// const cluster = this.inventory.computeResource[clusterId];
// u9n.clusterSummary = cluster.summary;
// if (u9n.clusterSummary) {
// u9n.overview.totalCpu = parseInt(u9n.clusterSummary.totalCpu + '', 10) / 1000; // in GHz
// u9n.overview.totalMem = parseInt(u9n.clusterSummary.totalMemory + '', 10) / Unit.GiB; // in GiB
// }
// break;
// }
for (const hostId of Object.keys(this.inventory.hostSystem)) {
const host = this.inventory.hostSystem[hostId];
if (!host || !host.summary) { continue; }
const hw = host.summary.hardware;
u9n.overview.totalCpu += hw.cpuMhz * hw.numCpuCores / 1000;
u9n.overview.totalMem += parseInt(hw.memorySize, 10) / Unit.GiB;
if (host.summary.quickStats) {
u9n.overview.consumedCpu += host.summary.quickStats.overallCpuUsage / 1000;
u9n.overview.consumedMem += host.summary.quickStats.overallMemoryUsage / 1024;
}
u9n.hostStats.push({ iid: hostId, name: host.name, networksCount: host.network.length, stats: host.summary.quickStats });
}
for (const dsId of Object.keys(this.inventory.datastore)) {
const ds = this.inventory.datastore[dsId];
if (!ds || !ds.info) { continue; }
if (ds.info.containerId === ds.info.aliasOf) {
const capBytes = parseInt(ds.summary.capacity, 10);
const freeBytes = parseInt(ds.summary.freeSpace, 10);
const usedBytes = capBytes - freeBytes;
u9n.overview.totalStorage += capBytes / Unit.GiB;
u9n.overview.consumedStorage += usedBytes / Unit.GiB;
}
u9n.storageStats.push({ iid: dsId, name: ds.name, info: ds.info, summary: ds.summary });
}
let cpuPercent = u9n.overview.consumedCpu / u9n.overview.totalCpu * 100; if (isNaN(cpuPercent)) { cpuPercent = 0; }
let memPercent = u9n.overview.consumedMem / u9n.overview.totalMem * 100; if (isNaN(memPercent)) { memPercent = 0; }
let diskPercent = u9n.overview.consumedStorage / u9n.overview.totalStorage * 100; if (isNaN(diskPercent)) { diskPercent = 0; }
u9n.percent.cpu = u9n.overview.percentCpu = cpuPercent;
u9n.percent.mem = u9n.overview.percentMem = memPercent;
u9n.percent.disk = u9n.overview.percentStorage = diskPercent;
const snapshot: VsphereDatacenterUtilizationSnapshotData = [
Math.floor(Date.now() / 1000) % 2592000, /** ts */
u9n.hostCount, /** h */
u9n.vmCount, /** v */
parseFloat(numFormat(u9n.percent.cpu)), /** c */
parseFloat(numFormat(u9n.percent.mem)), /** m */
parseFloat(numFormat(u9n.percent.disk)), /** s */
];
if (snapshot[3] === null || snapshot[4] === null || snapshot[5] === null) {
this.ix.pushError(new Error(`utilization snapshot has null values`), 2);
return null;
}
this.u9nHistory.push(snapshot);
if (this.u9nHistory.length > this.u9nHistoryMaxKept) { this.u9nHistory.shift(); }
this.utilization = u9n;
this.u9nSerialized = JSON.stringify(u9n);
this.emitEvent(this.u9nUpdate$, u9n);
return u9n;
}
updateU9nCheckInterval(seconds: number) {
this.u9nCheckInterval = seconds;
if (this.u9nCheckInterval < 5) { this.u9nCheckInterval = 5; }
return this.u9nCheckInterval;
}
startUtilizationWatch(checkIntervalSeconds: number = 60) {
this.updateU9nCheckInterval(checkIntervalSeconds);
if (this.u9nWatchStarted) { return; }
this.u9nWatchStarted = true;
const checker = async () => {
if (this.destroyed) { return; }
if (!this.u9nWatchStarted) { return; }
if (this.ix.reconn.defunctState) { return; }
if (Date.now() - this.u9nCheckLast > this.u9nCheckInterval * 1000) {
this.u9nCheckLast = Date.now();
let u9nResult;
try { u9nResult = this.recalculateUtilization(); } catch (e) { this.ix.pushError(e); }
if (!u9nResult) { this.ix.pushError(new Error(`utilization calculation has errors`), 2); }
}
};
Tasks.addForeground(this, 'utilization-watch', checker, 1000);
}
stopUtilizationWatch() {
if (!this.u9nWatchStarted) { return; }
this.u9nWatchStarted = false;
this.u9nWatchChecker.destroy();
this.u9nWatchChecker = null;
}
errorNoConnection(resolve?: (...a: any[]) => any) {
this.ix.pushError(new Error(`vCenter '${this.key}' has no usable connections`), 2);
if (resolve) { resolve(null); }
return null;
}
emitEvent<T = any>(obs: Observable<VsphereDatacenterEvent<T>>, eventData?: any) {
const rxObj = this.rx<VsphereDatacenterEvent<T>>(null, { findNameFromThisObservable: obs });
if (!rxObj) {
this.debugOutput('Unrecognized event type from: ' + new Error('').stack);
return false;
}
const eventType = rxObj.name;
if (!eventData) { eventData = {}; }
const sourceMo = eventData.target && eventData.target instanceof Mo.Base ? eventData.target : null;
const e: VsphereDatacenterEvent = {
eventTime: Date.now(),
eventType,
sourceKey: this.key,
data: eventData,
moData: sourceMo ? sourceMo.$src : null,
};
rxObj.next(e);
if (eventType !== 'datacenter_generic') { this.rx<any>('datacenter_generic')?.next(e); }
for (const hook of this.webhooks) {
if (!hook.events) { continue; }
let matched = false;
if (hook.events.all || hook.events[eventType]) {
matched = true;
} else {
const eventMatchers = Object.keys(hook.events);
for (const eventMatcher of eventMatchers) { if (eventType.startsWith(eventMatcher)) { matched = true; break; } }
}
if (!matched) { continue; }
const postObj = { headers: null };
if (hook.headers) { postObj.headers = hook.headers; }
if (hook.data) { e.extra = hook.data; }
try {
axios.post(hook.url, e, postObj).then(_ => {}).catch(_ => {});
} catch (e) { this.debugOutput(e); }
}
return true;
}
debugOutput(message: string | Error, keepOnly = false) {
if (this.verbose) {
if (this.jsonLogs) {
if (message instanceof Error) {
const d = {t: Date.now(), src: this.ix.base, key: this.key, msg: `${message.stack ? message.stack : message.message}`};
if (keepOnly) { VsphereDatacenter.keptData.push({type: 'error', data: d}); } else {
// tslint:disable-next-line: no-console
console.log(JSON.stringify(d));
}
} else {
const d = {t: Date.now(), src: this.ix.base, key: this.key, msg: `${message}`};
if (keepOnly) { VsphereDatacenter.keptData.push({type: 'error', data: d}); } else {
// tslint:disable-next-line: no-console
console.log(JSON.stringify(d));
}
}
} else {
const date = new Date();
const dateStr = date.toISOString();
const locTime = localTime(date);
if (message instanceof Error) {
const d = `[${dateStr}] [${locTime}] [${this.ix.base}] [${this.key}] :: ${message.stack ? message.stack : message.message}`;
if (keepOnly) { VsphereDatacenter.keptData.push({type: 'message', data: d}); } else {
// tslint:disable-next-line: no-console
console.log(d);
}
} else {
const d = `[${dateStr}] [${locTime}] [${this.ix.base}] [${this.key}] :: ${message}`;
if (keepOnly) { VsphereDatacenter.keptData.push({type: 'message', data: d}); } else {
// tslint:disable-next-line: no-console
console.log(d);
}
}
}
}
}
debugKeep(message: string | Error) { this.debugOutput(message, true); }
readyResetState() {
this.ready = false;
if (!this.readyResolver) {
this.readyPromise = new Promise<boolean>(resolve => this.readyResolver = resolve);
}
}
readyFulfill(value: boolean) {
if (this.readyResolver) {
this.readyResolver(value);
this.readyResolver = null;
}
}
}
function localTime(date: Date) {
let hours = date.getHours();
const minutes = date.getMinutes();
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12;
const hourStr = hours < 10 ? '0' + hours : hours;
const minuteStr = minutes < 10 ? '0' + minutes : minutes;
return hourStr + ':' + minuteStr + ' ' + ampm;
}
export class VsphereInfra extends ix.Entity {
static granted = false;
static grant() { VsphereInfra.granted = true; }
src: string; // metadata info on how/why this was craeted
datacenterData: {[key: string]: VsphereDatacenter} = {};
connections: [];
behavior: VsphereBehaviorController;
constructor(lcParent: ix.Entity) {
super('vinfra');
if (lcParent) { lcParent.lifecycle.manage(this); }
this.behavior = new VsphereBehaviorController(this);
}
getDatacenter(target: VsphereTarget): Promise<VsphereDatacenter> {
return promise<VsphereDatacenter>(async resolve => {
const vcenter = await VsphereDatacenter.fromTargetInfo(target, this);
resolve(vcenter);
if (target.watch) {
try {
vcenter.startInventoryWatch();
vcenter.startUtilizationWatch();
} catch (e) {
// no error output since first listen will fail with no connection present.
}
}
try { await vcenter.initializeConnection(); } catch (e) { vcenter.ix.pushError(e); }
if (!target.skipFetch) {
try {
await vcenter.getInventory();
await vcenter.recalculateUtilization(true);
vcenter.u9nCheckLast = Date.now();
} catch (e) { vcenter.ix.pushError(e); }
}
});
}
}
export class VsphereBehaviorController extends ix.Entity {
target: VsphereInfra;
verbose = false;
jsonLogs = false;
readonlyMode = false;
useSdkCache = true;
connection = {
timeout: 30
};
reconnect = new ix.ConfigSource(ix.ReconnEntityBehavior, { restoreCheckInterval: 300 });
webhooks: VsphereEventWebhook[] = [];
constructor(target: VsphereInfra) {
super('vinfra-controller');
this.target = target;
this.lifecycle.managedBy(target);
}
get dcMap() { return VsphereDatacenter.registry; }
setVerbose(value = true) {
value = value ? true : false;
this.verbose = value;
for (const key of Object.keys(this.dcMap)) { this.dcMap[key].verbose = value; }
}
setJsonLogs(value = true) {
value = value ? true : false;
this.jsonLogs = value;
for (const key of Object.keys(this.dcMap)) { this.dcMap[key].jsonLogs = value; }
}
setReadonlyMode(value = true) {
value = value ? true : false;
this.readonlyMode = value;
for (const key of Object.keys(this.dcMap)) { this.dcMap[key].readonlyMode = value; }
}
globallySetTlsUnchecked() { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; }
globallySetTlsChecked() { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; }
addWebhook(webhookDef: VsphereEventWebhook) { this.webhooks.push(webhookDef); }
removeWebhook(webhookName: string) {
let i = -1; for (const hook of this.webhooks) { ++i; if (hook.name === webhookName) { break; } }
if (i >= 0) { this.webhooks.splice(i, 1); }
}
}
function numFormat(n: number) {
if (n < 1) {
return n.toFixed(2);
} else if (n < 10) {
return n.toFixed(1);
} else {
return n.toFixed(0);
}
}
async function retry(timeout: number, fn: (...a) => Promise<any>) {
return promise<boolean>(async (resolve, reject) => {
let finished = false;
fn().then(r => {
if (finished) { return; }
finished = true;
resolve(true);
}).catch(e => {
if (finished) { return; }
finished = true;
resolve(false);
});
setTimeout(() => {
if (finished) { return; }
finished = true;
resolve(false);
}, timeout * 1000);
});
}