From 65d7fa34106b06c5c550e9b3f6ea5e18ebb03296 Mon Sep 17 00:00:00 2001 From: Alexandre Trovato <1839717+atrovato@users.noreply.github.com> Date: Mon, 22 Feb 2021 11:43:25 +0100 Subject: [PATCH] MQTT: Upgrade container to Mosquitto 2.0 and fix listener bug #1071 (#1074) Co-authored-by: Pierre-Gilles Leymarie --- server/lib/system/index.js | 2 + server/lib/system/system.getNetworkMode.js | 12 +- server/lib/system/system.removeContainer.js | 22 ++++ .../docker/eclipse-mosquitto-container.json | 2 +- .../mqtt/docker/eclipse-mosquitto-env.sh | 5 + server/services/mqtt/lib/constants.js | 2 + server/services/mqtt/lib/getConfiguration.js | 4 + server/services/mqtt/lib/index.js | 2 + server/services/mqtt/lib/init.js | 4 + server/services/mqtt/lib/installContainer.js | 22 ++-- server/services/mqtt/lib/saveConfiguration.js | 9 +- server/services/mqtt/lib/updateContainer.js | 47 ++++++++ server/test/lib/system/DockerodeMock.test.js | 2 + .../lib/system/system.getNetworkMode.test.js | 10 +- .../lib/system/system.removeContainer.test.js | 68 +++++++++++ .../mqtt/lib/getConfiguration.test.js | 23 +++- .../mqtt/lib/installContainer.test.js | 39 ++++++- .../mqtt/lib/saveConfiguration.test.js | 37 +++++- .../services/mqtt/lib/updateContainer.test.js | 106 ++++++++++++++++++ 19 files changed, 390 insertions(+), 28 deletions(-) create mode 100644 server/lib/system/system.removeContainer.js create mode 100644 server/services/mqtt/lib/updateContainer.js create mode 100644 server/test/lib/system/system.removeContainer.test.js create mode 100644 server/test/services/mqtt/lib/updateContainer.test.js diff --git a/server/lib/system/index.js b/server/lib/system/index.js index 5d6c332444..8e1fde642f 100644 --- a/server/lib/system/index.js +++ b/server/lib/system/index.js @@ -15,6 +15,7 @@ const { pull } = require('./system.pull'); const { exec } = require('./system.exec'); const { createContainer } = require('./system.createContainer'); const { restartContainer } = require('./system.restartContainer'); +const { removeContainer } = require('./system.removeContainer'); const { getNetworkMode } = require('./system.getNetworkMode'); const { shutdown } = require('./system.shutdown'); @@ -45,6 +46,7 @@ System.prototype.pull = pull; System.prototype.exec = exec; System.prototype.createContainer = createContainer; System.prototype.restartContainer = restartContainer; +System.prototype.removeContainer = removeContainer; System.prototype.getNetworkMode = getNetworkMode; System.prototype.shutdown = shutdown; diff --git a/server/lib/system/system.getNetworkMode.js b/server/lib/system/system.getNetworkMode.js index 9e96cda121..ebe5328134 100644 --- a/server/lib/system/system.getNetworkMode.js +++ b/server/lib/system/system.getNetworkMode.js @@ -1,8 +1,6 @@ const get = require('get-value'); const { PlatformNotCompatible } = require('../../utils/coreErrors'); -const getConfig = require('../../utils/getConfig'); - -const config = getConfig(); +const { exec } = require('../../utils/childProcess'); /** * @description Get Gladys network into Docker environment. @@ -16,9 +14,11 @@ async function getNetworkMode() { } if (!this.networkMode) { - const containers = await this.dockerode.listContainers(); - const gladysContainer = containers.find((c) => c.Image.startsWith(config.dockerImage)); - this.networkMode = get(gladysContainer, 'HostConfig.NetworkMode', { default: 'unknown' }); + const cmdResult = await exec('head -1 /proc/self/cgroup | cut -d/ -f3'); + const [containerId] = cmdResult.split('\n'); + const gladysContainer = this.dockerode.getContainer(containerId); + const gladysContainerInspect = await gladysContainer.inspect(); + this.networkMode = get(gladysContainerInspect, 'HostConfig.NetworkMode', { default: 'unknown' }); } return this.networkMode; diff --git a/server/lib/system/system.removeContainer.js b/server/lib/system/system.removeContainer.js new file mode 100644 index 0000000000..53651debc4 --- /dev/null +++ b/server/lib/system/system.removeContainer.js @@ -0,0 +1,22 @@ +const { PlatformNotCompatible } = require('../../utils/coreErrors'); + +/** + * @description Remove an Docker container. + * @param {string} containerId - Container id. + * @param {Object} options - Options for removal (see https://docs.docker.com/engine/api/v1.37/#operation/ContainerDelete). + * @returns {Promise} The removed container. + * @example + * await removeContainer(options); + */ +async function removeContainer(containerId, options = {}) { + if (!this.dockerode) { + throw new PlatformNotCompatible('SYSTEM_NOT_RUNNING_DOCKER'); + } + + const container = await this.dockerode.getContainer(containerId); + return container.remove(options); +} + +module.exports = { + removeContainer, +}; diff --git a/server/services/mqtt/docker/eclipse-mosquitto-container.json b/server/services/mqtt/docker/eclipse-mosquitto-container.json index 4d4832fcb8..153550fecd 100644 --- a/server/services/mqtt/docker/eclipse-mosquitto-container.json +++ b/server/services/mqtt/docker/eclipse-mosquitto-container.json @@ -1,6 +1,6 @@ { "name": "eclipse-mosquitto", - "Image": "eclipse-mosquitto:latest", + "Image": "eclipse-mosquitto:2", "ExposedPorts": { "1883/tcp": {} }, "HostConfig": { "Binds": ["/var/lib/gladysassistant/mosquitto:/mosquitto/config"], diff --git a/server/services/mqtt/docker/eclipse-mosquitto-env.sh b/server/services/mqtt/docker/eclipse-mosquitto-env.sh index e052babddf..04c85395be 100755 --- a/server/services/mqtt/docker/eclipse-mosquitto-env.sh +++ b/server/services/mqtt/docker/eclipse-mosquitto-env.sh @@ -28,5 +28,10 @@ else echo "eclipse-mosquitto configuration file already exists." fi +# Check for breaking change +if ! grep -q "listener 1883" "$mosquitto_config_file"; then + echo "listener 1883" >> $mosquitto_config_file +fi + # Create passwd file if not exists touch ${mosquitto_passwd_file} diff --git a/server/services/mqtt/lib/constants.js b/server/services/mqtt/lib/constants.js index e58c8694cc..2988adeea0 100644 --- a/server/services/mqtt/lib/constants.js +++ b/server/services/mqtt/lib/constants.js @@ -3,6 +3,7 @@ const CONFIGURATION = { MQTT_USERNAME_KEY: 'MQTT_USERNAME', MQTT_PASSWORD_KEY: 'MQTT_PASSWORD', MQTT_EMBEDDED_BROKER_KEY: 'MQTT_EMBEDDED_BROKER', + MQTT_MOSQUITTO_VERSION: 'MQTT_MOSQUITTO', }; const DEFAULT = { @@ -15,6 +16,7 @@ const DEFAULT = { IN_PROGRESS: 'IN_PROGRESS', ERROR: 'ERROR', }, + MOSQUITTO_VERSION: '2', }; module.exports = { diff --git a/server/services/mqtt/lib/getConfiguration.js b/server/services/mqtt/lib/getConfiguration.js index 5044209f7b..9627e9d43e 100644 --- a/server/services/mqtt/lib/getConfiguration.js +++ b/server/services/mqtt/lib/getConfiguration.js @@ -17,6 +17,7 @@ async function getConfiguration() { let useEmbeddedBroker = false; let networkModeValid = false; + let mosquittoVersion = null; // Look for broker docker image if (dockerBased) { @@ -36,6 +37,8 @@ async function getConfiguration() { }, }); brokerContainerAvailable = dockerImages.length > 0; + + mosquittoVersion = await this.gladys.variable.getValue(CONFIGURATION.MQTT_MOSQUITTO_VERSION, this.serviceId); } return { @@ -46,6 +49,7 @@ async function getConfiguration() { dockerBased, brokerContainerAvailable, networkModeValid, + mosquittoVersion, }; } diff --git a/server/services/mqtt/lib/index.js b/server/services/mqtt/lib/index.js index 0c981570c2..b4e8c8b702 100644 --- a/server/services/mqtt/lib/index.js +++ b/server/services/mqtt/lib/index.js @@ -10,6 +10,7 @@ const { status } = require('./status'); const { getConfiguration } = require('./getConfiguration'); const { saveConfiguration } = require('./saveConfiguration'); const { installContainer } = require('./installContainer'); +const { updateContainer } = require('./updateContainer'); const { checkDockerNetwork } = require('./checkDockerNetwork'); const { setValue } = require('./setValue'); @@ -44,6 +45,7 @@ MqttHandler.prototype.status = status; MqttHandler.prototype.getConfiguration = getConfiguration; MqttHandler.prototype.saveConfiguration = saveConfiguration; MqttHandler.prototype.installContainer = installContainer; +MqttHandler.prototype.updateContainer = updateContainer; MqttHandler.prototype.checkDockerNetwork = checkDockerNetwork; MqttHandler.prototype.setValue = setValue; diff --git a/server/services/mqtt/lib/init.js b/server/services/mqtt/lib/init.js index 8fbf0aea4b..c4b8f91793 100644 --- a/server/services/mqtt/lib/init.js +++ b/server/services/mqtt/lib/init.js @@ -11,6 +11,10 @@ async function init() { }); const configuration = await this.getConfiguration(); + + // Check for container configuration + await this.updateContainer(configuration); + await this.connect(configuration); } diff --git a/server/services/mqtt/lib/installContainer.js b/server/services/mqtt/lib/installContainer.js index 68c19bcdc8..dc585f6d26 100644 --- a/server/services/mqtt/lib/installContainer.js +++ b/server/services/mqtt/lib/installContainer.js @@ -9,13 +9,15 @@ const containerDescriptor = require('../docker/eclipse-mosquitto-container.json' /** * @description Get MQTT configuration. + * @param {boolean} saveConfiguration - Save new configuration. * @returns {Promise} Current MQTT network configuration. * @example * installContainer(); */ -async function installContainer() { +async function installContainer(saveConfiguration = true) { logger.info('MQTT broker is being installed as Docker container...'); + let container; try { logger.info(`Check Gladys network...`); const networkModeValid = await this.checkDockerNetwork(); @@ -32,7 +34,7 @@ async function installContainer() { logger.trace(brokerEnv); logger.info(`Creating container...`); - const container = await this.gladys.system.createContainer(containerDescriptor); + container = await this.gladys.system.createContainer(containerDescriptor); logger.trace(container); logger.info('MQTT broker successfully installed as Docker container'); @@ -54,12 +56,16 @@ async function installContainer() { throw e; } - await this.saveConfiguration({ - mqttUrl: 'mqtt://localhost', - mqttUsername: 'gladys', - mqttPassword: generate(20, { number: true, lowercase: true, uppercase: true }), - useEmbeddedBroker: true, - }); + if (saveConfiguration) { + await this.saveConfiguration({ + mqttUrl: 'mqtt://localhost', + mqttUsername: 'gladys', + mqttPassword: generate(20, { number: true, lowercase: true, uppercase: true }), + useEmbeddedBroker: true, + }); + } + + return container; } module.exports = { diff --git a/server/services/mqtt/lib/saveConfiguration.js b/server/services/mqtt/lib/saveConfiguration.js index c2c41b55aa..19279a0a6c 100644 --- a/server/services/mqtt/lib/saveConfiguration.js +++ b/server/services/mqtt/lib/saveConfiguration.js @@ -1,5 +1,5 @@ const { promisify } = require('util'); -const { CONFIGURATION } = require('./constants'); +const { CONFIGURATION, DEFAULT } = require('./constants'); const { NotFoundError } = require('../../../utils/coreErrors'); const containerParams = require('../docker/eclipse-mosquitto-container.json'); @@ -65,6 +65,13 @@ async function saveConfiguration({ mqttUrl, mqttUsername, mqttPassword, useEmbed await this.gladys.system.restartContainer(container.id); // wait 5 seconds for the container to restart await sleep(5 * 1000); + + await updateOrDestroyVariable( + variable, + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + this.serviceId, + ); } return this.connect({ mqttUrl, mqttUsername, mqttPassword, useEmbeddedBroker }); diff --git a/server/services/mqtt/lib/updateContainer.js b/server/services/mqtt/lib/updateContainer.js new file mode 100644 index 0000000000..def0e6b751 --- /dev/null +++ b/server/services/mqtt/lib/updateContainer.js @@ -0,0 +1,47 @@ +const logger = require('../../../utils/logger'); +const { CONFIGURATION, DEFAULT } = require('./constants'); +const containerParams = require('../docker/eclipse-mosquitto-container.json'); + +/** + * @description Updates MQTT container configuration according to required changes. + * @param {Object} configuration - MQTT service configuration. + * @returns {Promise} Current MQTT network configuration. + * @example + * updateContainer({ mqttUrl, mqttPort }); + */ +async function updateContainer(configuration) { + logger.info('MQTT: checking for required changes...'); + + // Check for port listener option + const { brokerContainerAvailable, mosquittoVersion } = configuration; + if (brokerContainerAvailable && !mosquittoVersion) { + logger.info('MQTT: update to mosquitto v2 required...'); + const dockerContainers = await this.gladys.system.getContainers({ + all: true, + filters: { name: [containerParams.name] }, + }); + + // Remove non versionned container + if (dockerContainers.length !== 0) { + const [container] = dockerContainers; + await this.gladys.system.removeContainer(container.id, { force: true }); + } + + // Reinstall container with explicit version + const newContainer = await this.installContainer(false); + await this.gladys.system.restartContainer(newContainer.id); + + await this.gladys.variable.setValue( + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + this.serviceId, + ); + logger.info('MQTT: update to mosquitto v2 done'); + } + + return configuration; +} + +module.exports = { + updateContainer, +}; diff --git a/server/test/lib/system/DockerodeMock.test.js b/server/test/lib/system/DockerodeMock.test.js index b9a24d976a..d37319100d 100644 --- a/server/test/lib/system/DockerodeMock.test.js +++ b/server/test/lib/system/DockerodeMock.test.js @@ -18,7 +18,9 @@ Docker.prototype.listImages = fake.resolves(images); Docker.prototype.createContainer = fake.resolves({ id: containers[0].Id }); Docker.prototype.getContainer = fake.returns({ + inspect: fake.resolves({ HostConfig: { NetworkMode: 'host' } }), restart: fake.resolves(true), + remove: fake.resolves(true), exec: ({ Cmd }) => { const mockedStream = new stream.Readable(); return fake.resolves({ diff --git a/server/test/lib/system/system.getNetworkMode.test.js b/server/test/lib/system/system.getNetworkMode.test.js index db25a21444..a70d4395f4 100644 --- a/server/test/lib/system/system.getNetworkMode.test.js +++ b/server/test/lib/system/system.getNetworkMode.test.js @@ -8,8 +8,13 @@ const proxyquire = require('proxyquire').noCallThru(); const { PlatformNotCompatible } = require('../../../utils/coreErrors'); const DockerodeMock = require('./DockerodeMock.test'); +const getNetworkMode = proxyquire('../../../lib/system/system.getNetworkMode', { + '../../utils/childProcess': { exec: () => 'containerId' }, +}); + const System = proxyquire('../../../lib/system', { dockerode: DockerodeMock, + './system.getNetworkMode': getNetworkMode, }); const sequelize = { @@ -51,7 +56,8 @@ describe('system.getNetworkMode', () => { }); it('should check network', async () => { - await system.getNetworkMode(); - assert.calledOnce(system.dockerode.listContainers); + const network = await system.getNetworkMode(); + expect(network).to.eq('host'); + assert.calledOnce(system.dockerode.getContainer); }); }); diff --git a/server/test/lib/system/system.removeContainer.test.js b/server/test/lib/system/system.removeContainer.test.js new file mode 100644 index 0000000000..f49f979d31 --- /dev/null +++ b/server/test/lib/system/system.removeContainer.test.js @@ -0,0 +1,68 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); + +const { fake, assert } = sinon; + +const proxyquire = require('proxyquire').noCallThru(); + +const { PlatformNotCompatible } = require('../../../utils/coreErrors'); +const DockerodeMock = require('./DockerodeMock.test'); + +const System = proxyquire('../../../lib/system', { + dockerode: DockerodeMock, +}); + +const sequelize = { + close: fake.resolves(null), +}; + +const event = { + on: fake.resolves(null), + emit: fake.resolves(null), +}; + +const config = { + tempFolder: '/tmp/gladys', +}; + +describe('system.removeContainer', () => { + let system; + + beforeEach(async () => { + system = new System(sequelize, event, config); + await system.init(); + // Reset all fakes invoked within init call + sinon.reset(); + }); + + afterEach(() => { + sinon.reset(); + }); + + it('should failed as not on docker env', async () => { + system.dockerode = undefined; + + try { + await system.removeContainer('my-container'); + assert.fail('should have fail'); + } catch (e) { + expect(e).be.instanceOf(PlatformNotCompatible); + + assert.notCalled(sequelize.close); + assert.notCalled(event.on); + assert.notCalled(event.emit); + } + }); + + it('should removeContainer command with success', async () => { + const result = await system.removeContainer('my-container'); + + expect(result).to.be.eq(true); + + assert.notCalled(sequelize.close); + assert.notCalled(event.on); + assert.notCalled(event.emit); + + assert.calledOnce(system.dockerode.getContainer); + }); +}); diff --git a/server/test/services/mqtt/lib/getConfiguration.test.js b/server/test/services/mqtt/lib/getConfiguration.test.js index b1fd8bfb15..b617c66490 100644 --- a/server/test/services/mqtt/lib/getConfiguration.test.js +++ b/server/test/services/mqtt/lib/getConfiguration.test.js @@ -32,6 +32,7 @@ describe('mqttHandler.getConfiguration', () => { dockerBased: false, brokerContainerAvailable: false, networkModeValid: false, + mosquittoVersion: null, }; expect(config).to.deep.eq(expectedConfig); @@ -48,6 +49,8 @@ describe('mqttHandler.getConfiguration', () => { .stub() .onCall(3) .resolves(null) + .onCall(4) + .resolves(null) .resolves('value'), }, system: { @@ -68,10 +71,11 @@ describe('mqttHandler.getConfiguration', () => { dockerBased: true, brokerContainerAvailable: false, networkModeValid: false, + mosquittoVersion: null, }; expect(config).to.deep.eq(expectedConfig); - assert.callCount(gladys.variable.getValue, 4); + assert.callCount(gladys.variable.getValue, 5); assert.calledOnce(gladys.system.isDocker); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.getNetworkMode); @@ -84,6 +88,8 @@ describe('mqttHandler.getConfiguration', () => { .stub() .onCall(3) .resolves(null) + .onCall(4) + .resolves('2') .resolves('value'), }, system: { @@ -108,10 +114,11 @@ describe('mqttHandler.getConfiguration', () => { dockerBased: true, brokerContainerAvailable: true, networkModeValid: false, + mosquittoVersion: '2', }; expect(config).to.deep.eq(expectedConfig); - assert.callCount(gladys.variable.getValue, 4); + assert.callCount(gladys.variable.getValue, 5); assert.calledOnce(gladys.system.isDocker); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.getNetworkMode); @@ -124,6 +131,8 @@ describe('mqttHandler.getConfiguration', () => { .stub() .onCall(3) .resolves('0') + .onCall(4) + .resolves(null) .resolves('value'), }, system: { @@ -148,10 +157,11 @@ describe('mqttHandler.getConfiguration', () => { dockerBased: true, brokerContainerAvailable: true, networkModeValid: true, + mosquittoVersion: null, }; expect(config).to.deep.eq(expectedConfig); - assert.callCount(gladys.variable.getValue, 4); + assert.callCount(gladys.variable.getValue, 5); assert.calledOnce(gladys.system.isDocker); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.getNetworkMode); @@ -180,10 +190,11 @@ describe('mqttHandler.getConfiguration', () => { dockerBased: true, brokerContainerAvailable: false, networkModeValid: false, + mosquittoVersion: null, }; expect(config).to.deep.eq(expectedConfig); - assert.callCount(gladys.variable.getValue, 4); + assert.callCount(gladys.variable.getValue, 5); assert.calledOnce(gladys.system.isDocker); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.getNetworkMode); @@ -212,6 +223,7 @@ describe('mqttHandler.getConfiguration', () => { dockerBased: false, brokerContainerAvailable: false, networkModeValid: false, + mosquittoVersion: null, }; expect(config).to.deep.eq(expectedConfig); @@ -244,10 +256,11 @@ describe('mqttHandler.getConfiguration', () => { dockerBased: true, brokerContainerAvailable: false, networkModeValid: true, + mosquittoVersion: null, }; expect(config).to.deep.eq(expectedConfig); - assert.callCount(gladys.variable.getValue, 4); + assert.callCount(gladys.variable.getValue, 5); assert.calledOnce(gladys.system.isDocker); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.getNetworkMode); diff --git a/server/test/services/mqtt/lib/installContainer.test.js b/server/test/services/mqtt/lib/installContainer.test.js index ecc53b208f..b03a14ef24 100644 --- a/server/test/services/mqtt/lib/installContainer.test.js +++ b/server/test/services/mqtt/lib/installContainer.test.js @@ -78,7 +78,44 @@ describe('mqttHandler.installContainer', function Describe() { await mqttHandler.installContainer(); - assert.callCount(gladys.variable.setValue, 4); + assert.callCount(gladys.variable.setValue, 5); + assert.calledOnce(execMock.exec); + assert.calledOnce(gladys.system.pull); + assert.calledOnce(gladys.system.createContainer); + assert.calledOnce(gladys.system.getNetworkMode); + assert.calledOnce(gladys.event.emit); + assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.MQTT.INSTALLATION_STATUS, + payload: { + status: DEFAULT.INSTALLATION_STATUS.DONE, + }, + }); + }); + + it('should installContainer: pull success (no save config)', async () => { + const gladys = { + event: { + emit: fake.resolves(true), + }, + system: { + pull: fake.resolves(false), + createContainer: fake.resolves(false), + getContainers: fake.resolves([{ state: 'running' }]), + exec: fake.resolves(true), + restartContainer: fake.resolves(true), + getNetworkMode: fake.resolves('host'), + }, + variable: { + setValue: fake.resolves(true), + getValue: fake.resolves(true), + }, + }; + + const mqttHandler = new MqttHandler(gladys, MockedMqttClient, serviceId); + + await mqttHandler.installContainer(false); + + assert.notCalled(gladys.variable.setValue); assert.calledOnce(execMock.exec); assert.calledOnce(gladys.system.pull); assert.calledOnce(gladys.system.createContainer); diff --git a/server/test/services/mqtt/lib/saveConfiguration.test.js b/server/test/services/mqtt/lib/saveConfiguration.test.js index f18f74d50b..b4ea90fd73 100644 --- a/server/test/services/mqtt/lib/saveConfiguration.test.js +++ b/server/test/services/mqtt/lib/saveConfiguration.test.js @@ -136,13 +136,20 @@ describe('mqttHandler.saveConfiguration', function Describe() { await mqttHandler.saveConfiguration(config); assert.callCount(gladys.variable.destroy, 2); - assert.callCount(gladys.variable.setValue, 2); + assert.callCount(gladys.variable.setValue, 3); + assert.calledWith(gladys.variable.setValue, CONFIGURATION.MQTT_URL_KEY, config.mqttUrl, serviceId); assert.calledWith( gladys.variable.setValue, CONFIGURATION.MQTT_EMBEDDED_BROKER_KEY, config.useEmbeddedBroker, serviceId, ); + assert.calledWith( + gladys.variable.setValue, + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + serviceId, + ); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.exec); @@ -176,13 +183,20 @@ describe('mqttHandler.saveConfiguration', function Describe() { await mqttHandler.saveConfiguration(config); assert.callCount(gladys.variable.destroy, 2); - assert.callCount(gladys.variable.setValue, 2); + assert.callCount(gladys.variable.setValue, 3); + assert.calledWith(gladys.variable.setValue, CONFIGURATION.MQTT_URL_KEY, config.mqttUrl, serviceId); assert.calledWith( gladys.variable.setValue, CONFIGURATION.MQTT_EMBEDDED_BROKER_KEY, config.useEmbeddedBroker, serviceId, ); + assert.calledWith( + gladys.variable.setValue, + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + serviceId, + ); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.exec); @@ -217,13 +231,20 @@ describe('mqttHandler.saveConfiguration', function Describe() { await mqttHandler.saveConfiguration(config); assert.callCount(gladys.variable.destroy, 2); - assert.callCount(gladys.variable.setValue, 2); + assert.callCount(gladys.variable.setValue, 3); + assert.calledWith(gladys.variable.setValue, CONFIGURATION.MQTT_URL_KEY, config.mqttUrl, serviceId); assert.calledWith( gladys.variable.setValue, CONFIGURATION.MQTT_EMBEDDED_BROKER_KEY, config.useEmbeddedBroker, serviceId, ); + assert.calledWith( + gladys.variable.setValue, + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + serviceId, + ); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.exec); @@ -259,13 +280,21 @@ describe('mqttHandler.saveConfiguration', function Describe() { await mqttHandler.saveConfiguration(config); assert.callCount(gladys.variable.destroy, 1); - assert.callCount(gladys.variable.setValue, 3); + assert.callCount(gladys.variable.setValue, 4); + assert.calledWith(gladys.variable.setValue, CONFIGURATION.MQTT_URL_KEY, config.mqttUrl, serviceId); + assert.calledWith(gladys.variable.setValue, CONFIGURATION.MQTT_USERNAME_KEY, config.mqttUsername, serviceId); assert.calledWith( gladys.variable.setValue, CONFIGURATION.MQTT_EMBEDDED_BROKER_KEY, config.useEmbeddedBroker, serviceId, ); + assert.calledWith( + gladys.variable.setValue, + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + serviceId, + ); assert.calledOnce(gladys.system.getContainers); assert.calledOnce(gladys.system.exec); diff --git a/server/test/services/mqtt/lib/updateContainer.test.js b/server/test/services/mqtt/lib/updateContainer.test.js new file mode 100644 index 0000000000..0ff68c10f5 --- /dev/null +++ b/server/test/services/mqtt/lib/updateContainer.test.js @@ -0,0 +1,106 @@ +const sinon = require('sinon'); + +const { assert, fake } = sinon; + +const proxiquire = require('proxyquire').noCallThru(); + +const { MockedMqttClient } = require('../mocks.test'); +const { CONFIGURATION, DEFAULT } = require('../../../../services/mqtt/lib/constants'); + +const installContainerMock = { installContainer: fake.resolves({ id: 'id' }) }; +const MqttHandler = proxiquire('../../../../services/mqtt/lib', { + './installContainer': installContainerMock, +}); + +const gladys = { + variable: { + setValue: fake.resolves(null), + }, + system: { + getContainers: fake.resolves([]), + removeContainer: fake.resolves(null), + restartContainer: fake.resolves(null), + }, +}; +const serviceId = 'faea9c35-759a-44d5-bcc9-2af1de37b8b4'; + +describe('mqttHandler.updateContainer', function Describe() { + this.timeout(8000); + + let mqttHandler; + + beforeEach(() => { + mqttHandler = new MqttHandler(gladys, MockedMqttClient, serviceId); + }); + + afterEach(() => { + sinon.reset(); + }); + + it('should updateContainer: not Docker configured', async () => { + const config = { brokerContainerAvailable: false }; + + await mqttHandler.updateContainer(config); + + assert.notCalled(gladys.system.getContainers); + assert.notCalled(gladys.system.removeContainer); + assert.notCalled(gladys.system.restartContainer); + assert.notCalled(installContainerMock.installContainer); + assert.notCalled(gladys.variable.setValue); + }); + + it('should updateContainer: already up-to-date', async () => { + const config = { brokerContainerAvailable: true, mosquittoVersion: '2' }; + + await mqttHandler.updateContainer(config); + + assert.notCalled(gladys.system.getContainers); + assert.notCalled(gladys.system.removeContainer); + assert.notCalled(gladys.system.restartContainer); + assert.notCalled(installContainerMock.installContainer); + assert.notCalled(gladys.variable.setValue); + }); + + it('should updateContainer: no MQTT container found', async () => { + const config = { brokerContainerAvailable: true }; + + await mqttHandler.updateContainer(config); + + assert.calledOnce(gladys.system.getContainers); + assert.notCalled(gladys.system.removeContainer); + assert.calledOnce(gladys.system.restartContainer); + assert.calledOnce(installContainerMock.installContainer); + + assert.calledOnce(gladys.variable.setValue); + assert.calledWith( + gladys.variable.setValue, + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + serviceId, + ); + }); + + it('should updateContainer: MQTT container found', async () => { + const config = { brokerContainerAvailable: true }; + gladys.system.getContainers = fake.resolves([{ id: 'container' }]); + + await mqttHandler.updateContainer(config); + + assert.calledOnce(gladys.system.getContainers); + + assert.calledOnce(gladys.system.removeContainer); + assert.calledWith(gladys.system.removeContainer, 'container', { force: true }); + + assert.calledOnce(gladys.system.restartContainer); + + assert.calledOnce(installContainerMock.installContainer); + + assert.calledOnce(gladys.variable.setValue); + assert.calledWith( + gladys.variable.setValue, + CONFIGURATION.MQTT_MOSQUITTO_VERSION, + DEFAULT.MOSQUITTO_VERSION, + serviceId, + ); + }); +});