Skip to content

Commit

Permalink
feat: force use of libPod if nvidia device requested (#10251)
Browse files Browse the repository at this point in the history
Requesting an nvidia device through the container toolkit
is only supported through the libPod API not the compatibility
API. Recognize the request and for libPod in a similar manner
to what's done when the container is associated with a pod.

Signed-off-by: Michael Dawson <[email protected]>
  • Loading branch information
mhdawson authored Dec 11, 2024
1 parent fbba37b commit cc38f43
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 3 deletions.
98 changes: 96 additions & 2 deletions packages/main/src/plugin/container-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4098,7 +4098,7 @@ describe('createContainerLibPod', () => {
HostConfig: {
Devices: [
{
PathOnHost: 'nvidia.com/gpu=all',
PathOnHost: 'device1',
PathInContainer: '',
CgroupPermissions: '',
},
Expand Down Expand Up @@ -4150,7 +4150,7 @@ describe('createContainerLibPod', () => {
const expectedOptions: PodmanContainerCreateOptions = {
name: options.name,
command: options.Cmd,
devices: [{ path: 'nvidia.com/gpu=all' }],
devices: [{ path: 'device1' }],
entrypoint: [options.Entrypoint as string],
env: {
key: 'value',
Expand Down Expand Up @@ -4228,6 +4228,100 @@ describe('createContainerLibPod', () => {
await containerRegistry.createContainer('podman1', options);
expect(createPodmanContainerMock).toBeCalledWith(expectedOptions);
});

test('check that use of libPod is forced by request for nvidia device', async () => {
const dockerAPI = new Dockerode({ protocol: 'http', host: 'localhost' });

const libpod = new LibpodDockerode();
libpod.enhancePrototypeWithLibPod();
containerRegistry.addInternalProvider('podman1', {
name: 'podman',
id: 'podman1',
api: dockerAPI,
libpodApi: dockerAPI,
connection: {
type: 'podman',
},
} as unknown as InternalContainerProvider);

const createPodmanContainerMock = vi
.spyOn(dockerAPI as unknown as LibPod, 'createPodmanContainer')
.mockImplementation(_options =>
Promise.resolve({
Id: 'id',
Warnings: [],
}),
);
vi.spyOn(dockerAPI as unknown as Dockerode, 'getContainer').mockImplementation((_id: string) => {
return {
start: () => {},
} as unknown as Dockerode.Container;
});
// use minimum set as the full of options is validated in the previous test
const options: ContainerCreateOptions = {
Image: 'image',
name: 'name',
HostConfig: {
Devices: [
{
PathOnHost: 'nvidia.com/gpu=all',
PathInContainer: '',
CgroupPermissions: '',
},
],
NetworkMode: 'mode',
AutoRemove: true,
},
Cmd: ['cmd'],
Entrypoint: 'entrypoint',
User: 'user',
};
const expectedOptions: PodmanContainerCreateOptions = {
image: options.Image,
name: options.name,
devices: [{ path: 'nvidia.com/gpu=all' }],
netns: {
nsmode: 'mode',
},
command: options.Cmd,
entrypoint: [options.Entrypoint as string],
user: options.User,
cap_add: undefined,
cap_drop: undefined,
dns_server: undefined,
env: undefined,
healthconfig: undefined,
hostadd: undefined,
hostname: undefined,
labels: undefined,
mounts: undefined,
pod: undefined,
portmappings: undefined,
privileged: undefined,
read_only_filesystem: undefined,
remove: true,
restart_policy: undefined,
restart_tries: undefined,
seccomp_policy: undefined,
seccomp_profile_path: undefined,
selinux_opts: [],
stop_timeout: undefined,
userns: undefined,
work_dir: undefined,
};
vi.spyOn(containerRegistry, 'attachToContainer').mockImplementation(
(
_engine: InternalContainerProvider,
_container: Dockerode.Container,
_hasTty?: boolean,
_openStdin?: boolean,
) => {
return Promise.resolve();
},
);
await containerRegistry.createContainer('podman1', options);
expect(createPodmanContainerMock).toBeCalledWith(expectedOptions);
});
});

describe('getContainerCreateMountOptionFromBind', () => {
Expand Down
13 changes: 12 additions & 1 deletion packages/main/src/plugin/container-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,18 @@ export class ContainerProviderRegistry {
let telemetryOptions = {};
try {
let container: Dockerode.Container;
if (options.pod) {
let forceLibPod = false;

// the device option requesting an nvidia gpu on linux only works
// if the LibPod API is used. Check if such a device is requested
// and if so force the use of LibPod
for (const device of options.HostConfig?.Devices ?? []) {
if (device.PathOnHost === 'nvidia.com/gpu=all') {
forceLibPod = true;
break;
}
}
if (options.pod ?? forceLibPod) {
container = await this.createContainerLibPod(engineId, options);
} else {
container = await this.createContainerDockerode(engineId, options);
Expand Down

0 comments on commit cc38f43

Please sign in to comment.