Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix get gladys container id call for Debian > 10 & Ubuntu > 20.04 #1459

Merged
merged 3 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions server/lib/system/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand Down
55 changes: 55 additions & 0 deletions server/lib/system/system.getGladysContainerId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const fs = require('fs');
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
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
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
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) {
// 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-');
// 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();
}
}

module.exports = {
getGladysContainerId,
};
4 changes: 1 addition & 3 deletions server/lib/system/system.getNetworkMode.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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' });
Expand Down
148 changes: 148 additions & 0 deletions server/test/lib/system/system.getGladysContainerId.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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;
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 = {
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 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('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);
}
});
});
8 changes: 3 additions & 5 deletions server/test/lib/system/system.getNetworkMode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down