UNPKG

iobroker.proxmox

Version:
354 lines (282 loc) 11.5 kB
const https = require('node:https'); const axios = require('axios').default; let TICKET = ''; let CSRF = ''; class ProxmoxUtils { constructor(adapter, nodeList) { this.adapter = adapter; this.nodeList = nodeList; this.name = ''; this.server = ''; this.password = ''; this.nodeURL = ''; this.currentIpId = -1; // damit wir den ersten host nehmen this.setNextUrlMain(); this.responseCache = {}; this.sharedMap = {}; this.stopped = false; } setNextUrlMain() { this.currentIpId++; if (this.currentIpId >= this.nodeList.length) { this.currentIpId = 0; } this.name = this.nodeList[this.currentIpId].realmUser; this.password = this.nodeList[this.currentIpId].realmPassword; this.server = this.nodeList[this.currentIpId].realm; const realmIp = this.nodeList[this.currentIpId].realmIp; const realmPort = this.nodeList[this.currentIpId].realmPort; this.nodeURL = `https://${realmIp}:${realmPort}/api2/json`; this.adapter.log.warn(`Using Proxmox API: ${this.nodeURL}`); } resetResponseCache() { this.responseCache = {}; this.sharedMap = {}; } async getNodes() { if (this.responseCache['/nodes']) { return JSON.parse(JSON.stringify(this.responseCache['/nodes'])); } try { const data = await this._getData('/nodes', 'get'); if (data && typeof data === 'object') { this.responseCache['/nodes'] = data.data; return data.data; } else { throw new Error('Invalid data received'); } } catch (error) { throw error; } } async getNodeStatus(node, useCache) { const cacheKey = `/nodes/${node}/status`; if (useCache && this.responseCache[cacheKey]) { return JSON.parse(JSON.stringify(this.responseCache[cacheKey])); } try { const data = await this._getData(cacheKey, 'get'); if (data && typeof data === 'object') { this.responseCache[cacheKey] = data.data; return data.data; } else { throw new Error('Invalid data received'); } } catch (error) { throw error; } } async getNodeDisks(node, useCache) { const cacheKey = `/nodes/${node}/disks/list`; if (useCache && this.responseCache[cacheKey]) { return JSON.parse(JSON.stringify(this.responseCache[cacheKey])); } try { const data = await this._getData(cacheKey, 'get'); if (data && typeof data === 'object') { this.responseCache[cacheKey] = data.data; return data.data; } else { throw new Error('Invalid data received'); } } catch (error) { throw error; } } async getNodeDisksSmart(node, disk) { return this._getData(`/nodes/${node}/disks/smart?disk=${disk}`, 'get', '', '', 'disk'); } async getCephInformation() { return this._getData(`/cluster/ceph/status`, 'get', '', '', 'ceph'); } async getHAStatusInformation() { return this._getData(`/cluster/ha/status/current`, 'get', '', '', 'ha'); } async getClusterResources(useCache) { const cacheKey = '/cluster/resources'; if (useCache && this.responseCache[cacheKey]) { return JSON.parse(JSON.stringify(this.responseCache[cacheKey])); } try { const data = await this._getData(cacheKey, 'get'); if (data && typeof data === 'object') { this.responseCache[cacheKey] = data.data; return data.data; } else { throw new Error('Invalid data received'); } } catch (error) { throw error; } } async getResourceStatus(node, type, ID, useCache) { const cacheKey = `/nodes/${node}/${type}/${ID}/status/current`; if (useCache && this.responseCache[cacheKey]) { return JSON.parse(JSON.stringify(this.responseCache[cacheKey])); } try { const data = await this._getData(cacheKey, 'get'); if (data && typeof data === 'object') { this.responseCache[cacheKey] = data.data; return data.data; } else { throw new Error('Invalid data received'); } } catch (error) { throw error; } } async getBackupStatus(node, storage) { try { const data = await this._getData(`/nodes/${node}/storage/${storage}/content`, 'get', '', '', 'storage'); if (data && typeof data === 'object') { return data.data; } else { throw new Error('Invalid data received'); } } catch (error) { throw error; } } async getStorageStatus(node, ID, shared, useCache) { const cacheKey = `/nodes/${shared ? 'SHARED' : node}/storage/${ID}/status`; if (useCache && this.responseCache[cacheKey]) { return JSON.parse(JSON.stringify(this.responseCache[cacheKey])); } if (shared) { node = this.sharedMap[cacheKey] || (this.sharedMap[cacheKey] = node); } try { const data = await this._getData(`/nodes/${node}/storage/${ID}/status`, 'get', '', '', 'storage'); if (data && typeof data === 'object') { this.responseCache[cacheKey] = data.data; return data.data; } else { this.adapter.log.error(`Problem with getStorageStatus. ${JSON.stringify(data)}`); throw new Error('Invalid data received'); } } catch (error) { throw error; } } async qemuStart(node, type, ID) { return this._getData(`/nodes/${node}/${type}/${ID}/status/start`, 'post'); } async qemuStop(node, type, ID) { return this._getData(`/nodes/${node}/${type}/${ID}/status/stop`, 'post'); } async qemuShutdown(node, type, ID) { return this._getData(`/nodes/${node}/${type}/${ID}/status/shutdown`, 'post'); } async qemuReset(node, type, ID) { return this._getData(`/nodes/${node}/${type}/${ID}/status/reset`, 'post'); } async qemuSuspend(node, type, ID) { return this._getData(`/nodes/${node}/${type}/${ID}/status/suspend`, 'post'); } async qemuResume(node, type, ID) { return this._getData(`/nodes/${node}/${type}/${ID}/status/resume`, 'post'); } async qemuReboot(node, type, ID) { return this._getData(`/nodes/${node}/${type}/${ID}/status/reboot`, 'post'); } async nodeReboot(node) { return this._getData(`/nodes/${node}/status`, 'post', 'command=reboot'); } async nodeShutdown(node) { return this._getData(`/nodes/${node}/status`, 'post', 'command=shutdown'); } async ticket() { const data = await this._getTicket(); this.adapter.log.debug(`Updating ticket to "${data.ticket}" and CSRF to "${data.CSRFPreventionToken}"`); TICKET = `PVEAuthCookie=${data.ticket}`; CSRF = data.CSRFPreventionToken; } async _getData(url, method, data = null, retry = false, additional = null) { if (this.stopped) { throw 'STOPPED'; } const pathU = url || ''; try { const response = await axios({ method, baseURL: this.nodeURL, url: pathU, data, timeout: 10000, headers: { CSRFPreventionToken: CSRF, Cookie: TICKET, }, validateStatus: (status) => [200, 401, 500, 595, 599].includes(status), httpsAgent: new https.Agent({ rejectUnauthorized: false }), }); this.adapter.log.debug(`received ${response.status} response from ${pathU} with content: ${JSON.stringify(response.data)}`); if ([500, 595, 599].includes(response.status)) { throw `${response.status} - ${JSON.stringify(response)}`; } if (response.status === 401 && !retry) { if (!additional) { await this.ticket(); return this._getData(url, method, data, true); } } if (response.status === 200) { return response.data; } } catch (error) { if (additional !== 'storage') { if (additional !== 'disk' ) { // kann sein dass die platte aus ist dann ignoriere es und schmeisse nur error message this.adapter.log.warn(`${additional} -- Use Next Proxmox Host because of communication failure ${this.nodeURL}${url}`); this.setNextUrlMain(); await this.ticket(); return this._getData(url, method, data, true); } } /* if (additional === 'storage') { this.adapter.log.error(`Check ${additional} -- Problem found.. maybe offline check path ${this.nodeURL}${url}`); } if (additional === 'disk') { this.adapter.log.error(`Check ${additional} -- Problem found.. maybe is backup storage offline `); } */ throw error; } } async _getTicket() { const url = `/access/ticket?username=${encodeURIComponent(this.name)}@${encodeURIComponent(this.server)}&password=${encodeURIComponent(this.password)}`; try { const response = await axios({ method: 'post', baseURL: this.nodeURL, url, timeout: 5000, httpsAgent: new https.Agent({ rejectUnauthorized: false }), }); this.adapter.log.debug(`received ${response.status} response from proxmox with content: ${JSON.stringify(response.data)}`); if (response.status === 200) { this.adapter.log.debug(`dataticket: ${JSON.stringify(response.data)}`); return response.data.data; // "data" is an attribute in the response JSON } else { this.adapter.log.error(`${response.status}: wrong User data, could not log in, please try again with correct user and pw`); throw new Error(response.status); } } catch (error) { if (error.response) { this.adapter.log.warn(`Error received ${error.response.status} response from proxmox with content: ${JSON.stringify(error.response.data)}`); throw new Error(error.response.status); } else if (error.request) { this.adapter.log.warn(`No response received from proxmox`); throw new Error(-1); } else { this.adapter.log.error(`Request setup error: ${error.message}`); throw new Error(-2); } } } stop() { this.stopped = true; } } module.exports = ProxmoxUtils;