Skip to content

Commit

Permalink
Make Gladys work with docker-compose: get container id from mountinfo…
Browse files Browse the repository at this point in the history
… when cgroup is empty (#1648)
  • Loading branch information
magarcia authored Nov 15, 2022
1 parent 03a4eb8 commit ff8a0fc
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 20 deletions.
95 changes: 82 additions & 13 deletions server/lib/system/system.getGladysContainerId.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ 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.
* @description Returns the containerId defined on cidfile.
* @returns {Promise} Resolve with container id or undefined.
* @example
* const containerId = await getGladysContainerId();
* const containerId = await getContainerIdFromCidFile();
*/
async function getGladysContainerId() {
if (!this.dockerode) {
throw new PlatformNotCompatible('SYSTEM_NOT_RUNNING_DOCKER');
}
async function getContainerIdFromCidFile() {
try {
// We try if the cidfile exist in the container
await fs.promises.access(CIDFILE_FILE_PATH_IN_CONTAINER, fs.constants.F_OK);
Expand All @@ -21,8 +18,24 @@ async function getGladysContainerId() {
// 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');
// container id not found return undefined
return undefined;
}
}

/**
* @description Returns the containerId found on /proc/self/cgroup.
* @returns {Promise} Resolve with container id or undefined.
* @example
* const containerId = await getContainerIdFromCgroup();
*/
async function getContainerIdFromCgroup() {
const cgroupFile = '/proc/self/cgroup';
try {
// We try if the cgroup file exist in the container
await fs.promises.access(cgroupFile, fs.constants.F_OK);
// if yes, we read it
const cgroup = await fs.promises.readFile(cgroupFile, 'utf-8');
// String looks like this in cgroup v2 (Debian 11)
// 0::/system.slice/docker-2bb2c94b0c395fc8fdff9fa4ce364a3be0dd05792145ffc93ce8d665d06521f1.scope
// Or this in cgroup v1 (Debian 10)
Expand All @@ -41,15 +54,71 @@ async function getGladysContainerId() {
[, firstPart] = lineWithDocker.split('docker-');
// then, we remove .scope
[containerId] = firstPart.split('.scope');
} else {
throw new PlatformNotCompatible('DOCKER_CGROUP_CONTAINER_ID_NOT_AVAILABLE');
}
return containerId;
} catch (e) {
// container id not found return undefined
return undefined;
}
}

// we return the containerId trimed, just in case
return containerId.trim();
/**
* @description Returns the containerId found on /proc/self/mountinfo.
* @returns {Promise} Resolve with container id or undefined.
* @example
* const containerId = await getContainerIdFromMountInfo();
*/
async function getContainerIdFromMountInfo() {
const mountInfoFile = '/proc/self/mountinfo';
try {
// We try if the mountinfo file exist in the container
await fs.promises.access(mountInfoFile, fs.constants.F_OK);
// container id not found return undefined
const mountinfo = await fs.promises.readFile(mountInfoFile, 'utf-8');
let containerId;
if (mountinfo.indexOf('/docker/containers/') !== -1) {
const allLines = mountinfo.split('\n');
const lineWithDocker = allLines.find((line) => line.indexOf('/docker/containers/') !== -1);
[, containerId] = /\/docker\/containers\/(\w+)/gm.exec(lineWithDocker);
}
return containerId;
} catch (e) {
// container id not found return undefined
return undefined;
}
}

/**
* @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');
}

let containerId = await getContainerIdFromCidFile();

if (containerId === undefined) {
// Not found in cidfile try on cgroup
containerId = await getContainerIdFromCgroup();
}

if (containerId === undefined) {
// Not found in cgroup try on mountinfo
containerId = await getContainerIdFromMountInfo();
}

if (containerId === undefined) {
throw new PlatformNotCompatible('DOCKER_CONTAINER_ID_NOT_AVAILABLE');
}

// we return the containerId trimed, just in case
return containerId.trim();
}

module.exports = {
getGladysContainerId,
};
83 changes: 76 additions & 7 deletions server/test/lib/system/system.getGladysContainerId.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,28 @@ describe('system.getGladysContainerId', () => {
0::/system.slice/containerd.service
`;

const procSelfMountInfo = `
888 708 0:47 / / rw,relatime master:264 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/6T3ZSFC7JNUBDFNLCKRD5QPPXY:/var/lib/docker/overlay2/l/5AJTXS5HN3KIZ5YR6BA2ZFRE3L:/var/lib/docker/overlay2/l/GVCKZC2NKRDM2WOSBBBQ46WIFN:/var/lib/docker/overlay2/l/KLX2J6EGBYQ34BL3JEGLOHQ7RU:/var/lib/docker/overlay2/l/PTGKFMXP6YB7S4QBYJ5O3ETJS5:/var/lib/docker/overlay2/l/7J46PQ3IIPKFHQNQZMMC6Z7BA5:/var/lib/docker/overlay2/l/CS73HPLIUVRXW6KIBPPH35QABK:/var/lib/docker/overlay2/l/3QXP33I66FIO6ZCZBZSRVJNWDL:/var/lib/docker/overlay2/l/TMM2SSXHFGB5IYQDOGIHMSNHEL:/var/lib/docker/overlay2/l/F4236R2CV5MLREPSLPVLAOPUEA:/var/lib/docker/overlay2/l/WOJUCHWNDFEPC62VG7MS2LXJO3:/var/lib/docker/overlay2/l/VOIOH6NPQNUY5HGM77CMD5OT4D:/var/lib/docker/overlay2/l/LLH5HDLCV4QB4LPJDLYOOSNMYY:/var/lib/docker/overlay2/l/PVZJ2K4NDE6FMENS7OWSWQSFKM,upperdir=/var/lib/docker/overlay2/ce769bb9a8388f80ef67a31944f39ce12c06e38c0fc952920996d0c455bca177/diff,workdir=/var/lib/docker/overlay2/ce769bb9a8388f80ef67a31944f39ce12c06e38c0fc952920996d0c455bca177/work
889 888 0:66 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
890 888 0:18 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
891 890 0:23 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate,memory_recursiveprot
892 888 0:5 / /dev rw,relatime - devtmpfs devtmpfs rw,size=1776952k,nr_inodes=444238,mode=755
893 892 0:19 / /dev/shm rw,nosuid,nodev - tmpfs tmpfs rw
894 892 0:20 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
895 892 0:15 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
830 888 0:21 /udev /run/udev ro - tmpfs tmpfs rw,size=777088k,nr_inodes=819200,mode=755
918 888 179:2 /var/lib/docker/containers/83fa542c0b140e45e63ad7263c539ac08e2cbf7916894f1c51c3f016397b168e/resolv.conf /etc/resolv.conf rw,noatime - ext4 /dev/root rw
1075 888 179:2 /var/lib/docker/containers/83fa542c0b140e45e63ad7263c539ac08e2cbf7916894f1c51c3f016397b168e/hostname /etc/hostname rw,noatime - ext4 /dev/root rw
1077 888 179:2 /var/lib/docker/containers/83fa542c0b140e45e63ad7263c539ac08e2cbf7916894f1c51c3f016397b168e/hosts /etc/hosts rw,noatime - ext4 /dev/root rw
1099 888 179:2 /home/pi/data/gladys /var/lib/gladysassistant rw,noatime - ext4 /dev/root rw
632 888 0:21 /docker.sock /run/docker.sock rw,nosuid,nodev - tmpfs tmpfs rw,size=777088k,nr_inodes=819200,mode=755
`;

beforeEach(async () => {
FsMock = {
promises: {
access: fake.resolves(null),
readFile: fake.resolves('967ef3114fa2ceb8c4f6dbdbc78ee411a6f33fb1fe1d32455686ef6e89f41d1c'),
access: sinon.stub(),
readFile: sinon.stub(),
},
constants: {
FS_OK: 1,
Expand Down Expand Up @@ -111,35 +128,87 @@ describe('system.getGladysContainerId', () => {
});

it('should return containerId through cidfile', async () => {
FsMock.promises.access.withArgs('/var/lib/gladysassistant/containerId').resolves(null);
FsMock.promises.readFile.resolves('967ef3114fa2ceb8c4f6dbdbc78ee411a6f33fb1fe1d32455686ef6e89f41d1c');
const containerId = await system.getGladysContainerId();
expect(containerId).to.eq('967ef3114fa2ceb8c4f6dbdbc78ee411a6f33fb1fe1d32455686ef6e89f41d1c');
});
it('should return containerId through exec in mountinfo (Debian 11)', async () => {
FsMock.promises.access
.withArgs('/var/lib/gladysassistant/containerId')
.rejects()
.withArgs('/proc/self/cgroup')
.resolves(null)
.withArgs('/proc/self/mountinfo')
.resolves(null);
FsMock.promises.readFile
.withArgs('/proc/self/cgroup', 'utf-8')
.resolves('0::/')
.withArgs('/proc/self/mountinfo', 'utf-8')
.resolves(procSelfMountInfo);
const containerId2 = await system.getGladysContainerId();
expect(containerId2).to.eq('83fa542c0b140e45e63ad7263c539ac08e2cbf7916894f1c51c3f016397b168e');
});
it('should return containerId through exec in mountinfo (Debian 11) when cgroup does not exist', async () => {
FsMock.promises.access
.withArgs('/var/lib/gladysassistant/containerId')
.rejects()
.withArgs('/proc/self/cgroup')
.rejects()
.withArgs('/proc/self/mountinfo')
.resolves(null);
FsMock.promises.readFile.withArgs('/proc/self/mountinfo', 'utf-8').resolves(procSelfMountInfo);
const containerId2 = await system.getGladysContainerId();
expect(containerId2).to.eq('83fa542c0b140e45e63ad7263c539ac08e2cbf7916894f1c51c3f016397b168e');
});
it('should return containerId through exec in cgroup v2 (Debian 11)', async () => {
FsMock.promises.access = fake.rejects();
FsMock.promises.access
.withArgs('/var/lib/gladysassistant/containerId')
.rejects()
.withArgs('/proc/self/cgroup')
.resolves(null);
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.access
.withArgs('/var/lib/gladysassistant/containerId')
.rejects()
.withArgs('/proc/self/cgroup')
.resolves(null);
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.access
.withArgs('/var/lib/gladysassistant/containerId')
.rejects()
.withArgs('/proc/self/cgroup')
.resolves(null);
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.access
.withArgs('/var/lib/gladysassistant/containerId')
.rejects()
.withArgs('/proc/self/cgroup')
.resolves(null);
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.access
.withArgs('/var/lib/gladysassistant/containerId')
.rejects()
.withArgs('/proc/self/mountinfo')
.rejects()
.withArgs('/proc/self/cgroup')
.resolves(null);
FsMock.promises.readFile = fake.resolves('3:rdma:/');
try {
await system.getGladysContainerId();
Expand Down

0 comments on commit ff8a0fc

Please sign in to comment.