From 4c3205c60090f5992931da3c8bd45a860f83d37e Mon Sep 17 00:00:00 2001 From: Pierre-Gilles Leymarie Date: Tue, 8 Mar 2022 11:16:41 +0000 Subject: [PATCH 1/3] Fix get gladys container id call for Debian > 10 & Ubuntu > 20.04 --- server/lib/system/index.js | 2 + .../lib/system/system.getGladysContainerId.js | 32 ++++++++ server/lib/system/system.getNetworkMode.js | 4 +- .../system.getGladysContainerId.test.js | 81 +++++++++++++++++++ .../lib/system/system.getNetworkMode.test.js | 8 +- 5 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 server/lib/system/system.getGladysContainerId.js create mode 100644 server/test/lib/system/system.getGladysContainerId.test.js diff --git a/server/lib/system/index.js b/server/lib/system/index.js index 6769de5c63..b7bbbaad79 100644 --- a/server/lib/system/index.js +++ b/server/lib/system/index.js @@ -8,6 +8,7 @@ const { installUpgrade } = require('./system.installUpgrade'); const { isDocker } = require('./system.isDocker'); const { getContainers } = require('./system.getContainers'); const { getContainerMounts } = require('./system.getContainerMounts'); +const { getGladysContainerId } = require('./system.getGladysContainerId'); const { getInfos } = require('./system.getInfos'); const { getDiskSpace } = require('./system.getDiskSpace'); const { saveLatestGladysVersion } = require('./system.saveLatestGladysVersion'); @@ -41,6 +42,7 @@ System.prototype.installUpgrade = installUpgrade; System.prototype.isDocker = isDocker; System.prototype.getContainers = getContainers; System.prototype.getContainerMounts = getContainerMounts; +System.prototype.getGladysContainerId = getGladysContainerId; System.prototype.getInfos = getInfos; System.prototype.getDiskSpace = getDiskSpace; System.prototype.saveLatestGladysVersion = saveLatestGladysVersion; diff --git a/server/lib/system/system.getGladysContainerId.js b/server/lib/system/system.getGladysContainerId.js new file mode 100644 index 0000000000..a43a915b72 --- /dev/null +++ b/server/lib/system/system.getGladysContainerId.js @@ -0,0 +1,32 @@ +const fs = require('fs'); +const { exec } = require('../../utils/childProcess'); +const { PlatformNotCompatible } = require('../../utils/coreErrors'); + +const CIDFILE_FILE_PATH_IN_CONTAINER = '/var/lib/gladysassistant/containerId'; + +/** + * @description Return the containerId of the currently running container. + * @returns {Promise} Resolve with list of mounts. + * @example + * const containerId = await getGladysContainerId(); + */ +async function getGladysContainerId() { + if (!this.dockerode) { + throw new PlatformNotCompatible('SYSTEM_NOT_RUNNING_DOCKER'); + } + try { + // We try if the cidfile exist in the container + await fs.promises.access(CIDFILE_FILE_PATH_IN_CONTAINER, fs.constants.F_OK); + // if yes, we read it + return fs.promises.readFile(CIDFILE_FILE_PATH_IN_CONTAINER, 'utf8'); + } catch (e) { + // if not, we get the containerId from the cgroup + return exec( + 'cat /proc/self/cgroup | grep -o -e "docker.*" | head -n 1 | sed -e "s/.scope//g;s/docker-//g;s!docker/!!g"', + ); + } +} + +module.exports = { + getGladysContainerId, +}; diff --git a/server/lib/system/system.getNetworkMode.js b/server/lib/system/system.getNetworkMode.js index ebe5328134..5b8be9e5d0 100644 --- a/server/lib/system/system.getNetworkMode.js +++ b/server/lib/system/system.getNetworkMode.js @@ -1,6 +1,5 @@ const get = require('get-value'); const { PlatformNotCompatible } = require('../../utils/coreErrors'); -const { exec } = require('../../utils/childProcess'); /** * @description Get Gladys network into Docker environment. @@ -14,8 +13,7 @@ async function getNetworkMode() { } if (!this.networkMode) { - const cmdResult = await exec('head -1 /proc/self/cgroup | cut -d/ -f3'); - const [containerId] = cmdResult.split('\n'); + const containerId = await this.getGladysContainerId(); const gladysContainer = this.dockerode.getContainer(containerId); const gladysContainerInspect = await gladysContainer.inspect(); this.networkMode = get(gladysContainerInspect, 'HostConfig.NetworkMode', { default: 'unknown' }); diff --git a/server/test/lib/system/system.getGladysContainerId.test.js b/server/test/lib/system/system.getGladysContainerId.test.js new file mode 100644 index 0000000000..2a63eb2171 --- /dev/null +++ b/server/test/lib/system/system.getGladysContainerId.test.js @@ -0,0 +1,81 @@ +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 sequelize = { + close: fake.resolves(null), +}; + +const event = { + on: fake.resolves(null), + emit: fake.resolves(null), +}; + +const config = { + tempFolder: '/tmp/gladys', +}; + +describe('system.getGladysContainerId', () => { + let system; + let FsMock; + + beforeEach(async () => { + FsMock = { + promises: { + access: fake.resolves(null), + readFile: fake.resolves('967ef3114fa2ceb8c4f6dbdbc78ee411a6f33fb1fe1d32455686ef6e89f41d1c'), + }, + constants: { + FS_OK: 1, + }, + }; + + const getGladysContainerId = proxyquire('../../../lib/system/system.getGladysContainerId', { + '../../utils/childProcess': { + exec: fake.resolves('499145208e86b9e2c1a5f11c135a45f207b399768be5ecb1f56b1b14d6b9c94a'), + }, + fs: FsMock, + }); + + const System = proxyquire('../../../lib/system', { + dockerode: DockerodeMock, + './system.getGladysContainerId': getGladysContainerId, + }); + + 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.getGladysContainerId(); + assert.fail('should have fail'); + } catch (e) { + expect(e).be.instanceOf(PlatformNotCompatible); + } + }); + + it('should return containerId through cidfile', async () => { + const containerId = await system.getGladysContainerId(); + expect(containerId).to.eq('967ef3114fa2ceb8c4f6dbdbc78ee411a6f33fb1fe1d32455686ef6e89f41d1c'); + }); + it('should return containerId through exec', async () => { + FsMock.promises.access = fake.rejects(); + const containerId2 = await system.getGladysContainerId(); + expect(containerId2).to.eq('499145208e86b9e2c1a5f11c135a45f207b399768be5ecb1f56b1b14d6b9c94a'); + }); +}); diff --git a/server/test/lib/system/system.getNetworkMode.test.js b/server/test/lib/system/system.getNetworkMode.test.js index a70d4395f4..3a4abefd09 100644 --- a/server/test/lib/system/system.getNetworkMode.test.js +++ b/server/test/lib/system/system.getNetworkMode.test.js @@ -8,13 +8,11 @@ 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, + './system.getGladysContainerId': { + getGladysContainerId: fake.resolves('967ef3114fa2ceb8c4f6dbdbc78ee411a6f33fb1fe1d32455686ef6e89f41d1c'), + }, }); const sequelize = { From f9e2bd4fad70aee56d1f9407540397e4796fdde0 Mon Sep 17 00:00:00 2001 From: Pierre-Gilles Leymarie Date: Tue, 8 Mar 2022 12:22:36 +0000 Subject: [PATCH 2/3] Handle all cases, in JS so it's testable --- .../lib/system/system.getGladysContainerId.js | 34 +++++++-- .../system.getGladysContainerId.test.js | 71 ++++++++++++++++++- 2 files changed, 98 insertions(+), 7 deletions(-) diff --git a/server/lib/system/system.getGladysContainerId.js b/server/lib/system/system.getGladysContainerId.js index a43a915b72..630a2921b7 100644 --- a/server/lib/system/system.getGladysContainerId.js +++ b/server/lib/system/system.getGladysContainerId.js @@ -1,5 +1,4 @@ const fs = require('fs'); -const { exec } = require('../../utils/childProcess'); const { PlatformNotCompatible } = require('../../utils/coreErrors'); const CIDFILE_FILE_PATH_IN_CONTAINER = '/var/lib/gladysassistant/containerId'; @@ -18,12 +17,37 @@ async function getGladysContainerId() { // We try if the cidfile exist in the container await fs.promises.access(CIDFILE_FILE_PATH_IN_CONTAINER, fs.constants.F_OK); // if yes, we read it - return fs.promises.readFile(CIDFILE_FILE_PATH_IN_CONTAINER, 'utf8'); + const containerId = await fs.promises.readFile(CIDFILE_FILE_PATH_IN_CONTAINER, 'utf-8'); + // we return the containerId trimed, just in case + return containerId.trim(); } catch (e) { // if not, we get the containerId from the cgroup - return exec( - 'cat /proc/self/cgroup | grep -o -e "docker.*" | head -n 1 | sed -e "s/.scope//g;s/docker-//g;s!docker/!!g"', - ); + const cgroup = await fs.promises.readFile('/proc/self/cgroup', 'utf-8'); + // String looks like this in cgroup v2 (Debian 11) + // 0::/system.slice/docker-2bb2c94b0c395fc8fdff9fa4ce364a3be0dd05792145ffc93ce8d665d06521f1.scope + // Or this in cgroup v1 (Debian 10) + // 12:cpuset:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + // First, we get the first line: + + let firstPart; + let containerId; + // If we are on cgroup v1 (debian 10) + if (cgroup.indexOf('/docker/') !== -1) { + const allLines = cgroup.split('\n'); + const lineWithDocker = allLines.find((line) => line.indexOf('/docker/') !== -1); + [, containerId] = lineWithDocker.split('/docker/'); + } else if (cgroup.indexOf('docker-') !== -1) { + const allLines = cgroup.split('\n'); + const lineWithDocker = allLines.find((line) => line.indexOf('docker-') !== -1); + [, firstPart] = lineWithDocker.split('docker-'); + // then, we remove .scope + [containerId] = firstPart.split('.scope'); + } else { + throw new PlatformNotCompatible('DOCKER_CGROUP_CONTAINER_ID_NOT_AVAILABLE'); + } + + // we return the containerId trimed, just in case + return containerId.trim(); } } diff --git a/server/test/lib/system/system.getGladysContainerId.test.js b/server/test/lib/system/system.getGladysContainerId.test.js index 2a63eb2171..effd04601d 100644 --- a/server/test/lib/system/system.getGladysContainerId.test.js +++ b/server/test/lib/system/system.getGladysContainerId.test.js @@ -24,6 +24,44 @@ const config = { describe('system.getGladysContainerId', () => { let system; let FsMock; + const procSelfCpuGroupDebia11 = + '0::/system.slice/docker-2bb2c94b0c395fc8fdff9fa4ce364a3be0dd05792145ffc93ce8d665d06521f1.scope'; + + const procSelfCpuGroupDebia11WithDataInSecondLine = ` + 3:rdma:/ + 0::/system.slice/docker-2bb2c94b0c395fc8fdff9fa4ce364a3be0dd05792145ffc93ce8d665d06521f1.scope + `; + + const procSelfCpuGroupDebian10 = `12:cpuset:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 11:cpu,cpuacct:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 10:freezer:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 9:devices:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 8:blkio:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 7:perf_event:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 6:net_cls,net_prio:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 5:hugetlb:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 4:pids:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 3:rdma:/ + 2:memory:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 1:name=systemd:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 0::/system.slice/containerd.service + `; + + const procSelfCpuGroupDebian10WithDataInSecondLine = ` + 3:rdma:/ + 12:cpuset:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 11:cpu,cpuacct:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 10:freezer:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 9:devices:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 8:blkio:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 7:perf_event:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 6:net_cls,net_prio:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 5:hugetlb:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 4:pids:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 2:memory:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 1:name=systemd:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 + 0::/system.slice/containerd.service + `; beforeEach(async () => { FsMock = { @@ -73,9 +111,38 @@ describe('system.getGladysContainerId', () => { const containerId = await system.getGladysContainerId(); expect(containerId).to.eq('967ef3114fa2ceb8c4f6dbdbc78ee411a6f33fb1fe1d32455686ef6e89f41d1c'); }); - it('should return containerId through exec', async () => { + it('should return containerId through exec in cgroup v2 (Debian 11)', async () => { + FsMock.promises.access = fake.rejects(); + FsMock.promises.readFile = fake.resolves(procSelfCpuGroupDebia11); + const containerId2 = await system.getGladysContainerId(); + expect(containerId2).to.eq('2bb2c94b0c395fc8fdff9fa4ce364a3be0dd05792145ffc93ce8d665d06521f1'); + }); + it('should return containerId through exec in cgroup v2 (Debian 11) with containerId not on first line', async () => { + FsMock.promises.access = fake.rejects(); + FsMock.promises.readFile = fake.resolves(procSelfCpuGroupDebia11WithDataInSecondLine); + const containerId2 = await system.getGladysContainerId(); + expect(containerId2).to.eq('2bb2c94b0c395fc8fdff9fa4ce364a3be0dd05792145ffc93ce8d665d06521f1'); + }); + it('should return containerId through exec in cgroup v1 (Debian 10)', async () => { + FsMock.promises.access = fake.rejects(); + FsMock.promises.readFile = fake.resolves(procSelfCpuGroupDebian10); + const containerId2 = await system.getGladysContainerId(); + expect(containerId2).to.eq('357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350'); + }); + it('should return containerId through exec in cgroup v1 (Debian 10) with containerId not on first line', async () => { FsMock.promises.access = fake.rejects(); + FsMock.promises.readFile = fake.resolves(procSelfCpuGroupDebian10WithDataInSecondLine); const containerId2 = await system.getGladysContainerId(); - expect(containerId2).to.eq('499145208e86b9e2c1a5f11c135a45f207b399768be5ecb1f56b1b14d6b9c94a'); + expect(containerId2).to.eq('357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350'); + }); + it('should return error, system not compatible', async () => { + FsMock.promises.access = fake.rejects(); + FsMock.promises.readFile = fake.resolves('3:rdma:/'); + try { + await system.getGladysContainerId(); + assert.fail('should have fail'); + } catch (e) { + expect(e).be.instanceOf(PlatformNotCompatible); + } }); }); From 13bedf07c3df922effb276543337d456bec24afc Mon Sep 17 00:00:00 2001 From: Pierre-Gilles Leymarie Date: Tue, 8 Mar 2022 12:26:19 +0000 Subject: [PATCH 3/3] Improve comments --- server/lib/system/system.getGladysContainerId.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/lib/system/system.getGladysContainerId.js b/server/lib/system/system.getGladysContainerId.js index 630a2921b7..5ffbdf0ef5 100644 --- a/server/lib/system/system.getGladysContainerId.js +++ b/server/lib/system/system.getGladysContainerId.js @@ -27,8 +27,6 @@ async function getGladysContainerId() { // 0::/system.slice/docker-2bb2c94b0c395fc8fdff9fa4ce364a3be0dd05792145ffc93ce8d665d06521f1.scope // Or this in cgroup v1 (Debian 10) // 12:cpuset:/docker/357e73ad015211a5acd76a8973b9287d4de75922e9802d94ba46b756f2bb5350 - // First, we get the first line: - let firstPart; let containerId; // If we are on cgroup v1 (debian 10) @@ -37,6 +35,7 @@ async function getGladysContainerId() { const lineWithDocker = allLines.find((line) => line.indexOf('/docker/') !== -1); [, containerId] = lineWithDocker.split('/docker/'); } else if (cgroup.indexOf('docker-') !== -1) { + // if we are on cgroupv2 (debian 11) const allLines = cgroup.split('\n'); const lineWithDocker = allLines.find((line) => line.indexOf('docker-') !== -1); [, firstPart] = lineWithDocker.split('docker-');