UNPKG

iobroker.js-controller

Version:

Updated by reinstall.js on 2018-06-11T15:19:56.688Z

281 lines • 9.38 kB
import { tools } from '@iobroker/js-controller-common'; import http from 'node:http'; import https from 'node:https'; import { setTimeout as wait } from 'node:timers/promises'; import { Upgrade } from '@iobroker/js-controller-cli'; export class AdapterUpgradeManager { /** Wait ms until adapter is stopped */ STOP_TIMEOUT_MS = 3_000; /** Wait ms for delivery of final response */ SHUTDOWN_TIMEOUT = 10_000; /** Name of the adapter to upgrade */ adapterName; /** Desired adapter version */ version; /** Response send by webserver */ response = { running: true, stderr: [], stdout: [], }; /** Used to stop the stop shutdown timeout */ shutdownAbortController; /** Logger to log to file and other transports */ logger; /** The server used for communicating upgrade status */ server; /** All socket connections of the webserver */ sockets = new Set(); /** Name of the host for logging purposes */ hostname = tools.getHostName(); /** The objects DB client */ objects; /** The states DB client */ states; /** List of instances which have been stopped */ stoppedInstances = []; /** If webserver should be started with https */ useHttps; /** Public certificate name if https is desired */ certPublicName; /** Private certificate name if https is desired */ certPrivateName; /** Port where the webserver should be running */ port; constructor(options) { this.adapterName = options.adapterName; this.version = options.version; this.logger = options.logger; this.objects = options.objects; this.states = options.states; this.useHttps = options.useHttps; this.port = options.port; if (options.useHttps) { this.certPublicName = options.certPublicName; this.certPrivateName = options.certPrivateName; } } /** * Stops the adapter and returns ids of stopped instances */ async stopAdapter() { this.stoppedInstances = await this.getAllEnabledInstances(); await this.enableInstances(this.stoppedInstances, false); await wait(this.STOP_TIMEOUT_MS); } /** * Start all instances which were enabled before the upgrade */ async startAdapter() { await this.enableInstances(this.stoppedInstances, true); } /** * Start or stop given instances * * @param instances id of instances which will be stopped * @param enabled if enable or disable instances */ async enableInstances(instances, enabled) { const ts = Date.now(); for (const instance of instances) { const updatedObj = { common: { enabled, }, from: `system.host.${this.hostname}`, ts, }; await this.objects.extendObjectAsync(instance, updatedObj); } } /** * Install given version of adapter */ async performUpgrade() { const processExitHandler = exitCode => { this.log(`Upgrade process exited with code: ${exitCode}`, true); }; const upgrade = new Upgrade({ objects: this.objects, processExit: processExitHandler, states: this.states, params: {}, }); try { await upgrade.upgradeAdapter(undefined, `${this.adapterName}@${this.version}`, true, true, false); this.response.success = true; this.log(`Successfully upgraded ${this.adapterName} to version ${this.version}`); } catch (e) { this.log(e.message, true); this.response.success = false; } await this.setFinished(); } /** * Starts the web server for admin communication either secure or insecure */ async startWebServer() { if (this.useHttps && this.certPublicName && this.certPrivateName) { await this.startSecureWebServer({ certPublicName: this.certPublicName, certPrivateName: this.certPrivateName, port: this.port, useHttps: true, }); } else { this.startInsecureWebServer({ port: this.port, useHttps: false }); } } /** * Shuts down the server, restarts the adapter */ shutdownServer() { if (this.shutdownAbortController) { this.shutdownAbortController.abort(); } if (!this.server) { return; } this.destroySockets(); this.server.close(async () => { await this.startAdapter(); this.log('Successfully started adapter'); }); } /** * Destroy all sockets, to prevent requests from keeping server alive */ destroySockets() { for (const socket of this.sockets) { socket.destroy(); this.sockets.delete(socket); } } /** * This function is called when the webserver receives a message * * @param req received message * @param res server response */ webServerCallback(req, res) { res.writeHead(200); res.end(JSON.stringify(this.response)); if (!this.response.running) { this.log('Final information delivered'); this.shutdownServer(); } } /** * Get all instances of the adapter */ async getAllEnabledInstances() { const res = await this.objects.getObjectListAsync({ startkey: `system.adapter.${this.adapterName}.`, endkey: `system.adapter.${this.adapterName}.\u9999`, }); let enabledInstances = []; enabledInstances = res.rows .filter(row => row.value.common.enabled && this.hostname === row.value.common.host) .map(row => row.value._id); return enabledInstances; } /** * Log via logger and provide the logs for the server too * * @param message the message which will be logged * @param error if it is an error */ log(message, error = false) { if (error) { this.logger.error(`host.${this.hostname} ${message}`); this.response.stderr.push(message); return; } this.logger.info(`host.${this.hostname} [WEBSERVER_UPGRADE] (${this.adapterName}) ${message}`); this.response.stdout.push(message); } /** * Start an insecure web server for admin communication * * @param params Web server configuration */ startInsecureWebServer(params) { const { port } = params; this.server = http.createServer((req, res) => { this.webServerCallback(req, res); }); this.monitorSockets(this.server); this.server.listen(port, () => { this.log(`Server is running on http://localhost:${port}`); }); } /** * Start a secure web server for admin communication * * @param params Web server configuration */ async startSecureWebServer(params) { const { port, certPublicName, certPrivateName } = params; const { certPublic, certPrivate } = await this.getCertificates({ certPublicName, certPrivateName }); this.server = https.createServer({ key: certPrivate, cert: certPublic }, (req, res) => { this.webServerCallback(req, res); }); this.monitorSockets(this.server); this.server.listen(port, () => { this.log(`Server is running on http://localhost:${port}`); }); } /** * Keep track of all existing sockets * * @param server the webserver */ monitorSockets(server) { server.on('connection', socket => { this.sockets.add(socket); server.once('close', () => { this.sockets.delete(socket); }); }); } /** * Get certificates from the DB * * @param params certificate information */ async getCertificates(params) { const { certPublicName, certPrivateName } = params; const obj = await this.objects.getObjectAsync('system.certificates'); if (!obj) { throw new Error('No certificates found'); } const certs = obj.native.certificates; return { certPrivate: certs[certPrivateName], certPublic: certs[certPublicName] }; } /** * Tells the upgrade manager, that server can be shut down on next response or on timeout */ async setFinished() { this.response.running = false; await this.startShutdownTimeout(); } /** * Start a timeout which starts adapter and shuts down the server if expired */ async startShutdownTimeout() { this.shutdownAbortController = new AbortController(); try { await wait(this.SHUTDOWN_TIMEOUT, null, { signal: this.shutdownAbortController.signal }); this.log('Timeout expired, initializing shutdown'); this.shutdownServer(); } catch (e) { if (e.code !== 'ABORT_ERR') { this.log(e.message, true); } } } } //# sourceMappingURL=adapterUpgradeManager.js.map