mirror of
https://github.com/NVIDIA/nvidia-container-toolkit
synced 2025-06-26 18:18:24 +00:00
Compare commits
27 Commits
pull-reque
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8551c71a8a | ||
|
|
7ee81e91de | ||
|
|
5a6860adbf | ||
|
|
ce2b0dfdf0 | ||
|
|
6f0129bdf2 | ||
|
|
ffd98424d8 | ||
|
|
cf192169a8 | ||
|
|
eeabd2b94d | ||
|
|
d81329da1f | ||
|
|
3b84f527cc | ||
|
|
8da6e42d88 | ||
|
|
1a755d471a | ||
|
|
c05f9dffc5 | ||
|
|
7c5b913e09 | ||
|
|
c0d2d41f23 | ||
|
|
08cf87eb21 | ||
|
|
d3a9f512c4 | ||
|
|
73baa74ea8 | ||
|
|
39aa24690c | ||
|
|
dc6b7c703b | ||
|
|
b044b56c6e | ||
|
|
46cdf8c41a | ||
|
|
22ee5eb64f | ||
|
|
a7f3398a68 | ||
|
|
72fe259745 | ||
|
|
3b27d91026 | ||
|
|
d3997eceb2 |
168
.common-ci.yml
168
.common-ci.yml
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2021-2022, NVIDIA CORPORATION. All rights reserved.
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,17 +12,16 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
default:
|
default:
|
||||||
image: docker
|
image: docker:stable
|
||||||
services:
|
services:
|
||||||
- name: docker:dind
|
- name: docker:stable-dind
|
||||||
command: ["--experimental"]
|
command: ["--experimental"]
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
BUILD_MULTI_ARCH_IMAGES: "true"
|
BUILDIMAGE: "${CI_REGISTRY_IMAGE}/build:${CI_COMMIT_SHORT_SHA}"
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- trigger
|
|
||||||
- image
|
- image
|
||||||
- lint
|
- lint
|
||||||
- go-checks
|
- go-checks
|
||||||
@@ -33,71 +32,46 @@ stages:
|
|||||||
- test
|
- test
|
||||||
- scan
|
- scan
|
||||||
- release
|
- release
|
||||||
- sign
|
- build-all
|
||||||
|
|
||||||
.pipeline-trigger-rules:
|
|
||||||
rules:
|
|
||||||
# We trigger the pipeline if started manually
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "web"
|
|
||||||
# We trigger the pipeline on the main branch
|
|
||||||
- if: $CI_COMMIT_BRANCH == "main"
|
|
||||||
# We trigger the pipeline on the release- branches
|
|
||||||
- if: $CI_COMMIT_BRANCH =~ /^release-.*$/
|
|
||||||
# We trigger the pipeline on tags
|
|
||||||
- if: $CI_COMMIT_TAG && $CI_COMMIT_TAG != ""
|
|
||||||
|
|
||||||
workflow:
|
|
||||||
rules:
|
|
||||||
# We trigger the pipeline on a merge request
|
|
||||||
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
|
||||||
# We then add all the regular triggers
|
|
||||||
- !reference [.pipeline-trigger-rules, rules]
|
|
||||||
|
|
||||||
# The main or manual job is used to filter out distributions or architectures that are not required on
|
|
||||||
# every build.
|
|
||||||
.main-or-manual:
|
|
||||||
rules:
|
|
||||||
- !reference [.pipeline-trigger-rules, rules]
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
|
||||||
when: manual
|
|
||||||
|
|
||||||
# The trigger-pipeline job adds a manualy triggered job to the pipeline on merge requests.
|
|
||||||
trigger-pipeline:
|
|
||||||
stage: trigger
|
|
||||||
script:
|
|
||||||
- echo "starting pipeline"
|
|
||||||
rules:
|
|
||||||
- !reference [.main-or-manual, rules]
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
when: manual
|
|
||||||
allow_failure: false
|
|
||||||
- when: always
|
|
||||||
|
|
||||||
# Define the distribution targets
|
# Define the distribution targets
|
||||||
|
.dist-amazonlinux2:
|
||||||
|
variables:
|
||||||
|
DIST: amazonlinux2
|
||||||
|
|
||||||
.dist-centos7:
|
.dist-centos7:
|
||||||
rules:
|
|
||||||
- !reference [.main-or-manual, rules]
|
|
||||||
variables:
|
variables:
|
||||||
DIST: centos7
|
DIST: centos7
|
||||||
|
CVE_UPDATES: "nss"
|
||||||
|
|
||||||
.dist-centos8:
|
.dist-centos8:
|
||||||
variables:
|
variables:
|
||||||
DIST: centos8
|
DIST: centos8
|
||||||
|
|
||||||
|
.dist-debian10:
|
||||||
|
variables:
|
||||||
|
DIST: debian10
|
||||||
|
|
||||||
|
.dist-debian9:
|
||||||
|
variables:
|
||||||
|
DIST: debian9
|
||||||
|
|
||||||
|
.dist-opensuse-leap15.1:
|
||||||
|
variables:
|
||||||
|
DIST: opensuse-leap15.1
|
||||||
|
|
||||||
.dist-ubi8:
|
.dist-ubi8:
|
||||||
rules:
|
|
||||||
- !reference [.main-or-manual, rules]
|
|
||||||
variables:
|
variables:
|
||||||
DIST: ubi8
|
DIST: ubi8
|
||||||
|
|
||||||
|
.dist-ubuntu16.04:
|
||||||
|
variables:
|
||||||
|
DIST: ubuntu16.04
|
||||||
|
|
||||||
.dist-ubuntu18.04:
|
.dist-ubuntu18.04:
|
||||||
variables:
|
variables:
|
||||||
DIST: ubuntu18.04
|
DIST: ubuntu18.04
|
||||||
|
|
||||||
.dist-ubuntu20.04:
|
|
||||||
variables:
|
|
||||||
DIST: ubuntu20.04
|
|
||||||
|
|
||||||
.dist-packaging:
|
.dist-packaging:
|
||||||
variables:
|
variables:
|
||||||
DIST: packaging
|
DIST: packaging
|
||||||
@@ -116,8 +90,6 @@ trigger-pipeline:
|
|||||||
ARCH: arm64
|
ARCH: arm64
|
||||||
|
|
||||||
.arch-ppc64le:
|
.arch-ppc64le:
|
||||||
rules:
|
|
||||||
- !reference [.main-or-manual, rules]
|
|
||||||
variables:
|
variables:
|
||||||
ARCH: ppc64le
|
ARCH: ppc64le
|
||||||
|
|
||||||
@@ -125,15 +97,6 @@ trigger-pipeline:
|
|||||||
variables:
|
variables:
|
||||||
ARCH: x86_64
|
ARCH: x86_64
|
||||||
|
|
||||||
# Define the platform targets
|
|
||||||
.platform-amd64:
|
|
||||||
variables:
|
|
||||||
PLATFORM: linux/amd64
|
|
||||||
|
|
||||||
.platform-arm64:
|
|
||||||
variables:
|
|
||||||
PLATFORM: linux/arm64
|
|
||||||
|
|
||||||
# Define test helpers
|
# Define test helpers
|
||||||
.integration:
|
.integration:
|
||||||
stage: test
|
stage: test
|
||||||
@@ -145,7 +108,7 @@ trigger-pipeline:
|
|||||||
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
||||||
- docker pull "${IMAGE_NAME}:${VERSION}-${DIST}"
|
- docker pull "${IMAGE_NAME}:${VERSION}-${DIST}"
|
||||||
script:
|
script:
|
||||||
- make -f deployments/container/Makefile test-${DIST}
|
- make -f build/container/Makefile test-${DIST}
|
||||||
|
|
||||||
# Define the test targets
|
# Define the test targets
|
||||||
test-packaging:
|
test-packaging:
|
||||||
@@ -155,44 +118,37 @@ test-packaging:
|
|||||||
needs:
|
needs:
|
||||||
- image-packaging
|
- image-packaging
|
||||||
|
|
||||||
# Download the regctl binary for use in the release steps
|
|
||||||
.regctl-setup:
|
|
||||||
before_script:
|
|
||||||
- export REGCTL_VERSION=v0.4.5
|
|
||||||
- apk add --no-cache curl
|
|
||||||
- mkdir -p bin
|
|
||||||
- curl -sSLo bin/regctl https://github.com/regclient/regclient/releases/download/${REGCTL_VERSION}/regctl-linux-amd64
|
|
||||||
- chmod a+x bin/regctl
|
|
||||||
- export PATH=$(pwd)/bin:${PATH}
|
|
||||||
|
|
||||||
# .release forms the base of the deployment jobs which push images to the CI registry.
|
# .release forms the base of the deployment jobs which push images to the CI registry.
|
||||||
# This is extended with the version to be deployed (e.g. the SHA or TAG) and the
|
# This is extended with the version to be deployed (e.g. the SHA or TAG) and the
|
||||||
# target os.
|
# target os.
|
||||||
.release:
|
.release:
|
||||||
stage: release
|
stage:
|
||||||
|
release
|
||||||
variables:
|
variables:
|
||||||
# Define the source image for the release
|
# Define the source image for the release
|
||||||
IMAGE_NAME: "${CI_REGISTRY_IMAGE}/container-toolkit"
|
IMAGE_NAME: "${CI_REGISTRY_IMAGE}/container-toolkit"
|
||||||
VERSION: "${CI_COMMIT_SHORT_SHA}"
|
VERSION: "${CI_COMMIT_SHORT_SHA}"
|
||||||
# OUT_IMAGE_VERSION is overridden for external releases
|
# OUT_IMAGE_VERSION is overridden for external releases
|
||||||
OUT_IMAGE_VERSION: "${CI_COMMIT_SHORT_SHA}"
|
OUT_IMAGE_VERSION: "${CI_COMMIT_SHORT_SHA}"
|
||||||
|
stage: release
|
||||||
before_script:
|
before_script:
|
||||||
- !reference [.regctl-setup, before_script]
|
# We ensure that the OUT_IMAGE_VERSION is set
|
||||||
# We ensure that the components of the output image are set:
|
|
||||||
- 'echo Image Name: ${OUT_IMAGE_NAME} ; [[ -n "${OUT_IMAGE_NAME}" ]] || exit 1'
|
|
||||||
- 'echo Version: ${OUT_IMAGE_VERSION} ; [[ -n "${OUT_IMAGE_VERSION}" ]] || exit 1'
|
- 'echo Version: ${OUT_IMAGE_VERSION} ; [[ -n "${OUT_IMAGE_VERSION}" ]] || exit 1'
|
||||||
|
|
||||||
|
# In the case where we are deploying a different version to the CI_COMMIT_SHA, we
|
||||||
|
# need to tag the image.
|
||||||
|
# Note: a leading 'v' is stripped from the version if present
|
||||||
- apk add --no-cache make bash
|
- apk add --no-cache make bash
|
||||||
script:
|
|
||||||
# Log in to the "output" registry, tag the image and push the image
|
|
||||||
- 'echo "Logging in to CI registry ${CI_REGISTRY}"'
|
- 'echo "Logging in to CI registry ${CI_REGISTRY}"'
|
||||||
- regctl registry login "${CI_REGISTRY}" -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}"
|
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
||||||
- '[ ${CI_REGISTRY} = ${OUT_REGISTRY} ] || echo "Logging in to output registry ${OUT_REGISTRY}"'
|
- docker pull "${IMAGE_NAME}:${VERSION}-${DIST}"
|
||||||
- '[ ${CI_REGISTRY} = ${OUT_REGISTRY} ] || regctl registry login "${OUT_REGISTRY}" -u "${OUT_REGISTRY_USER}" -p "${OUT_REGISTRY_TOKEN}"'
|
script:
|
||||||
|
- docker tag "${IMAGE_NAME}:${VERSION}-${DIST}" "${OUT_IMAGE_NAME}:${OUT_IMAGE_VERSION}-${DIST}"
|
||||||
# Since OUT_IMAGE_NAME and OUT_IMAGE_VERSION are set, this will push the CI image to the
|
# Log in to the "output" registry, tag the image and push the image
|
||||||
# Target
|
- 'echo "Logging in to output registry ${OUT_REGISTRY}"'
|
||||||
- make -f deployments/container/Makefile push-${DIST}
|
- docker logout
|
||||||
|
- docker login -u "${OUT_REGISTRY_USER}" -p "${OUT_REGISTRY_TOKEN}" "${OUT_REGISTRY}"
|
||||||
|
- make IMAGE_NAME=${OUT_IMAGE_NAME} VERSION=${OUT_IMAGE_VERSION} -f build/container/Makefile push-${DIST}
|
||||||
|
|
||||||
# Define a staging release step that pushes an image to an internal "staging" repository
|
# Define a staging release step that pushes an image to an internal "staging" repository
|
||||||
# This is triggered for all pipelines (i.e. not only tags) to test the pipeline steps
|
# This is triggered for all pipelines (i.e. not only tags) to test the pipeline steps
|
||||||
@@ -201,18 +157,16 @@ test-packaging:
|
|||||||
extends:
|
extends:
|
||||||
- .release
|
- .release
|
||||||
variables:
|
variables:
|
||||||
OUT_REGISTRY_USER: "${NGC_REGISTRY_USER}"
|
OUT_REGISTRY_USER: "${CI_REGISTRY_USER}"
|
||||||
OUT_REGISTRY_TOKEN: "${NGC_REGISTRY_TOKEN}"
|
OUT_REGISTRY_TOKEN: "${CI_REGISTRY_PASSWORD}"
|
||||||
OUT_REGISTRY: "${NGC_REGISTRY}"
|
OUT_REGISTRY: "${CI_REGISTRY}"
|
||||||
OUT_IMAGE_NAME: "${NGC_REGISTRY_STAGING_IMAGE_NAME}"
|
OUT_IMAGE_NAME: "${CI_REGISTRY_IMAGE}/staging/container-toolkit"
|
||||||
|
|
||||||
# Define an external release step that pushes an image to an external repository.
|
# Define an external release step that pushes an image to an external repository.
|
||||||
# This includes a devlopment image off main.
|
# This includes a devlopment image off master.
|
||||||
.release:external:
|
.release:external:
|
||||||
extends:
|
extends:
|
||||||
- .release
|
- .release
|
||||||
variables:
|
|
||||||
FORCE_PUBLISH_IMAGES: "yes"
|
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
variables:
|
variables:
|
||||||
@@ -222,6 +176,20 @@ test-packaging:
|
|||||||
OUT_IMAGE_VERSION: "${DEVEL_RELEASE_IMAGE_VERSION}"
|
OUT_IMAGE_VERSION: "${DEVEL_RELEASE_IMAGE_VERSION}"
|
||||||
|
|
||||||
# Define the release jobs
|
# Define the release jobs
|
||||||
|
release:staging-centos7:
|
||||||
|
extends:
|
||||||
|
- .release:staging
|
||||||
|
- .dist-centos7
|
||||||
|
needs:
|
||||||
|
- image-centos7
|
||||||
|
|
||||||
|
release:staging-centos8:
|
||||||
|
extends:
|
||||||
|
- .release:staging
|
||||||
|
- .dist-centos8
|
||||||
|
needs:
|
||||||
|
- image-centos8
|
||||||
|
|
||||||
release:staging-ubi8:
|
release:staging-ubi8:
|
||||||
extends:
|
extends:
|
||||||
- .release:staging
|
- .release:staging
|
||||||
@@ -229,15 +197,15 @@ release:staging-ubi8:
|
|||||||
needs:
|
needs:
|
||||||
- image-ubi8
|
- image-ubi8
|
||||||
|
|
||||||
release:staging-ubuntu20.04:
|
release:staging-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .release:staging
|
- .release:staging
|
||||||
- .dist-ubuntu20.04
|
- .dist-ubuntu18.04
|
||||||
needs:
|
needs:
|
||||||
- test-toolkit-ubuntu20.04
|
- test-toolkit-ubuntu18.04
|
||||||
- test-containerd-ubuntu20.04
|
- test-containerd-ubuntu18.04
|
||||||
- test-crio-ubuntu20.04
|
- test-crio-ubuntu18.04
|
||||||
- test-docker-ubuntu20.04
|
- test-docker-ubuntu18.04
|
||||||
|
|
||||||
release:staging-packaging:
|
release:staging-packaging:
|
||||||
extends:
|
extends:
|
||||||
|
|||||||
3
.github/copy-pr-bot.yaml
vendored
3
.github/copy-pr-bot.yaml
vendored
@@ -1,3 +0,0 @@
|
|||||||
# https://docs.gha-runners.nvidia.com/apps/copy-pr-bot/#configuration
|
|
||||||
|
|
||||||
enabled: true
|
|
||||||
120
.github/dependabot.yml
vendored
120
.github/dependabot.yml
vendored
@@ -1,120 +0,0 @@
|
|||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
# main branch
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
target-branch: main
|
|
||||||
directories:
|
|
||||||
- "/"
|
|
||||||
- "deployments/devel"
|
|
||||||
- "tests"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
groups:
|
|
||||||
k8sio:
|
|
||||||
patterns:
|
|
||||||
- k8s.io/*
|
|
||||||
exclude-patterns:
|
|
||||||
- k8s.io/klog/*
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
target-branch: main
|
|
||||||
directories:
|
|
||||||
# CUDA image
|
|
||||||
- "/deployments/container"
|
|
||||||
# Golang version
|
|
||||||
- "/deployments/devel"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
target-branch: main
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
|
|
||||||
# Allow dependabot to update the libnvidia-container submodule.
|
|
||||||
- package-ecosystem: "gitsubmodule"
|
|
||||||
target-branch: main
|
|
||||||
directory: "/"
|
|
||||||
allow:
|
|
||||||
- dependency-name: "third_party/libnvidia-container"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
- libnvidia-container
|
|
||||||
|
|
||||||
# The release branch(es):
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
target-branch: release-1.17
|
|
||||||
directories:
|
|
||||||
- "/"
|
|
||||||
# We don't update development or test dependencies on release branches
|
|
||||||
# - "deployments/devel"
|
|
||||||
# - "tests"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
day: "sunday"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
- maintenance
|
|
||||||
ignore:
|
|
||||||
# For release branches we only consider patch updates.
|
|
||||||
- dependency-name: "*"
|
|
||||||
update-types:
|
|
||||||
- version-update:semver-major
|
|
||||||
- version-update:semver-minor
|
|
||||||
groups:
|
|
||||||
k8sio:
|
|
||||||
patterns:
|
|
||||||
- k8s.io/*
|
|
||||||
exclude-patterns:
|
|
||||||
- k8s.io/klog/*
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
target-branch: release-1.17
|
|
||||||
directories:
|
|
||||||
# CUDA image
|
|
||||||
- "/deployments/container"
|
|
||||||
# Golang version
|
|
||||||
- "/deployments/devel"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
day: "sunday"
|
|
||||||
ignore:
|
|
||||||
# For release branches we only apply patch updates to the golang version.
|
|
||||||
- dependency-name: "*golang*"
|
|
||||||
update-types:
|
|
||||||
- version-update:semver-major
|
|
||||||
- version-update:semver-minor
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
- maintenance
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
target-branch: release-1.17
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
day: "sunday"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
- maintenance
|
|
||||||
|
|
||||||
# Github actions need to be gh-pages branches.
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
target-branch: gh-pages
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
53
.github/workflows/ci.yaml
vendored
53
.github/workflows/ci.yaml
vendored
@@ -1,53 +0,0 @@
|
|||||||
# Copyright 2025 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: CI Pipeline
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "pull-request/[0-9]+"
|
|
||||||
- main
|
|
||||||
- release-*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
code-scanning:
|
|
||||||
uses: ./.github/workflows/code_scanning.yaml
|
|
||||||
|
|
||||||
variables:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.version.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Generate Commit Short SHA
|
|
||||||
id: version
|
|
||||||
run: echo "version=$(echo $GITHUB_SHA | cut -c1-8)" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
golang:
|
|
||||||
uses: ./.github/workflows/golang.yaml
|
|
||||||
|
|
||||||
image:
|
|
||||||
uses: ./.github/workflows/image.yaml
|
|
||||||
needs: [variables, golang, code-scanning]
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
version: ${{ needs.variables.outputs.version }}
|
|
||||||
build_multi_arch_images: ${{ github.ref_name == 'main' || startsWith(github.ref_name, 'release-') }}
|
|
||||||
|
|
||||||
e2e-test:
|
|
||||||
needs: [image, variables]
|
|
||||||
secrets: inherit
|
|
||||||
uses: ./.github/workflows/e2e.yaml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.variables.outputs.version }}
|
|
||||||
49
.github/workflows/code_scanning.yaml
vendored
49
.github/workflows/code_scanning.yaml
vendored
@@ -1,49 +0,0 @@
|
|||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call: {}
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- release-*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze Go code with CodeQL
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 360
|
|
||||||
permissions:
|
|
||||||
security-events: write
|
|
||||||
packages: read
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v3
|
|
||||||
with:
|
|
||||||
languages: go
|
|
||||||
build-mode: manual
|
|
||||||
- shell: bash
|
|
||||||
run: |
|
|
||||||
make build
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v3
|
|
||||||
with:
|
|
||||||
category: "/language:go"
|
|
||||||
103
.github/workflows/e2e.yaml
vendored
103
.github/workflows/e2e.yaml
vendored
@@ -1,103 +0,0 @@
|
|||||||
# Copyright 2025 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: End-to-end Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
secrets:
|
|
||||||
AWS_ACCESS_KEY_ID:
|
|
||||||
required: true
|
|
||||||
AWS_SECRET_ACCESS_KEY:
|
|
||||||
required: true
|
|
||||||
AWS_SSH_KEY:
|
|
||||||
required: true
|
|
||||||
E2E_SSH_USER:
|
|
||||||
required: true
|
|
||||||
SLACK_BOT_TOKEN:
|
|
||||||
required: true
|
|
||||||
SLACK_CHANNEL_ID:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
e2e-tests:
|
|
||||||
runs-on: linux-amd64-cpu4
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Calculate build vars
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "COMMIT_SHORT_SHA=${GITHUB_SHA:0:8}" >> $GITHUB_ENV
|
|
||||||
echo "LOWERCASE_REPO_OWNER=$(echo "${GITHUB_REPOSITORY_OWNER}" | awk '{print tolower($0)}')" >> $GITHUB_ENV
|
|
||||||
GOLANG_VERSION=$(./hack/golang-version.sh)
|
|
||||||
echo "GOLANG_VERSION=${GOLANG_VERSION##GOLANG_VERSION := }" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
|
||||||
|
|
||||||
- name: Set up Holodeck
|
|
||||||
uses: NVIDIA/holodeck@v0.2.12
|
|
||||||
with:
|
|
||||||
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
aws_ssh_key: ${{ secrets.AWS_SSH_KEY }}
|
|
||||||
holodeck_config: "tests/e2e/infra/aws.yaml"
|
|
||||||
|
|
||||||
- name: Get public dns name
|
|
||||||
id: holodeck_public_dns_name
|
|
||||||
uses: mikefarah/yq@master
|
|
||||||
with:
|
|
||||||
cmd: yq '.status.properties[] | select(.name == "public-dns-name") | .value' /github/workspace/.cache/holodeck.yaml
|
|
||||||
|
|
||||||
- name: Run e2e tests
|
|
||||||
env:
|
|
||||||
E2E_INSTALL_CTK: "true"
|
|
||||||
E2E_IMAGE_NAME: ghcr.io/nvidia/container-toolkit
|
|
||||||
E2E_IMAGE_TAG: ${{ inputs.version }}-ubuntu20.04
|
|
||||||
E2E_SSH_USER: ${{ secrets.E2E_SSH_USER }}
|
|
||||||
E2E_SSH_HOST: ${{ steps.holodeck_public_dns_name.outputs.result }}
|
|
||||||
run: |
|
|
||||||
e2e_ssh_key=$(mktemp)
|
|
||||||
echo "${{ secrets.AWS_SSH_KEY }}" > "$e2e_ssh_key"
|
|
||||||
chmod 600 "$e2e_ssh_key"
|
|
||||||
export E2E_SSH_KEY="$e2e_ssh_key"
|
|
||||||
|
|
||||||
make -f tests/e2e/Makefile test
|
|
||||||
|
|
||||||
- name: Archive Ginkgo logs
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ginkgo-logs
|
|
||||||
path: ginkgo.json
|
|
||||||
retention-days: 15
|
|
||||||
- name: Send Slack alert notification
|
|
||||||
if: ${{ failure() }}
|
|
||||||
uses: slackapi/slack-github-action@v2.1.0
|
|
||||||
with:
|
|
||||||
method: chat.postMessage
|
|
||||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
|
||||||
payload: |
|
|
||||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
|
||||||
text: |
|
|
||||||
:x: On repository ${{ github.repository }}, the Workflow *${{ github.workflow }}* has failed.
|
|
||||||
|
|
||||||
Details: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
102
.github/workflows/golang.yaml
vendored
102
.github/workflows/golang.yaml
vendored
@@ -1,102 +0,0 @@
|
|||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: Golang
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call: {}
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- release-*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
name: Checkout code
|
|
||||||
|
|
||||||
- name: Get Golang version
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
GOLANG_VERSION=$(./hack/golang-version.sh)
|
|
||||||
echo "GOLANG_VERSION=${GOLANG_VERSION##GOLANG_VERSION := }" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
uses: golangci/golangci-lint-action@v8
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: -v --timeout 5m
|
|
||||||
skip-cache: true
|
|
||||||
|
|
||||||
- name: Check golang modules
|
|
||||||
run: |
|
|
||||||
make check-vendor
|
|
||||||
make -C deployments/devel check-modules
|
|
||||||
|
|
||||||
test:
|
|
||||||
name: Unit test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Golang version
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
GOLANG_VERSION=$(./hack/golang-version.sh)
|
|
||||||
echo "GOLANG_VERSION=${GOLANG_VERSION##GOLANG_VERSION := }" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
|
||||||
|
|
||||||
- name: Run unit tests and generate coverage report
|
|
||||||
run: make coverage
|
|
||||||
|
|
||||||
- name: Upload to Coveralls
|
|
||||||
uses: coverallsapp/github-action@v2
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
file: coverage.out
|
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Golang version
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
GOLANG_VERSION=$(./hack/golang-version.sh)
|
|
||||||
echo "GOLANG_VERSION=${GOLANG_VERSION##GOLANG_VERSION ?= }" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
|
||||||
|
|
||||||
- run: make build
|
|
||||||
126
.github/workflows/image.yaml
vendored
126
.github/workflows/image.yaml
vendored
@@ -1,126 +0,0 @@
|
|||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# Run this workflow on pull requests
|
|
||||||
name: image
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
build_multi_arch_images:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
packages:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
target:
|
|
||||||
- ubuntu18.04-arm64
|
|
||||||
- ubuntu18.04-amd64
|
|
||||||
- ubuntu18.04-ppc64le
|
|
||||||
- centos7-aarch64
|
|
||||||
- centos7-x86_64
|
|
||||||
- centos8-ppc64le
|
|
||||||
ispr:
|
|
||||||
- ${{ github.ref_name != 'main' && !startsWith( github.ref_name, 'release-' ) }}
|
|
||||||
exclude:
|
|
||||||
- ispr: true
|
|
||||||
target: ubuntu18.04-arm64
|
|
||||||
- ispr: true
|
|
||||||
target: ubuntu18.04-ppc64le
|
|
||||||
- ispr: true
|
|
||||||
target: centos7-aarch64
|
|
||||||
- ispr: true
|
|
||||||
target: centos8-ppc64le
|
|
||||||
fail-fast: false
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
name: Check out code
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
image: tonistiigi/binfmt:master
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: build ${{ matrix.target }} packages
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -y coreutils build-essential sed git bash make
|
|
||||||
echo "Building packages"
|
|
||||||
./scripts/build-packages.sh ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: 'Upload Artifacts'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
compression-level: 0
|
|
||||||
name: toolkit-container-${{ matrix.target }}-${{ github.run_id }}
|
|
||||||
path: ${{ github.workspace }}/dist/*
|
|
||||||
|
|
||||||
image:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
dist:
|
|
||||||
- ubuntu20.04
|
|
||||||
- ubi8
|
|
||||||
- packaging
|
|
||||||
ispr:
|
|
||||||
- ${{ github.ref_name != 'main' && !startsWith( github.ref_name, 'release-' ) }}
|
|
||||||
exclude:
|
|
||||||
- ispr: true
|
|
||||||
dist: ubi8
|
|
||||||
needs: packages
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
name: Check out code
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
image: tonistiigi/binfmt:master
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Get built packages
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ${{ github.workspace }}/dist/
|
|
||||||
pattern: toolkit-container-*-${{ github.run_id }}
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build image
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: ghcr.io/nvidia/container-toolkit
|
|
||||||
VERSION: ${{ inputs.version }}
|
|
||||||
PUSH_ON_BUILD: "true"
|
|
||||||
BUILD_MULTI_ARCH_IMAGES: ${{ inputs.build_multi_arch_images }}
|
|
||||||
run: |
|
|
||||||
echo "${VERSION}"
|
|
||||||
make -f deployments/container/Makefile build-${{ matrix.dist }}
|
|
||||||
38
.github/workflows/release.yaml
vendored
38
.github/workflows/release.yaml
vendored
@@ -1,38 +0,0 @@
|
|||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# Run this workflow on new tags
|
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
name: Check out code
|
|
||||||
|
|
||||||
- name: Prepare Artifacts
|
|
||||||
run: |
|
|
||||||
./hack/prepare-artifacts.sh ${{ github.ref_name }}
|
|
||||||
|
|
||||||
- name: Create Draft Release
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
./hack/create-release.sh ${{ github.ref_name }}
|
|
||||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,11 +1,8 @@
|
|||||||
/dist
|
dist
|
||||||
/artifacts
|
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
/coverage.out*
|
/coverage.out
|
||||||
/tests/output/
|
/test/output/
|
||||||
/nvidia-*
|
/nvidia-container-runtime
|
||||||
|
/nvidia-container-toolkit
|
||||||
/shared-*
|
/shared-*
|
||||||
/release-*
|
|
||||||
/bin
|
|
||||||
/toolkit-test
|
|
||||||
|
|||||||
300
.gitlab-ci.yml
300
.gitlab-ci.yml
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved.
|
# Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,6 +15,68 @@
|
|||||||
include:
|
include:
|
||||||
- .common-ci.yml
|
- .common-ci.yml
|
||||||
|
|
||||||
|
build-dev-image:
|
||||||
|
stage: image
|
||||||
|
script:
|
||||||
|
- apk --no-cache add make bash
|
||||||
|
- make .build-image
|
||||||
|
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
||||||
|
- make .push-build-image
|
||||||
|
|
||||||
|
.requires-build-image:
|
||||||
|
image: "${BUILDIMAGE}"
|
||||||
|
|
||||||
|
.go-check:
|
||||||
|
extends:
|
||||||
|
- .requires-build-image
|
||||||
|
stage: go-checks
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
extends:
|
||||||
|
- .go-check
|
||||||
|
script:
|
||||||
|
- make assert-fmt
|
||||||
|
|
||||||
|
vet:
|
||||||
|
extends:
|
||||||
|
- .go-check
|
||||||
|
script:
|
||||||
|
- make vet
|
||||||
|
|
||||||
|
lint:
|
||||||
|
extends:
|
||||||
|
- .go-check
|
||||||
|
script:
|
||||||
|
- make lint
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
|
ineffassign:
|
||||||
|
extends:
|
||||||
|
- .go-check
|
||||||
|
script:
|
||||||
|
- make ineffassign
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
|
misspell:
|
||||||
|
extends:
|
||||||
|
- .go-check
|
||||||
|
script:
|
||||||
|
- make misspell
|
||||||
|
|
||||||
|
go-build:
|
||||||
|
extends:
|
||||||
|
- .requires-build-image
|
||||||
|
stage: go-build
|
||||||
|
script:
|
||||||
|
- make build
|
||||||
|
|
||||||
|
unit-tests:
|
||||||
|
extends:
|
||||||
|
- .requires-build-image
|
||||||
|
stage: unit-tests
|
||||||
|
script:
|
||||||
|
- make coverage
|
||||||
|
|
||||||
# Define the package build helpers
|
# Define the package build helpers
|
||||||
.multi-arch-build:
|
.multi-arch-build:
|
||||||
before_script:
|
before_script:
|
||||||
@@ -32,43 +94,32 @@ include:
|
|||||||
- .multi-arch-build
|
- .multi-arch-build
|
||||||
- .package-artifacts
|
- .package-artifacts
|
||||||
stage: package-build
|
stage: package-build
|
||||||
timeout: 3h
|
|
||||||
script:
|
script:
|
||||||
- ./scripts/build-packages.sh ${DIST}-${ARCH}
|
- ./scripts/release.sh ${DIST}-${ARCH}
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
name: ${ARTIFACTS_NAME}
|
name: ${ARTIFACTS_NAME}
|
||||||
paths:
|
paths:
|
||||||
- ${ARTIFACTS_ROOT}
|
- ${ARTIFACTS_ROOT}
|
||||||
needs:
|
|
||||||
- job: package-meta-packages
|
|
||||||
artifacts: true
|
|
||||||
|
|
||||||
# Define the package build targets
|
# Define the package build targets
|
||||||
package-meta-packages:
|
package-amazonlinux2-aarch64:
|
||||||
extends:
|
extends:
|
||||||
- .package-artifacts
|
- .package-build
|
||||||
stage: package-build
|
- .dist-amazonlinux2
|
||||||
variables:
|
- .arch-aarch64
|
||||||
SKIP_LIBNVIDIA_CONTAINER: "yes"
|
|
||||||
SKIP_NVIDIA_CONTAINER_TOOLKIT: "yes"
|
|
||||||
parallel:
|
|
||||||
matrix:
|
|
||||||
- PACKAGING: [deb, rpm]
|
|
||||||
before_script:
|
|
||||||
- apk add --no-cache coreutils build-base sed git bash make
|
|
||||||
script:
|
|
||||||
- ./scripts/build-packages.sh ${PACKAGING}
|
|
||||||
artifacts:
|
|
||||||
name: ${ARTIFACTS_NAME}
|
|
||||||
paths:
|
|
||||||
- ${ARTIFACTS_ROOT}
|
|
||||||
|
|
||||||
package-centos7-aarch64:
|
package-amazonlinux2-x86_64:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-amazonlinux2
|
||||||
|
- .arch-x86_64
|
||||||
|
|
||||||
|
package-centos7-ppc64le:
|
||||||
extends:
|
extends:
|
||||||
- .package-build
|
- .package-build
|
||||||
- .dist-centos7
|
- .dist-centos7
|
||||||
- .arch-aarch64
|
- .arch-ppc64le
|
||||||
|
|
||||||
package-centos7-x86_64:
|
package-centos7-x86_64:
|
||||||
extends:
|
extends:
|
||||||
@@ -76,12 +127,54 @@ package-centos7-x86_64:
|
|||||||
- .dist-centos7
|
- .dist-centos7
|
||||||
- .arch-x86_64
|
- .arch-x86_64
|
||||||
|
|
||||||
|
package-centos8-aarch64:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-centos8
|
||||||
|
- .arch-aarch64
|
||||||
|
|
||||||
package-centos8-ppc64le:
|
package-centos8-ppc64le:
|
||||||
extends:
|
extends:
|
||||||
- .package-build
|
- .package-build
|
||||||
- .dist-centos8
|
- .dist-centos8
|
||||||
- .arch-ppc64le
|
- .arch-ppc64le
|
||||||
|
|
||||||
|
package-centos8-x86_64:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-centos8
|
||||||
|
- .arch-x86_64
|
||||||
|
|
||||||
|
package-debian10-amd64:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-debian10
|
||||||
|
- .arch-amd64
|
||||||
|
|
||||||
|
package-debian9-amd64:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-debian9
|
||||||
|
- .arch-amd64
|
||||||
|
|
||||||
|
package-opensuse-leap15.1-x86_64:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-opensuse-leap15.1
|
||||||
|
- .arch-x86_64
|
||||||
|
|
||||||
|
package-ubuntu16.04-amd64:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-ubuntu16.04
|
||||||
|
- .arch-amd64
|
||||||
|
|
||||||
|
package-ubuntu16.04-ppc64le:
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
- .dist-ubuntu16.04
|
||||||
|
- .arch-ppc64le
|
||||||
|
|
||||||
package-ubuntu18.04-amd64:
|
package-ubuntu18.04-amd64:
|
||||||
extends:
|
extends:
|
||||||
- .package-build
|
- .package-build
|
||||||
@@ -100,33 +193,38 @@ package-ubuntu18.04-ppc64le:
|
|||||||
- .dist-ubuntu18.04
|
- .dist-ubuntu18.04
|
||||||
- .arch-ppc64le
|
- .arch-ppc64le
|
||||||
|
|
||||||
.buildx-setup:
|
|
||||||
before_script:
|
|
||||||
- export BUILDX_VERSION=v0.6.3
|
|
||||||
- apk add --no-cache curl
|
|
||||||
- mkdir -p ~/.docker/cli-plugins
|
|
||||||
- curl -sSLo ~/.docker/cli-plugins/docker-buildx "https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.linux-amd64"
|
|
||||||
- chmod a+x ~/.docker/cli-plugins/docker-buildx
|
|
||||||
|
|
||||||
- docker buildx create --use --platform=linux/amd64,linux/arm64
|
|
||||||
|
|
||||||
- '[[ -n "${SKIP_QEMU_SETUP}" ]] || docker run --rm --privileged multiarch/qemu-user-static --reset -p yes'
|
|
||||||
|
|
||||||
# Define the image build targets
|
# Define the image build targets
|
||||||
.image-build:
|
.image-build:
|
||||||
stage: image-build
|
stage: image-build
|
||||||
variables:
|
variables:
|
||||||
IMAGE_NAME: "${CI_REGISTRY_IMAGE}/container-toolkit"
|
IMAGE_NAME: "${CI_REGISTRY_IMAGE}/container-toolkit"
|
||||||
VERSION: "${CI_COMMIT_SHORT_SHA}"
|
VERSION: "${CI_COMMIT_SHORT_SHA}"
|
||||||
PUSH_ON_BUILD: "true"
|
|
||||||
before_script:
|
before_script:
|
||||||
- !reference [.buildx-setup, before_script]
|
- apk add --no-cache bash make
|
||||||
|
|
||||||
- apk add --no-cache bash make git
|
|
||||||
- 'echo "Logging in to CI registry ${CI_REGISTRY}"'
|
- 'echo "Logging in to CI registry ${CI_REGISTRY}"'
|
||||||
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
||||||
script:
|
script:
|
||||||
- make -f deployments/container/Makefile build-${DIST}
|
- make -f build/container/Makefile build-${DIST}
|
||||||
|
- make -f build/container/Makefile push-${DIST}
|
||||||
|
|
||||||
|
image-centos7:
|
||||||
|
extends:
|
||||||
|
- .image-build
|
||||||
|
- .package-artifacts
|
||||||
|
- .dist-centos7
|
||||||
|
needs:
|
||||||
|
- package-centos7-ppc64le
|
||||||
|
- package-centos7-x86_64
|
||||||
|
|
||||||
|
image-centos8:
|
||||||
|
extends:
|
||||||
|
- .image-build
|
||||||
|
- .package-artifacts
|
||||||
|
- .dist-centos8
|
||||||
|
needs:
|
||||||
|
- package-centos8-aarch64
|
||||||
|
- package-centos8-x86_64
|
||||||
|
- package-centos8-ppc64le
|
||||||
|
|
||||||
image-ubi8:
|
image-ubi8:
|
||||||
extends:
|
extends:
|
||||||
@@ -134,20 +232,19 @@ image-ubi8:
|
|||||||
- .package-artifacts
|
- .package-artifacts
|
||||||
- .dist-ubi8
|
- .dist-ubi8
|
||||||
needs:
|
needs:
|
||||||
# Note: The ubi8 image uses the centos7 packages
|
# Note: The ubi8 image currently uses the centos7 packages
|
||||||
- package-centos7-aarch64
|
- package-centos7-ppc64le
|
||||||
- package-centos7-x86_64
|
- package-centos7-x86_64
|
||||||
|
|
||||||
image-ubuntu20.04:
|
image-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .image-build
|
- .image-build
|
||||||
- .package-artifacts
|
- .package-artifacts
|
||||||
- .dist-ubuntu20.04
|
- .dist-ubuntu18.04
|
||||||
needs:
|
needs:
|
||||||
- package-ubuntu18.04-amd64
|
- package-ubuntu18.04-amd64
|
||||||
- package-ubuntu18.04-arm64
|
- package-ubuntu18.04-arm64
|
||||||
- job: package-ubuntu18.04-ppc64le
|
- package-ubuntu18.04-ppc64le
|
||||||
optional: true
|
|
||||||
|
|
||||||
# The DIST=packaging target creates an image containing all built packages
|
# The DIST=packaging target creates an image containing all built packages
|
||||||
image-packaging:
|
image-packaging:
|
||||||
@@ -156,26 +253,29 @@ image-packaging:
|
|||||||
- .package-artifacts
|
- .package-artifacts
|
||||||
- .dist-packaging
|
- .dist-packaging
|
||||||
needs:
|
needs:
|
||||||
- job: package-ubuntu18.04-amd64
|
- package-amazonlinux2-aarch64
|
||||||
- job: package-ubuntu18.04-arm64
|
- package-amazonlinux2-x86_64
|
||||||
- job: package-amazonlinux2-aarch64
|
- package-centos7-ppc64le
|
||||||
optional: true
|
- package-centos7-x86_64
|
||||||
- job: package-amazonlinux2-x86_64
|
- package-centos8-aarch64
|
||||||
optional: true
|
- package-centos8-ppc64le
|
||||||
- job: package-centos7-aarch64
|
- package-centos8-x86_64
|
||||||
optional: true
|
- package-debian10-amd64
|
||||||
- job: package-centos7-x86_64
|
- package-debian9-amd64
|
||||||
optional: true
|
- package-opensuse-leap15.1-x86_64
|
||||||
- job: package-centos8-ppc64le
|
- package-ubuntu16.04-amd64
|
||||||
optional: true
|
- package-ubuntu16.04-ppc64le
|
||||||
- job: package-debian10-amd64
|
- package-ubuntu18.04-amd64
|
||||||
optional: true
|
- package-ubuntu18.04-arm64
|
||||||
- job: package-opensuse-leap15.1-x86_64
|
- package-ubuntu18.04-ppc64le
|
||||||
optional: true
|
|
||||||
- job: package-ubuntu18.04-ppc64le
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
# Define publish test helpers
|
# Define publish test helpers
|
||||||
|
.test:toolkit:
|
||||||
|
extends:
|
||||||
|
- .integration
|
||||||
|
variables:
|
||||||
|
TEST_CASES: "toolkit"
|
||||||
|
|
||||||
.test:docker:
|
.test:docker:
|
||||||
extends:
|
extends:
|
||||||
- .integration
|
- .integration
|
||||||
@@ -199,30 +299,74 @@ image-packaging:
|
|||||||
TEST_CASES: "crio"
|
TEST_CASES: "crio"
|
||||||
|
|
||||||
# Define the test targets
|
# Define the test targets
|
||||||
test-toolkit-ubuntu20.04:
|
test-toolkit-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .test:toolkit
|
- .test:toolkit
|
||||||
- .dist-ubuntu20.04
|
- .dist-ubuntu18.04
|
||||||
needs:
|
needs:
|
||||||
- image-ubuntu20.04
|
- image-ubuntu18.04
|
||||||
|
|
||||||
test-containerd-ubuntu20.04:
|
test-containerd-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .test:containerd
|
- .test:containerd
|
||||||
- .dist-ubuntu20.04
|
- .dist-ubuntu18.04
|
||||||
needs:
|
needs:
|
||||||
- image-ubuntu20.04
|
- image-ubuntu18.04
|
||||||
|
|
||||||
test-crio-ubuntu20.04:
|
test-crio-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .test:crio
|
- .test:crio
|
||||||
- .dist-ubuntu20.04
|
- .dist-ubuntu18.04
|
||||||
needs:
|
needs:
|
||||||
- image-ubuntu20.04
|
- image-ubuntu18.04
|
||||||
|
|
||||||
test-docker-ubuntu20.04:
|
test-docker-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .test:docker
|
- .test:docker
|
||||||
- .dist-ubuntu20.04
|
- .dist-ubuntu18.04
|
||||||
needs:
|
needs:
|
||||||
- image-ubuntu20.04
|
- image-ubuntu18.04
|
||||||
|
|
||||||
|
# build-all jobs build packages for every OS / ARCH combination we support.
|
||||||
|
#
|
||||||
|
# They are run under two conditions:
|
||||||
|
# 1) Automatically whenever a new tag is pushed to the repo (e.g. v1.1.0)
|
||||||
|
# 2) Manually by a reviewer just before merging a MR.
|
||||||
|
.build-all-for-arch:
|
||||||
|
variables:
|
||||||
|
# Setting DIST=docker invokes the docker- release targets
|
||||||
|
DIST: docker
|
||||||
|
extends:
|
||||||
|
- .package-build
|
||||||
|
stage: build-all
|
||||||
|
timeout: 2h 30m
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
when: always
|
||||||
|
|
||||||
|
# The full set of build-all jobs organized to
|
||||||
|
# have builds for each ARCH run in parallel.
|
||||||
|
build-all-amd64:
|
||||||
|
extends:
|
||||||
|
- .build-all-for-arch
|
||||||
|
- .arch-amd64
|
||||||
|
|
||||||
|
build-all-x86_64:
|
||||||
|
extends:
|
||||||
|
- .build-all-for-arch
|
||||||
|
- .arch-x86_64
|
||||||
|
|
||||||
|
build-all-ppc64le:
|
||||||
|
extends:
|
||||||
|
- .build-all-for-arch
|
||||||
|
- .arch-ppc64le
|
||||||
|
|
||||||
|
build-all-arm64:
|
||||||
|
extends:
|
||||||
|
- .build-all-for-arch
|
||||||
|
- .arch-arm64
|
||||||
|
|
||||||
|
build-all-aarch64:
|
||||||
|
extends:
|
||||||
|
- .build-all-for-arch
|
||||||
|
- .arch-aarch64
|
||||||
|
|||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -1,4 +1,9 @@
|
|||||||
[submodule "third_party/libnvidia-container"]
|
[submodule "third_party/libnvidia-container"]
|
||||||
path = third_party/libnvidia-container
|
path = third_party/libnvidia-container
|
||||||
url = https://github.com/NVIDIA/libnvidia-container.git
|
url = https://gitlab.com/nvidia/container-toolkit/libnvidia-container.git
|
||||||
branch = main
|
[submodule "third_party/nvidia-container-runtime"]
|
||||||
|
path = third_party/nvidia-container-runtime
|
||||||
|
url = https://gitlab.com/nvidia/container-toolkit/container-runtime.git
|
||||||
|
[submodule "third_party/nvidia-docker"]
|
||||||
|
path = third_party/nvidia-docker
|
||||||
|
url = https://gitlab.com/nvidia/container-toolkit/nvidia-docker.git
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
version: "2"
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- contextcheck
|
|
||||||
- gocritic
|
|
||||||
- gosec
|
|
||||||
- misspell
|
|
||||||
- unconvert
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
|
||||||
rules:
|
|
||||||
# Exclude the gocritic dupSubExpr issue for cgo files.
|
|
||||||
- linters:
|
|
||||||
- gocritic
|
|
||||||
path: internal/dxcore/dxcore.go
|
|
||||||
text: dupSubExpr
|
|
||||||
# Exclude the checks for usage of returns to config.Delete(Path) in the
|
|
||||||
# crio and containerd config packages.
|
|
||||||
- linters:
|
|
||||||
- errcheck
|
|
||||||
path: pkg/config/engine/
|
|
||||||
text: config.Delete
|
|
||||||
# RENDERD refers to the Render Device and not the past tense of render.
|
|
||||||
- linters:
|
|
||||||
- misspell
|
|
||||||
path: .*.go
|
|
||||||
text: '`RENDERD` is a misspelling of `RENDERED`'
|
|
||||||
# The legacy hook relies on spec.Hooks.Prestart, which is deprecated as of
|
|
||||||
# the v1.2.0 OCI runtime spec.
|
|
||||||
- path: (.+)\.go$
|
|
||||||
text: SA1019:(.+).Prestart is deprecated(.+)
|
|
||||||
# TODO: We should address each of the following integer overflows.
|
|
||||||
- path: (.+)\.go$
|
|
||||||
text: 'G115: integer overflow conversion(.+)'
|
|
||||||
paths:
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
settings:
|
|
||||||
goimports:
|
|
||||||
local-prefixes:
|
|
||||||
- github.com/NVIDIA/nvidia-container-toolkit
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
231
.nvidia-ci.yml
231
.nvidia-ci.yml
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2021-2022, NVIDIA CORPORATION. All rights reserved.
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -27,17 +27,14 @@ default:
|
|||||||
variables:
|
variables:
|
||||||
DOCKER_DRIVER: overlay2
|
DOCKER_DRIVER: overlay2
|
||||||
DOCKER_TLS_CERTDIR: "/certs"
|
DOCKER_TLS_CERTDIR: "/certs"
|
||||||
# Release "devel"-tagged images off the main branch
|
# Release "devel"-tagged images off the master branch
|
||||||
RELEASE_DEVEL_BRANCH: "main"
|
RELEASE_DEVEL_BRANCH: "master"
|
||||||
DEVEL_RELEASE_IMAGE_VERSION: "devel"
|
DEVEL_RELEASE_IMAGE_VERSION: "devel"
|
||||||
# On the multi-arch builder we don't need the qemu setup.
|
# On the multi-arch builder we don't need the qemu setup.
|
||||||
SKIP_QEMU_SETUP: "1"
|
SKIP_QEMU_SETUP: "1"
|
||||||
# Define the public staging registry
|
# Define the public staging registry
|
||||||
STAGING_REGISTRY: ghcr.io/nvidia
|
STAGING_REGISTRY: registry.gitlab.com/nvidia/container-toolkit/container-toolkit/staging
|
||||||
STAGING_VERSION: ${CI_COMMIT_SHORT_SHA}
|
STAGING_VERSION: ${CI_COMMIT_SHORT_SHA}
|
||||||
ARTIFACTORY_REPO_BASE: "https://urm.nvidia.com/artifactory/sw-gpu-cloudnative"
|
|
||||||
KITMAKER_RELEASE_FOLDER: "kitmaker"
|
|
||||||
PACKAGE_ARCHIVE_RELEASE_FOLDER: "releases"
|
|
||||||
|
|
||||||
.image-pull:
|
.image-pull:
|
||||||
stage: image-build
|
stage: image-build
|
||||||
@@ -49,11 +46,9 @@ variables:
|
|||||||
OUT_REGISTRY_TOKEN: "${CI_REGISTRY_PASSWORD}"
|
OUT_REGISTRY_TOKEN: "${CI_REGISTRY_PASSWORD}"
|
||||||
OUT_REGISTRY: "${CI_REGISTRY}"
|
OUT_REGISTRY: "${CI_REGISTRY}"
|
||||||
OUT_IMAGE_NAME: "${CI_REGISTRY_IMAGE}/container-toolkit"
|
OUT_IMAGE_NAME: "${CI_REGISTRY_IMAGE}/container-toolkit"
|
||||||
PUSH_MULTIPLE_TAGS: "false"
|
|
||||||
# We delay the job start to allow the public pipeline to generate the required images.
|
# We delay the job start to allow the public pipeline to generate the required images.
|
||||||
rules:
|
when: delayed
|
||||||
- when: delayed
|
start_in: 30 minutes
|
||||||
start_in: 30 minutes
|
|
||||||
timeout: 30 minutes
|
timeout: 30 minutes
|
||||||
retry:
|
retry:
|
||||||
max: 2
|
max: 2
|
||||||
@@ -61,29 +56,39 @@ variables:
|
|||||||
- job_execution_timeout
|
- job_execution_timeout
|
||||||
- stuck_or_timeout_failure
|
- stuck_or_timeout_failure
|
||||||
before_script:
|
before_script:
|
||||||
- !reference [.regctl-setup, before_script]
|
|
||||||
- apk add --no-cache make bash
|
|
||||||
- >
|
- >
|
||||||
regctl manifest get ${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST} --list > /dev/null && echo "${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST}" || ( echo "${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST} does not exist" && sleep infinity )
|
docker pull ${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST} > /dev/null && echo "${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST}" || ( echo "${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST} does not exist" && sleep infinity )
|
||||||
script:
|
script:
|
||||||
- regctl registry login "${OUT_REGISTRY}" -u "${OUT_REGISTRY_USER}" -p "${OUT_REGISTRY_TOKEN}"
|
- docker pull ${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST}
|
||||||
- make -f deployments/container/Makefile IMAGE=${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST} OUT_IMAGE=${OUT_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}-${DIST} push-${DIST}
|
- docker tag ${IN_REGISTRY}/${IN_IMAGE_NAME}:${IN_VERSION}-${DIST} ${OUT_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}-${DIST}
|
||||||
|
- docker login -u "${OUT_REGISTRY_USER}" -p "${OUT_REGISTRY_TOKEN}" "${OUT_REGISTRY}"
|
||||||
|
- docker push ${OUT_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}-${DIST}
|
||||||
|
|
||||||
|
image-centos7:
|
||||||
|
extends:
|
||||||
|
- .image-pull
|
||||||
|
- .dist-centos7
|
||||||
|
|
||||||
|
image-centos8:
|
||||||
|
extends:
|
||||||
|
- .image-pull
|
||||||
|
- .dist-centos8
|
||||||
|
|
||||||
image-ubi8:
|
image-ubi8:
|
||||||
extends:
|
extends:
|
||||||
|
- .image-pull
|
||||||
- .dist-ubi8
|
- .dist-ubi8
|
||||||
- .image-pull
|
|
||||||
|
|
||||||
image-ubuntu20.04:
|
image-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .dist-ubuntu20.04
|
|
||||||
- .image-pull
|
- .image-pull
|
||||||
|
- .dist-ubuntu18.04
|
||||||
|
|
||||||
# The DIST=packaging target creates an image containing all built packages
|
# The DIST=packaging target creates an image containing all built packages
|
||||||
image-packaging:
|
image-packaging:
|
||||||
extends:
|
extends:
|
||||||
- .dist-packaging
|
|
||||||
- .image-pull
|
- .image-pull
|
||||||
|
- .dist-packaging
|
||||||
|
|
||||||
# We skip the integration tests for the internal CI:
|
# We skip the integration tests for the internal CI:
|
||||||
.integration:
|
.integration:
|
||||||
@@ -100,14 +105,14 @@ image-packaging:
|
|||||||
image: "${PULSE_IMAGE}"
|
image: "${PULSE_IMAGE}"
|
||||||
variables:
|
variables:
|
||||||
IMAGE: "${CI_REGISTRY_IMAGE}/container-toolkit:${CI_COMMIT_SHORT_SHA}-${DIST}"
|
IMAGE: "${CI_REGISTRY_IMAGE}/container-toolkit:${CI_COMMIT_SHORT_SHA}-${DIST}"
|
||||||
IMAGE_ARCHIVE: "container-toolkit-${DIST}-${ARCH}-${CI_JOB_ID}.tar"
|
IMAGE_ARCHIVE: "container-toolkit.tar"
|
||||||
rules:
|
except:
|
||||||
- if: $SKIP_SCANS != "yes"
|
variables:
|
||||||
- when: manual
|
- $SKIP_SCANS && $SKIP_SCANS == "yes"
|
||||||
before_script:
|
before_script:
|
||||||
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
|
||||||
# TODO: We should specify the architecture here and scan all architectures
|
# TODO: We should specify the architecture here and scan all architectures
|
||||||
- docker pull --platform="${PLATFORM}" "${IMAGE}"
|
- docker pull "${IMAGE}"
|
||||||
- docker save "${IMAGE}" -o "${IMAGE_ARCHIVE}"
|
- docker save "${IMAGE}" -o "${IMAGE_ARCHIVE}"
|
||||||
- AuthHeader=$(echo -n $SSA_CLIENT_ID:$SSA_CLIENT_SECRET | base64 -w0)
|
- AuthHeader=$(echo -n $SSA_CLIENT_ID:$SSA_CLIENT_SECRET | base64 -w0)
|
||||||
- >
|
- >
|
||||||
@@ -115,7 +120,6 @@ image-packaging:
|
|||||||
- if [ -z "$SSA_TOKEN" ]; then exit 1; else echo "SSA_TOKEN set!"; fi
|
- if [ -z "$SSA_TOKEN" ]; then exit 1; else echo "SSA_TOKEN set!"; fi
|
||||||
script:
|
script:
|
||||||
- pulse-cli -n $NSPECT_ID --ssa $SSA_TOKEN scan -i $IMAGE_ARCHIVE -p $CONTAINER_POLICY -o
|
- pulse-cli -n $NSPECT_ID --ssa $SSA_TOKEN scan -i $IMAGE_ARCHIVE -p $CONTAINER_POLICY -o
|
||||||
- rm -f "${IMAGE_ARCHIVE}"
|
|
||||||
artifacts:
|
artifacts:
|
||||||
when: always
|
when: always
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
@@ -127,47 +131,34 @@ image-packaging:
|
|||||||
- policy_evaluation.json
|
- policy_evaluation.json
|
||||||
|
|
||||||
# Define the scan targets
|
# Define the scan targets
|
||||||
scan-ubuntu20.04-amd64:
|
scan-centos7:
|
||||||
extends:
|
extends:
|
||||||
- .dist-ubuntu20.04
|
|
||||||
- .platform-amd64
|
|
||||||
- .scan
|
- .scan
|
||||||
|
- .dist-centos7
|
||||||
needs:
|
needs:
|
||||||
- image-ubuntu20.04
|
- image-centos7
|
||||||
|
|
||||||
scan-ubuntu20.04-arm64:
|
scan-centos8:
|
||||||
extends:
|
extends:
|
||||||
- .dist-ubuntu20.04
|
|
||||||
- .platform-arm64
|
|
||||||
- .scan
|
- .scan
|
||||||
|
- .dist-centos8
|
||||||
needs:
|
needs:
|
||||||
- image-ubuntu20.04
|
- image-centos8
|
||||||
- scan-ubuntu20.04-amd64
|
|
||||||
|
|
||||||
scan-ubi8-amd64:
|
scan-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
|
- .scan
|
||||||
|
- .dist-ubuntu18.04
|
||||||
|
needs:
|
||||||
|
- image-ubuntu18.04
|
||||||
|
|
||||||
|
scan-ubi8:
|
||||||
|
extends:
|
||||||
|
- .scan
|
||||||
- .dist-ubi8
|
- .dist-ubi8
|
||||||
- .platform-amd64
|
|
||||||
- .scan
|
|
||||||
needs:
|
needs:
|
||||||
- image-ubi8
|
- image-ubi8
|
||||||
|
|
||||||
scan-ubi8-arm64:
|
|
||||||
extends:
|
|
||||||
- .dist-ubi8
|
|
||||||
- .platform-arm64
|
|
||||||
- .scan
|
|
||||||
needs:
|
|
||||||
- image-ubi8
|
|
||||||
- scan-ubi8-amd64
|
|
||||||
|
|
||||||
scan-packaging:
|
|
||||||
extends:
|
|
||||||
- .dist-packaging
|
|
||||||
- .scan
|
|
||||||
needs:
|
|
||||||
- image-packaging
|
|
||||||
|
|
||||||
# Define external release helpers
|
# Define external release helpers
|
||||||
.release:ngc:
|
.release:ngc:
|
||||||
extends:
|
extends:
|
||||||
@@ -178,111 +169,61 @@ scan-packaging:
|
|||||||
OUT_REGISTRY: "${NGC_REGISTRY}"
|
OUT_REGISTRY: "${NGC_REGISTRY}"
|
||||||
OUT_IMAGE_NAME: "${NGC_REGISTRY_IMAGE}"
|
OUT_IMAGE_NAME: "${NGC_REGISTRY_IMAGE}"
|
||||||
|
|
||||||
.release:packages:
|
.release:dockerhub:
|
||||||
stage: release
|
|
||||||
needs:
|
|
||||||
- image-packaging
|
|
||||||
variables:
|
|
||||||
VERSION: "${CI_COMMIT_SHORT_SHA}"
|
|
||||||
PACKAGE_REGISTRY: "${CI_REGISTRY}"
|
|
||||||
PACKAGE_REGISTRY_USER: "${CI_REGISTRY_USER}"
|
|
||||||
PACKAGE_REGISTRY_TOKEN: "${CI_REGISTRY_PASSWORD}"
|
|
||||||
PACKAGE_IMAGE_NAME: "${CI_REGISTRY_IMAGE}/container-toolkit"
|
|
||||||
PACKAGE_IMAGE_TAG: "${CI_COMMIT_SHORT_SHA}-packaging"
|
|
||||||
KITMAKER_ARTIFACTORY_REPO: "${ARTIFACTORY_REPO_BASE}-generic-local/${KITMAKER_RELEASE_FOLDER}"
|
|
||||||
ARTIFACTS_DIR: "${CI_PROJECT_DIR}/artifacts"
|
|
||||||
script:
|
|
||||||
- !reference [.regctl-setup, before_script]
|
|
||||||
- apk add --no-cache bash git
|
|
||||||
- regctl registry login "${PACKAGE_REGISTRY}" -u "${PACKAGE_REGISTRY_USER}" -p "${PACKAGE_REGISTRY_TOKEN}"
|
|
||||||
- ./scripts/extract-packages.sh "${PACKAGE_IMAGE_NAME}:${PACKAGE_IMAGE_TAG}"
|
|
||||||
- ./scripts/release-kitmaker-artifactory.sh "${KITMAKER_ARTIFACTORY_REPO}"
|
|
||||||
- rm -rf ${ARTIFACTS_DIR}
|
|
||||||
|
|
||||||
# Define the package release targets
|
|
||||||
release:packages:kitmaker:
|
|
||||||
extends:
|
extends:
|
||||||
- .release:packages
|
- .release:external
|
||||||
|
variables:
|
||||||
|
OUT_REGISTRY_USER: "${REGISTRY_USER}"
|
||||||
|
OUT_REGISTRY_TOKEN: "${REGISTRY_TOKEN}"
|
||||||
|
OUT_REGISTRY: "${DOCKERHUB_REGISTRY}"
|
||||||
|
OUT_IMAGE_NAME: "${REGISTRY_IMAGE}"
|
||||||
|
|
||||||
release:staging-ubuntu20.04:
|
release:staging-ubuntu18.04:
|
||||||
extends:
|
extends:
|
||||||
- .release:staging
|
- .release:staging
|
||||||
- .dist-ubuntu20.04
|
- .dist-ubuntu18.04
|
||||||
needs:
|
needs:
|
||||||
- image-ubuntu20.04
|
- image-ubuntu18.04
|
||||||
|
|
||||||
# Define the external release targets
|
# Define the external release targets
|
||||||
# Release to NGC
|
# Release to NGC
|
||||||
release:ngc-ubuntu20.04:
|
release:ngc-centos7:
|
||||||
extends:
|
extends:
|
||||||
- .dist-ubuntu20.04
|
|
||||||
- .release:ngc
|
- .release:ngc
|
||||||
|
- .dist-centos7
|
||||||
|
|
||||||
|
release:ngc-centos8:
|
||||||
|
extends:
|
||||||
|
- .release:ngc
|
||||||
|
- .dist-centos8
|
||||||
|
|
||||||
|
release:ngc-ubuntu18:
|
||||||
|
extends:
|
||||||
|
- .release:ngc
|
||||||
|
- .dist-ubuntu18.04
|
||||||
|
|
||||||
release:ngc-ubi8:
|
release:ngc-ubi8:
|
||||||
extends:
|
extends:
|
||||||
- .dist-ubi8
|
|
||||||
- .release:ngc
|
- .release:ngc
|
||||||
|
|
||||||
release:ngc-packaging:
|
|
||||||
extends:
|
|
||||||
- .dist-packaging
|
|
||||||
- .release:ngc
|
|
||||||
|
|
||||||
# Define the external image signing steps for NGC
|
|
||||||
# Download the ngc cli binary for use in the sign steps
|
|
||||||
.ngccli-setup:
|
|
||||||
before_script:
|
|
||||||
- apt-get update && apt-get install -y curl unzip jq
|
|
||||||
- |
|
|
||||||
if [ -z "${NGCCLI_VERSION}" ]; then
|
|
||||||
NGC_VERSION_URL="https://api.ngc.nvidia.com/v2/resources/nvidia/ngc-apps/ngc_cli/versions"
|
|
||||||
# Extract the latest version from the JSON data using jq
|
|
||||||
export NGCCLI_VERSION=$(curl -s $NGC_VERSION_URL | jq -r '.recipe.latestVersionIdStr')
|
|
||||||
fi
|
|
||||||
echo "NGCCLI_VERSION ${NGCCLI_VERSION}"
|
|
||||||
- curl -sSLo ngccli_linux.zip https://api.ngc.nvidia.com/v2/resources/nvidia/ngc-apps/ngc_cli/versions/${NGCCLI_VERSION}/files/ngccli_linux.zip
|
|
||||||
- unzip ngccli_linux.zip
|
|
||||||
- chmod u+x ngc-cli/ngc
|
|
||||||
|
|
||||||
# .sign forms the base of the deployment jobs which signs images in the CI registry.
|
|
||||||
# This is extended with the image name and version to be deployed.
|
|
||||||
.sign:ngc:
|
|
||||||
image: ubuntu:latest
|
|
||||||
stage: sign
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_TAG
|
|
||||||
variables:
|
|
||||||
NGC_CLI_API_KEY: "${NGC_REGISTRY_TOKEN}"
|
|
||||||
IMAGE_NAME: "${NGC_REGISTRY_IMAGE}"
|
|
||||||
IMAGE_TAG: "${CI_COMMIT_TAG}-${DIST}"
|
|
||||||
retry:
|
|
||||||
max: 2
|
|
||||||
before_script:
|
|
||||||
- !reference [.ngccli-setup, before_script]
|
|
||||||
# We ensure that the IMAGE_NAME and IMAGE_TAG is set
|
|
||||||
- 'echo Image Name: ${IMAGE_NAME} && [[ -n "${IMAGE_NAME}" ]] || exit 1'
|
|
||||||
- 'echo Image Tag: ${IMAGE_TAG} && [[ -n "${IMAGE_TAG}" ]] || exit 1'
|
|
||||||
script:
|
|
||||||
- 'echo "Signing the image ${IMAGE_NAME}:${IMAGE_TAG}"'
|
|
||||||
- ngc-cli/ngc registry image publish --source ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:${IMAGE_TAG} --public --discoverable --allow-guest --sign --org nvidia
|
|
||||||
|
|
||||||
sign:ngc-ubuntu20.04:
|
|
||||||
extends:
|
|
||||||
- .dist-ubuntu20.04
|
|
||||||
- .sign:ngc
|
|
||||||
needs:
|
|
||||||
- release:ngc-ubuntu20.04
|
|
||||||
|
|
||||||
sign:ngc-ubi8:
|
|
||||||
extends:
|
|
||||||
- .dist-ubi8
|
- .dist-ubi8
|
||||||
- .sign:ngc
|
|
||||||
needs:
|
|
||||||
- release:ngc-ubi8
|
|
||||||
|
|
||||||
sign:ngc-packaging:
|
# Release to Dockerhub
|
||||||
|
release:dockerhub-centos7:
|
||||||
extends:
|
extends:
|
||||||
- .dist-packaging
|
- .release:dockerhub
|
||||||
- .sign:ngc
|
- .dist-centos7
|
||||||
needs:
|
|
||||||
- release:ngc-packaging
|
release:dockerhub-centos8:
|
||||||
|
extends:
|
||||||
|
- .release:dockerhub
|
||||||
|
- .dist-centos8
|
||||||
|
|
||||||
|
release:dockerhub-ubuntu18:
|
||||||
|
extends:
|
||||||
|
- .release:dockerhub
|
||||||
|
- .dist-ubuntu18.04
|
||||||
|
|
||||||
|
release:dockerhub-ubi8:
|
||||||
|
extends:
|
||||||
|
- .release:dockerhub
|
||||||
|
- .dist-ubi8
|
||||||
|
|||||||
541
CHANGELOG.md
541
CHANGELOG.md
@@ -1,541 +0,0 @@
|
|||||||
# NVIDIA Container Toolkit Changelog
|
|
||||||
|
|
||||||
## v1.17.4
|
|
||||||
- Disable mounting of compat libs from container by default
|
|
||||||
- Add allow-cuda-compat-libs-from-container feature flag
|
|
||||||
- Skip graphics modifier in CSV mode
|
|
||||||
- Properly pass configSearchPaths to a Driver constructor
|
|
||||||
- Add support for containerd version 3 config
|
|
||||||
- Add string TOML source
|
|
||||||
|
|
||||||
### Changes in libnvidia-container
|
|
||||||
- Add no-cntlibs CLI option to nvidia-container-cli
|
|
||||||
|
|
||||||
### Changes in the Toolkit Container
|
|
||||||
- Bump CUDA base image version to 12.6.3
|
|
||||||
|
|
||||||
## v1.17.3
|
|
||||||
- Only allow host-relative LDConfig paths by default.
|
|
||||||
### Changes in libnvidia-container
|
|
||||||
- Create virtual copy of host ldconfig binary before calling fexecve()
|
|
||||||
|
|
||||||
## v1.17.2
|
|
||||||
- Fixed a bug where legacy images would set imex channels as `all`.
|
|
||||||
|
|
||||||
## v1.17.1
|
|
||||||
- Fixed a bug where specific symlinks existing in a container image could cause a container to fail to start.
|
|
||||||
- Fixed a bug on Tegra-based systems where a container would fail to start.
|
|
||||||
- Fixed a bug where the default container runtime config path was not properly set.
|
|
||||||
|
|
||||||
### Changes in the Toolkit Container
|
|
||||||
- Fallback to using a config file if the current runtime config can not be determined from the command line.
|
|
||||||
|
|
||||||
## v1.17.0
|
|
||||||
- Promote v1.17.0-rc.2 to v1.17.0
|
|
||||||
- Fix bug when using just-in-time CDI spec generation
|
|
||||||
- Check for valid paths in create-symlinks hook
|
|
||||||
|
|
||||||
## v1.17.0-rc.2
|
|
||||||
- Fix bug in locating libcuda.so from ldcache
|
|
||||||
- Fix bug in sorting of symlink chain
|
|
||||||
- Remove unsupported print-ldcache command
|
|
||||||
- Remove csv-filename support from create-symlinks
|
|
||||||
|
|
||||||
### Changes in the Toolkit Container
|
|
||||||
- Fallback to `crio-status` if `crio status` does not work when configuring the crio runtime
|
|
||||||
|
|
||||||
## v1.17.0-rc.1
|
|
||||||
- Allow IMEX channels to be requested as volume mounts
|
|
||||||
- Fix typo in error message
|
|
||||||
- Add disable-imex-channel-creation feature flag
|
|
||||||
- Add -z,lazy to LDFLAGS
|
|
||||||
- Add imex channels to management CDI spec
|
|
||||||
- Add support to fetch current container runtime config from the command line.
|
|
||||||
- Add creation of select driver symlinks to CDI spec generation.
|
|
||||||
- Remove support for config overrides when configuring runtimes.
|
|
||||||
- Skip explicit creation of libnvidia-allocator.so.1 symlink
|
|
||||||
- Add vdpau as as a driver library search path.
|
|
||||||
- Add support for using libnvsandboxutils to generate CDI specifications.
|
|
||||||
|
|
||||||
### Changes in the Toolkit Container
|
|
||||||
|
|
||||||
- Allow opt-in features to be selected when deploying the toolkit-container.
|
|
||||||
- Bump CUDA base image version to 12.6.2
|
|
||||||
- Remove support for config overrides when configuring runtimes.
|
|
||||||
|
|
||||||
### Changes in libnvidia-container
|
|
||||||
|
|
||||||
- Add no-create-imex-channels command line option.
|
|
||||||
|
|
||||||
## v1.16.2
|
|
||||||
- Exclude libnvidia-allocator from graphics mounts. This fixes a bug that leaks mounts when a container is started with bi-directional mount propagation.
|
|
||||||
- Use empty string for default runtime-config-override. This removes a redundant warning for runtimes (e.g. Docker) where this is not applicable.
|
|
||||||
|
|
||||||
### Changes in the Toolkit Container
|
|
||||||
- Bump CUDA base image version to 12.6.0
|
|
||||||
|
|
||||||
### Changes in libnvidia-container
|
|
||||||
- Add no-gsp-firmware command line option
|
|
||||||
- Add no-fabricmanager command line option
|
|
||||||
- Add no-persistenced command line option
|
|
||||||
- Skip directories and symlinks when mounting libraries.
|
|
||||||
|
|
||||||
## v1.16.1
|
|
||||||
- Fix bug with processing errors during CDI spec generation for MIG devices
|
|
||||||
|
|
||||||
## v1.16.0
|
|
||||||
- Promote v1.16.0-rc.2 to v1.16.0
|
|
||||||
|
|
||||||
### Changes in the Toolkit Container
|
|
||||||
- Bump CUDA base image version to 12.5.1
|
|
||||||
|
|
||||||
## v1.16.0-rc.2
|
|
||||||
- Use relative path to locate driver libraries
|
|
||||||
- Add RelativeToRoot function to Driver
|
|
||||||
- Inject additional libraries for full X11 functionality
|
|
||||||
- Extract options from default runtime if runc does not exist
|
|
||||||
- Avoid using map pointers as maps are always passed by reference
|
|
||||||
- Reduce logging for the NVIDIA Container runtime
|
|
||||||
- Fix bug in argument parsing for logger creation
|
|
||||||
|
|
||||||
## v1.16.0-rc.1
|
|
||||||
|
|
||||||
- Support vulkan ICD files directly in a driver root. This allows for the discovery of vulkan files in GKE driver installations.
|
|
||||||
- Increase priority of ld.so.conf.d config file injected into container. This ensures that injected libraries are preferred over libraries present in the container.
|
|
||||||
- Set default CDI spec permissions to 644. This fixes permission issues when using the `nvidia-ctk cdi transform` functions.
|
|
||||||
- Add `dev-root` option to `nvidia-ctk system create-device-nodes` command.
|
|
||||||
- Fix location of `libnvidia-ml.so.1` when a non-standard driver root is used. This enabled CDI spec generation when using the driver container on a host.
|
|
||||||
- Recalculate minimum required CDI spec version on save.
|
|
||||||
- Move `nvidia-ctk hook` commands to a separate `nvidia-cdi-hook` binary. The same subcommands are supported.
|
|
||||||
- Use `:` as an `nvidia-ctk config --set` list separator. This fixes a bug when trying to set config options that are lists.
|
|
||||||
|
|
||||||
- [toolkit-container] Bump CUDA base image version to 12.5.0
|
|
||||||
- [toolkit-container] Allow the path to `toolkit.pid` to be specified directly.
|
|
||||||
- [toolkit-container] Remove provenance information from image manifests.
|
|
||||||
- [toolkit-container] Add `dev-root` option when configuring the toolkit. This adds support for GKE driver installations.
|
|
||||||
|
|
||||||
## v1.15.0
|
|
||||||
|
|
||||||
* Remove `nvidia-container-runtime` and `nvidia-docker2` packages.
|
|
||||||
* Use `XDG_DATA_DIRS` environment variable when locating config files such as graphics config files.
|
|
||||||
* Add support for v0.7.0 Container Device Interface (CDI) specification.
|
|
||||||
* Add `--config-search-path` option to `nvidia-ctk cdi generate` command. These paths are used when locating driver files such as graphics config files.
|
|
||||||
* Use D3DKMTEnumAdapters3 to enumerate adpaters on WSL2 if available.
|
|
||||||
* Add support for v1.2.0 OCI Runtime specification.
|
|
||||||
* Explicitly set `NVIDIA_VISIBLE_DEVICES=void` in generated CDI specifications. This prevents the NVIDIA Container Runtime from making additional modifications.
|
|
||||||
|
|
||||||
* [libnvidia-container] Use D3DKMTEnumAdapters3 to enumerate adpaters on WSL2 if available.
|
|
||||||
|
|
||||||
* [toolkit-container] Bump CUDA base image version to 12.4.1
|
|
||||||
|
|
||||||
## v1.15.0-rc.4
|
|
||||||
* Add a `--spec-dir` option to the `nvidia-ctk cdi generate` command. This allows specs outside of `/etc/cdi` and `/var/run/cdi` to be processed.
|
|
||||||
* Add support for extracting device major number from `/proc/devices` if `nvidia` is used as a device name over `nvidia-frontend`.
|
|
||||||
* Allow multiple device naming strategies for `nvidia-ctk cdi generate` command. This allows a single
|
|
||||||
CDI spec to be generated that includes GPUs by index and UUID.
|
|
||||||
* Set the default `--device-name-strategy` for the `nvidia-ctk cdi generate` command to `[index, uuid]`.
|
|
||||||
* Remove `libnvidia-container0` jetpack dependency included for legacy Tegra-based systems.
|
|
||||||
* Add `NVIDIA_VISIBLE_DEVICES=void` to generated CDI specifications.
|
|
||||||
|
|
||||||
* [toolkit-container] Remove centos7 image. The ubi8 image can be used on all RPM-based platforms.
|
|
||||||
* [toolkit-container] Bump CUDA base image version to 12.3.2
|
|
||||||
|
|
||||||
## v1.15.0-rc.3
|
|
||||||
* Fix bug in `nvidia-ctk hook update-ldcache` where default `--ldconfig-path` value was not applied.
|
|
||||||
|
|
||||||
## v1.15.0-rc.2
|
|
||||||
* Extend the `runtime.nvidia.com/gpu` CDI kind to support full-GPUs and MIG devices specified by index or UUID.
|
|
||||||
* Fix bug when specifying `--dev-root` for Tegra-based systems.
|
|
||||||
* Log explicitly requested runtime mode.
|
|
||||||
* Remove package dependency on libseccomp.
|
|
||||||
* Added detection of libnvdxgdmal.so.1 on WSL2
|
|
||||||
* Use devRoot to resolve MIG device nodes.
|
|
||||||
* Fix bug in determining default nvidia-container-runtime.user config value on SUSE-based systems.
|
|
||||||
* Add `crun` to the list of configured low-level runtimes.
|
|
||||||
* Added support for `--ldconfig-path` to `nvidia-ctk cdi generate` command.
|
|
||||||
* Fix `nvidia-ctk runtime configure --cdi.enabled` for Docker.
|
|
||||||
* Add discovery of the GDRCopy device (`gdrdrv`) if the `NVIDIA_GDRCOPY` environment variable of the container is set to `enabled`
|
|
||||||
|
|
||||||
* [toolkit-container] Bump CUDA base image version to 12.3.1.
|
|
||||||
|
|
||||||
## v1.15.0-rc.1
|
|
||||||
* Skip update of ldcache in containers without ldconfig. The .so.SONAME symlinks are still created.
|
|
||||||
* Normalize ldconfig path on use. This automatically adjust the ldconfig setting applied to ldconfig.real on systems where this exists.
|
|
||||||
* Include `nvidia/nvoptix.bin` in list of graphics mounts.
|
|
||||||
* Include `vulkan/icd.d/nvidia_layers.json` in list of graphics mounts.
|
|
||||||
* Add support for `--library-search-paths` to `nvidia-ctk cdi generate` command.
|
|
||||||
* Add support for injecting /dev/nvidia-nvswitch* devices if the NVIDIA_NVSWITCH=enabled envvar is specified.
|
|
||||||
* Added support for `nvidia-ctk runtime configure --enable-cdi` for the `docker` runtime. Note that this requires Docker >= 25.
|
|
||||||
* Fixed bug in `nvidia-ctk config` command when using `--set`. The types of applied config options are now applied correctly.
|
|
||||||
* Add `--relative-to` option to `nvidia-ctk transform root` command. This controls whether the root transformation is applied to host or container paths.
|
|
||||||
* Added automatic CDI spec generation when the `runtime.nvidia.com/gpu=all` device is requested by a container.
|
|
||||||
|
|
||||||
* [libnvidia-container] Fix device permission check when using cgroupv2 (fixes #227)
|
|
||||||
|
|
||||||
## v1.14.3
|
|
||||||
* [toolkit-container] Bump CUDA base image version to 12.2.2.
|
|
||||||
|
|
||||||
## v1.14.2
|
|
||||||
* Fix bug on Tegra-based systems where symlinks were not created in containers.
|
|
||||||
* Add --csv.ignore-pattern command line option to nvidia-ctk cdi generate command.
|
|
||||||
|
|
||||||
## v1.14.1
|
|
||||||
* Fixed bug where contents of `/etc/nvidia-container-runtime/config.toml` is ignored by the NVIDIA Container Runtime Hook.
|
|
||||||
|
|
||||||
* [libnvidia-container] Use libelf.so on RPM-based systems due to removed mageia repositories hosting pmake and bmake.
|
|
||||||
|
|
||||||
## v1.14.0
|
|
||||||
* Promote v1.14.0-rc.3 to v1.14.0
|
|
||||||
|
|
||||||
## v1.14.0-rc.3
|
|
||||||
* Added support for generating OCI hook JSON file to `nvidia-ctk runtime configure` command.
|
|
||||||
* Remove installation of OCI hook JSON from RPM package.
|
|
||||||
* Refactored config for `nvidia-container-runtime-hook`.
|
|
||||||
* Added a `nvidia-ctk config` command which supports setting config options using a `--set` flag.
|
|
||||||
* Added `--library-search-path` option to `nvidia-ctk cdi generate` command in `csv` mode. This allows folders where
|
|
||||||
libraries are located to be specified explicitly.
|
|
||||||
* Updated go-nvlib to support devices which are not present in the PCI device database. This allows the creation of dev/char symlinks on systems with such devices installed.
|
|
||||||
* Added `UsesNVGPUModule` info function for more robust platform detection. This is required on Tegra-based systems where libnvidia-ml.so is also supported.
|
|
||||||
|
|
||||||
* [toolkit-container] Set `NVIDIA_VISIBLE_DEVICES=void` to prevent injection of NVIDIA devices and drivers into the NVIDIA Container Toolkit container.
|
|
||||||
|
|
||||||
## v1.14.0-rc.2
|
|
||||||
* Fix bug causing incorrect nvidia-smi symlink to be created on WSL2 systems with multiple driver roots.
|
|
||||||
* Remove dependency on coreutils when installing package on RPM-based systems.
|
|
||||||
* Create output folders if required when running `nvidia-ctk runtime configure`
|
|
||||||
* Generate default config as post-install step.
|
|
||||||
* Added support for detecting GSP firmware at custom paths when generating CDI specifications.
|
|
||||||
* Added logic to skip the extraction of image requirements if `NVIDIA_DISABLE_REQUIRES` is set to `true`.
|
|
||||||
|
|
||||||
* [libnvidia-container] Include Shared Compiler Library (libnvidia-gpucomp.so) in the list of compute libaries.
|
|
||||||
|
|
||||||
* [toolkit-container] Ensure that common envvars have higher priority when configuring the container engines.
|
|
||||||
* [toolkit-container] Bump CUDA base image version to 12.2.0.
|
|
||||||
* [toolkit-container] Remove installation of nvidia-experimental runtime. This is superceded by the NVIDIA Container Runtime in CDI mode.
|
|
||||||
|
|
||||||
## v1.14.0-rc.1
|
|
||||||
|
|
||||||
* Add support for updating containerd configs to the `nvidia-ctk runtime configure` command.
|
|
||||||
* Create file in `etc/ld.so.conf.d` with permissions `644` to support non-root containers.
|
|
||||||
* Generate CDI specification files with `644` permissions to allow rootless applications (e.g. podman)
|
|
||||||
* Add `nvidia-ctk cdi list` command to show the known CDI devices.
|
|
||||||
* Add support for generating merged devices (e.g. `all` device) to the nvcdi API.
|
|
||||||
* Use *.* pattern to locate libcuda.so when generating a CDI specification to support platforms where a patch version is not specified.
|
|
||||||
* Update go-nvlib to skip devices that are not MIG capable when generating CDI specifications.
|
|
||||||
* Add `nvidia-container-runtime-hook.path` config option to specify NVIDIA Container Runtime Hook path explicitly.
|
|
||||||
* Fix bug in creation of `/dev/char` symlinks by failing operation if kernel modules are not loaded.
|
|
||||||
* Add option to load kernel modules when creating device nodes
|
|
||||||
* Add option to create device nodes when creating `/dev/char` symlinks
|
|
||||||
|
|
||||||
* [libnvidia-container] Support OpenSSL 3 with the Encrypt/Decrypt library
|
|
||||||
|
|
||||||
* [toolkit-container] Allow same envars for all runtime configs
|
|
||||||
|
|
||||||
## v1.13.1
|
|
||||||
|
|
||||||
* Update `update-ldcache` hook to only update ldcache if it exists.
|
|
||||||
* Update `update-ldcache` hook to create `/etc/ld.so.conf.d` folder if it doesn't exist.
|
|
||||||
* Fix failure when libcuda cannot be located during XOrg library discovery.
|
|
||||||
* Fix CDI spec generation on systems that use `/etc/alternatives` (e.g. Debian)
|
|
||||||
|
|
||||||
## v1.13.0
|
|
||||||
|
|
||||||
* Promote 1.13.0-rc.3 to 1.13.0
|
|
||||||
|
|
||||||
## v1.13.0-rc.3
|
|
||||||
|
|
||||||
* Only initialize NVML for modes that require it when runing `nvidia-ctk cdi generate`.
|
|
||||||
* Prefer /run over /var/run when locating nvidia-persistenced and nvidia-fabricmanager sockets.
|
|
||||||
* Fix the generation of CDI specifications for management containers when the driver libraries are not in the LDCache.
|
|
||||||
* Add transformers to deduplicate and simplify CDI specifications.
|
|
||||||
* Generate a simplified CDI specification by default. This means that entities in the common edits in a spec are not included in device definitions.
|
|
||||||
* Also return an error from the nvcdi.New constructor instead of panicing.
|
|
||||||
* Detect XOrg libraries for injection and CDI spec generation.
|
|
||||||
* Add `nvidia-ctk system create-device-nodes` command to create control devices.
|
|
||||||
* Add `nvidia-ctk cdi transform` command to apply transforms to CDI specifications.
|
|
||||||
* Add `--vendor` and `--class` options to `nvidia-ctk cdi generate`
|
|
||||||
|
|
||||||
* [libnvidia-container] Fix segmentation fault when RPC initialization fails.
|
|
||||||
* [libnvidia-container] Build centos variants of the NVIDIA Container Library with static libtirpc v1.3.2.
|
|
||||||
* [libnvidia-container] Remove make targets for fedora35 as the centos8 packages are compatible.
|
|
||||||
|
|
||||||
* [toolkit-container] Add `nvidia-container-runtime.modes.cdi.annotation-prefixes` config option that allows the CDI annotation prefixes that are read to be overridden.
|
|
||||||
* [toolkit-container] Create device nodes when generating CDI specification for management containers.
|
|
||||||
* [toolkit-container] Add `nvidia-container-runtime.runtimes` config option to set the low-level runtime for the NVIDIA Container Runtime
|
|
||||||
|
|
||||||
## v1.13.0-rc.2
|
|
||||||
|
|
||||||
* Don't fail chmod hook if paths are not injected
|
|
||||||
* Only create `by-path` symlinks if CDI devices are actually requested.
|
|
||||||
* Fix possible blank `nvidia-ctk` path in generated CDI specifications
|
|
||||||
* Fix error in postun scriplet on RPM-based systems
|
|
||||||
* Only check `NVIDIA_VISIBLE_DEVICES` for environment variables if no annotations are specified.
|
|
||||||
* Add `cdi.default-kind` config option for constructing fully-qualified CDI device names in CDI mode
|
|
||||||
* Add support for `accept-nvidia-visible-devices-envvar-unprivileged` config setting in CDI mode
|
|
||||||
* Add `nvidia-container-runtime-hook.skip-mode-detection` config option to bypass mode detection. This allows `legacy` and `cdi` mode, for example, to be used at the same time.
|
|
||||||
* Add support for generating CDI specifications for GDS and MOFED devices
|
|
||||||
* Ensure CDI specification is validated on save when generating a spec
|
|
||||||
* Rename `--discovery-mode` argument to `--mode` for `nvidia-ctk cdi generate`
|
|
||||||
* [libnvidia-container] Fix segfault on WSL2 systems
|
|
||||||
* [toolkit-container] Add `--cdi-enabled` flag to toolkit config
|
|
||||||
* [toolkit-container] Install `nvidia-ctk` from toolkit container
|
|
||||||
* [toolkit-container] Use installed `nvidia-ctk` path in NVIDIA Container Toolkit config
|
|
||||||
* [toolkit-container] Bump CUDA base images to 12.1.0
|
|
||||||
* [toolkit-container] Set `nvidia-ctk` path in the
|
|
||||||
* [toolkit-container] Add `cdi.k8s.io/*` to set of allowed annotations in containerd config
|
|
||||||
* [toolkit-container] Generate CDI specification for use in management containers
|
|
||||||
* [toolkit-container] Install experimental runtime as `nvidia-container-runtime.experimental` instead of `nvidia-container-runtime-experimental`
|
|
||||||
* [toolkit-container] Install and configure mode-specific runtimes for `cdi` and `legacy` modes
|
|
||||||
|
|
||||||
## v1.13.0-rc.1
|
|
||||||
|
|
||||||
* Include MIG-enabled devices as GPUs when generating CDI specification
|
|
||||||
* Fix missing NVML symbols when running `nvidia-ctk` on some platforms [#49]
|
|
||||||
* Add CDI spec generation for WSL2-based systems to `nvidia-ctk cdi generate` command
|
|
||||||
* Add `auto` mode to `nvidia-ctk cdi generate` command to automatically detect a WSL2-based system over a standard NVML-based system.
|
|
||||||
* Add mode-specific (`.cdi` and `.legacy`) NVIDIA Container Runtime binaries for use in the GPU Operator
|
|
||||||
* Discover all `gsb*.bin` GSP firmware files when generating CDI specification.
|
|
||||||
* Align `.deb` and `.rpm` release candidate package versions
|
|
||||||
* Remove `fedora35` packaging targets
|
|
||||||
* [libnvidia-container] Include all `gsp*.bin` firmware files if present
|
|
||||||
* [libnvidia-container] Align `.deb` and `.rpm` release candidate package versions
|
|
||||||
* [libnvidia-container] Remove `fedora35` packaging targets
|
|
||||||
* [toolkit-container] Install `nvidia-container-toolkit-operator-extensions` package for mode-specific executables.
|
|
||||||
* [toolkit-container] Allow `nvidia-container-runtime.mode` to be set when configuring the NVIDIA Container Toolkit
|
|
||||||
|
|
||||||
## v1.12.0
|
|
||||||
|
|
||||||
* Promote `v1.12.0-rc.5` to `v1.12.0`
|
|
||||||
* Rename `nvidia cdi generate` `--root` flag to `--driver-root` to better indicate intent
|
|
||||||
* [libnvidia-container] Add nvcubins.bin to DriverStore components under WSL2
|
|
||||||
* [toolkit-container] Bump CUDA base images to 12.0.1
|
|
||||||
|
|
||||||
## v1.12.0-rc.5
|
|
||||||
|
|
||||||
* Fix bug here the `nvidia-ctk` path was not properly resolved. This causes failures to run containers when the runtime is configured in `csv` mode or if the `NVIDIA_DRIVER_CAPABILITIES` includes `graphics` or `display` (e.g. `all`).
|
|
||||||
|
|
||||||
## v1.12.0-rc.4
|
|
||||||
|
|
||||||
* Generate a minimum CDI spec version for improved compatibility.
|
|
||||||
* Add `--device-name-strategy` options to the `nvidia-ctk cdi generate` command that can be used to control how device names are constructed.
|
|
||||||
* Set default for CDI device name generation to `index` to generate device names such as `nvidia.com/gpu=0` or `nvidia.com/gpu=1:0` by default.
|
|
||||||
|
|
||||||
## v1.12.0-rc.3
|
|
||||||
|
|
||||||
* Don't fail if by-path symlinks for DRM devices do not exist
|
|
||||||
* Replace the --json flag with a --format [json|yaml] flag for the nvidia-ctk cdi generate command
|
|
||||||
* Ensure that the CDI output folder is created if required
|
|
||||||
* When generating a CDI specification use a blank host path for devices to ensure compatibility with the v0.4.0 CDI specification
|
|
||||||
* Add injection of Wayland JSON files
|
|
||||||
* Add GSP firmware paths to generated CDI specification
|
|
||||||
* Add --root flag to nvidia-ctk cdi generate command
|
|
||||||
|
|
||||||
## v1.12.0-rc.2
|
|
||||||
|
|
||||||
* Inject Direct Rendering Manager (DRM) devices into a container using the NVIDIA Container Runtime
|
|
||||||
* Improve logging of errors from the NVIDIA Container Runtime
|
|
||||||
* Improve CDI specification generation to support rootless podman
|
|
||||||
* Use `nvidia-ctk cdi generate` to generate CDI specifications instead of `nvidia-ctk info generate-cdi`
|
|
||||||
* [libnvidia-container] Skip creation of existing files when these are already mounted
|
|
||||||
|
|
||||||
## v1.12.0-rc.1
|
|
||||||
|
|
||||||
* Add support for multiple Docker Swarm resources
|
|
||||||
* Improve injection of Vulkan configurations and libraries
|
|
||||||
* Add `nvidia-ctk info generate-cdi` command to generated CDI specification for available devices
|
|
||||||
* [libnvidia-container] Include NVVM compiler library in compute libs
|
|
||||||
|
|
||||||
## v1.11.0
|
|
||||||
|
|
||||||
* Promote v1.11.0-rc.3 to v1.11.0
|
|
||||||
|
|
||||||
## v1.11.0-rc.3
|
|
||||||
|
|
||||||
* Build fedora35 packages
|
|
||||||
* Introduce an `nvidia-container-toolkit-base` package for better dependency management
|
|
||||||
* Fix removal of `nvidia-container-runtime-hook` on RPM-based systems
|
|
||||||
* Inject platform files into container on Tegra-based systems
|
|
||||||
* [toolkit container] Update CUDA base images to 11.7.1
|
|
||||||
* [libnvidia-container] Preload libgcc_s.so.1 on arm64 systems
|
|
||||||
|
|
||||||
## v1.11.0-rc.2
|
|
||||||
|
|
||||||
* Allow `accept-nvidia-visible-devices-*` config options to be set by toolkit container
|
|
||||||
* [libnvidia-container] Fix bug where LDCache was not updated when the `--no-pivot-root` option was specified
|
|
||||||
|
|
||||||
## v1.11.0-rc.1
|
|
||||||
|
|
||||||
* Add discovery of GPUDirect Storage (`nvidia-fs*`) devices if the `NVIDIA_GDS` environment variable of the container is set to `enabled`
|
|
||||||
* Add discovery of MOFED Infiniband devices if the `NVIDIA_MOFED` environment variable of the container is set to `enabled`
|
|
||||||
* Fix bug in CSV mode where libraries listed as `sym` entries in mount specification are not added to the LDCache.
|
|
||||||
* Rename `nvidia-container-toolkit` executable to `nvidia-container-runtime-hook` and create `nvidia-container-toolkit` as a symlink to `nvidia-container-runtime-hook` instead.
|
|
||||||
* Add `nvidia-ctk runtime configure` command to configure the Docker config file (e.g. `/etc/docker/daemon.json`) for use with the NVIDIA Container Runtime.
|
|
||||||
|
|
||||||
## v1.10.0
|
|
||||||
|
|
||||||
* Promote v1.10.0-rc.3 to v1.10.0
|
|
||||||
|
|
||||||
## v1.10.0-rc.3
|
|
||||||
|
|
||||||
* Use default config instead of raising an error if config file cannot be found
|
|
||||||
* Ignore NVIDIA_REQUIRE_JETPACK* environment variables for requirement checks
|
|
||||||
* Fix bug in detection of Tegra systems where `/sys/devices/soc0/family` is ignored
|
|
||||||
* Fix bug where links to devices were detected as devices
|
|
||||||
* [libnvida-container] Fix bug introduced when adding libcudadebugger.so to list of libraries
|
|
||||||
|
|
||||||
## v1.10.0-rc.2
|
|
||||||
|
|
||||||
* Add support for NVIDIA_REQUIRE_* checks for cuda version and arch to csv mode
|
|
||||||
* Switch to debug logging to reduce log verbosity
|
|
||||||
* Support logging to logs requested in command line
|
|
||||||
* Fix bug when launching containers with relative root path (e.g. using containerd)
|
|
||||||
* Allow low-level runtime path to be set explicitly as nvidia-container-runtime.runtimes option
|
|
||||||
* Fix failure to locate low-level runtime if PATH envvar is unset
|
|
||||||
* Replace experimental option for NVIDIA Container Runtime with nvidia-container-runtime.mode = csv option
|
|
||||||
* Use csv as default mode on Tegra systems without NVML
|
|
||||||
* Add --version flag to all CLIs
|
|
||||||
* [libnvidia-container] Bump libtirpc to 1.3.2
|
|
||||||
* [libnvidia-container] Fix bug when running host ldconfig using glibc compiled with a non-standard prefix
|
|
||||||
* [libnvidia-container] Add libcudadebugger.so to list of compute libraries
|
|
||||||
|
|
||||||
## v1.10.0-rc.1
|
|
||||||
|
|
||||||
* Include nvidia-ctk CLI in installed binaries
|
|
||||||
* Add experimental option to NVIDIA Container Runtime
|
|
||||||
|
|
||||||
## v1.9.0
|
|
||||||
|
|
||||||
* [libnvidia-container] Add additional check for Tegra in /sys/.../family file in CLI
|
|
||||||
* [libnvidia-container] Update jetpack-specific CLI option to only load Base CSV files by default
|
|
||||||
* [libnvidia-container] Fix bug (from 1.8.0) when mounting GSP firmware into containers without /lib to /usr/lib symlinks
|
|
||||||
* [libnvidia-container] Update nvml.h to CUDA 11.6.1 nvML_DEV 11.6.55
|
|
||||||
* [libnvidia-container] Update switch statement to include new brands from latest nvml.h
|
|
||||||
* [libnvidia-container] Process all --require flags on Jetson platforms
|
|
||||||
* [libnvidia-container] Fix long-standing issue with running ldconfig on Debian systems
|
|
||||||
|
|
||||||
## v1.8.1
|
|
||||||
|
|
||||||
* [libnvidia-container] Fix bug in determining cgroup root when running in nested containers
|
|
||||||
* [libnvidia-container] Fix permission issue when determining cgroup version
|
|
||||||
|
|
||||||
## v1.8.0
|
|
||||||
|
|
||||||
* Promote 1.8.0-rc.2-1 to 1.8.0
|
|
||||||
|
|
||||||
## v1.8.0-rc.2
|
|
||||||
|
|
||||||
* Remove support for building amazonlinux1 packages
|
|
||||||
|
|
||||||
## v1.8.0-rc.1
|
|
||||||
|
|
||||||
* [libnvidia-container] Add support for cgroupv2
|
|
||||||
* Release toolkit-container images from nvidia-container-toolkit repository
|
|
||||||
|
|
||||||
## v1.7.0
|
|
||||||
|
|
||||||
* Promote 1.7.0-rc.1-1 to 1.7.0
|
|
||||||
* Bump Golang version to 1.16.4
|
|
||||||
|
|
||||||
## v1.7.0-rc.1
|
|
||||||
|
|
||||||
* Specify containerd runtime type as string in config tools to remove dependency on containerd package
|
|
||||||
* Add supported-driver-capabilities config option to allow for a subset of all driver capabilities to be specified
|
|
||||||
|
|
||||||
## v1.6.0
|
|
||||||
|
|
||||||
* Promote 1.6.0-rc.3-1 to 1.6.0
|
|
||||||
* Fix unnecessary logging to stderr instead of configured nvidia-container-runtime log file
|
|
||||||
|
|
||||||
## v1.6.0-rc.3
|
|
||||||
|
|
||||||
* Add supported-driver-capabilities config option to the nvidia-container-toolkit
|
|
||||||
* Move OCI and command line checks for runtime to internal oci package
|
|
||||||
|
|
||||||
## v1.6.0-rc.2
|
|
||||||
|
|
||||||
* Use relative path to OCI specification file (config.json) if bundle path is not specified as an argument to the nvidia-container-runtime
|
|
||||||
|
|
||||||
## v1.6.0-rc.1
|
|
||||||
|
|
||||||
* Add AARCH64 package for Amazon Linux 2
|
|
||||||
* Include nvidia-container-runtime into nvidia-container-toolkit package
|
|
||||||
|
|
||||||
## v1.5.1
|
|
||||||
|
|
||||||
* Fix bug where Docker Swarm device selection is ignored if NVIDIA_VISIBLE_DEVICES is also set
|
|
||||||
* Improve unit testing by using require package and adding coverage reports
|
|
||||||
* Remove unneeded go dependencies by running go mod tidy
|
|
||||||
* Move contents of pkg directory to cmd for CLI tools
|
|
||||||
* Ensure make binary target explicitly sets GOOS
|
|
||||||
|
|
||||||
## v1.5.0
|
|
||||||
|
|
||||||
* Add dependence on libnvidia-container-tools >= 1.4.0
|
|
||||||
* Add golang check targets to Makefile
|
|
||||||
* Add Jenkinsfile definition for build targets
|
|
||||||
* Move docker.mk to docker folder
|
|
||||||
|
|
||||||
## v1.4.2
|
|
||||||
|
|
||||||
* Add dependence on libnvidia-container-tools >= 1.3.3
|
|
||||||
|
|
||||||
## v1.4.1
|
|
||||||
|
|
||||||
* Ignore NVIDIA_VISIBLE_DEVICES for containers with insufficent privileges
|
|
||||||
* Add dependence on libnvidia-container-tools >= 1.3.2
|
|
||||||
|
|
||||||
## v1.4.0
|
|
||||||
|
|
||||||
* Add 'compute' capability to list of defaults
|
|
||||||
* Add dependence on libnvidia-container-tools >= 1.3.1
|
|
||||||
|
|
||||||
## v1.3.0
|
|
||||||
|
|
||||||
* Promote 1.3.0-rc.2-1 to 1.3.0
|
|
||||||
* Add dependence on libnvidia-container-tools >= 1.3.0
|
|
||||||
|
|
||||||
## v1.3.0-rc.2
|
|
||||||
|
|
||||||
* 2c180947 Add more tests for new semantics with device list from volume mounts
|
|
||||||
* 7c003857 Refactor accepting device lists from volume mounts as a boolean
|
|
||||||
|
|
||||||
## v1.3.0-rc.1
|
|
||||||
|
|
||||||
* b50d86c1 Update build system to accept a TAG variable for things like rc.x
|
|
||||||
* fe65573b Add common CI tests for things like golint, gofmt, unit tests, etc.
|
|
||||||
* da6fbb34 Revert "Add ability to merge envars of the form NVIDIA_VISIBLE_DEVICES_*"
|
|
||||||
* a7fb3330 Flip build-all targets to run automatically on merge requests
|
|
||||||
* 8b248b66 Rename github.com/NVIDIA/container-toolkit to nvidia-container-toolkit
|
|
||||||
* da36874e Add new config options to pull device list from mounted files instead of ENVVAR
|
|
||||||
|
|
||||||
## v1.2.1
|
|
||||||
|
|
||||||
* 4e6e0ed4 Add 'ngx' to list of*all* driver capabilities
|
|
||||||
* 2f4af743 List config.toml as a config file in the RPM SPEC
|
|
||||||
|
|
||||||
## v1.2.0
|
|
||||||
|
|
||||||
* 8e0aab46 Fix repo listed in changelog for debian distributions
|
|
||||||
* 320bb6e4 Update dependence on libnvidia-container to 1.2.0
|
|
||||||
* 6cfc8097 Update package license to match source license
|
|
||||||
* e7dc3cbb Fix debian copyright file
|
|
||||||
* d3aee3e0 Add the 'ngx' driver capability
|
|
||||||
|
|
||||||
## v1.1.2
|
|
||||||
|
|
||||||
* c32237f3 Add support for parsing Linux Capabilities for older OCI specs
|
|
||||||
|
|
||||||
## v1.1.1
|
|
||||||
|
|
||||||
* d202aded Update dependence to libnvidia-container 1.1.1
|
|
||||||
|
|
||||||
## v1.1.0
|
|
||||||
|
|
||||||
* 4e4de762 Update build system to support multi-arch builds
|
|
||||||
* fcc1d116 Add support for MIG (Multi-Instance GPUs)
|
|
||||||
* d4ff0416 Add ability to merge envars of the form NVIDIA_VISIBLE_DEVICES_*
|
|
||||||
* 60f165ad Add no-pivot option to toolkit
|
|
||||||
|
|
||||||
## v1.0.5
|
|
||||||
|
|
||||||
* Initial release. Replaces older package nvidia-container-runtime-hook. (Closes: #XXXXXX)
|
|
||||||
@@ -13,15 +13,13 @@ The `nvidia-container-toolkit` resides in this repo directly.
|
|||||||
|
|
||||||
In oder to build the packages, the following command is executed
|
In oder to build the packages, the following command is executed
|
||||||
```sh
|
```sh
|
||||||
./scripts/build-packages.sh TARGET
|
./scripts/build-all-components.sh TARGET
|
||||||
```
|
```
|
||||||
where `TARGET` is a make target that is valid for each of the sub-components.
|
where `TARGET` is a make target that is valid for each of the sub-components.
|
||||||
|
|
||||||
These include:
|
These include:
|
||||||
* `ubuntu18.04-amd64`
|
* `ubuntu18.04-amd64`
|
||||||
* `centos7-x86_64`
|
* `centos8-x86_64`
|
||||||
|
|
||||||
If no `TARGET` is specified, all valid release targets are built.
|
|
||||||
|
|
||||||
The packages are generated in the `dist` folder.
|
The packages are generated in the `dist` folder.
|
||||||
|
|
||||||
@@ -34,28 +32,14 @@ environment variables.
|
|||||||
|
|
||||||
## Testing packages locally
|
## Testing packages locally
|
||||||
|
|
||||||
The [tests/release](./tests/release/) folder contains documentation on how the installation of local or staged packages can be tested.
|
The [test/release](./test/release/) folder contains documentation on how the installation of local or staged packages can be tested.
|
||||||
|
|
||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
In order to release packages required for a release, a utility script
|
A utility script [`scripts/release.sh`](./scripts/release.sh) is provided to build
|
||||||
[`scripts/release-packages.sh`](./scripts/release-packages.sh) is provided.
|
packages required for release. If run without arguments, all supported distribution-architecture combinations are built. A specific distribution-architecture pair can also be provided
|
||||||
This script can be executed as follows:
|
```sh
|
||||||
|
./scripts/release.sh ubuntu18.04-amd64
|
||||||
```bash
|
|
||||||
GPG_LOCAL_USER="GPG_USER" \
|
|
||||||
MASTER_KEY_PATH=/path/to/gpg-master.key \
|
|
||||||
SUB_KEY_PATH=/path/to/gpg-subkey.key \
|
|
||||||
./scripts/release-packages.sh REPO PACKAGE_REPO_ROOT [REFERENCE]
|
|
||||||
```
|
```
|
||||||
|
where the `amd64` builds for `ubuntu18.04` are provided as an example.
|
||||||
Where `REPO` is one of `stable` or `experimental`, `PACKAGE_REPO_ROOT` is the local path to the `libnvidia-container` repository checked out to the `gh-pages` branch, and `REFERENCE` is the git SHA that is to be released. If reference is not specified `HEAD` is assumed.
|
|
||||||
|
|
||||||
This scripts performs the following basic functions:
|
|
||||||
* Pulls the package image defined by the `REFERENCE` git SHA from the staging registry,
|
|
||||||
* Copies the required packages to the package repository at `PACKAGE_REPO_ROOT/REPO`,
|
|
||||||
* Signs the packages using the specified GPG keys
|
|
||||||
|
|
||||||
While the last two are performed, commits are added to the package repository. These can be pushed to the relevant repository.
|
|
||||||
|
|
||||||
|
|||||||
142
Jenkinsfile
vendored
Normal file
142
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
podTemplate (cloud:'sw-gpu-cloudnative',
|
||||||
|
containers: [
|
||||||
|
containerTemplate(name: 'docker', image: 'docker:dind', ttyEnabled: true, privileged: true),
|
||||||
|
containerTemplate(name: 'golang', image: 'golang:1.16.3', ttyEnabled: true)
|
||||||
|
]) {
|
||||||
|
node(POD_LABEL) {
|
||||||
|
def scmInfo
|
||||||
|
|
||||||
|
stage('checkout') {
|
||||||
|
scmInfo = checkout(scm)
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('dependencies') {
|
||||||
|
container('golang') {
|
||||||
|
sh 'GO111MODULE=off go get -u github.com/client9/misspell/cmd/misspell'
|
||||||
|
sh 'GO111MODULE=off go get -u github.com/gordonklaus/ineffassign'
|
||||||
|
sh 'GO111MODULE=off go get -u golang.org/x/lint/golint'
|
||||||
|
}
|
||||||
|
container('docker') {
|
||||||
|
sh 'apk add --no-cache make bash git'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('check') {
|
||||||
|
parallel (
|
||||||
|
getGolangStages(["assert-fmt", "lint", "vet", "ineffassign", "misspell"])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
stage('test') {
|
||||||
|
parallel (
|
||||||
|
getGolangStages(["test"])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def versionInfo
|
||||||
|
stage('version') {
|
||||||
|
container('docker') {
|
||||||
|
versionInfo = getVersionInfo(scmInfo)
|
||||||
|
println "versionInfo=${versionInfo}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def dist = 'ubuntu20.04'
|
||||||
|
def arch = 'amd64'
|
||||||
|
def stageLabel = "${dist}-${arch}"
|
||||||
|
|
||||||
|
stage('build-one') {
|
||||||
|
container('docker') {
|
||||||
|
stage (stageLabel) {
|
||||||
|
sh "make ${dist}-${arch}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('release') {
|
||||||
|
container('docker') {
|
||||||
|
stage (stageLabel) {
|
||||||
|
|
||||||
|
def component = 'main'
|
||||||
|
def repository = 'sw-gpu-cloudnative-debian-local/pool/main/'
|
||||||
|
|
||||||
|
def uploadSpec = """{
|
||||||
|
"files":
|
||||||
|
[ {
|
||||||
|
"pattern": "./dist/${dist}/${arch}/*.deb",
|
||||||
|
"target": "${repository}",
|
||||||
|
"props": "deb.distribution=${dist};deb.component=${component};deb.architecture=${arch}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
sh "echo starting release with versionInfo=${versionInfo}"
|
||||||
|
if (versionInfo.isTag) {
|
||||||
|
// upload to artifactory repository
|
||||||
|
def server = Artifactory.server 'sw-gpu-artifactory'
|
||||||
|
server.upload spec: uploadSpec
|
||||||
|
} else {
|
||||||
|
sh "echo skipping release for non-tagged build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getGolangStages(def targets) {
|
||||||
|
stages = [:]
|
||||||
|
|
||||||
|
for (t in targets) {
|
||||||
|
stages[t] = getLintClosure(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stages
|
||||||
|
}
|
||||||
|
|
||||||
|
def getLintClosure(def target) {
|
||||||
|
return {
|
||||||
|
container('golang') {
|
||||||
|
stage(target) {
|
||||||
|
sh "make ${target}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVersionInfo returns a hash of version info
|
||||||
|
def getVersionInfo(def scmInfo) {
|
||||||
|
def versionInfo = [
|
||||||
|
isTag: isTag(scmInfo.GIT_BRANCH)
|
||||||
|
]
|
||||||
|
|
||||||
|
scmInfo.each { k, v -> versionInfo[k] = v }
|
||||||
|
return versionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
def isTag(def branch) {
|
||||||
|
if (!branch.startsWith('v')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
def version = shOutput('git describe --all --exact-match --always')
|
||||||
|
return version == "tags/${branch}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def shOuptut(def script) {
|
||||||
|
return sh(script: script, returnStdout: true).trim()
|
||||||
|
}
|
||||||
134
Makefile
134
Makefile
@@ -38,41 +38,32 @@ EXAMPLE_TARGETS := $(patsubst %,example-%, $(EXAMPLES))
|
|||||||
CMDS := $(patsubst ./cmd/%/,%,$(sort $(dir $(wildcard ./cmd/*/))))
|
CMDS := $(patsubst ./cmd/%/,%,$(sort $(dir $(wildcard ./cmd/*/))))
|
||||||
CMD_TARGETS := $(patsubst %,cmd-%, $(CMDS))
|
CMD_TARGETS := $(patsubst %,cmd-%, $(CMDS))
|
||||||
|
|
||||||
CHECK_TARGETS := lint
|
$(info CMD_TARGETS=$(CMD_TARGETS))
|
||||||
MAKE_TARGETS := binaries build check fmt test examples cmds coverage generate licenses vendor check-vendor $(CHECK_TARGETS)
|
|
||||||
|
CHECK_TARGETS := assert-fmt vet lint ineffassign misspell
|
||||||
|
MAKE_TARGETS := binaries build check fmt lint-internal test examples cmds coverage generate $(CHECK_TARGETS)
|
||||||
|
|
||||||
TARGETS := $(MAKE_TARGETS) $(EXAMPLE_TARGETS) $(CMD_TARGETS)
|
TARGETS := $(MAKE_TARGETS) $(EXAMPLE_TARGETS) $(CMD_TARGETS)
|
||||||
|
|
||||||
DOCKER_TARGETS := $(patsubst %,docker-%, $(TARGETS))
|
DOCKER_TARGETS := $(patsubst %,docker-%, $(TARGETS))
|
||||||
.PHONY: $(TARGETS) $(DOCKER_TARGETS)
|
.PHONY: $(TARGETS) $(DOCKER_TARGETS)
|
||||||
|
|
||||||
ifeq ($(VERSION),)
|
GOOS ?= linux
|
||||||
CLI_VERSION = $(LIB_VERSION)$(if $(LIB_TAG),-$(LIB_TAG))
|
|
||||||
else
|
|
||||||
CLI_VERSION = $(VERSION)
|
|
||||||
endif
|
|
||||||
CLI_VERSION_PACKAGE = github.com/NVIDIA/nvidia-container-toolkit/internal/info
|
|
||||||
|
|
||||||
binaries: cmds
|
binaries: cmds
|
||||||
ifneq ($(PREFIX),)
|
ifneq ($(PREFIX),)
|
||||||
cmd-%: COMMAND_BUILD_OPTIONS = -o $(PREFIX)/$(*)
|
cmd-%: COMMAND_BUILD_OPTIONS = -o $(PREFIX)/$(*)
|
||||||
endif
|
endif
|
||||||
cmds: $(CMD_TARGETS)
|
cmds: $(CMD_TARGETS)
|
||||||
|
|
||||||
ifneq ($(shell uname),Darwin)
|
|
||||||
EXTLDFLAGS = -Wl,--export-dynamic -Wl,--unresolved-symbols=ignore-in-object-files -Wl,-z,lazy
|
|
||||||
else
|
|
||||||
EXTLDFLAGS = -Wl,-undefined,dynamic_lookup
|
|
||||||
endif
|
|
||||||
$(CMD_TARGETS): cmd-%:
|
$(CMD_TARGETS): cmd-%:
|
||||||
go build -ldflags "-s -w '-extldflags=$(EXTLDFLAGS)' -X $(CLI_VERSION_PACKAGE).gitCommit=$(GIT_COMMIT) -X $(CLI_VERSION_PACKAGE).version=$(CLI_VERSION)" $(COMMAND_BUILD_OPTIONS) $(MODULE)/cmd/$(*)
|
GOOS=$(GOOS) go build -ldflags "-s -w" $(COMMAND_BUILD_OPTIONS) $(MODULE)/cmd/$(*)
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build ./...
|
GOOS=$(GOOS) go build ./...
|
||||||
|
|
||||||
examples: $(EXAMPLE_TARGETS)
|
examples: $(EXAMPLE_TARGETS)
|
||||||
$(EXAMPLE_TARGETS): example-%:
|
$(EXAMPLE_TARGETS): example-%:
|
||||||
go build ./examples/$(*)
|
GOOS=$(GOOS) go build ./examples/$(*)
|
||||||
|
|
||||||
all: check test build binary
|
all: check test build binary
|
||||||
check: $(CHECK_TARGETS)
|
check: $(CHECK_TARGETS)
|
||||||
@@ -82,89 +73,72 @@ fmt:
|
|||||||
go list -f '{{.Dir}}' $(MODULE)/... \
|
go list -f '{{.Dir}}' $(MODULE)/... \
|
||||||
| xargs gofmt -s -l -w
|
| xargs gofmt -s -l -w
|
||||||
|
|
||||||
# Apply goimports -local github.com/NVIDIA/container-toolkit to the codebase
|
assert-fmt:
|
||||||
goimports:
|
go list -f '{{.Dir}}' $(MODULE)/... \
|
||||||
go list -f {{.Dir}} $(MODULE)/... \
|
| xargs gofmt -s -l > fmt.out
|
||||||
| xargs goimports -local $(MODULE) -w
|
@if [ -s fmt.out ]; then \
|
||||||
|
echo "\nERROR: The following files are not formatted:\n"; \
|
||||||
|
cat fmt.out; \
|
||||||
|
rm fmt.out; \
|
||||||
|
exit 1; \
|
||||||
|
else \
|
||||||
|
rm fmt.out; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
ineffassign:
|
||||||
|
ineffassign $(MODULE)/...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run ./...
|
# We use `go list -f '{{.Dir}}' $(MODULE)/...` to skip the `vendor` folder.
|
||||||
|
go list -f '{{.Dir}}' $(MODULE)/... | grep -v /internal/ | xargs golint -set_exit_status
|
||||||
|
|
||||||
vendor: | mod-tidy mod-vendor mod-verify
|
lint-internal:
|
||||||
|
# We use `go list -f '{{.Dir}}' $(MODULE)/...` to skip the `vendor` folder.
|
||||||
|
go list -f '{{.Dir}}' $(MODULE)/internal/... | xargs golint -set_exit_status
|
||||||
|
|
||||||
mod-tidy:
|
misspell:
|
||||||
@for mod in $$(find . -name go.mod -not -path "./testdata/*" -not -path "./third_party/*"); do \
|
misspell $(MODULE)/...
|
||||||
echo "Tidying $$mod..."; ( \
|
|
||||||
cd $$(dirname $$mod) && go mod tidy \
|
|
||||||
) || exit 1; \
|
|
||||||
done
|
|
||||||
|
|
||||||
mod-vendor:
|
vet:
|
||||||
@for mod in $$(find . -name go.mod -not -path "./testdata/*" -not -path "./third_party/*" -not -path "./deployments/*"); do \
|
go vet $(MODULE)/...
|
||||||
echo "Vendoring $$mod..."; ( \
|
|
||||||
cd $$(dirname $$mod) && go mod vendor \
|
|
||||||
) || exit 1; \
|
|
||||||
done
|
|
||||||
|
|
||||||
mod-verify:
|
|
||||||
@for mod in $$(find . -name go.mod -not -path "./testdata/*" -not -path "./third_party/*"); do \
|
|
||||||
echo "Verifying $$mod..."; ( \
|
|
||||||
cd $$(dirname $$mod) && go mod verify | sed 's/^/ /g' \
|
|
||||||
) || exit 1; \
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
check-vendor: vendor
|
|
||||||
git diff --exit-code HEAD -- go.mod go.sum vendor
|
|
||||||
|
|
||||||
licenses:
|
|
||||||
go-licenses csv $(MODULE)/...
|
|
||||||
|
|
||||||
COVERAGE_FILE := coverage.out
|
COVERAGE_FILE := coverage.out
|
||||||
test: build cmds
|
test: build cmds
|
||||||
go test -coverprofile=$(COVERAGE_FILE).with-mocks $(MODULE)/...
|
go test -v -coverprofile=$(COVERAGE_FILE) $(MODULE)/...
|
||||||
|
|
||||||
coverage: test
|
coverage: test
|
||||||
cat $(COVERAGE_FILE).with-mocks | grep -v "_mock.go" > $(COVERAGE_FILE)
|
cat $(COVERAGE_FILE) | grep -v "_mock.go" > $(COVERAGE_FILE).no-mocks
|
||||||
go tool cover -func=$(COVERAGE_FILE)
|
go tool cover -func=$(COVERAGE_FILE).no-mocks
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
go generate $(MODULE)/...
|
go generate $(MODULE)/...
|
||||||
|
|
||||||
# Generate an image for containerized builds
|
# Generate an image for containerized builds
|
||||||
# Note: This image is local only
|
# Note: This image is local only
|
||||||
.PHONY: .build-image
|
.PHONY: .build-image .pull-build-image .push-build-image
|
||||||
.build-image:
|
.build-image: docker/Dockerfile.devel
|
||||||
make -f deployments/devel/Makefile .build-image
|
if [ x"$(SKIP_IMAGE_BUILD)" = x"" ]; then \
|
||||||
|
$(DOCKER) build \
|
||||||
|
--progress=plain \
|
||||||
|
--build-arg GOLANG_VERSION="$(GOLANG_VERSION)" \
|
||||||
|
--tag $(BUILDIMAGE) \
|
||||||
|
-f $(^) \
|
||||||
|
docker; \
|
||||||
|
fi
|
||||||
|
|
||||||
ifeq ($(BUILD_DEVEL_IMAGE),yes)
|
.pull-build-image:
|
||||||
$(DOCKER_TARGETS): .build-image
|
$(DOCKER) pull $(BUILDIMAGE)
|
||||||
.shell: .build-image
|
|
||||||
endif
|
|
||||||
|
|
||||||
$(DOCKER_TARGETS): docker-%:
|
.push-build-image:
|
||||||
@echo "Running 'make $(*)' in container image $(BUILDIMAGE)"
|
$(DOCKER) push $(BUILDIMAGE)
|
||||||
|
|
||||||
|
$(DOCKER_TARGETS): docker-%: .build-image
|
||||||
|
@echo "Running 'make $(*)' in docker container $(BUILDIMAGE)"
|
||||||
$(DOCKER) run \
|
$(DOCKER) run \
|
||||||
--rm \
|
--rm \
|
||||||
-e GOCACHE=/tmp/.cache/go \
|
-e GOCACHE=/tmp/.cache \
|
||||||
-e GOMODCACHE=/tmp/.cache/gomod \
|
-v $(PWD):$(PWD) \
|
||||||
-e GOLANGCI_LINT_CACHE=/tmp/.cache/golangci-lint \
|
-w $(PWD) \
|
||||||
-v $(PWD):/work \
|
|
||||||
-w /work \
|
|
||||||
--user $$(id -u):$$(id -g) \
|
--user $$(id -u):$$(id -g) \
|
||||||
$(BUILDIMAGE) \
|
$(BUILDIMAGE) \
|
||||||
make $(*)
|
make $(*)
|
||||||
|
|
||||||
# Start an interactive shell using the development image.
|
|
||||||
PHONY: .shell
|
|
||||||
.shell:
|
|
||||||
$(DOCKER) run \
|
|
||||||
--rm \
|
|
||||||
-ti \
|
|
||||||
-e GOCACHE=/tmp/.cache/go \
|
|
||||||
-e GOMODCACHE=/tmp/.cache/gomod \
|
|
||||||
-e GOLANGCI_LINT_CACHE=/tmp/.cache/golangci-lint \
|
|
||||||
-v $(PWD):/work \
|
|
||||||
-w /work \
|
|
||||||
--user $$(id -u):$$(id -g) \
|
|
||||||
$(BUILDIMAGE)
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# NVIDIA Container Toolkit
|
# NVIDIA Container Toolkit
|
||||||
|
|
||||||
[](https://raw.githubusercontent.com/NVIDIA/nvidia-container-toolkit/main/LICENSE)
|
[](https://raw.githubusercontent.com/NVIDIA/nvidia-container-toolkit/master/LICENSE)
|
||||||
[](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/overview.html)
|
[](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/overview.html)
|
||||||
[](https://nvidia.github.io/libnvidia-container)
|
[](https://nvidia.github.io/libnvidia-container)
|
||||||
|
|
||||||
|
|||||||
36
RELEASE.md
36
RELEASE.md
@@ -1,36 +0,0 @@
|
|||||||
# Release Process
|
|
||||||
|
|
||||||
The NVIDIA Container Toolkit consists of the following artifacts:
|
|
||||||
- The NVIDIA Container Toolkit container
|
|
||||||
- Packages for debian-based systems
|
|
||||||
- Packages for rpm-based systems
|
|
||||||
|
|
||||||
# Release Process Checklist:
|
|
||||||
- [ ] Create a release PR:
|
|
||||||
- [ ] Run the `./hack/prepare-release.sh` script to update the version in all the needed files. This also creates a [release issue](https://github.com/NVIDIA/cloud-native-team/issues?q=is%3Aissue+is%3Aopen+label%3Arelease)
|
|
||||||
- [ ] Run the `./hack/generate-changelog.sh` script to generate the a draft changelog and update `CHANGELOG.md` with the changes.
|
|
||||||
- [ ] Create a PR from the created `bump-release-{{ .VERSION }}` branch.
|
|
||||||
- [ ] Merge the release PR
|
|
||||||
- [ ] Tag the release and push the tag to the `internal` mirror:
|
|
||||||
- [ ] Image release pipeline: https://gitlab-master.nvidia.com/dl/container-dev/container-toolkit/-/pipelines/16466098
|
|
||||||
- [ ] Wait for the image release to complete.
|
|
||||||
- [ ] Push the tag to the the upstream GitHub repo.
|
|
||||||
- [ ] Wait for the [`Release`](https://github.com/NVIDIA/k8s-device-plugin/actions/workflows/release.yaml) GitHub Action to complete
|
|
||||||
- [ ] Publish the [draft release](https://github.com/NVIDIA/k8s-device-plugin/releases) created by the GitHub Action
|
|
||||||
- [ ] Publish the packages to the gh-pages branch of the libnvidia-container repo
|
|
||||||
- [ ] Create a KitPick
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
*Note*: This assumes that we have the release tag checked out locally.
|
|
||||||
|
|
||||||
- If the `Release` GitHub Action fails:
|
|
||||||
- Check the logs for the error first.
|
|
||||||
- Create the helm packages locally by running:
|
|
||||||
```bash
|
|
||||||
./hack/prepare-artifacts.sh {{ .VERSION }}
|
|
||||||
```
|
|
||||||
- Create the draft release by running:
|
|
||||||
```bash
|
|
||||||
./hack/create-release.sh {{ .VERSION }}
|
|
||||||
```
|
|
||||||
24
SECURITY.md
24
SECURITY.md
@@ -1,24 +0,0 @@
|
|||||||
# Security
|
|
||||||
|
|
||||||
NVIDIA is dedicated to the security and trust of our software products and services, including all source code repositories managed through our organization.
|
|
||||||
|
|
||||||
If you need to report a security issue, please use the appropriate contact points outlined below. **Please do not report security vulnerabilities through GitHub.**
|
|
||||||
|
|
||||||
## Reporting Potential Security Vulnerability in an NVIDIA Product
|
|
||||||
|
|
||||||
To report a potential security vulnerability in any NVIDIA product:
|
|
||||||
- Web: [Security Vulnerability Submission Form](https://www.nvidia.com/object/submit-security-vulnerability.html)
|
|
||||||
- E-Mail: psirt@nvidia.com
|
|
||||||
- We encourage you to use the following PGP key for secure email communication: [NVIDIA public PGP Key for communication](https://www.nvidia.com/en-us/security/pgp-key)
|
|
||||||
- Please include the following information:
|
|
||||||
- Product/Driver name and version/branch that contains the vulnerability
|
|
||||||
- Type of vulnerability (code execution, denial of service, buffer overflow, etc.)
|
|
||||||
- Instructions to reproduce the vulnerability
|
|
||||||
- Proof-of-concept or exploit code
|
|
||||||
- Potential impact of the vulnerability, including how an attacker could exploit the vulnerability
|
|
||||||
|
|
||||||
While NVIDIA currently does not have a bug bounty program, we do offer acknowledgement when an externally reported security issue is addressed under our coordinated vulnerability disclosure policy. Please visit our [Product Security Incident Response Team (PSIRT)](https://www.nvidia.com/en-us/security/psirt-policies/) policies page for more information.
|
|
||||||
|
|
||||||
## NVIDIA Product Security
|
|
||||||
|
|
||||||
For all security-related concerns, please visit NVIDIA's Product Security portal at https://www.nvidia.com/en-us/security
|
|
||||||
85
build/container/Dockerfile.centos
Normal file
85
build/container/Dockerfile.centos
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
ARG BASE_DIST
|
||||||
|
ARG CUDA_VERSION
|
||||||
|
ARG GOLANG_VERSION=x.x.x
|
||||||
|
ARG VERSION="N/A"
|
||||||
|
|
||||||
|
# NOTE: In cases where the libc version is a concern, we would have to use an
|
||||||
|
# image based on the target OS to build the golang executables here -- especially
|
||||||
|
# if cgo code is included.
|
||||||
|
FROM golang:${GOLANG_VERSION} as build
|
||||||
|
|
||||||
|
# We override the GOPATH to ensure that the binaries are installed to
|
||||||
|
# /artifacts/bin
|
||||||
|
ARG GOPATH=/artifacts
|
||||||
|
|
||||||
|
# Install the experiemental nvidia-container-runtime
|
||||||
|
# NOTE: This will be integrated into the nvidia-container-toolkit package / repo
|
||||||
|
ARG NVIDIA_CONTAINER_RUNTIME_EXPERIMENTAL_VERSION=experimental
|
||||||
|
RUN GOPATH=/artifacts go install github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-container-runtime.experimental@${NVIDIA_CONTAINER_RUNTIME_EXPERIMENTAL_VERSION}
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# NOTE: Until the config utilities are properly integrated into the
|
||||||
|
# nvidia-container-toolkit repository, these are built from the `tools` folder
|
||||||
|
# and not `cmd`.
|
||||||
|
RUN GOPATH=/artifacts go install -ldflags="-s -w -X 'main.Version=${VERSION}'" ./tools/...
|
||||||
|
|
||||||
|
|
||||||
|
FROM nvidia/cuda:${CUDA_VERSION}-base-${BASE_DIST}
|
||||||
|
|
||||||
|
ENV NVIDIA_DISABLE_REQUIRE="true"
|
||||||
|
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
ENV NVIDIA_DRIVER_CAPABILITIES=utility
|
||||||
|
|
||||||
|
ARG ARTIFACTS_ROOT
|
||||||
|
ARG PACKAGE_DIST
|
||||||
|
COPY ${ARTIFACTS_ROOT}/${PACKAGE_DIST} /artifacts/packages/${PACKAGE_DIST}
|
||||||
|
|
||||||
|
WORKDIR /artifacts/packages
|
||||||
|
|
||||||
|
ARG PACKAGE_VERSION
|
||||||
|
ARG PACKAGE_ARCH
|
||||||
|
RUN yum localinstall -y \
|
||||||
|
${PACKAGE_DIST}/${PACKAGE_ARCH}/libnvidia-container1-${PACKAGE_VERSION}*.rpm \
|
||||||
|
${PACKAGE_DIST}/${PACKAGE_ARCH}/libnvidia-container-tools-${PACKAGE_VERSION}*.rpm \
|
||||||
|
${PACKAGE_DIST}/${PACKAGE_ARCH}/nvidia-container-toolkit-${PACKAGE_VERSION}*.rpm
|
||||||
|
|
||||||
|
WORKDIR /work
|
||||||
|
|
||||||
|
COPY --from=build /artifacts/bin /work
|
||||||
|
|
||||||
|
ENV PATH=/work:$PATH
|
||||||
|
|
||||||
|
LABEL io.k8s.display-name="NVIDIA Container Runtime Config"
|
||||||
|
LABEL name="NVIDIA Container Runtime Config"
|
||||||
|
LABEL vendor="NVIDIA"
|
||||||
|
LABEL version="${VERSION}"
|
||||||
|
LABEL release="N/A"
|
||||||
|
LABEL summary="Automatically Configure your Container Runtime for GPU support."
|
||||||
|
LABEL description="See summary"
|
||||||
|
|
||||||
|
COPY ./LICENSE /licenses/LICENSE
|
||||||
|
|
||||||
|
# Install / upgrade packages here that are required to resolve CVEs
|
||||||
|
ARG CVE_UPDATES
|
||||||
|
RUN if [ -n "${CVE_UPDATES}" ]; then \
|
||||||
|
yum update -y ${CVE_UPDATES} && \
|
||||||
|
rm -rf /var/cache/yum/*; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENTRYPOINT ["/work/nvidia-toolkit"]
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
/**
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,24 +11,19 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
**/
|
|
||||||
|
|
||||||
package toml
|
ARG BASE_DIST
|
||||||
|
ARG CUDA_VERSION
|
||||||
|
ARG GOLANG_VERSION=x.x.x
|
||||||
|
ARG VERSION="N/A"
|
||||||
|
|
||||||
import (
|
FROM nvidia/cuda:${CUDA_VERSION}-base-${BASE_DIST}
|
||||||
"github.com/pelletier/go-toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type empty string
|
ENV NVIDIA_CONTAINER_TOOLKIT_VERSION="${VERSION}"
|
||||||
|
|
||||||
var _ Loader = (*empty)(nil)
|
ARG ARTIFACTS_ROOT
|
||||||
|
COPY ${ARTIFACTS_ROOT} /artifacts/packages/
|
||||||
|
|
||||||
// Load is a no-op for an empty source.
|
WORKDIR /artifacts/packages
|
||||||
func (e empty) Load() (*Tree, error) {
|
|
||||||
return newEmpty(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEmpty() *Tree {
|
COPY ./LICENSE /licenses/LICENSE
|
||||||
tomlTree, _ := toml.TreeFromMap(nil)
|
|
||||||
return (*Tree)(tomlTree)
|
|
||||||
}
|
|
||||||
84
build/container/Dockerfile.ubuntu
Normal file
84
build/container/Dockerfile.ubuntu
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
ARG BASE_DIST
|
||||||
|
ARG CUDA_VERSION
|
||||||
|
ARG GOLANG_VERSION=x.x.x
|
||||||
|
ARG VERSION="N/A"
|
||||||
|
|
||||||
|
# NOTE: In cases where the libc version is a concern, we would have to use an
|
||||||
|
# image based on the target OS to build the golang executables here -- especially
|
||||||
|
# if cgo code is included.
|
||||||
|
FROM golang:${GOLANG_VERSION} as build
|
||||||
|
|
||||||
|
# We override the GOPATH to ensure that the binaries are installed to
|
||||||
|
# /artifacts/bin
|
||||||
|
ARG GOPATH=/artifacts
|
||||||
|
|
||||||
|
# Install the experiemental nvidia-container-runtime
|
||||||
|
# NOTE: This will be integrated into the nvidia-container-toolkit package / repo
|
||||||
|
ARG NVIDIA_CONTAINER_RUNTIME_EXPERIMENTAL_VERSION=experimental
|
||||||
|
RUN GOPATH=/artifacts go install github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-container-runtime.experimental@${NVIDIA_CONTAINER_RUNTIME_EXPERIMENTAL_VERSION}
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# NOTE: Until the config utilities are properly integrated into the
|
||||||
|
# nvidia-container-toolkit repository, these are built from the `tools` folder
|
||||||
|
# and not `cmd`.
|
||||||
|
RUN GOPATH=/artifacts go install -ldflags="-s -w -X 'main.Version=${VERSION}'" ./tools/...
|
||||||
|
|
||||||
|
|
||||||
|
FROM nvidia/cuda:${CUDA_VERSION}-base-${BASE_DIST}
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libcap2 \
|
||||||
|
&& \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV NVIDIA_DISABLE_REQUIRE="true"
|
||||||
|
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
ENV NVIDIA_DRIVER_CAPABILITIES=utility
|
||||||
|
|
||||||
|
ARG ARTIFACTS_ROOT
|
||||||
|
ARG PACKAGE_DIST
|
||||||
|
COPY ${ARTIFACTS_ROOT}/${PACKAGE_DIST} /artifacts/packages/${PACKAGE_DIST}
|
||||||
|
|
||||||
|
WORKDIR /artifacts/packages
|
||||||
|
|
||||||
|
ARG PACKAGE_VERSION
|
||||||
|
ARG PACKAGE_ARCH
|
||||||
|
RUN dpkg -i \
|
||||||
|
${PACKAGE_DIST}/${PACKAGE_ARCH}/libnvidia-container1_${PACKAGE_VERSION}*.deb \
|
||||||
|
${PACKAGE_DIST}/${PACKAGE_ARCH}/libnvidia-container-tools_${PACKAGE_VERSION}*.deb \
|
||||||
|
${PACKAGE_DIST}/${PACKAGE_ARCH}/nvidia-container-toolkit_${PACKAGE_VERSION}*.deb
|
||||||
|
|
||||||
|
WORKDIR /work
|
||||||
|
|
||||||
|
COPY --from=build /artifacts/bin /work/
|
||||||
|
|
||||||
|
ENV PATH=/work:$PATH
|
||||||
|
|
||||||
|
LABEL io.k8s.display-name="NVIDIA Container Runtime Config"
|
||||||
|
LABEL name="NVIDIA Container Runtime Config"
|
||||||
|
LABEL vendor="NVIDIA"
|
||||||
|
LABEL version="${VERSION}"
|
||||||
|
LABEL release="N/A"
|
||||||
|
LABEL summary="Automatically Configure your Container Runtime for GPU support."
|
||||||
|
LABEL description="See summary"
|
||||||
|
|
||||||
|
COPY ./LICENSE /licenses/LICENSE
|
||||||
|
|
||||||
|
ENTRYPOINT ["/work/nvidia-toolkit"]
|
||||||
136
build/container/Makefile
Normal file
136
build/container/Makefile
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
DOCKER ?= docker
|
||||||
|
MKDIR ?= mkdir
|
||||||
|
DIST_DIR ?= $(CURDIR)/dist
|
||||||
|
|
||||||
|
##### Global variables #####
|
||||||
|
include $(CURDIR)/versions.mk
|
||||||
|
|
||||||
|
ifeq ($(IMAGE_NAME),)
|
||||||
|
REGISTRY ?= nvidia
|
||||||
|
IMAGE_NAME := $(REGISTRY)/container-toolkit
|
||||||
|
endif
|
||||||
|
|
||||||
|
VERSION ?= $(LIB_VERSION)$(if $(LIB_TAG),-$(LIB_TAG))
|
||||||
|
|
||||||
|
IMAGE_TAG ?= $(VERSION)-$(DIST)
|
||||||
|
IMAGE = $(IMAGE_NAME):$(IMAGE_TAG)
|
||||||
|
|
||||||
|
##### Public rules #####
|
||||||
|
DEFAULT_PUSH_TARGET := ubuntu18.04
|
||||||
|
TARGETS := ubuntu20.04 ubuntu18.04 ubi8 centos7 centos8
|
||||||
|
|
||||||
|
META_TARGETS := packaging
|
||||||
|
|
||||||
|
BUILD_TARGETS := $(patsubst %,build-%,$(TARGETS) $(META_TARGETS))
|
||||||
|
PUSH_TARGETS := $(patsubst %,push-%,$(TARGETS) $(META_TARGETS))
|
||||||
|
TEST_TARGETS := $(patsubst %,test-%, $(TARGETS))
|
||||||
|
|
||||||
|
.PHONY: $(TARGETS) $(PUSH_TARGETS) $(BUILD_TARGETS) $(TEST_TARGETS)
|
||||||
|
|
||||||
|
push-%: DIST = $(*)
|
||||||
|
$(PUSH_TARGETS): push-%:
|
||||||
|
$(DOCKER) push "$(IMAGE_NAME):$(IMAGE_TAG)"
|
||||||
|
|
||||||
|
# For the default push target we also push a short tag equal to the version.
|
||||||
|
# We skip this for the development release
|
||||||
|
DEVEL_RELEASE_IMAGE_VERSION ?= devel
|
||||||
|
ifneq ($(strip $(VERSION)),$(DEVEL_RELEASE_IMAGE_VERSION))
|
||||||
|
push-$(DEFAULT_PUSH_TARGET): push-short
|
||||||
|
endif
|
||||||
|
push-short:
|
||||||
|
$(DOCKER) tag "$(IMAGE_NAME):$(VERSION)-$(DEFAULT_PUSH_TARGET)" "$(IMAGE_NAME):$(VERSION)"
|
||||||
|
$(DOCKER) push "$(IMAGE_NAME):$(VERSION)"
|
||||||
|
|
||||||
|
|
||||||
|
build-%: DIST = $(*)
|
||||||
|
build-%: DOCKERFILE = $(CURDIR)/build/container/Dockerfile.$(DOCKERFILE_SUFFIX)
|
||||||
|
|
||||||
|
ARTIFACTS_ROOT ?= $(shell realpath --relative-to=$(CURDIR) $(DIST_DIR))
|
||||||
|
|
||||||
|
# Use a generic build target to build the relevant images
|
||||||
|
$(BUILD_TARGETS): build-%: $(ARTIFACTS_ROOT)
|
||||||
|
DOCKER_BUILDKIT=1 \
|
||||||
|
$(DOCKER) build --pull \
|
||||||
|
--platform=linux/amd64 \
|
||||||
|
--tag $(IMAGE) \
|
||||||
|
--build-arg ARTIFACTS_ROOT="$(ARTIFACTS_ROOT)" \
|
||||||
|
--build-arg BASE_DIST="$(BASE_DIST)" \
|
||||||
|
--build-arg CUDA_VERSION="$(CUDA_VERSION)" \
|
||||||
|
--build-arg GOLANG_VERSION="$(GOLANG_VERSION)" \
|
||||||
|
--build-arg PACKAGE_DIST="$(PACKAGE_DIST)" \
|
||||||
|
--build-arg PACKAGE_VERSION="$(PACKAGE_VERSION)" \
|
||||||
|
--build-arg PACKAGE_ARCH="$(PACKAGE_ARCH)" \
|
||||||
|
--build-arg VERSION="$(VERSION)" \
|
||||||
|
--build-arg CVE_UPDATES="$(CVE_UPDATES)" \
|
||||||
|
-f $(DOCKERFILE) \
|
||||||
|
$(CURDIR)
|
||||||
|
|
||||||
|
|
||||||
|
build-ubuntu%: BASE_DIST = $(*)
|
||||||
|
build-ubuntu%: DOCKERFILE_SUFFIX := ubuntu
|
||||||
|
build-ubuntu%: PACKAGE_ARCH := amd64
|
||||||
|
build-ubuntu%: PACKAGE_DIST = $(BASE_DIST)
|
||||||
|
build-ubuntu%: PACKAGE_VERSION := $(LIB_VERSION)$(if $(LIB_TAG),~$(LIB_TAG))
|
||||||
|
|
||||||
|
# TODO: Update this to use the centos8 packages
|
||||||
|
build-ubi8: BASE_DIST := ubi8
|
||||||
|
build-ubi8: DOCKERFILE_SUFFIX := centos
|
||||||
|
build-ubi8: PACKAGE_ARCH := x86_64
|
||||||
|
build-ubi8: PACKAGE_DIST = centos7
|
||||||
|
build-ubi8: PACKAGE_VERSION := $(LIB_VERSION)-$(if $(LIB_TAG),0.1.$(LIB_TAG),1)
|
||||||
|
|
||||||
|
build-centos%: BASE_DIST = $(*)
|
||||||
|
build-centos%: DOCKERFILE_SUFFIX := centos
|
||||||
|
build-centos%: PACKAGE_ARCH := x86_64
|
||||||
|
build-centos%: PACKAGE_DIST = $(BASE_DIST)
|
||||||
|
build-centos%: PACKAGE_VERSION := $(LIB_VERSION)-$(if $(LIB_TAG),0.1.$(LIB_TAG),1)
|
||||||
|
|
||||||
|
build-packaging: BASE_DIST := ubuntu20.04
|
||||||
|
build-packaging: DOCKERFILE_SUFFIX := packaging
|
||||||
|
build-packaging: PACKAGE_ARCH := amd64
|
||||||
|
build-packaging: PACKAGE_DIST = all
|
||||||
|
build-packaging: PACKAGE_VERSION := $(LIB_VERSION)$(if $(LIB_TAG),-$(LIB_TAG))
|
||||||
|
|
||||||
|
# Test targets
|
||||||
|
test-%: DIST = $(*)
|
||||||
|
|
||||||
|
TEST_CASES ?= toolkit docker crio containerd
|
||||||
|
$(TEST_TARGETS): test-%:
|
||||||
|
TEST_CASES="$(TEST_CASES)" bash -x $(CURDIR)/test/container/main.sh run \
|
||||||
|
$(CURDIR)/shared-$(*) \
|
||||||
|
$(IMAGE) \
|
||||||
|
--no-cleanup-on-error
|
||||||
|
|
||||||
|
.PHONY: test-packaging
|
||||||
|
test-packaging: DIST = packaging
|
||||||
|
test-packaging:
|
||||||
|
@echo "Testing package image contents"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/amazonlinux2/aarch64" || echo "Missing amazonlinux2/aarch64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/amazonlinux2/x86_64" || echo "Missing amazonlinux2/x86_64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/centos7/ppc64le" || echo "Missing centos7/ppc64le"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/centos7/x86_64" || echo "Missing centos7/x86_64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/centos8/aarch64" || echo "Missing centos8/aarch64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/centos8/ppc64le" || echo "Missing centos8/ppc64le"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/centos8/x86_64" || echo "Missing centos8/x86_64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/debian10/amd64" || echo "Missing debian10/amd64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/debian9/amd64" || echo "Missing debian9/amd64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/opensuse-leap15.1/x86_64" || echo "Missing opensuse-leap15.1/x86_64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/ubuntu16.04/amd64" || echo "Missing ubuntu16.04/amd64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/ubuntu16.04/ppc64le" || echo "Missing ubuntu16.04/ppc64le"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/ubuntu18.04/amd64" || echo "Missing ubuntu18.04/amd64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/ubuntu18.04/arm64" || echo "Missing ubuntu18.04/arm64"
|
||||||
|
@$(DOCKER) run --rm $(IMAGE) test -d "/artifacts/packages/ubuntu18.04/ppc64le" || echo "Missing ubuntu18.04/ppc64le"
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# NVIDIA CDI Hook
|
|
||||||
|
|
||||||
The CLI `nvidia-cdi-hook` provides container device runtime hook capabilities when
|
|
||||||
called by a container runtime, as specific in a
|
|
||||||
[Container Device Interface](https://tags.cncf.io/container-device-interface/blob/main/SPEC.md)
|
|
||||||
file.
|
|
||||||
|
|
||||||
## Generating a CDI
|
|
||||||
|
|
||||||
The CDI itself is created for an NVIDIA-capable device using the
|
|
||||||
[`nvidia-ctk cdi generate`](../nvidia-ctk/) command.
|
|
||||||
|
|
||||||
When `nvidia-ctk cdi generate` is run, the CDI specification is generated as a yaml file.
|
|
||||||
The CDI specification provides instructions for a container runtime to set up devices, files and
|
|
||||||
other resources for the container prior to starting it. Those instructions
|
|
||||||
may include executing command-line tools to prepare the filesystem. The execution
|
|
||||||
of such command-line tools is called a hook.
|
|
||||||
|
|
||||||
`nvidia-cdi-hook` is the CLI tool that is expected to be called by the container runtime,
|
|
||||||
when specified by the CDI file.
|
|
||||||
|
|
||||||
See the [`nvidia-ctk` documentation](../nvidia-ctk/README.md) for more information
|
|
||||||
on generating a CDI file.
|
|
||||||
|
|
||||||
## Functionality
|
|
||||||
|
|
||||||
The `nvidia-cdi-hook` CLI provides the following functionality:
|
|
||||||
|
|
||||||
* `chmod` - Change the permissions of a file or directory inside the directory path to be mounted into a container.
|
|
||||||
* `create-symlinks` - Create symlinks inside the directory path to be mounted into a container.
|
|
||||||
* `update-ldcache` - Update the dynamic linker cache inside the directory path to be mounted into a container.
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package chmod
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
|
||||||
)
|
|
||||||
|
|
||||||
type command struct {
|
|
||||||
logger logger.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
paths cli.StringSlice
|
|
||||||
modeStr string
|
|
||||||
mode fs.FileMode
|
|
||||||
containerSpec string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommand constructs a chmod command with the specified logger
|
|
||||||
func NewCommand(logger logger.Interface) *cli.Command {
|
|
||||||
c := command{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
return c.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the chmod command
|
|
||||||
func (m command) build() *cli.Command {
|
|
||||||
cfg := config{}
|
|
||||||
|
|
||||||
// Create the 'chmod' command
|
|
||||||
c := cli.Command{
|
|
||||||
Name: "chmod",
|
|
||||||
Usage: "Set the permissions of folders in the container by running chmod. The container root is prefixed to the specified paths.",
|
|
||||||
Before: func(c *cli.Context) error {
|
|
||||||
return validateFlags(c, &cfg)
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return m.run(c, &cfg)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Flags = []cli.Flag{
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "path",
|
|
||||||
Usage: "Specify a path to apply the specified mode to",
|
|
||||||
Destination: &cfg.paths,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "mode",
|
|
||||||
Usage: "Specify the file mode",
|
|
||||||
Destination: &cfg.modeStr,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "container-spec",
|
|
||||||
Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN",
|
|
||||||
Destination: &cfg.containerSpec,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateFlags(c *cli.Context, cfg *config) error {
|
|
||||||
if strings.TrimSpace(cfg.modeStr) == "" {
|
|
||||||
return fmt.Errorf("a non-empty mode must be specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
modeInt, err := strconv.ParseUint(cfg.modeStr, 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse mode as octal: %v", err)
|
|
||||||
}
|
|
||||||
cfg.mode = fs.FileMode(modeInt)
|
|
||||||
|
|
||||||
for _, p := range cfg.paths.Value() {
|
|
||||||
if strings.TrimSpace(p) == "" {
|
|
||||||
return fmt.Errorf("paths must not be empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m command) run(c *cli.Context, cfg *config) error {
|
|
||||||
s, err := oci.LoadContainerState(cfg.containerSpec)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load container state: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containerRoot, err := s.GetContainerRoot()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to determined container root: %v", err)
|
|
||||||
}
|
|
||||||
if containerRoot == "" {
|
|
||||||
return fmt.Errorf("empty container root detected")
|
|
||||||
}
|
|
||||||
|
|
||||||
paths := m.getPaths(containerRoot, cfg.paths.Value(), cfg.mode)
|
|
||||||
if len(paths) == 0 {
|
|
||||||
m.logger.Debugf("No paths specified; exiting")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
err = os.Chmod(path, cfg.mode)
|
|
||||||
// in some cases this is not an issue (e.g. whole /dev mounted), see #143
|
|
||||||
if errors.Is(err, fs.ErrPermission) {
|
|
||||||
m.logger.Debugf("Ignoring permission error with chmod: %v", err)
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPaths updates the specified paths relative to the root.
|
|
||||||
func (m command) getPaths(root string, paths []string, desiredMode fs.FileMode) []string {
|
|
||||||
var pathsInRoot []string
|
|
||||||
for _, f := range paths {
|
|
||||||
path := filepath.Join(root, f)
|
|
||||||
stat, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
m.logger.Debugf("Skipping path %q: %v", path, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (stat.Mode()&(fs.ModePerm|fs.ModeSetuid|fs.ModeSetgid|fs.ModeSticky))^desiredMode == 0 {
|
|
||||||
m.logger.Debugf("Skipping path %q: already desired mode", path)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathsInRoot = append(pathsInRoot, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathsInRoot
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/chmod"
|
|
||||||
symlinks "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/create-symlinks"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/cudacompat"
|
|
||||||
ldcache "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/update-ldcache"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// New creates the commands associated with supported CDI hooks.
|
|
||||||
// These are shared by the nvidia-cdi-hook and nvidia-ctk hook commands.
|
|
||||||
func New(logger logger.Interface) []*cli.Command {
|
|
||||||
return []*cli.Command{
|
|
||||||
ldcache.NewCommand(logger),
|
|
||||||
symlinks.NewCommand(logger),
|
|
||||||
chmod.NewCommand(logger),
|
|
||||||
cudacompat.NewCommand(logger),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssueUnsupportedHookWarning logs a warning that no hook or an unsupported
|
|
||||||
// hook has been specified.
|
|
||||||
// This happens if a subcommand is provided that does not match one of the
|
|
||||||
// subcommands that has been explicitly specified.
|
|
||||||
func IssueUnsupportedHookWarning(logger logger.Interface, c *cli.Context) {
|
|
||||||
args := c.Args().Slice()
|
|
||||||
if len(args) == 0 {
|
|
||||||
logger.Warningf("No CDI hook specified")
|
|
||||||
} else {
|
|
||||||
logger.Warningf("Unsupported CDI hook: %v", args[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package symlinks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/moby/sys/symlink"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
|
||||||
)
|
|
||||||
|
|
||||||
type command struct {
|
|
||||||
logger logger.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
links cli.StringSlice
|
|
||||||
containerSpec string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommand constructs a hook command with the specified logger
|
|
||||||
func NewCommand(logger logger.Interface) *cli.Command {
|
|
||||||
c := command{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
return c.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// build creates the create-symlink command.
|
|
||||||
func (m command) build() *cli.Command {
|
|
||||||
cfg := config{}
|
|
||||||
|
|
||||||
c := cli.Command{
|
|
||||||
Name: "create-symlinks",
|
|
||||||
Usage: "A hook to create symlinks in the container.",
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return m.run(c, &cfg)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Flags = []cli.Flag{
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "link",
|
|
||||||
Usage: "Specify a specific link to create. The link is specified as target::link. If the link exists in the container root, it is removed.",
|
|
||||||
Destination: &cfg.links,
|
|
||||||
},
|
|
||||||
// The following flags are testing-only flags.
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "container-spec",
|
|
||||||
Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN. This is only intended for testing.",
|
|
||||||
Destination: &cfg.containerSpec,
|
|
||||||
Hidden: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m command) run(c *cli.Context, cfg *config) error {
|
|
||||||
s, err := oci.LoadContainerState(cfg.containerSpec)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load container state: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containerRoot, err := s.GetContainerRoot()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to determined container root: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
created := make(map[string]bool)
|
|
||||||
for _, l := range cfg.links.Value() {
|
|
||||||
if created[l] {
|
|
||||||
m.logger.Debugf("Link %v already processed", l)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Split(l, "::")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("invalid symlink specification %v", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := m.createLink(containerRoot, parts[0], parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create link %v: %w", parts, err)
|
|
||||||
}
|
|
||||||
created[l] = true
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createLink creates a symbolic link in the specified container root.
|
|
||||||
// This is equivalent to:
|
|
||||||
//
|
|
||||||
// chroot {{ .containerRoot }} ln -f -s {{ .target }} {{ .link }}
|
|
||||||
//
|
|
||||||
// If the specified link already exists and points to the same target, this
|
|
||||||
// operation is a no-op.
|
|
||||||
// If a file exists at the link path or the link points to a different target
|
|
||||||
// this file is removed before creating the link.
|
|
||||||
//
|
|
||||||
// Note that if the link path resolves to an absolute path oudside of the
|
|
||||||
// specified root, this is treated as an absolute path in this root.
|
|
||||||
func (m command) createLink(containerRoot string, targetPath string, link string) error {
|
|
||||||
linkPath := filepath.Join(containerRoot, link)
|
|
||||||
|
|
||||||
exists, err := linkExists(targetPath, linkPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to check if link exists: %w", err)
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
m.logger.Debugf("Link %s already exists", linkPath)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// We resolve the parent of the symlink that we're creating in the container root.
|
|
||||||
// If we resolve the full link path, an existing link at the location itself
|
|
||||||
// is also resolved here and we are unable to force create the link.
|
|
||||||
resolvedLinkParent, err := symlink.FollowSymlinkInScope(filepath.Dir(linkPath), containerRoot)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to follow path for link %v relative to %v: %w", link, containerRoot, err)
|
|
||||||
}
|
|
||||||
resolvedLinkPath := filepath.Join(resolvedLinkParent, filepath.Base(linkPath))
|
|
||||||
|
|
||||||
m.logger.Infof("Symlinking %v to %v", resolvedLinkPath, targetPath)
|
|
||||||
err = os.MkdirAll(filepath.Dir(resolvedLinkPath), 0755)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create directory: %v", err)
|
|
||||||
}
|
|
||||||
err = symlinks.ForceCreate(targetPath, resolvedLinkPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create symlink: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// linkExists checks whether the specified link exists.
|
|
||||||
// A link exists if the path exists, is a symlink, and points to the specified target.
|
|
||||||
func linkExists(target string, link string) (bool, error) {
|
|
||||||
currentTarget, err := symlinks.Resolve(link)
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to resolve existing symlink %s: %w", link, err)
|
|
||||||
}
|
|
||||||
if currentTarget == target {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
@@ -1,297 +0,0 @@
|
|||||||
package symlinks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
testlog "github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLinkExist(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
require.NoError(
|
|
||||||
t,
|
|
||||||
makeFs(tmpDir,
|
|
||||||
dirOrLink{path: "/a/b/c", target: "d"},
|
|
||||||
dirOrLink{path: "/a/b/e", target: "/a/b/f"},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
exists, err := linkExists("d", filepath.Join(tmpDir, "/a/b/c"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, exists)
|
|
||||||
|
|
||||||
exists, err = linkExists("/a/b/f", filepath.Join(tmpDir, "/a/b/e"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, exists)
|
|
||||||
|
|
||||||
exists, err = linkExists("different-target", filepath.Join(tmpDir, "/a/b/c"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, exists)
|
|
||||||
|
|
||||||
exists, err = linkExists("/a/b/d", filepath.Join(tmpDir, "/a/b/c"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, exists)
|
|
||||||
|
|
||||||
exists, err = linkExists("foo", filepath.Join(tmpDir, "/a/b/does-not-exist"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, exists)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateLink(t *testing.T) {
|
|
||||||
type link struct {
|
|
||||||
path string
|
|
||||||
target string
|
|
||||||
}
|
|
||||||
type expectedLink struct {
|
|
||||||
link
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
containerContents []dirOrLink
|
|
||||||
link link
|
|
||||||
expectedCreateError error
|
|
||||||
expectedLinks []expectedLink
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "link to / resolves to container root",
|
|
||||||
containerContents: []dirOrLink{
|
|
||||||
{path: "/lib/foo", target: "/"},
|
|
||||||
},
|
|
||||||
link: link{
|
|
||||||
path: "/lib/foo/libfoo.so",
|
|
||||||
target: "libfoo.so.1",
|
|
||||||
},
|
|
||||||
expectedLinks: []expectedLink{
|
|
||||||
{
|
|
||||||
link: link{
|
|
||||||
path: "{{ .containerRoot }}/libfoo.so",
|
|
||||||
target: "libfoo.so.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "link to / resolves to container root; parent relative link",
|
|
||||||
containerContents: []dirOrLink{
|
|
||||||
{path: "/lib/foo", target: "/"},
|
|
||||||
},
|
|
||||||
link: link{
|
|
||||||
path: "/lib/foo/libfoo.so",
|
|
||||||
target: "../libfoo.so.1",
|
|
||||||
},
|
|
||||||
expectedLinks: []expectedLink{
|
|
||||||
{
|
|
||||||
link: link{
|
|
||||||
path: "{{ .containerRoot }}/libfoo.so",
|
|
||||||
target: "../libfoo.so.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "link to / resolves to container root; absolute link",
|
|
||||||
containerContents: []dirOrLink{
|
|
||||||
{path: "/lib/foo", target: "/"},
|
|
||||||
},
|
|
||||||
link: link{
|
|
||||||
path: "/lib/foo/libfoo.so",
|
|
||||||
target: "/a-path-in-container/foo/libfoo.so.1",
|
|
||||||
},
|
|
||||||
expectedLinks: []expectedLink{
|
|
||||||
{
|
|
||||||
link: link{
|
|
||||||
path: "{{ .containerRoot }}/libfoo.so",
|
|
||||||
target: "/a-path-in-container/foo/libfoo.so.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// We also check that the target is NOT created.
|
|
||||||
link: link{
|
|
||||||
path: "{{ .containerRoot }}/a-path-in-container/foo/libfoo.so.1",
|
|
||||||
},
|
|
||||||
err: os.ErrNotExist,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
hostRoot := filepath.Join(tmpDir, "/host-root/")
|
|
||||||
containerRoot := filepath.Join(tmpDir, "/container-root")
|
|
||||||
|
|
||||||
require.NoError(t, makeFs(hostRoot))
|
|
||||||
require.NoError(t, makeFs(containerRoot, tc.containerContents...))
|
|
||||||
|
|
||||||
// nvidia-cdi-hook create-symlinks --link linkSpec
|
|
||||||
err := getTestCommand().createLink(containerRoot, tc.link.target, tc.link.path)
|
|
||||||
// TODO: We may be able to replace this with require.ErrorIs.
|
|
||||||
if tc.expectedCreateError != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, expectedLink := range tc.expectedLinks {
|
|
||||||
path := strings.ReplaceAll(expectedLink.path, "{{ .containerRoot }}", containerRoot)
|
|
||||||
path = strings.ReplaceAll(path, "{{ .hostRoot }}", hostRoot)
|
|
||||||
if expectedLink.target != "" {
|
|
||||||
target, err := symlinks.Resolve(path)
|
|
||||||
require.ErrorIs(t, err, expectedLink.err)
|
|
||||||
require.Equal(t, expectedLink.target, target)
|
|
||||||
} else {
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
require.ErrorIs(t, err, expectedLink.err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateLinkRelativePath(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
hostRoot := filepath.Join(tmpDir, "/host-root/")
|
|
||||||
containerRoot := filepath.Join(tmpDir, "/container-root")
|
|
||||||
|
|
||||||
require.NoError(t, makeFs(hostRoot))
|
|
||||||
require.NoError(t, makeFs(containerRoot, dirOrLink{path: "/lib/"}))
|
|
||||||
|
|
||||||
// nvidia-cdi-hook create-symlinks --link libfoo.so.1::/lib/libfoo.so
|
|
||||||
err := getTestCommand().createLink(containerRoot, "libfoo.so.1", "/lib/libfoo.so")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
target, err := symlinks.Resolve(filepath.Join(containerRoot, "/lib/libfoo.so"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "libfoo.so.1", target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateLinkAbsolutePath(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
hostRoot := filepath.Join(tmpDir, "/host-root/")
|
|
||||||
containerRoot := filepath.Join(tmpDir, "/container-root")
|
|
||||||
|
|
||||||
require.NoError(t, makeFs(hostRoot))
|
|
||||||
require.NoError(t, makeFs(containerRoot, dirOrLink{path: "/lib/"}))
|
|
||||||
|
|
||||||
// nvidia-cdi-hook create-symlinks --link /lib/libfoo.so.1::/lib/libfoo.so
|
|
||||||
err := getTestCommand().createLink(containerRoot, "/lib/libfoo.so.1", "/lib/libfoo.so")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
target, err := symlinks.Resolve(filepath.Join(containerRoot, "/lib/libfoo.so"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "/lib/libfoo.so.1", target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateLinkAlreadyExists(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
containerContents []dirOrLink
|
|
||||||
shouldExist []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "link already exists with correct target",
|
|
||||||
containerContents: []dirOrLink{{path: "/lib/libfoo.so", target: "libfoo.so.1"}},
|
|
||||||
shouldExist: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "link already exists with different target",
|
|
||||||
containerContents: []dirOrLink{{path: "/lib/libfoo.so", target: "different-target"}, {path: "different-target"}},
|
|
||||||
shouldExist: []string{"{{ .containerRoot }}/different-target"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
hostRoot := filepath.Join(tmpDir, "/host-root/")
|
|
||||||
containerRoot := filepath.Join(tmpDir, "/container-root")
|
|
||||||
require.NoError(t, makeFs(hostRoot))
|
|
||||||
require.NoError(t, makeFs(containerRoot, tc.containerContents...))
|
|
||||||
|
|
||||||
// nvidia-cdi-hook create-symlinks --link libfoo.so.1::/lib/libfoo.so
|
|
||||||
err := getTestCommand().createLink(containerRoot, "libfoo.so.1", "/lib/libfoo.so")
|
|
||||||
require.NoError(t, err)
|
|
||||||
target, err := symlinks.Resolve(filepath.Join(containerRoot, "lib/libfoo.so"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "libfoo.so.1", target)
|
|
||||||
|
|
||||||
for _, p := range tc.shouldExist {
|
|
||||||
require.DirExists(t, strings.ReplaceAll(p, "{{ .containerRoot }}", containerRoot))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateLinkOutOfBounds(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
hostRoot := filepath.Join(tmpDir, "/host-root")
|
|
||||||
containerRoot := filepath.Join(tmpDir, "/container-root")
|
|
||||||
|
|
||||||
require.NoError(t,
|
|
||||||
makeFs(hostRoot,
|
|
||||||
dirOrLink{path: "libfoo.so"},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
require.NoError(t,
|
|
||||||
makeFs(containerRoot,
|
|
||||||
dirOrLink{path: "/lib"},
|
|
||||||
dirOrLink{path: "/lib/foo", target: hostRoot},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
path, err := symlinks.Resolve(filepath.Join(containerRoot, "/lib/foo"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, hostRoot, path)
|
|
||||||
|
|
||||||
// nvidia-cdi-hook create-symlinks --link ../libfoo.so.1::/lib/foo/libfoo.so
|
|
||||||
_ = getTestCommand().createLink(containerRoot, "../libfoo.so.1", "/lib/foo/libfoo.so")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
target, err := symlinks.Resolve(filepath.Join(containerRoot, hostRoot, "libfoo.so"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "../libfoo.so.1", target)
|
|
||||||
|
|
||||||
require.DirExists(t, filepath.Join(hostRoot, "libfoo.so"))
|
|
||||||
}
|
|
||||||
|
|
||||||
type dirOrLink struct {
|
|
||||||
path string
|
|
||||||
target string
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeFs(tmpdir string, fs ...dirOrLink) error {
|
|
||||||
if err := os.MkdirAll(tmpdir, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, s := range fs {
|
|
||||||
s.path = filepath.Join(tmpdir, s.path)
|
|
||||||
if s.target == "" {
|
|
||||||
_ = os.MkdirAll(s.path, 0o755)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(filepath.Dir(s.path), 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.Symlink(s.target, s.path); err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTestCommand creates a command for running tests against.
|
|
||||||
func getTestCommand() *command {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
return &command{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package cudacompat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/moby/sys/symlink"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A containerRoot represents the root filesystem of a container.
|
|
||||||
type containerRoot string
|
|
||||||
|
|
||||||
// hasPath checks whether the specified path exists in the root.
|
|
||||||
func (r containerRoot) hasPath(path string) bool {
|
|
||||||
resolved, err := r.resolve(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(resolved); err != nil && os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// globFiles matches the specified pattern in the root.
|
|
||||||
// The files that match must be regular files.
|
|
||||||
func (r containerRoot) globFiles(pattern string) ([]string, error) {
|
|
||||||
patternPath, err := r.resolve(pattern)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
matches, err := filepath.Glob(patternPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var files []string
|
|
||||||
for _, match := range matches {
|
|
||||||
info, err := os.Lstat(match)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Ignore symlinks.
|
|
||||||
if info.Mode()&os.ModeSymlink != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Ignore directories.
|
|
||||||
if info.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files = append(files, match)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve returns the absolute path including root path.
|
|
||||||
// Symlinks are resolved, but are guaranteed to resolve in the root.
|
|
||||||
func (r containerRoot) resolve(path string) (string, error) {
|
|
||||||
absolute := filepath.Clean(filepath.Join(string(r), path))
|
|
||||||
return symlink.FollowSymlinkInScope(absolute, string(r))
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package cudacompat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cudaCompatPath = "/usr/local/cuda/compat"
|
|
||||||
// cudaCompatLdsoconfdFilenamePattern specifies the pattern for the filename
|
|
||||||
// in ld.so.conf.d that includes a reference to the CUDA compat path.
|
|
||||||
// The 00-compat prefix is chosen to ensure that these libraries have a
|
|
||||||
// higher precedence than other libraries on the system.
|
|
||||||
cudaCompatLdsoconfdFilenamePattern = "00-compat-*.conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
type command struct {
|
|
||||||
logger logger.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
type options struct {
|
|
||||||
hostDriverVersion string
|
|
||||||
containerSpec string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommand constructs a cuda-compat command with the specified logger
|
|
||||||
func NewCommand(logger logger.Interface) *cli.Command {
|
|
||||||
c := command{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
return c.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the enable-cuda-compat command
|
|
||||||
func (m command) build() *cli.Command {
|
|
||||||
cfg := options{}
|
|
||||||
|
|
||||||
// Create the 'enable-cuda-compat' command
|
|
||||||
c := cli.Command{
|
|
||||||
Name: "enable-cuda-compat",
|
|
||||||
Usage: "This hook ensures that the folder containing the CUDA compat libraries is added to the ldconfig search path if required.",
|
|
||||||
Before: func(c *cli.Context) error {
|
|
||||||
return m.validateFlags(c, &cfg)
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return m.run(c, &cfg)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Flags = []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "host-driver-version",
|
|
||||||
Usage: "Specify the host driver version. If the CUDA compat libraries detected in the container do not have a higher MAJOR version, the hook is a no-op.",
|
|
||||||
Destination: &cfg.hostDriverVersion,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "container-spec",
|
|
||||||
Hidden: true,
|
|
||||||
Category: "testing-only",
|
|
||||||
Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN",
|
|
||||||
Destination: &cfg.containerSpec,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m command) validateFlags(_ *cli.Context, cfg *options) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m command) run(_ *cli.Context, cfg *options) error {
|
|
||||||
if cfg.hostDriverVersion == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := oci.LoadContainerState(cfg.containerSpec)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load container state: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containerRootDir, err := s.GetContainerRoot()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to determined container root: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containerForwardCompatDir, err := m.getContainerForwardCompatDir(containerRoot(containerRootDir), cfg.hostDriverVersion)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get container forward compat directory: %w", err)
|
|
||||||
}
|
|
||||||
if containerForwardCompatDir == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.createLdsoconfdFile(containerRoot(containerRootDir), cudaCompatLdsoconfdFilenamePattern, containerForwardCompatDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m command) getContainerForwardCompatDir(containerRoot containerRoot, hostDriverVersion string) (string, error) {
|
|
||||||
if hostDriverVersion == "" {
|
|
||||||
m.logger.Debugf("Host driver version not specified")
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
if !containerRoot.hasPath(cudaCompatPath) {
|
|
||||||
m.logger.Debugf("No CUDA forward compatibility libraries directory in container")
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
if !containerRoot.hasPath("/etc/ld.so.cache") {
|
|
||||||
m.logger.Debugf("The container does not have an LDCache")
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
libs, err := containerRoot.globFiles(filepath.Join(cudaCompatPath, "libcuda.so.*.*"))
|
|
||||||
if err != nil {
|
|
||||||
m.logger.Warningf("Failed to find CUDA compat library: %w", err)
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(libs) == 0 {
|
|
||||||
m.logger.Debugf("No CUDA forward compatibility libraries container")
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(libs) != 1 {
|
|
||||||
m.logger.Warningf("Unexpected number of CUDA compat libraries in container: %v", libs)
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
compatDriverVersion := strings.TrimPrefix(filepath.Base(libs[0]), "libcuda.so.")
|
|
||||||
compatMajor, err := extractMajorVersion(compatDriverVersion)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to extract major version from %q: %v", compatDriverVersion, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
driverMajor, err := extractMajorVersion(hostDriverVersion)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to extract major version from %q: %v", hostDriverVersion, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if driverMajor >= compatMajor {
|
|
||||||
m.logger.Debugf("Compat major version is not greater than the host driver major version (%v >= %v)", hostDriverVersion, compatDriverVersion)
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedCompatDir := strings.TrimPrefix(filepath.Dir(libs[0]), string(containerRoot))
|
|
||||||
return resolvedCompatDir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/ in the specified root.
|
|
||||||
// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and
|
|
||||||
// contains the specified directories on each line.
|
|
||||||
func (m command) createLdsoconfdFile(in containerRoot, pattern string, dirs ...string) error {
|
|
||||||
if len(dirs) == 0 {
|
|
||||||
m.logger.Debugf("No directories to add to /etc/ld.so.conf")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ldsoconfdDir, err := in.resolve("/etc/ld.so.conf.d")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(ldsoconfdDir, 0755); err != nil {
|
|
||||||
return fmt.Errorf("failed to create ld.so.conf.d: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
configFile, err := os.CreateTemp(ldsoconfdDir, pattern)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create config file: %w", err)
|
|
||||||
}
|
|
||||||
defer configFile.Close()
|
|
||||||
|
|
||||||
m.logger.Debugf("Adding directories %v to %v", dirs, configFile.Name())
|
|
||||||
|
|
||||||
added := make(map[string]bool)
|
|
||||||
for _, dir := range dirs {
|
|
||||||
if added[dir] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err = fmt.Fprintf(configFile, "%s\n", dir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update config file: %w", err)
|
|
||||||
}
|
|
||||||
added[dir] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The created file needs to be world readable for the cases where the container is run as a non-root user.
|
|
||||||
if err := configFile.Chmod(0644); err != nil {
|
|
||||||
return fmt.Errorf("failed to chmod config file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractMajorVersion parses a version string and returns the major version as an int.
|
|
||||||
func extractMajorVersion(version string) (int, error) {
|
|
||||||
majorString := strings.SplitN(version, ".", 2)[0]
|
|
||||||
return strconv.Atoi(majorString)
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
/*
|
|
||||||
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cudacompat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
testlog "github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCompatLibs(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
contents map[string]string
|
|
||||||
hostDriverVersion string
|
|
||||||
expectedContainerForwardCompatDir string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "empty root",
|
|
||||||
hostDriverVersion: "222.55.66",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "compat lib is newer; no ldcache",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/usr/local/cuda/compat/libcuda.so.333.88.99": "",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "222.55.66",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "compat lib is newer; ldcache",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/etc/ld.so.cache": "",
|
|
||||||
"/usr/local/cuda/compat/libcuda.so.333.88.99": "",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "222.55.66",
|
|
||||||
expectedContainerForwardCompatDir: "/usr/local/cuda/compat",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "compat lib is older; ldcache",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/etc/ld.so.cache": "",
|
|
||||||
"/usr/local/cuda/compat/libcuda.so.111.88.99": "",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "222.55.66",
|
|
||||||
expectedContainerForwardCompatDir: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "compat lib has same major version; ldcache",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/etc/ld.so.cache": "",
|
|
||||||
"/usr/local/cuda/compat/libcuda.so.222.88.99": "",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "222.55.66",
|
|
||||||
expectedContainerForwardCompatDir: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "numeric comparison is used; ldcache",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/etc/ld.so.cache": "",
|
|
||||||
"/usr/local/cuda/compat/libcuda.so.222.88.99": "",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "99.55.66",
|
|
||||||
expectedContainerForwardCompatDir: "/usr/local/cuda/compat",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "driver version empty; ldcache",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/etc/ld.so.cache": "",
|
|
||||||
"/usr/local/cuda/compat/libcuda.so.222.88.99": "",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "symlinks are followed",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/etc/ld.so.cache": "",
|
|
||||||
"/etc/alternatives/cuda/compat/libcuda.so.333.88.99": "",
|
|
||||||
"/usr/local/cuda": "symlink=/etc/alternatives/cuda",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "222.55.66",
|
|
||||||
expectedContainerForwardCompatDir: "/etc/alternatives/cuda/compat",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "symlinks stay in container",
|
|
||||||
contents: map[string]string{
|
|
||||||
"/etc/ld.so.cache": "",
|
|
||||||
"/compat/libcuda.so.333.88.99": "",
|
|
||||||
"/usr/local/cuda": "symlink=../../../../../../",
|
|
||||||
},
|
|
||||||
hostDriverVersion: "222.55.66",
|
|
||||||
expectedContainerForwardCompatDir: "/compat",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
containerRootDir := t.TempDir()
|
|
||||||
for name, contents := range tc.contents {
|
|
||||||
target := filepath.Join(containerRootDir, name)
|
|
||||||
require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755))
|
|
||||||
|
|
||||||
if strings.HasPrefix(contents, "symlink=") {
|
|
||||||
require.NoError(t, os.Symlink(strings.TrimPrefix(contents, "symlink="), target))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, os.WriteFile(target, []byte(contents), 0600))
|
|
||||||
}
|
|
||||||
|
|
||||||
c := command{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
containerForwardCompatDir, err := c.getContainerForwardCompatDir(containerRoot(containerRootDir), tc.hostDriverVersion)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, tc.expectedContainerForwardCompatDir, containerForwardCompatDir)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateLdconfig(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
folders []string
|
|
||||||
expectedContents string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "no folders; have no contents",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "single folder is added",
|
|
||||||
folders: []string{"/usr/local/cuda/compat"},
|
|
||||||
expectedContents: "/usr/local/cuda/compat\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
containerRootDir := t.TempDir()
|
|
||||||
c := command{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
err := c.createLdsoconfdFile(containerRoot(containerRootDir), cudaCompatLdsoconfdFilenamePattern, tc.folders...)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
matches, err := filepath.Glob(filepath.Join(containerRootDir, "/etc/ld.so.conf.d/00-compat-*.conf"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if tc.expectedContents == "" {
|
|
||||||
require.Empty(t, matches)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Len(t, matches, 1)
|
|
||||||
contents, err := os.ReadFile(matches[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.EqualValues(t, tc.expectedContents, string(contents))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/info"
|
|
||||||
|
|
||||||
cli "github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/commands"
|
|
||||||
)
|
|
||||||
|
|
||||||
// options defines the options that can be set for the CLI through config files,
|
|
||||||
// environment variables, or command line flags
|
|
||||||
type options struct {
|
|
||||||
// Debug indicates whether the CLI is started in "debug" mode
|
|
||||||
Debug bool
|
|
||||||
// Quiet indicates whether the CLI is started in "quiet" mode
|
|
||||||
Quiet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
logger := logrus.New()
|
|
||||||
|
|
||||||
// Create a options struct to hold the parsed environment variables or command line flags
|
|
||||||
opts := options{}
|
|
||||||
|
|
||||||
// Create the top-level CLI
|
|
||||||
c := cli.NewApp()
|
|
||||||
c.Name = "NVIDIA CDI Hook"
|
|
||||||
c.UseShortOptionHandling = true
|
|
||||||
c.EnableBashCompletion = true
|
|
||||||
c.Usage = "Command to structure files for usage inside a container, called as hooks from a container runtime, defined in a CDI yaml file"
|
|
||||||
c.Version = info.GetVersionString()
|
|
||||||
|
|
||||||
// We set the default action for the `nvidia-cdi-hook` command to issue a
|
|
||||||
// warning and exit with no error.
|
|
||||||
// This means that if an unsupported hook is run, a container will not fail
|
|
||||||
// to launch. An unsupported hook could be the result of a CDI specification
|
|
||||||
// referring to a new hook that is not yet supported by an older NVIDIA
|
|
||||||
// Container Toolkit version or a hook that has been removed in newer
|
|
||||||
// version.
|
|
||||||
c.Action = func(ctx *cli.Context) error {
|
|
||||||
commands.IssueUnsupportedHookWarning(logger, ctx)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the flags for this command
|
|
||||||
c.Flags = []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "debug",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Enable debug-level logging",
|
|
||||||
Destination: &opts.Debug,
|
|
||||||
// TODO: Support for NVIDIA_CDI_DEBUG is deprecated and NVIDIA_CTK_DEBUG should be used instead.
|
|
||||||
EnvVars: []string{"NVIDIA_CTK_DEBUG", "NVIDIA_CDI_DEBUG"},
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "quiet",
|
|
||||||
Usage: "Suppress all output except for errors; overrides --debug",
|
|
||||||
Destination: &opts.Quiet,
|
|
||||||
// TODO: Support for NVIDIA_CDI_QUIET is deprecated and NVIDIA_CTK_QUIET should be used instead.
|
|
||||||
EnvVars: []string{"NVDIA_CTK_QUIET", "NVIDIA_CDI_QUIET"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set log-level for all subcommands
|
|
||||||
c.Before = func(c *cli.Context) error {
|
|
||||||
logLevel := logrus.InfoLevel
|
|
||||||
if opts.Debug {
|
|
||||||
logLevel = logrus.DebugLevel
|
|
||||||
}
|
|
||||||
if opts.Quiet {
|
|
||||||
logLevel = logrus.ErrorLevel
|
|
||||||
}
|
|
||||||
logger.SetLevel(logLevel)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the subcommands
|
|
||||||
c.Commands = commands.New(logger)
|
|
||||||
|
|
||||||
// Run the CLI
|
|
||||||
err := c.Run(os.Args)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("%v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package ldcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/moby/sys/symlink"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A containerRoot represents the root filesystem of a container.
|
|
||||||
type containerRoot string
|
|
||||||
|
|
||||||
// hasPath checks whether the specified path exists in the root.
|
|
||||||
func (r containerRoot) hasPath(path string) bool {
|
|
||||||
resolved, err := r.resolve(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(resolved); err != nil && os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve returns the absolute path including root path.
|
|
||||||
// Symlinks are resolved, but are guaranteed to resolve in the root.
|
|
||||||
func (r containerRoot) resolve(path string) (string, error) {
|
|
||||||
absolute := filepath.Clean(filepath.Join(string(r), path))
|
|
||||||
return symlink.FollowSymlinkInScope(absolute, string(r))
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package ldcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
securejoin "github.com/cyphar/filepath-securejoin"
|
|
||||||
|
|
||||||
"github.com/moby/sys/reexec"
|
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pivotRoot will call pivot_root such that rootfs becomes the new root
|
|
||||||
// filesystem, and everything else is cleaned up.
|
|
||||||
// This is adapted from the implementation here:
|
|
||||||
//
|
|
||||||
// https://github.com/opencontainers/runc/blob/e89a29929c775025419ab0d218a43588b4c12b9a/libcontainer/rootfs_linux.go#L1056-L1113
|
|
||||||
//
|
|
||||||
// With the `mount` and `unmount` calls changed to direct unix.Mount and unix.Unmount calls.
|
|
||||||
func pivotRoot(rootfs string) error {
|
|
||||||
// While the documentation may claim otherwise, pivot_root(".", ".") is
|
|
||||||
// actually valid. What this results in is / being the new root but
|
|
||||||
// /proc/self/cwd being the old root. Since we can play around with the cwd
|
|
||||||
// with pivot_root this allows us to pivot without creating directories in
|
|
||||||
// the rootfs. Shout-outs to the LXC developers for giving us this idea.
|
|
||||||
|
|
||||||
oldroot, err := unix.Open("/", unix.O_DIRECTORY|unix.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return &os.PathError{Op: "open", Path: "/", Err: err}
|
|
||||||
}
|
|
||||||
defer unix.Close(oldroot) //nolint: errcheck
|
|
||||||
|
|
||||||
newroot, err := unix.Open(rootfs, unix.O_DIRECTORY|unix.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return &os.PathError{Op: "open", Path: rootfs, Err: err}
|
|
||||||
}
|
|
||||||
defer unix.Close(newroot) //nolint: errcheck
|
|
||||||
|
|
||||||
// Change to the new root so that the pivot_root actually acts on it.
|
|
||||||
if err := unix.Fchdir(newroot); err != nil {
|
|
||||||
return &os.PathError{Op: "fchdir", Path: "fd " + strconv.Itoa(newroot), Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unix.PivotRoot(".", "."); err != nil {
|
|
||||||
return &os.PathError{Op: "pivot_root", Path: ".", Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currently our "." is oldroot (according to the current kernel code).
|
|
||||||
// However, purely for safety, we will fchdir(oldroot) since there isn't
|
|
||||||
// really any guarantee from the kernel what /proc/self/cwd will be after a
|
|
||||||
// pivot_root(2).
|
|
||||||
|
|
||||||
if err := unix.Fchdir(oldroot); err != nil {
|
|
||||||
return &os.PathError{Op: "fchdir", Path: "fd " + strconv.Itoa(oldroot), Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make oldroot rslave to make sure our unmounts don't propagate to the
|
|
||||||
// host (and thus bork the machine). We don't use rprivate because this is
|
|
||||||
// known to cause issues due to races where we still have a reference to a
|
|
||||||
// mount while a process in the host namespace are trying to operate on
|
|
||||||
// something they think has no mounts (devicemapper in particular).
|
|
||||||
if err := unix.Mount("", ".", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Perform the unmount. MNT_DETACH allows us to unmount /proc/self/cwd.
|
|
||||||
if err := unix.Unmount(".", unix.MNT_DETACH); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch back to our shiny new root.
|
|
||||||
if err := unix.Chdir("/"); err != nil {
|
|
||||||
return &os.PathError{Op: "chdir", Path: "/", Err: err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mountLdConfig mounts the host ldconfig to the mount namespace of the hook.
|
|
||||||
// We use WithProcfd to perform the mount operations to ensure that the changes
|
|
||||||
// are persisted across the pivot root.
|
|
||||||
func mountLdConfig(hostLdconfigPath string, containerRootDirPath string) (string, error) {
|
|
||||||
hostLdconfigInfo, err := os.Stat(hostLdconfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error reading host ldconfig: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hookScratchDirPath := "/var/run/nvidia-ctk-hook"
|
|
||||||
ldconfigPath := filepath.Join(hookScratchDirPath, "ldconfig")
|
|
||||||
if err := utils.MkdirAllInRoot(containerRootDirPath, hookScratchDirPath, 0755); err != nil {
|
|
||||||
return "", fmt.Errorf("error creating hook scratch folder: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = utils.WithProcfd(containerRootDirPath, hookScratchDirPath, func(hookScratchDirFdPath string) error {
|
|
||||||
return createTmpFs(hookScratchDirFdPath, int(hostLdconfigInfo.Size()))
|
|
||||||
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error creating tmpfs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := createFileInRoot(containerRootDirPath, ldconfigPath, hostLdconfigInfo.Mode()); err != nil {
|
|
||||||
return "", fmt.Errorf("error creating ldconfig: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = utils.WithProcfd(containerRootDirPath, ldconfigPath, func(ldconfigFdPath string) error {
|
|
||||||
return unix.Mount(hostLdconfigPath, ldconfigFdPath, "", unix.MS_BIND|unix.MS_RDONLY|unix.MS_NODEV|unix.MS_PRIVATE|unix.MS_NOSYMFOLLOW, "")
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error bind mounting host ldconfig: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ldconfigPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFileInRoot(containerRootDirPath string, destinationPath string, mode os.FileMode) (string, error) {
|
|
||||||
dest, err := securejoin.SecureJoin(containerRootDirPath, destinationPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// Make the parent directory.
|
|
||||||
destDir, destBase := filepath.Split(dest)
|
|
||||||
destDirFd, err := utils.MkdirAllInRootOpen(containerRootDirPath, destDir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error creating parent dir: %w", err)
|
|
||||||
}
|
|
||||||
defer destDirFd.Close()
|
|
||||||
// Make the target file. We want to avoid opening any file that is
|
|
||||||
// already there because it could be a "bad" file like an invalid
|
|
||||||
// device or hung tty that might cause a DoS, so we use mknodat.
|
|
||||||
// destBase does not contain any "/" components, and mknodat does
|
|
||||||
// not follow trailing symlinks, so we can safely just call mknodat
|
|
||||||
// here.
|
|
||||||
if err := unix.Mknodat(int(destDirFd.Fd()), destBase, unix.S_IFREG|uint32(mode), 0); err != nil {
|
|
||||||
// If we get EEXIST, there was already an inode there and
|
|
||||||
// we can consider that a success.
|
|
||||||
if !errors.Is(err, unix.EEXIST) {
|
|
||||||
return "", fmt.Errorf("error creating empty file: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mountProc mounts a clean proc filesystem in the new root.
|
|
||||||
func mountProc(newroot string) error {
|
|
||||||
target := filepath.Join(newroot, "/proc")
|
|
||||||
|
|
||||||
if err := os.MkdirAll(target, 0755); err != nil {
|
|
||||||
return fmt.Errorf("error creating directory: %w", err)
|
|
||||||
}
|
|
||||||
return unix.Mount("proc", target, "proc", 0, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createTmpFs creates a tmpfs at the specified location with the specified size.
|
|
||||||
func createTmpFs(target string, size int) error {
|
|
||||||
return unix.Mount("tmpfs", target, "tmpfs", 0, fmt.Sprintf("size=%d", size))
|
|
||||||
}
|
|
||||||
|
|
||||||
// createReexecCommand creates a command that can be used to trigger the reexec
|
|
||||||
// initializer.
|
|
||||||
// On linux this command runs in new namespaces.
|
|
||||||
func createReexecCommand(args []string) *exec.Cmd {
|
|
||||||
cmd := reexec.Command(args...)
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
||||||
Cloneflags: syscall.CLONE_NEWNS |
|
|
||||||
syscall.CLONE_NEWUTS |
|
|
||||||
syscall.CLONE_NEWIPC |
|
|
||||||
syscall.CLONE_NEWPID |
|
|
||||||
syscall.CLONE_NEWNET,
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package ldcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/moby/sys/reexec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func pivotRoot(newroot string) error {
|
|
||||||
return fmt.Errorf("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func mountLdConfig(hostLdconfigPath string, containerRootDirPath string) (string, error) {
|
|
||||||
return "", fmt.Errorf("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func mountProc(newroot string) error {
|
|
||||||
return fmt.Errorf("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createReexecCommand creates a command that can be used ot trigger the reexec
|
|
||||||
// initializer.
|
|
||||||
func createReexecCommand(args []string) *exec.Cmd {
|
|
||||||
cmd := reexec.Command(args...)
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
/**
|
|
||||||
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package ldcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/exeseal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SafeExec attempts to clone the specified binary (as an memfd, for example) before executing it.
|
|
||||||
func SafeExec(path string, args []string, envv []string) error {
|
|
||||||
safeExe, err := cloneBinary(path)
|
|
||||||
if err != nil {
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
return syscall.Exec(path, args, envv)
|
|
||||||
}
|
|
||||||
defer safeExe.Close()
|
|
||||||
|
|
||||||
exePath := "/proc/self/fd/" + strconv.Itoa(int(safeExe.Fd()))
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
return syscall.Exec(exePath, args, envv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneBinary(path string) (*os.File, error) {
|
|
||||||
exe, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("opening current binary: %w", err)
|
|
||||||
}
|
|
||||||
defer exe.Close()
|
|
||||||
|
|
||||||
stat, err := exe.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("checking %v size: %w", path, err)
|
|
||||||
}
|
|
||||||
size := stat.Size()
|
|
||||||
|
|
||||||
return exeseal.CloneBinary(exe, size, path, os.TempDir())
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
/**
|
|
||||||
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package ldcache
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
// SafeExec is not implemented on non-linux systems and forwards directly to the
|
|
||||||
// Exec syscall.
|
|
||||||
func SafeExec(path string, args []string, envv []string) error {
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
return syscall.Exec(path, args, envv)
|
|
||||||
}
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package ldcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/moby/sys/reexec"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ldsoconfdFilenamePattern specifies the pattern for the filename
|
|
||||||
// in ld.so.conf.d that includes references to the specified directories.
|
|
||||||
// The 00-nvcr prefix is chosen to ensure that these libraries have a
|
|
||||||
// higher precedence than other libraries on the system, but lower than
|
|
||||||
// the 00-cuda-compat that is included in some containers.
|
|
||||||
ldsoconfdFilenamePattern = "00-nvcr-*.conf"
|
|
||||||
|
|
||||||
reexecUpdateLdCacheCommandName = "reexec-update-ldcache"
|
|
||||||
)
|
|
||||||
|
|
||||||
type command struct {
|
|
||||||
logger logger.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
type options struct {
|
|
||||||
folders cli.StringSlice
|
|
||||||
ldconfigPath string
|
|
||||||
containerSpec string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
reexec.Register(reexecUpdateLdCacheCommandName, updateLdCacheHandler)
|
|
||||||
if reexec.Init() {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommand constructs an update-ldcache command with the specified logger
|
|
||||||
func NewCommand(logger logger.Interface) *cli.Command {
|
|
||||||
c := command{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
return c.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the update-ldcache command
|
|
||||||
func (m command) build() *cli.Command {
|
|
||||||
cfg := options{}
|
|
||||||
|
|
||||||
// Create the 'update-ldcache' command
|
|
||||||
c := cli.Command{
|
|
||||||
Name: "update-ldcache",
|
|
||||||
Usage: "Update ldcache in a container by running ldconfig",
|
|
||||||
Before: func(c *cli.Context) error {
|
|
||||||
return m.validateFlags(c, &cfg)
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return m.run(c, &cfg)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Flags = []cli.Flag{
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "folder",
|
|
||||||
Usage: "Specify a folder to add to /etc/ld.so.conf before updating the ld cache",
|
|
||||||
Destination: &cfg.folders,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "ldconfig-path",
|
|
||||||
Usage: "Specify the path to the ldconfig program",
|
|
||||||
Destination: &cfg.ldconfigPath,
|
|
||||||
Value: "/sbin/ldconfig",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "container-spec",
|
|
||||||
Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN",
|
|
||||||
Destination: &cfg.containerSpec,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m command) validateFlags(c *cli.Context, cfg *options) error {
|
|
||||||
if cfg.ldconfigPath == "" {
|
|
||||||
return errors.New("ldconfig-path must be specified")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m command) run(c *cli.Context, cfg *options) error {
|
|
||||||
s, err := oci.LoadContainerState(cfg.containerSpec)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load container state: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containerRootDir, err := s.GetContainerRoot()
|
|
||||||
if err != nil || containerRootDir == "" || containerRootDir == "/" {
|
|
||||||
return fmt.Errorf("failed to determined container root: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
reexecUpdateLdCacheCommandName,
|
|
||||||
strings.TrimPrefix(config.NormalizeLDConfigPath("@"+cfg.ldconfigPath), "@"),
|
|
||||||
containerRootDir,
|
|
||||||
}
|
|
||||||
args = append(args, cfg.folders.Value()...)
|
|
||||||
|
|
||||||
cmd := createReexecCommand(args)
|
|
||||||
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateLdCacheHandler wraps updateLdCache with error handling.
|
|
||||||
func updateLdCacheHandler() {
|
|
||||||
if err := updateLdCache(os.Args); err != nil {
|
|
||||||
log.Printf("Error updating ldcache: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateLdCache is invoked from a reexec'd handler and provides namespace
|
|
||||||
// isolation for the operations performed by this hook.
|
|
||||||
// At the point where this is invoked, we are in a new mount namespace that is
|
|
||||||
// cloned from the parent.
|
|
||||||
//
|
|
||||||
// args[0] is the reexec initializer function name
|
|
||||||
// args[1] is the path of the ldconfig binary on the host
|
|
||||||
// args[2] is the container root directory
|
|
||||||
// The remaining args are folders that need to be added to the ldcache.
|
|
||||||
func updateLdCache(args []string) error {
|
|
||||||
if len(args) < 3 {
|
|
||||||
return fmt.Errorf("incorrect arguments: %v", args)
|
|
||||||
}
|
|
||||||
hostLdconfigPath := args[1]
|
|
||||||
containerRootDirPath := args[2]
|
|
||||||
|
|
||||||
// To prevent leaking the parent proc filesystem, we create a new proc mount
|
|
||||||
// in the container root.
|
|
||||||
if err := mountProc(containerRootDirPath); err != nil {
|
|
||||||
return fmt.Errorf("error mounting /proc: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We mount the host ldconfig before we pivot root since host paths are not
|
|
||||||
// visible after the pivot root operation.
|
|
||||||
ldconfigPath, err := mountLdConfig(hostLdconfigPath, containerRootDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error mounting host ldconfig: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We pivot to the container root for the new process, this further limits
|
|
||||||
// access to the host.
|
|
||||||
if err := pivotRoot(containerRootDirPath); err != nil {
|
|
||||||
return fmt.Errorf("error running pivot_root: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return runLdconfig(ldconfigPath, args[3:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// runLdconfig runs the ldconfig binary and ensures that the specified directories
|
|
||||||
// are processed for the ldcache.
|
|
||||||
func runLdconfig(ldconfigPath string, directories ...string) error {
|
|
||||||
args := []string{
|
|
||||||
"ldconfig",
|
|
||||||
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
|
|
||||||
// be configured to use a different config file by default.
|
|
||||||
// Note that since we apply the `-r {{ .containerRootDir }}` argument, /etc/ld.so.conf is
|
|
||||||
// in the container.
|
|
||||||
"-f", "/etc/ld.so.conf",
|
|
||||||
}
|
|
||||||
|
|
||||||
containerRoot := containerRoot("/")
|
|
||||||
|
|
||||||
if containerRoot.hasPath("/etc/ld.so.cache") {
|
|
||||||
args = append(args, "-C", "/etc/ld.so.cache")
|
|
||||||
} else {
|
|
||||||
args = append(args, "-N")
|
|
||||||
}
|
|
||||||
|
|
||||||
if containerRoot.hasPath("/etc/ld.so.conf.d") {
|
|
||||||
err := createLdsoconfdFile(ldsoconfdFilenamePattern, directories...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update ld.so.conf.d: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args = append(args, directories...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return SafeExec(ldconfigPath, args, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
|
|
||||||
// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and
|
|
||||||
// contains the specified directories on each line.
|
|
||||||
func createLdsoconfdFile(pattern string, dirs ...string) error {
|
|
||||||
if len(dirs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ldsoconfdDir := "/etc/ld.so.conf.d"
|
|
||||||
if err := os.MkdirAll(ldsoconfdDir, 0755); err != nil {
|
|
||||||
return fmt.Errorf("failed to create ld.so.conf.d: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
configFile, err := os.CreateTemp(ldsoconfdDir, pattern)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create config file: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = configFile.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
added := make(map[string]bool)
|
|
||||||
for _, dir := range dirs {
|
|
||||||
if added[dir] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err = fmt.Fprintf(configFile, "%s\n", dir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update config file: %w", err)
|
|
||||||
}
|
|
||||||
added[dir] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The created file needs to be world readable for the cases where the container is run as a non-root user.
|
|
||||||
if err := configFile.Chmod(0644); err != nil {
|
|
||||||
return fmt.Errorf("failed to chmod config file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func capabilityToCLI(cap string) string {
|
|
||||||
switch cap {
|
|
||||||
case "compute":
|
|
||||||
return "--compute"
|
|
||||||
case "compat32":
|
|
||||||
return "--compat32"
|
|
||||||
case "graphics":
|
|
||||||
return "--graphics"
|
|
||||||
case "utility":
|
|
||||||
return "--utility"
|
|
||||||
case "video":
|
|
||||||
return "--video"
|
|
||||||
case "display":
|
|
||||||
return "--display"
|
|
||||||
case "ngx":
|
|
||||||
return "--ngx"
|
|
||||||
default:
|
|
||||||
log.Panicln("unknown driver capability:", cap)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
"golang.org/x/mod/semver"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/image"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
capSysAdmin = "CAP_SYS_ADMIN"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nvidiaConfig struct {
|
|
||||||
Devices []string
|
|
||||||
MigConfigDevices string
|
|
||||||
MigMonitorDevices string
|
|
||||||
ImexChannels []string
|
|
||||||
DriverCapabilities string
|
|
||||||
// Requirements defines the requirements DSL for the container to run.
|
|
||||||
// This is empty if no specific requirements are needed, or if requirements are
|
|
||||||
// explicitly disabled.
|
|
||||||
Requirements []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type containerConfig struct {
|
|
||||||
Pid int
|
|
||||||
Rootfs string
|
|
||||||
Image image.CUDA
|
|
||||||
Nvidia *nvidiaConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root from OCI runtime spec
|
|
||||||
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L94-L100
|
|
||||||
type Root struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process from OCI runtime spec
|
|
||||||
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L57
|
|
||||||
type Process struct {
|
|
||||||
Env []string `json:"env,omitempty"`
|
|
||||||
Capabilities *json.RawMessage `json:"capabilities,omitempty" platform:"linux"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinuxCapabilities from OCI runtime spec
|
|
||||||
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L61
|
|
||||||
type LinuxCapabilities struct {
|
|
||||||
Bounding []string `json:"bounding,omitempty" platform:"linux"`
|
|
||||||
Effective []string `json:"effective,omitempty" platform:"linux"`
|
|
||||||
Inheritable []string `json:"inheritable,omitempty" platform:"linux"`
|
|
||||||
Permitted []string `json:"permitted,omitempty" platform:"linux"`
|
|
||||||
Ambient []string `json:"ambient,omitempty" platform:"linux"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spec from OCI runtime spec
|
|
||||||
// We use pointers to structs, similarly to the latest version of runtime-spec:
|
|
||||||
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L5-L28
|
|
||||||
type Spec struct {
|
|
||||||
Version *string `json:"ociVersion"`
|
|
||||||
Process *Process `json:"process,omitempty"`
|
|
||||||
Root *Root `json:"root,omitempty"`
|
|
||||||
Mounts []specs.Mount `json:"mounts,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HookState holds state information about the hook
|
|
||||||
type HookState struct {
|
|
||||||
Pid int `json:"pid,omitempty"`
|
|
||||||
// After 17.06, runc is using the runtime spec:
|
|
||||||
// github.com/docker/runc/blob/17.06/libcontainer/configs/config.go#L262-L263
|
|
||||||
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/state.go#L3-L17
|
|
||||||
Bundle string `json:"bundle"`
|
|
||||||
// Before 17.06, runc used a custom struct that didn't conform to the spec:
|
|
||||||
// github.com/docker/runc/blob/17.03.x/libcontainer/configs/config.go#L245-L252
|
|
||||||
BundlePath string `json:"bundlePath"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadSpec(path string) (spec *Spec) {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln("could not open OCI spec:", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if err = json.NewDecoder(f).Decode(&spec); err != nil {
|
|
||||||
log.Panicln("could not decode OCI spec:", err)
|
|
||||||
}
|
|
||||||
if spec.Version == nil {
|
|
||||||
log.Panicln("Version is empty in OCI spec")
|
|
||||||
}
|
|
||||||
if spec.Process == nil {
|
|
||||||
log.Panicln("Process is empty in OCI spec")
|
|
||||||
}
|
|
||||||
if spec.Root == nil {
|
|
||||||
log.Panicln("Root is empty in OCI spec")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPrivileged(s *Spec) bool {
|
|
||||||
if s.Process.Capabilities == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var caps []string
|
|
||||||
// If v1.0.0-rc1 <= OCI version < v1.0.0-rc5 parse s.Process.Capabilities as:
|
|
||||||
// github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/specs-go/config.go#L30-L54
|
|
||||||
rc1cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc1")
|
|
||||||
rc5cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc5")
|
|
||||||
if (rc1cmp == 1 || rc1cmp == 0) && (rc5cmp == -1) {
|
|
||||||
err := json.Unmarshal(*s.Process.Capabilities, &caps)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln("could not decode Process.Capabilities in OCI spec:", err)
|
|
||||||
}
|
|
||||||
for _, c := range caps {
|
|
||||||
if c == capSysAdmin {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, parse s.Process.Capabilities as:
|
|
||||||
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L54
|
|
||||||
process := specs.Process{
|
|
||||||
Env: s.Process.Env,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal(*s.Process.Capabilities, &process.Capabilities)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln("could not decode Process.Capabilities in OCI spec:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fullSpec := specs.Spec{
|
|
||||||
Version: *s.Version,
|
|
||||||
Process: &process,
|
|
||||||
}
|
|
||||||
|
|
||||||
return image.IsPrivileged(&fullSpec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDevicesFromEnvvar(containerImage image.CUDA, swarmResourceEnvvars []string) []string {
|
|
||||||
// We check if the image has at least one of the Swarm resource envvars defined and use this
|
|
||||||
// if specified.
|
|
||||||
for _, envvar := range swarmResourceEnvvars {
|
|
||||||
if containerImage.HasEnvvar(envvar) {
|
|
||||||
return containerImage.DevicesFromEnvvars(swarmResourceEnvvars...).List()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return containerImage.VisibleDevicesFromEnvVar()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hookConfig *hookConfig) getDevices(image image.CUDA, privileged bool) []string {
|
|
||||||
// If enabled, try and get the device list from volume mounts first
|
|
||||||
if hookConfig.AcceptDeviceListAsVolumeMounts {
|
|
||||||
devices := image.VisibleDevicesFromMounts()
|
|
||||||
if len(devices) > 0 {
|
|
||||||
return devices
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to reading from the environment variable if privileges are correct
|
|
||||||
devices := getDevicesFromEnvvar(image, hookConfig.getSwarmResourceEnvvars())
|
|
||||||
if len(devices) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if privileged || hookConfig.AcceptEnvvarUnprivileged {
|
|
||||||
return devices
|
|
||||||
}
|
|
||||||
|
|
||||||
configName := hookConfig.getConfigOption("AcceptEnvvarUnprivileged")
|
|
||||||
log.Printf("Ignoring devices specified in NVIDIA_VISIBLE_DEVICES (privileged=%v, %v=%v) ", privileged, configName, hookConfig.AcceptEnvvarUnprivileged)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMigConfigDevices(i image.CUDA) *string {
|
|
||||||
return getMigDevices(i, image.EnvVarNvidiaMigConfigDevices)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMigMonitorDevices(i image.CUDA) *string {
|
|
||||||
return getMigDevices(i, image.EnvVarNvidiaMigMonitorDevices)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMigDevices(image image.CUDA, envvar string) *string {
|
|
||||||
if !image.HasEnvvar(envvar) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
devices := image.Getenv(envvar)
|
|
||||||
return &devices
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hookConfig *hookConfig) getImexChannels(image image.CUDA, privileged bool) []string {
|
|
||||||
if hookConfig.Features.IgnoreImexChannelRequests.IsEnabled() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If enabled, try and get the device list from volume mounts first
|
|
||||||
if hookConfig.AcceptDeviceListAsVolumeMounts {
|
|
||||||
devices := image.ImexChannelsFromMounts()
|
|
||||||
if len(devices) > 0 {
|
|
||||||
return devices
|
|
||||||
}
|
|
||||||
}
|
|
||||||
devices := image.ImexChannelsFromEnvVar()
|
|
||||||
if len(devices) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if privileged || hookConfig.AcceptEnvvarUnprivileged {
|
|
||||||
return devices
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hookConfig *hookConfig) getDriverCapabilities(cudaImage image.CUDA, legacyImage bool) image.DriverCapabilities {
|
|
||||||
// We use the default driver capabilities by default. This is filtered to only include the
|
|
||||||
// supported capabilities
|
|
||||||
supportedDriverCapabilities := image.NewDriverCapabilities(hookConfig.SupportedDriverCapabilities)
|
|
||||||
|
|
||||||
capabilities := supportedDriverCapabilities.Intersection(image.DefaultDriverCapabilities)
|
|
||||||
|
|
||||||
capsEnvSpecified := cudaImage.HasEnvvar(image.EnvVarNvidiaDriverCapabilities)
|
|
||||||
capsEnv := cudaImage.Getenv(image.EnvVarNvidiaDriverCapabilities)
|
|
||||||
|
|
||||||
if !capsEnvSpecified && legacyImage {
|
|
||||||
// Environment variable unset with legacy image: set all capabilities.
|
|
||||||
return supportedDriverCapabilities
|
|
||||||
}
|
|
||||||
|
|
||||||
if capsEnvSpecified && len(capsEnv) > 0 {
|
|
||||||
// If the envvironment variable is specified and is non-empty, use the capabilities value
|
|
||||||
envCapabilities := image.NewDriverCapabilities(capsEnv)
|
|
||||||
capabilities = supportedDriverCapabilities.Intersection(envCapabilities)
|
|
||||||
if !envCapabilities.IsAll() && len(capabilities) != len(envCapabilities) {
|
|
||||||
log.Panicln(fmt.Errorf("unsupported capabilities found in '%v' (allowed '%v')", envCapabilities, capabilities))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return capabilities
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hookConfig *hookConfig) getNvidiaConfig(image image.CUDA, privileged bool) *nvidiaConfig {
|
|
||||||
legacyImage := image.IsLegacy()
|
|
||||||
|
|
||||||
devices := hookConfig.getDevices(image, privileged)
|
|
||||||
if len(devices) == 0 {
|
|
||||||
// empty devices means this is not a GPU container.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var migConfigDevices string
|
|
||||||
if d := getMigConfigDevices(image); d != nil {
|
|
||||||
migConfigDevices = *d
|
|
||||||
}
|
|
||||||
if !privileged && migConfigDevices != "" {
|
|
||||||
log.Panicln("cannot set MIG_CONFIG_DEVICES in non privileged container")
|
|
||||||
}
|
|
||||||
|
|
||||||
var migMonitorDevices string
|
|
||||||
if d := getMigMonitorDevices(image); d != nil {
|
|
||||||
migMonitorDevices = *d
|
|
||||||
}
|
|
||||||
if !privileged && migMonitorDevices != "" {
|
|
||||||
log.Panicln("cannot set MIG_MONITOR_DEVICES in non privileged container")
|
|
||||||
}
|
|
||||||
|
|
||||||
imexChannels := hookConfig.getImexChannels(image, privileged)
|
|
||||||
|
|
||||||
driverCapabilities := hookConfig.getDriverCapabilities(image, legacyImage).String()
|
|
||||||
|
|
||||||
requirements, err := image.GetRequirements()
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln("failed to get requirements", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &nvidiaConfig{
|
|
||||||
Devices: devices,
|
|
||||||
MigConfigDevices: migConfigDevices,
|
|
||||||
MigMonitorDevices: migMonitorDevices,
|
|
||||||
ImexChannels: imexChannels,
|
|
||||||
DriverCapabilities: driverCapabilities,
|
|
||||||
Requirements: requirements,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hookConfig *hookConfig) getContainerConfig() (config containerConfig) {
|
|
||||||
var h HookState
|
|
||||||
d := json.NewDecoder(os.Stdin)
|
|
||||||
if err := d.Decode(&h); err != nil {
|
|
||||||
log.Panicln("could not decode container state:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b := h.Bundle
|
|
||||||
if len(b) == 0 {
|
|
||||||
b = h.BundlePath
|
|
||||||
}
|
|
||||||
|
|
||||||
s := loadSpec(path.Join(b, "config.json"))
|
|
||||||
|
|
||||||
image, err := image.New(
|
|
||||||
image.WithEnv(s.Process.Env),
|
|
||||||
image.WithMounts(s.Mounts),
|
|
||||||
image.WithDisableRequire(hookConfig.DisableRequire),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
privileged := isPrivileged(s)
|
|
||||||
return containerConfig{
|
|
||||||
Pid: h.Pid,
|
|
||||||
Rootfs: s.Root.Path,
|
|
||||||
Image: image,
|
|
||||||
Nvidia: hookConfig.getNvidiaConfig(image, privileged),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,977 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/image"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetNvidiaConfig(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
description string
|
|
||||||
env map[string]string
|
|
||||||
privileged bool
|
|
||||||
hookConfig *hookConfig
|
|
||||||
expectedConfig *nvidiaConfig
|
|
||||||
expectedPanic bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "No environment, unprivileged",
|
|
||||||
env: map[string]string{},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "No environment, privileged",
|
|
||||||
env: map[string]string{},
|
|
||||||
privileged: true,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, no devices, no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
DriverCapabilities: image.SupportedDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices 'all', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
DriverCapabilities: image.SupportedDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices 'empty', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices 'void', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "void",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices 'none', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "none",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{""},
|
|
||||||
DriverCapabilities: image.SupportedDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices set, no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: image.SupportedDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices set, capabilities 'empty', no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices set, capabilities 'all', no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "all",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: image.SupportedDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices set, capabilities set, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "video,display",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices set, capabilities set, requirements set",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "video,display",
|
|
||||||
image.NvidiaRequirePrefix + "REQ0": "req0=true",
|
|
||||||
image.NvidiaRequirePrefix + "REQ1": "req1=false",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
Requirements: []string{"cuda>=9.0", "req0=true", "req1=false"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Legacy image, devices set, capabilities set, requirements set, disable requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "video,display",
|
|
||||||
image.NvidiaRequirePrefix + "REQ0": "req0=true",
|
|
||||||
image.NvidiaRequirePrefix + "REQ1": "req1=false",
|
|
||||||
image.EnvVarNvidiaDisableRequire: "true",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
Requirements: []string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, no devices, no capabilities, no requirements, no image.EnvVarCudaVersion",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, no devices, no capabilities, no requirement, image.EnvVarCudaVersion set",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "9.0",
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'all', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'empty', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'void', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "void",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'none', no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "none",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{""},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices set, no capabilities, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices set, capabilities 'empty', no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices set, capabilities 'all', no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "all",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: image.SupportedDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices set, capabilities set, no requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "video,display",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices set, capabilities set, requirements set",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "video,display",
|
|
||||||
image.NvidiaRequirePrefix + "REQ0": "req0=true",
|
|
||||||
image.NvidiaRequirePrefix + "REQ1": "req1=false",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
Requirements: []string{"cuda>=9.0", "req0=true", "req1=false"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices set, capabilities set, requirements set, disable requirements",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "gpu0,gpu1",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "video,display",
|
|
||||||
image.NvidiaRequirePrefix + "REQ0": "req0=true",
|
|
||||||
image.NvidiaRequirePrefix + "REQ1": "req1=false",
|
|
||||||
image.EnvVarNvidiaDisableRequire: "true",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"gpu0", "gpu1"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
Requirements: []string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "No cuda envs, devices 'all'",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'all', migConfig set, privileged",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
image.EnvVarNvidiaMigConfigDevices: "mig0,mig1",
|
|
||||||
},
|
|
||||||
privileged: true,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
MigConfigDevices: "mig0,mig1",
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'all', migConfig set, unprivileged",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
image.EnvVarNvidiaMigConfigDevices: "mig0,mig1",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedPanic: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'all', migMonitor set, privileged",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
image.EnvVarNvidiaMigMonitorDevices: "mig0,mig1",
|
|
||||||
},
|
|
||||||
privileged: true,
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
MigMonitorDevices: "mig0,mig1",
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
Requirements: []string{"cuda>=9.0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Modern image, devices 'all', migMonitor set, unprivileged",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaRequireCuda: "cuda>=9.0",
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
image.EnvVarNvidiaMigMonitorDevices: "mig0,mig1",
|
|
||||||
},
|
|
||||||
privileged: false,
|
|
||||||
expectedPanic: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Hook config set as driver-capabilities-all",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "all",
|
|
||||||
},
|
|
||||||
privileged: true,
|
|
||||||
hookConfig: &hookConfig{
|
|
||||||
Config: &config.Config{
|
|
||||||
SupportedDriverCapabilities: "video,display",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Hook config set, envvar sets driver-capabilities",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "video,display",
|
|
||||||
},
|
|
||||||
privileged: true,
|
|
||||||
hookConfig: &hookConfig{
|
|
||||||
Config: &config.Config{
|
|
||||||
SupportedDriverCapabilities: "video,display,compute,utility",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
DriverCapabilities: "display,video",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Hook config set, envvar unset sets default driver-capabilities",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
},
|
|
||||||
privileged: true,
|
|
||||||
hookConfig: &hookConfig{
|
|
||||||
Config: &config.Config{
|
|
||||||
SupportedDriverCapabilities: "video,display,utility,compute",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"all"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Hook config set, swarmResource overrides device selection",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
"DOCKER_SWARM_RESOURCE": "GPU1,GPU2",
|
|
||||||
},
|
|
||||||
privileged: true,
|
|
||||||
hookConfig: &hookConfig{
|
|
||||||
Config: &config.Config{
|
|
||||||
SwarmResource: "DOCKER_SWARM_RESOURCE",
|
|
||||||
SupportedDriverCapabilities: "video,display,utility,compute",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"GPU1", "GPU2"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Hook config set, comma separated swarmResource is split and overrides device selection",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "all",
|
|
||||||
"DOCKER_SWARM_RESOURCE": "GPU1,GPU2",
|
|
||||||
},
|
|
||||||
privileged: true,
|
|
||||||
hookConfig: &hookConfig{
|
|
||||||
Config: &config.Config{
|
|
||||||
SwarmResource: "NOT_DOCKER_SWARM_RESOURCE,DOCKER_SWARM_RESOURCE",
|
|
||||||
SupportedDriverCapabilities: "video,display,utility,compute",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedConfig: &nvidiaConfig{
|
|
||||||
Devices: []string{"GPU1", "GPU2"},
|
|
||||||
DriverCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
image, _ := image.New(
|
|
||||||
image.WithEnvMap(tc.env),
|
|
||||||
)
|
|
||||||
// Wrap the call to getNvidiaConfig() in a closure.
|
|
||||||
var cfg *nvidiaConfig
|
|
||||||
getConfig := func() {
|
|
||||||
hookCfg := tc.hookConfig
|
|
||||||
if hookCfg == nil {
|
|
||||||
defaultConfig, _ := config.GetDefault()
|
|
||||||
hookCfg = &hookConfig{defaultConfig}
|
|
||||||
}
|
|
||||||
cfg = hookCfg.getNvidiaConfig(image, tc.privileged)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For any tests that are expected to panic, make sure they do.
|
|
||||||
if tc.expectedPanic {
|
|
||||||
require.Panics(t, getConfig)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// For all other tests, just grab the config
|
|
||||||
getConfig()
|
|
||||||
|
|
||||||
// And start comparing the test results to the expected results.
|
|
||||||
if tc.expectedConfig == nil {
|
|
||||||
require.Nil(t, cfg, tc.description)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NotNil(t, cfg, tc.description)
|
|
||||||
|
|
||||||
require.Equal(t, tc.expectedConfig.Devices, cfg.Devices)
|
|
||||||
require.Equal(t, tc.expectedConfig.MigConfigDevices, cfg.MigConfigDevices)
|
|
||||||
require.Equal(t, tc.expectedConfig.MigMonitorDevices, cfg.MigMonitorDevices)
|
|
||||||
require.Equal(t, tc.expectedConfig.DriverCapabilities, cfg.DriverCapabilities)
|
|
||||||
|
|
||||||
require.ElementsMatch(t, tc.expectedConfig.Requirements, cfg.Requirements)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeviceListSourcePriority(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
description string
|
|
||||||
mountDevices []specs.Mount
|
|
||||||
envvarDevices string
|
|
||||||
privileged bool
|
|
||||||
acceptUnprivileged bool
|
|
||||||
acceptMounts bool
|
|
||||||
expectedDevices []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "Mount devices, unprivileged, no accept unprivileged",
|
|
||||||
mountDevices: []specs.Mount{
|
|
||||||
{
|
|
||||||
Source: "/dev/null",
|
|
||||||
Destination: filepath.Join(image.DeviceListAsVolumeMountsRoot, "GPU0"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Source: "/dev/null",
|
|
||||||
Destination: filepath.Join(image.DeviceListAsVolumeMountsRoot, "GPU1"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
envvarDevices: "GPU2,GPU3",
|
|
||||||
privileged: false,
|
|
||||||
acceptUnprivileged: false,
|
|
||||||
acceptMounts: true,
|
|
||||||
expectedDevices: []string{"GPU0", "GPU1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "No mount devices, unprivileged, no accept unprivileged",
|
|
||||||
mountDevices: nil,
|
|
||||||
envvarDevices: "GPU0,GPU1",
|
|
||||||
privileged: false,
|
|
||||||
acceptUnprivileged: false,
|
|
||||||
acceptMounts: true,
|
|
||||||
expectedDevices: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "No mount devices, privileged, no accept unprivileged",
|
|
||||||
mountDevices: nil,
|
|
||||||
envvarDevices: "GPU0,GPU1",
|
|
||||||
privileged: true,
|
|
||||||
acceptUnprivileged: false,
|
|
||||||
acceptMounts: true,
|
|
||||||
expectedDevices: []string{"GPU0", "GPU1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "No mount devices, unprivileged, accept unprivileged",
|
|
||||||
mountDevices: nil,
|
|
||||||
envvarDevices: "GPU0,GPU1",
|
|
||||||
privileged: false,
|
|
||||||
acceptUnprivileged: true,
|
|
||||||
acceptMounts: true,
|
|
||||||
expectedDevices: []string{"GPU0", "GPU1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Mount devices, unprivileged, accept unprivileged, no accept mounts",
|
|
||||||
mountDevices: []specs.Mount{
|
|
||||||
{
|
|
||||||
Source: "/dev/null",
|
|
||||||
Destination: filepath.Join(image.DeviceListAsVolumeMountsRoot, "GPU0"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Source: "/dev/null",
|
|
||||||
Destination: filepath.Join(image.DeviceListAsVolumeMountsRoot, "GPU1"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
envvarDevices: "GPU2,GPU3",
|
|
||||||
privileged: false,
|
|
||||||
acceptUnprivileged: true,
|
|
||||||
acceptMounts: false,
|
|
||||||
expectedDevices: []string{"GPU2", "GPU3"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Mount devices, unprivileged, no accept unprivileged, no accept mounts",
|
|
||||||
mountDevices: []specs.Mount{
|
|
||||||
{
|
|
||||||
Source: "/dev/null",
|
|
||||||
Destination: filepath.Join(image.DeviceListAsVolumeMountsRoot, "GPU0"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Source: "/dev/null",
|
|
||||||
Destination: filepath.Join(image.DeviceListAsVolumeMountsRoot, "GPU1"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
envvarDevices: "GPU2,GPU3",
|
|
||||||
privileged: false,
|
|
||||||
acceptUnprivileged: false,
|
|
||||||
acceptMounts: false,
|
|
||||||
expectedDevices: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
// Wrap the call to getDevices() in a closure.
|
|
||||||
var devices []string
|
|
||||||
getDevices := func() {
|
|
||||||
image, _ := image.New(
|
|
||||||
image.WithEnvMap(
|
|
||||||
map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: tc.envvarDevices,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
image.WithMounts(tc.mountDevices),
|
|
||||||
)
|
|
||||||
defaultConfig, _ := config.GetDefault()
|
|
||||||
cfg := &hookConfig{defaultConfig}
|
|
||||||
cfg.AcceptEnvvarUnprivileged = tc.acceptUnprivileged
|
|
||||||
cfg.AcceptDeviceListAsVolumeMounts = tc.acceptMounts
|
|
||||||
devices = cfg.getDevices(image, tc.privileged)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For all other tests, just grab the devices and check the results
|
|
||||||
getDevices()
|
|
||||||
|
|
||||||
require.Equal(t, tc.expectedDevices, devices)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDevicesFromEnvvar(t *testing.T) {
|
|
||||||
envDockerResourceGPUs := "DOCKER_RESOURCE_GPUS"
|
|
||||||
gpuID := "GPU-12345"
|
|
||||||
anotherGPUID := "GPU-67890"
|
|
||||||
thirdGPUID := "MIG-12345"
|
|
||||||
|
|
||||||
var tests = []struct {
|
|
||||||
description string
|
|
||||||
swarmResourceEnvvars []string
|
|
||||||
env map[string]string
|
|
||||||
expectedDevices []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "empty env returns nil for non-legacy image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "blank NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "'void' NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "void",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "'none' NVIDIA_VISIBLE_DEVICES returns empty for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "none",
|
|
||||||
},
|
|
||||||
expectedDevices: []string{""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "NVIDIA_VISIBLE_DEVICES set returns value for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{gpuID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "NVIDIA_VISIBLE_DEVICES set returns value for legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
image.EnvVarCudaVersion: "legacy",
|
|
||||||
},
|
|
||||||
expectedDevices: []string{gpuID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "empty env returns all for legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarCudaVersion: "legacy",
|
|
||||||
},
|
|
||||||
expectedDevices: []string{"all"},
|
|
||||||
},
|
|
||||||
// Add the `DOCKER_RESOURCE_GPUS` envvar and ensure that this is ignored when
|
|
||||||
// not enabled
|
|
||||||
{
|
|
||||||
description: "missing NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "blank NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "",
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "'void' NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "void",
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "'none' NVIDIA_VISIBLE_DEVICES returns empty for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: "none",
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "NVIDIA_VISIBLE_DEVICES set returns value for non-legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{gpuID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "NVIDIA_VISIBLE_DEVICES set returns value for legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
image.EnvVarCudaVersion: "legacy",
|
|
||||||
},
|
|
||||||
expectedDevices: []string{gpuID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "empty env returns all for legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
image.EnvVarCudaVersion: "legacy",
|
|
||||||
},
|
|
||||||
expectedDevices: []string{"all"},
|
|
||||||
},
|
|
||||||
// Add the `DOCKER_RESOURCE_GPUS` envvar and ensure that this is selected when
|
|
||||||
// enabled
|
|
||||||
{
|
|
||||||
description: "empty env returns nil for non-legacy image",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "blank DOCKER_RESOURCE_GPUS returns nil for non-legacy image",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "'void' DOCKER_RESOURCE_GPUS returns nil for non-legacy image",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: "void",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "'none' DOCKER_RESOURCE_GPUS returns empty for non-legacy image",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: "none",
|
|
||||||
},
|
|
||||||
expectedDevices: []string{""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "DOCKER_RESOURCE_GPUS set returns value for non-legacy image",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: gpuID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{gpuID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "DOCKER_RESOURCE_GPUS set returns value for legacy image",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: gpuID,
|
|
||||||
image.EnvVarCudaVersion: "legacy",
|
|
||||||
},
|
|
||||||
expectedDevices: []string{gpuID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "DOCKER_RESOURCE_GPUS is selected if present",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
env: map[string]string{
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{anotherGPUID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "DOCKER_RESOURCE_GPUS overrides NVIDIA_VISIBLE_DEVICES if present",
|
|
||||||
swarmResourceEnvvars: []string{envDockerResourceGPUs},
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
envDockerResourceGPUs: anotherGPUID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{anotherGPUID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "DOCKER_RESOURCE_GPUS_ADDITIONAL overrides NVIDIA_VISIBLE_DEVICES if present",
|
|
||||||
swarmResourceEnvvars: []string{"DOCKER_RESOURCE_GPUS_ADDITIONAL"},
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
"DOCKER_RESOURCE_GPUS_ADDITIONAL": anotherGPUID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{anotherGPUID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "All available swarm resource envvars are selected and override NVIDIA_VISIBLE_DEVICES if present",
|
|
||||||
swarmResourceEnvvars: []string{"DOCKER_RESOURCE_GPUS", "DOCKER_RESOURCE_GPUS_ADDITIONAL"},
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
"DOCKER_RESOURCE_GPUS": thirdGPUID,
|
|
||||||
"DOCKER_RESOURCE_GPUS_ADDITIONAL": anotherGPUID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{thirdGPUID, anotherGPUID},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "DOCKER_RESOURCE_GPUS_ADDITIONAL or DOCKER_RESOURCE_GPUS override NVIDIA_VISIBLE_DEVICES if present",
|
|
||||||
swarmResourceEnvvars: []string{"DOCKER_RESOURCE_GPUS", "DOCKER_RESOURCE_GPUS_ADDITIONAL"},
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaVisibleDevices: gpuID,
|
|
||||||
"DOCKER_RESOURCE_GPUS_ADDITIONAL": anotherGPUID,
|
|
||||||
},
|
|
||||||
expectedDevices: []string{anotherGPUID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
image, _ := image.New(
|
|
||||||
image.WithEnvMap(tc.env),
|
|
||||||
)
|
|
||||||
devices := getDevicesFromEnvvar(image, tc.swarmResourceEnvvars)
|
|
||||||
require.EqualValues(t, tc.expectedDevices, devices)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDriverCapabilities(t *testing.T) {
|
|
||||||
|
|
||||||
supportedCapabilities := "compute,display,utility,video"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
env map[string]string
|
|
||||||
legacyImage bool
|
|
||||||
supportedCapabilities string
|
|
||||||
expectedPanic bool
|
|
||||||
expectedCapabilities string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "Env is set for legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "display,video",
|
|
||||||
},
|
|
||||||
legacyImage: true,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: "display,video",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Env is all for legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "all",
|
|
||||||
},
|
|
||||||
legacyImage: true,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: supportedCapabilities,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Env is empty for legacy image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "",
|
|
||||||
},
|
|
||||||
legacyImage: true,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Env unset for legacy image is 'all'",
|
|
||||||
env: map[string]string{},
|
|
||||||
legacyImage: true,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: supportedCapabilities,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Env is set for modern image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "display,video",
|
|
||||||
},
|
|
||||||
legacyImage: false,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: "display,video",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Env unset for modern image is default",
|
|
||||||
env: map[string]string{},
|
|
||||||
legacyImage: false,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Env is all for modern image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "all",
|
|
||||||
},
|
|
||||||
legacyImage: false,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: supportedCapabilities,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Env is empty for modern image",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "",
|
|
||||||
},
|
|
||||||
legacyImage: false,
|
|
||||||
supportedCapabilities: supportedCapabilities,
|
|
||||||
expectedCapabilities: image.DefaultDriverCapabilities.String(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Invalid capabilities panic",
|
|
||||||
env: map[string]string{
|
|
||||||
image.EnvVarNvidiaDriverCapabilities: "compute,utility",
|
|
||||||
},
|
|
||||||
supportedCapabilities: "not-compute,not-utility",
|
|
||||||
expectedPanic: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Default is restricted for modern image",
|
|
||||||
legacyImage: false,
|
|
||||||
supportedCapabilities: "compute",
|
|
||||||
expectedCapabilities: "compute",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
var capabilities string
|
|
||||||
|
|
||||||
c := hookConfig{
|
|
||||||
Config: &config.Config{
|
|
||||||
SupportedDriverCapabilities: tc.supportedCapabilities,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
image, _ := image.New(
|
|
||||||
image.WithEnvMap(tc.env),
|
|
||||||
)
|
|
||||||
getDriverCapabilities := func() {
|
|
||||||
capabilities = c.getDriverCapabilities(image, tc.legacyImage).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.expectedPanic {
|
|
||||||
require.Panics(t, getDriverCapabilities)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
getDriverCapabilities()
|
|
||||||
require.EqualValues(t, tc.expectedCapabilities, capabilities)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/image"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
configPath = "/etc/nvidia-container-runtime/config.toml"
|
|
||||||
driverPath = "/run/nvidia/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// hookConfig wraps the toolkit config.
|
|
||||||
// This allows for functions to be defined on the local type.
|
|
||||||
type hookConfig struct {
|
|
||||||
*config.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadConfig loads the required paths for the hook config.
|
|
||||||
func loadConfig() (*config.Config, error) {
|
|
||||||
var configPaths []string
|
|
||||||
var required bool
|
|
||||||
if len(*configflag) != 0 {
|
|
||||||
configPaths = append(configPaths, *configflag)
|
|
||||||
required = true
|
|
||||||
} else {
|
|
||||||
configPaths = append(configPaths, path.Join(driverPath, configPath), configPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range configPaths {
|
|
||||||
cfg, err := config.New(
|
|
||||||
config.WithConfigFile(p),
|
|
||||||
config.WithRequired(true),
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
return cfg.Config()
|
|
||||||
} else if os.IsNotExist(err) && !required {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("couldn't open required configuration file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config.GetDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHookConfig() (*hookConfig, error) {
|
|
||||||
cfg, err := loadConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load config: %v", err)
|
|
||||||
}
|
|
||||||
config := &hookConfig{cfg}
|
|
||||||
|
|
||||||
allSupportedDriverCapabilities := image.SupportedDriverCapabilities
|
|
||||||
if config.SupportedDriverCapabilities == "all" {
|
|
||||||
config.SupportedDriverCapabilities = allSupportedDriverCapabilities.String()
|
|
||||||
}
|
|
||||||
configuredCapabilities := image.NewDriverCapabilities(config.SupportedDriverCapabilities)
|
|
||||||
// We ensure that the configured value is a subset of all supported capabilities
|
|
||||||
if !allSupportedDriverCapabilities.IsSuperset(configuredCapabilities) {
|
|
||||||
configName := config.getConfigOption("SupportedDriverCapabilities")
|
|
||||||
log.Panicf("Invalid value for config option '%v'; %v (supported: %v)\n", configName, config.SupportedDriverCapabilities, allSupportedDriverCapabilities.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConfigOption returns the toml config option associated with the
|
|
||||||
// specified struct field.
|
|
||||||
func (c hookConfig) getConfigOption(fieldName string) string {
|
|
||||||
t := reflect.TypeOf(c)
|
|
||||||
f, ok := t.FieldByName(fieldName)
|
|
||||||
if !ok {
|
|
||||||
return fieldName
|
|
||||||
}
|
|
||||||
v, ok := f.Tag.Lookup("toml")
|
|
||||||
if !ok {
|
|
||||||
return fieldName
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSwarmResourceEnvvars returns the swarm resource envvars for the config.
|
|
||||||
func (c *hookConfig) getSwarmResourceEnvvars() []string {
|
|
||||||
if c.SwarmResource == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
candidates := strings.Split(c.SwarmResource, ",")
|
|
||||||
|
|
||||||
var envvars []string
|
|
||||||
for _, c := range candidates {
|
|
||||||
trimmed := strings.TrimSpace(c)
|
|
||||||
if len(trimmed) > 0 {
|
|
||||||
envvars = append(envvars, trimmed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return envvars
|
|
||||||
}
|
|
||||||
|
|
||||||
// nvidiaContainerCliCUDACompatModeFlags returns required --cuda-compat-mode
|
|
||||||
// flag(s) depending on the hook and runtime configurations.
|
|
||||||
func (c *hookConfig) nvidiaContainerCliCUDACompatModeFlags() []string {
|
|
||||||
var flag string
|
|
||||||
switch c.NVIDIAContainerRuntimeConfig.Modes.Legacy.CUDACompatMode {
|
|
||||||
case config.CUDACompatModeLdconfig:
|
|
||||||
flag = "--cuda-compat-mode=ldconfig"
|
|
||||||
case config.CUDACompatModeMount:
|
|
||||||
flag = "--cuda-compat-mode=mount"
|
|
||||||
case config.CUDACompatModeDisabled, config.CUDACompatModeHook:
|
|
||||||
flag = "--cuda-compat-mode=disabled"
|
|
||||||
default:
|
|
||||||
if !c.Features.AllowCUDACompatLibsFromContainer.IsEnabled() {
|
|
||||||
flag = "--cuda-compat-mode=disabled"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if flag == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []string{flag}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/info"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
debugflag = flag.Bool("debug", false, "enable debug output")
|
|
||||||
versionflag = flag.Bool("version", false, "enable version output")
|
|
||||||
configflag = flag.String("config", "", "configuration file")
|
|
||||||
)
|
|
||||||
|
|
||||||
func exit() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
if _, ok := err.(runtime.Error); ok {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
if *debugflag {
|
|
||||||
log.Printf("%s", debug.Stack())
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCLIPath(config config.ContainerCLIConfig) string {
|
|
||||||
if config.Path != "" {
|
|
||||||
return config.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Setenv("PATH", lookup.GetPath(config.Root)); err != nil {
|
|
||||||
log.Panicln("couldn't set PATH variable:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := exec.LookPath("nvidia-container-cli")
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln("couldn't find binary nvidia-container-cli in", os.Getenv("PATH"), ":", err)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRootfsPath returns an absolute path. We don't need to resolve symlinks for now.
|
|
||||||
func getRootfsPath(config containerConfig) string {
|
|
||||||
rootfs, err := filepath.Abs(config.Rootfs)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
return rootfs
|
|
||||||
}
|
|
||||||
|
|
||||||
func doPrestart() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
defer exit()
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
hook, err := getHookConfig()
|
|
||||||
if err != nil || hook == nil {
|
|
||||||
log.Panicln("error getting hook config:", err)
|
|
||||||
}
|
|
||||||
cli := hook.NVIDIAContainerCLIConfig
|
|
||||||
|
|
||||||
container := hook.getContainerConfig()
|
|
||||||
nvidia := container.Nvidia
|
|
||||||
if nvidia == nil {
|
|
||||||
// Not a GPU container, nothing to do.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hook.NVIDIAContainerRuntimeHookConfig.SkipModeDetection && info.ResolveAutoMode(&logInterceptor{}, hook.NVIDIAContainerRuntimeConfig.Mode, container.Image) != "legacy" {
|
|
||||||
log.Panicln("invoking the NVIDIA Container Runtime Hook directly (e.g. specifying the docker --gpus flag) is not supported. Please use the NVIDIA Container Runtime (e.g. specify the --runtime=nvidia flag) instead.")
|
|
||||||
}
|
|
||||||
|
|
||||||
rootfs := getRootfsPath(container)
|
|
||||||
|
|
||||||
args := []string{getCLIPath(cli)}
|
|
||||||
if cli.Root != "" {
|
|
||||||
args = append(args, fmt.Sprintf("--root=%s", cli.Root))
|
|
||||||
}
|
|
||||||
if cli.LoadKmods {
|
|
||||||
args = append(args, "--load-kmods")
|
|
||||||
}
|
|
||||||
if hook.Features.DisableImexChannelCreation.IsEnabled() {
|
|
||||||
args = append(args, "--no-create-imex-channels")
|
|
||||||
}
|
|
||||||
if cli.NoPivot {
|
|
||||||
args = append(args, "--no-pivot")
|
|
||||||
}
|
|
||||||
if *debugflag {
|
|
||||||
args = append(args, "--debug=/dev/stderr")
|
|
||||||
} else if cli.Debug != "" {
|
|
||||||
args = append(args, fmt.Sprintf("--debug=%s", cli.Debug))
|
|
||||||
}
|
|
||||||
if cli.Ldcache != "" {
|
|
||||||
args = append(args, fmt.Sprintf("--ldcache=%s", cli.Ldcache))
|
|
||||||
}
|
|
||||||
if cli.User != "" {
|
|
||||||
args = append(args, fmt.Sprintf("--user=%s", cli.User))
|
|
||||||
}
|
|
||||||
args = append(args, "configure")
|
|
||||||
|
|
||||||
args = append(args, hook.nvidiaContainerCliCUDACompatModeFlags()...)
|
|
||||||
|
|
||||||
if ldconfigPath := cli.NormalizeLDConfigPath(); ldconfigPath != "" {
|
|
||||||
args = append(args, fmt.Sprintf("--ldconfig=%s", ldconfigPath))
|
|
||||||
}
|
|
||||||
if cli.NoCgroups {
|
|
||||||
args = append(args, "--no-cgroups")
|
|
||||||
}
|
|
||||||
if devicesString := strings.Join(nvidia.Devices, ","); len(devicesString) > 0 {
|
|
||||||
args = append(args, fmt.Sprintf("--device=%s", devicesString))
|
|
||||||
}
|
|
||||||
if len(nvidia.MigConfigDevices) > 0 {
|
|
||||||
args = append(args, fmt.Sprintf("--mig-config=%s", nvidia.MigConfigDevices))
|
|
||||||
}
|
|
||||||
if len(nvidia.MigMonitorDevices) > 0 {
|
|
||||||
args = append(args, fmt.Sprintf("--mig-monitor=%s", nvidia.MigMonitorDevices))
|
|
||||||
}
|
|
||||||
if imexString := strings.Join(nvidia.ImexChannels, ","); len(imexString) > 0 {
|
|
||||||
args = append(args, fmt.Sprintf("--imex-channel=%s", imexString))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cap := range strings.Split(nvidia.DriverCapabilities, ",") {
|
|
||||||
if len(cap) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
args = append(args, capabilityToCLI(cap))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, req := range nvidia.Requirements {
|
|
||||||
args = append(args, fmt.Sprintf("--require=%s", req))
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, fmt.Sprintf("--pid=%s", strconv.FormatUint(uint64(container.Pid), 10)))
|
|
||||||
args = append(args, rootfs)
|
|
||||||
|
|
||||||
env := append(os.Environ(), cli.Environment...)
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection?
|
|
||||||
err = syscall.Exec(args[0], args, env)
|
|
||||||
log.Panicln("exec failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
|
||||||
flag.PrintDefaults()
|
|
||||||
fmt.Fprintf(os.Stderr, "\nCommands:\n")
|
|
||||||
fmt.Fprintf(os.Stderr, " prestart\n run the prestart hook\n")
|
|
||||||
fmt.Fprintf(os.Stderr, " poststart\n no-op\n")
|
|
||||||
fmt.Fprintf(os.Stderr, " poststop\n no-op\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *versionflag {
|
|
||||||
fmt.Printf("%v version %v\n", "NVIDIA Container Runtime Hook", info.GetVersionString())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
args := flag.Args()
|
|
||||||
if len(args) == 0 {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch args[0] {
|
|
||||||
case "prestart":
|
|
||||||
doPrestart()
|
|
||||||
os.Exit(0)
|
|
||||||
case "poststart":
|
|
||||||
fallthrough
|
|
||||||
case "poststop":
|
|
||||||
os.Exit(0)
|
|
||||||
default:
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// logInterceptor implements the logger.Interface to allow for logging from executable.
|
|
||||||
type logInterceptor struct {
|
|
||||||
logger.NullLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logInterceptor) Infof(format string, args ...interface{}) {
|
|
||||||
log.Printf(format, args...)
|
|
||||||
}
|
|
||||||
170
cmd/nvidia-container-runtime.experimental/README.md
Normal file
170
cmd/nvidia-container-runtime.experimental/README.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# The Experimental NVIDIA Container Runtime
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The experimental NVIDIA Container Runtime is a proof-of-concept runtime that
|
||||||
|
approaches the problem of making GPUs (or other NVIDIA devices) available in
|
||||||
|
containerized environments in a different manner to the existing
|
||||||
|
[NVIDIA Container Runtime](../nvidia-container-runtime). Wherease the current
|
||||||
|
runtime relies on the [NVIDIA Container Library](https://github.com/NVIDIA/libnvidia-container)
|
||||||
|
to perform the modifications to a container, the experiemental runtime aims to
|
||||||
|
express the required modifications in terms of changes to a container's [OCI
|
||||||
|
runtime specification](https://github.com/opencontainers/runtime-spec). This
|
||||||
|
also aligns with open initiatives such as the [Container Device Interface (CDI)](https://github.com/container-orchestrated-devices/container-device-interface).
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
* The path of NVIDIA CUDA libraries / binaries injected into the container currently match that of the host system. This means that on an Ubuntu-based host systems these would be at `/usr/lib/x86_64-linux-gnu`
|
||||||
|
even if the container distribution would normally expect these at another location (e.g. `/usr/lib64`)
|
||||||
|
* Tools such as `nvidia-smi` may create additional device nodes in the container when run. This is
|
||||||
|
prevented in the "classic" runtime (and the NVIDIA Container Library) by modifying
|
||||||
|
the `/proc/driver/nvidia/params` file in the container.
|
||||||
|
* Other `NVIDIA_*` environment variables (e.g. `NVIDIA_DRIVER_CAPABILITIES`) are
|
||||||
|
not considered to filter mounted libraries or binaries. This is equivalent to
|
||||||
|
always using `NVIDIA_DRIVER_CAPABILITIES=all`.
|
||||||
|
|
||||||
|
## Building / Installing
|
||||||
|
|
||||||
|
The experimental NVIDIA Container Runtime is a self-contained golang binary and
|
||||||
|
can thus be built from source, or installed directly using `go install`.
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
|
After cloning the `nvidia-container-toolkit` repository from [GitLab](https://gitlab.com/nvidia/container-toolkit/container-toolkit)
|
||||||
|
or from the read-only mirror on [GitHub](https://github.com/NVIDIA/nvidia-container-toolkit)
|
||||||
|
running the following make command in the repository root:
|
||||||
|
```bash
|
||||||
|
make cmd-nvidia-container-runtime.experimental
|
||||||
|
```
|
||||||
|
will create an executable file `nvidia-container-runtime.experimental` in the
|
||||||
|
root.
|
||||||
|
|
||||||
|
A dockerized target:
|
||||||
|
```bash
|
||||||
|
make docker-cmd-nvidia-container-runtime.experimental
|
||||||
|
```
|
||||||
|
will also create the executable file `nvidia-container-runtime.experimental`
|
||||||
|
without requiring the setup of a development environment (with the exception)
|
||||||
|
of having `make` and `docker` installed.
|
||||||
|
|
||||||
|
### Go install
|
||||||
|
|
||||||
|
The experimental NVIDIA Container Runtime can also be `go installed` by running
|
||||||
|
the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-container-runtime.experimental@experimental
|
||||||
|
```
|
||||||
|
which will build and install the `nvidia-container-runtime.experimental`
|
||||||
|
executable in the `${GOPATH}/bin` folder.
|
||||||
|
|
||||||
|
## Using the Runtime
|
||||||
|
|
||||||
|
The experimental NVIDIA Container Runtime is intended as a drop-in replacement
|
||||||
|
for the "classic" NVIDIA Container Runtime. As such it is used in the same
|
||||||
|
way (with the exception of the known limitiations noted above).
|
||||||
|
|
||||||
|
In general terms, to use the experimental NVIDIA Container Runtime to launch a
|
||||||
|
container with GPU support, it should be inserted as a shim for the desired
|
||||||
|
low-level OCI-compliant runtime (e.g. `runc` or `crun`). How this is achieved
|
||||||
|
depends on how containers are being launched.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
In the case of `docker` for example, the runtime must be registered with the
|
||||||
|
Docker daemon. This can be done by modifying the `/etc/docker/daemon.json` file
|
||||||
|
to contain the following:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"runtimes": {
|
||||||
|
"nvidia-experimental": {
|
||||||
|
"path": "nvidia-container-runtime.experimental",
|
||||||
|
"runtimeArgs": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This can then be invoked from docker by including the `--runtime=nvidia-experimental`
|
||||||
|
option when executing a `docker run` command.
|
||||||
|
|
||||||
|
### Runc
|
||||||
|
|
||||||
|
If `runc` is being used to run a container directly substituting the `runc`
|
||||||
|
command for `nvidia-container-runtime.experimental` should be sufficient as
|
||||||
|
the latter will `exec` to `runc` once the required (in-place) modifications have
|
||||||
|
been made to the container's OCI spec (`config.json` file).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Runtime Path
|
||||||
|
The experimental NVIDIA Container Runtime allows for the path to the low-level
|
||||||
|
runtime to be specified. This is done by setting the following option in the
|
||||||
|
`/etc/nvidia-container-runtime/config.toml` file or setting the
|
||||||
|
`NVIDIA_CONTAINER_RUNTIME_PATH` environment variable.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[nvidia-container-runtime.experimental]
|
||||||
|
|
||||||
|
runtime-path = "/path/to/low-level-runtime"
|
||||||
|
```
|
||||||
|
This path can be set to the path for `runc` or `crun` on a system and if it is
|
||||||
|
a relative path, the `PATH` is searched for a matching executable.
|
||||||
|
|
||||||
|
### Device Selection
|
||||||
|
In order to select a specific device, the experimental NVIDIA Container Runtime
|
||||||
|
mimics the behaviour of the "classic" runtime. That is to say that the values of
|
||||||
|
certain [environment variables](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/user-guide.html#environment-variables-oci-spec)
|
||||||
|
**in the container's OCI specification** control the behaviour of the runtime.
|
||||||
|
|
||||||
|
#### `NVIDIA_VISIBLE_DEVICES`
|
||||||
|
This variable controls which GPUs will be made accessible inside the container.
|
||||||
|
|
||||||
|
##### Possible values
|
||||||
|
* `0,1,2`, `GPU-fef8089b` …: a comma-separated list of GPU UUID(s) or index(es).
|
||||||
|
* `all`: all GPUs will be accessible, this is the default value in our container images.
|
||||||
|
* `none`: no GPU will be accessible, but driver capabilities will be enabled.
|
||||||
|
* `void` or *empty* or *unset*: `nvidia-container-runtime` will have the same behavior as `runc`.
|
||||||
|
|
||||||
|
**Note**: When running on a MIG capable device, the following values will also be available:
|
||||||
|
* `0:0,0:1,1:0`, `MIG-GPU-fef8089b/0/1` …: a comma-separated list of MIG Device UUID(s) or index(es).
|
||||||
|
|
||||||
|
Where the MIG device indices have the form `<GPU Device Index>:<MIG Device Index>` as seen in the example output:
|
||||||
|
```
|
||||||
|
$ nvidia-smi -L
|
||||||
|
GPU 0: Graphics Device (UUID: GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5)
|
||||||
|
MIG Device 0: (UUID: MIG-GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5/1/0)
|
||||||
|
MIG Device 1: (UUID: MIG-GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5/1/1)
|
||||||
|
MIG Device 2: (UUID: MIG-GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5/11/0)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `NVIDIA_MIG_CONFIG_DEVICES`
|
||||||
|
This variable controls which of the visible GPUs can have their MIG
|
||||||
|
configuration managed from within the container. This includes enabling and
|
||||||
|
disabling MIG mode, creating and destroying GPU Instances and Compute
|
||||||
|
Instances, etc.
|
||||||
|
|
||||||
|
##### Possible values
|
||||||
|
* `all`: Allow all MIG-capable GPUs in the visible device list to have their
|
||||||
|
MIG configurations managed.
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
* This feature is only available on MIG capable devices (e.g. the A100).
|
||||||
|
* To use this feature, the container must be started with `CAP_SYS_ADMIN` privileges.
|
||||||
|
* When not running as `root`, the container user must have read access to the
|
||||||
|
`/proc/driver/nvidia/capabilities/mig/config` file on the host.
|
||||||
|
|
||||||
|
#### `NVIDIA_MIG_MONITOR_DEVICES`
|
||||||
|
This variable controls which of the visible GPUs can have aggregate information
|
||||||
|
about all of their MIG devices monitored from within the container. This
|
||||||
|
includes inspecting the aggregate memory usage, listing the aggregate running
|
||||||
|
processes, etc.
|
||||||
|
|
||||||
|
##### Possible values
|
||||||
|
* `all`: Allow all MIG-capable GPUs in the visible device list to have their
|
||||||
|
MIG devices monitored.
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
* This feature is only available on MIG capable devices (e.g. the A100).
|
||||||
|
* To use this feature, the container must be started with `CAP_SYS_ADMIN` privileges.
|
||||||
|
* When not running as `root`, the container user must have read access to the
|
||||||
|
`/proc/driver/nvidia/capabilities/mig/monitor` file on the host.
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// list represents a list of config updaters that are applied in order, with later
|
||||||
|
// configs having preference.
|
||||||
|
type list struct {
|
||||||
|
logger *log.Logger
|
||||||
|
configs []configUpdater
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ configUpdater = (*list)(nil)
|
||||||
|
|
||||||
|
func newListWithLogger(logger *log.Logger, ci ...configUpdater) configUpdater {
|
||||||
|
c := list{
|
||||||
|
logger: logger,
|
||||||
|
configs: ci,
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c list) Update(cfg *Config) error {
|
||||||
|
for i, u := range c.configs {
|
||||||
|
c.logger.Debugf("Applying config %v: %v", i, u)
|
||||||
|
err := u.Update(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error applying config %v: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
testlog "github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComposite(t *testing.T) {
|
||||||
|
logger, _ := testlog.NewNullLogger()
|
||||||
|
|
||||||
|
// Empty list
|
||||||
|
c := newListWithLogger(logger)
|
||||||
|
|
||||||
|
cfg := &Config{}
|
||||||
|
err := c.Update(cfg)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, &Config{}, cfg)
|
||||||
|
|
||||||
|
// Add a single mock config
|
||||||
|
mockConfigUpdater := &configUpdaterMock{
|
||||||
|
UpdateFunc: func(cfg *Config) error {
|
||||||
|
cfg.DebugFilePath = "updated"
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c = newListWithLogger(logger, mockConfigUpdater)
|
||||||
|
cfg = &Config{}
|
||||||
|
err = c.Update(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, &Config{DebugFilePath: "updated"}, cfg)
|
||||||
|
|
||||||
|
require.Len(t, mockConfigUpdater.UpdateCalls(), 1)
|
||||||
|
|
||||||
|
// Reset the calls
|
||||||
|
mockConfigUpdater.calls = struct{ Update []struct{ Config *Config } }{}
|
||||||
|
|
||||||
|
// define an error config
|
||||||
|
errorConfigUpdater := &configUpdaterMock{
|
||||||
|
UpdateFunc: func(cfg *Config) error {
|
||||||
|
return fmt.Errorf("mock error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c = newListWithLogger(logger, errorConfigUpdater, mockConfigUpdater)
|
||||||
|
cfg = &Config{}
|
||||||
|
err = c.Update(cfg)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.EqualValues(t, &Config{}, cfg)
|
||||||
|
|
||||||
|
require.Len(t, errorConfigUpdater.UpdateCalls(), 1)
|
||||||
|
require.Len(t, mockConfigUpdater.UpdateCalls(), 0)
|
||||||
|
|
||||||
|
// Reset the calls
|
||||||
|
mockConfigUpdater.calls = struct{ Update []struct{ Config *Config } }{}
|
||||||
|
errorConfigUpdater.calls = struct{ Update []struct{ Config *Config } }{}
|
||||||
|
|
||||||
|
// Change the order of the config and error
|
||||||
|
c = newListWithLogger(logger, mockConfigUpdater, errorConfigUpdater)
|
||||||
|
cfg = &Config{}
|
||||||
|
err = c.Update(cfg)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.EqualValues(t, &Config{DebugFilePath: "updated"}, cfg)
|
||||||
|
|
||||||
|
require.Len(t, errorConfigUpdater.UpdateCalls(), 1)
|
||||||
|
require.Len(t, mockConfigUpdater.UpdateCalls(), 1)
|
||||||
|
|
||||||
|
// Reset the calls
|
||||||
|
mockConfigUpdater.calls = struct{ Update []struct{ Config *Config } }{}
|
||||||
|
errorConfigUpdater.calls = struct{ Update []struct{ Config *Config } }{}
|
||||||
|
|
||||||
|
// Call the mock twice
|
||||||
|
c = newListWithLogger(logger, mockConfigUpdater, mockConfigUpdater)
|
||||||
|
cfg = &Config{}
|
||||||
|
err = c.Update(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, &Config{DebugFilePath: "updated"}, cfg)
|
||||||
|
|
||||||
|
require.Len(t, mockConfigUpdater.UpdateCalls(), 2)
|
||||||
|
}
|
||||||
63
cmd/nvidia-container-runtime.experimental/config/config.go
Normal file
63
cmd/nvidia-container-runtime.experimental/config/config.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config defines the configuration options for the NVIDIA Container Runtime
|
||||||
|
type Config struct {
|
||||||
|
// Root defines the root of the file system to be used for locating mounts
|
||||||
|
Root string
|
||||||
|
// DebugFilePath defines a log file to print debug output to
|
||||||
|
DebugFilePath string
|
||||||
|
// RuntimePath defines the path to an OCI compliant runtime
|
||||||
|
RuntimePath string
|
||||||
|
// LogLevel defines the logging level for the application
|
||||||
|
LogLevel string
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate moq -stub -out config_mock.go . configUpdater
|
||||||
|
|
||||||
|
// configUpdate represents an interface for applying updates to a config.
|
||||||
|
type configUpdater interface {
|
||||||
|
Update(*Config) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns a config struct with the values resolved. The values are defined in order of
|
||||||
|
// priority:
|
||||||
|
// 1. From the associated environment variables
|
||||||
|
// 2. From the loaded config file
|
||||||
|
// 3. From the default values defined in the `defaultConfig` function
|
||||||
|
func GetConfig(logger *log.Logger) (*Config, error) {
|
||||||
|
cfg := &Config{}
|
||||||
|
|
||||||
|
configs := newListWithLogger(logger,
|
||||||
|
newDefaultConfig(),
|
||||||
|
newDefaultConfigFileWithLogger(logger),
|
||||||
|
newConfigFromEnvironment(),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := configs.Update(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting config: %v", err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// Code generated by moq; DO NOT EDIT.
|
||||||
|
// github.com/matryer/moq
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure, that configUpdaterMock does implement configUpdater.
|
||||||
|
// If this is not the case, regenerate this file with moq.
|
||||||
|
var _ configUpdater = &configUpdaterMock{}
|
||||||
|
|
||||||
|
// configUpdaterMock is a mock implementation of configUpdater.
|
||||||
|
//
|
||||||
|
// func TestSomethingThatUsesconfigUpdater(t *testing.T) {
|
||||||
|
//
|
||||||
|
// // make and configure a mocked configUpdater
|
||||||
|
// mockedconfigUpdater := &configUpdaterMock{
|
||||||
|
// UpdateFunc: func(config *Config) error {
|
||||||
|
// panic("mock out the Update method")
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // use mockedconfigUpdater in code that requires configUpdater
|
||||||
|
// // and then make assertions.
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
type configUpdaterMock struct {
|
||||||
|
// UpdateFunc mocks the Update method.
|
||||||
|
UpdateFunc func(config *Config) error
|
||||||
|
|
||||||
|
// calls tracks calls to the methods.
|
||||||
|
calls struct {
|
||||||
|
// Update holds details about calls to the Update method.
|
||||||
|
Update []struct {
|
||||||
|
// Config is the config argument value.
|
||||||
|
Config *Config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lockUpdate sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update calls UpdateFunc.
|
||||||
|
func (mock *configUpdaterMock) Update(config *Config) error {
|
||||||
|
callInfo := struct {
|
||||||
|
Config *Config
|
||||||
|
}{
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
mock.lockUpdate.Lock()
|
||||||
|
mock.calls.Update = append(mock.calls.Update, callInfo)
|
||||||
|
mock.lockUpdate.Unlock()
|
||||||
|
if mock.UpdateFunc == nil {
|
||||||
|
var (
|
||||||
|
errOut error
|
||||||
|
)
|
||||||
|
return errOut
|
||||||
|
}
|
||||||
|
return mock.UpdateFunc(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCalls gets all the calls that were made to Update.
|
||||||
|
// Check the length with:
|
||||||
|
// len(mockedconfigUpdater.UpdateCalls())
|
||||||
|
func (mock *configUpdaterMock) UpdateCalls() []struct {
|
||||||
|
Config *Config
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Config *Config
|
||||||
|
}
|
||||||
|
mock.lockUpdate.RLock()
|
||||||
|
calls = mock.calls.Update
|
||||||
|
mock.lockUpdate.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
testlog "github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetConfigWithCustomConfig(t *testing.T) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// By default debug is disabled
|
||||||
|
contents := []byte("[nvidia-container-runtime]\ndebug = \"/nvidia-container-toolkit.log\"")
|
||||||
|
testDir := path.Join(wd, "test")
|
||||||
|
filename := path.Join(testDir, configFileRelativePath)
|
||||||
|
|
||||||
|
previousConfig, present := os.LookupEnv(configOverride)
|
||||||
|
os.Setenv(configOverride, testDir)
|
||||||
|
defer func() {
|
||||||
|
if present {
|
||||||
|
os.Setenv(configOverride, previousConfig)
|
||||||
|
} else {
|
||||||
|
os.Unsetenv(configOverride)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(filename), 0766))
|
||||||
|
require.NoError(t, ioutil.WriteFile(filename, contents, 0766))
|
||||||
|
|
||||||
|
defer func() { require.NoError(t, os.RemoveAll(testDir)) }()
|
||||||
|
|
||||||
|
logger, _ := testlog.NewNullLogger()
|
||||||
|
cfg, err := GetConfig(logger)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "/nvidia-container-toolkit.log", cfg.DebugFilePath)
|
||||||
|
}
|
||||||
@@ -14,33 +14,34 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package lookup
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
log "github.com/sirupsen/logrus"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDirectoryLocator creates a Locator that can be used to find directories at the specified root.
|
type defaultConfig struct{}
|
||||||
func NewDirectoryLocator(opts ...Option) Locator {
|
|
||||||
return NewFileLocator(
|
var _ configUpdater = (*defaultConfig)(nil)
|
||||||
append(
|
|
||||||
opts,
|
func newDefaultConfig() configUpdater {
|
||||||
WithFilter(assertDirectory),
|
c := defaultConfig{}
|
||||||
)...,
|
return &c
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// assertDirectory checks wither the specified path is a directory.
|
// Update defines the the default values for the config options
|
||||||
func assertDirectory(filename string) error {
|
func (c defaultConfig) Update(cfg *Config) error {
|
||||||
info, err := os.Stat(filename)
|
*cfg = Config{
|
||||||
if err != nil {
|
DebugFilePath: "/dev/null",
|
||||||
return fmt.Errorf("error getting info for %v: %v", filename, err)
|
LogLevel: log.InfoLevel.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !info.IsDir() {
|
|
||||||
return fmt.Errorf("specified path '%v' is not a directory", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDefaultConfig() *Config {
|
||||||
|
cfg := &Config{}
|
||||||
|
|
||||||
|
defaultConfig{}.Update(cfg)
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/*
|
||||||
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,25 +12,26 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
**/
|
*/
|
||||||
|
|
||||||
package runtime
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogger(t *testing.T) {
|
func TestDefaultConfigUpdate(t *testing.T) {
|
||||||
l := NewLogger()
|
cfg := &Config{}
|
||||||
|
|
||||||
l.Update("", "debug", nil)
|
require.Empty(t, cfg.DebugFilePath)
|
||||||
|
|
||||||
ll := l.Interface.(*logrus.Logger)
|
c := defaultConfig{}
|
||||||
require.Equal(t, logrus.DebugLevel, ll.Level)
|
|
||||||
|
|
||||||
lp := l.previousLogger.(*logrus.Logger)
|
err := c.Update(cfg)
|
||||||
require.Equal(t, logrus.InfoLevel, lp.Level)
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "/dev/null", cfg.DebugFilePath)
|
||||||
|
require.Equal(t, "", cfg.Root)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debugFilePathEnvvarName = "NVIDIA_CONTAINER_RUNTIME_DEBUG"
|
||||||
|
runtimePathEnvvarName = "NVIDIA_CONTAINER_RUNTIME_PATH"
|
||||||
|
rootEnvvarName = "NVIDIA_CONTAINER_RUNTIME_ROOT"
|
||||||
|
logLevelEnvvarName = "NVIDIA_CONTAINER_RUNTIME_LOG_LEVEL"
|
||||||
|
)
|
||||||
|
|
||||||
|
type envConfig struct{}
|
||||||
|
|
||||||
|
func newConfigFromEnvironment() configUpdater {
|
||||||
|
c := envConfig{}
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c envConfig) Update(cfg *Config) error {
|
||||||
|
debugFilePathEnvvar, exists := os.LookupEnv(debugFilePathEnvvarName)
|
||||||
|
if exists && strings.TrimSpace(debugFilePathEnvvar) != "" {
|
||||||
|
cfg.DebugFilePath = debugFilePathEnvvar
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimePathEnvvar, exists := os.LookupEnv(runtimePathEnvvarName)
|
||||||
|
if exists && strings.TrimSpace(runtimePathEnvvar) != "" {
|
||||||
|
cfg.RuntimePath = runtimePathEnvvar
|
||||||
|
}
|
||||||
|
|
||||||
|
rootEnvvar, exists := os.LookupEnv(rootEnvvarName)
|
||||||
|
if exists && strings.TrimSpace(rootEnvvar) != "" {
|
||||||
|
cfg.Root = rootEnvvar
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevelEnvvar, exists := os.LookupEnv(logLevelEnvvarName)
|
||||||
|
if exists && strings.TrimSpace(logLevelEnvvar) != "" {
|
||||||
|
cfg.LogLevel = logLevelEnvvar
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/*
|
||||||
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,11 +12,18 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
**/
|
*/
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
// CTKConfig stores the config options for the NVIDIA Container Toolkit CLI (nvidia-ctk)
|
// noop implements a configUpdater that does not update a config.
|
||||||
type CTKConfig struct {
|
type noop struct{}
|
||||||
Path string `toml:"path"`
|
|
||||||
|
func newNoopConfigUpdater() configUpdater {
|
||||||
|
c := noop{}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c noop) Update(cfg *Config) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/*
|
||||||
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,21 +12,25 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
**/
|
*/
|
||||||
|
|
||||||
package edits
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFromDiscovererAllowsMountsToIterate(t *testing.T) {
|
func TestNoopDoesNotModifyConfig(t *testing.T) {
|
||||||
edits, err := FromDiscoverer(discover.None{})
|
cfg := &Config{
|
||||||
require.NoError(t, err)
|
DebugFilePath: "test-path",
|
||||||
|
}
|
||||||
|
|
||||||
require.Empty(t, edits.Mounts)
|
c := newNoopConfigUpdater()
|
||||||
|
|
||||||
|
err := c.Update(cfg)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "test-path", cfg.DebugFilePath)
|
||||||
}
|
}
|
||||||
158
cmd/nvidia-container-runtime.experimental/config/toml.go
Normal file
158
cmd/nvidia-container-runtime.experimental/config/toml.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configFileRelativePath = "nvidia-container-runtime/config.toml"
|
||||||
|
configOverride = "XDG_CONFIG_HOME"
|
||||||
|
defaultConfigRoot = "/etc"
|
||||||
|
|
||||||
|
nvidiaContainerCliSection = "nvidia-container-cli"
|
||||||
|
nvidiaContainerRuntimeConfigSection = "nvidia-container-runtime"
|
||||||
|
nvidiaContainerRuntimeExperimentalConfigSection = "nvidia-container-runtime.experimental"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tomlConfig struct {
|
||||||
|
logger *log.Logger
|
||||||
|
path string
|
||||||
|
sections []tomlSection
|
||||||
|
}
|
||||||
|
|
||||||
|
type tomlSection struct {
|
||||||
|
section string
|
||||||
|
keys map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultConfigFileWithLogger(logger *log.Logger) configUpdater {
|
||||||
|
configDir := defaultConfigRoot
|
||||||
|
if XDGConfigDir := os.Getenv(configOverride); len(XDGConfigDir) != 0 {
|
||||||
|
configDir = XDGConfigDir
|
||||||
|
}
|
||||||
|
configFilePath := filepath.Join(configDir, configFileRelativePath)
|
||||||
|
|
||||||
|
return newConfigFromFileWithLogger(logger, configFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfigFromFileWithLogger(logger *log.Logger, filepath string) configUpdater {
|
||||||
|
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
||||||
|
logger.Warnf("The config file '%v' does not exist", filepath)
|
||||||
|
return newNoopConfigUpdater()
|
||||||
|
}
|
||||||
|
|
||||||
|
sections := []tomlSection{
|
||||||
|
{
|
||||||
|
section: nvidiaContainerRuntimeConfigSection,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: nvidiaContainerRuntimeExperimentalConfigSection,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: nvidiaContainerCliSection,
|
||||||
|
keys: map[string]struct{}{
|
||||||
|
"root": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c := tomlConfig{
|
||||||
|
logger: logger,
|
||||||
|
path: filepath,
|
||||||
|
sections: sections,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tomlConfig) Update(cfg *Config) error {
|
||||||
|
configFile, err := os.Open(c.path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening config file %v: %v", c.path, err)
|
||||||
|
}
|
||||||
|
defer configFile.Close()
|
||||||
|
|
||||||
|
return c.updateFromReader(cfg, configFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tomlConfig) updateFromReader(cfg *Config, reader io.Reader) error {
|
||||||
|
toml, err := toml.LoadReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading TOML contents: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, section := range c.sections {
|
||||||
|
if v, ok := section.GetStringFrom(toml, "debug"); ok {
|
||||||
|
cfg.DebugFilePath = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := section.GetStringFrom(toml, "runtime-path"); ok {
|
||||||
|
cfg.RuntimePath = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := section.GetStringFrom(toml, "root"); ok {
|
||||||
|
cfg.Root = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := section.GetStringFrom(toml, "log-level"); ok {
|
||||||
|
cfg.LogLevel = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tomlSection) GetStringFrom(toml *toml.Tree, key string) (string, bool) {
|
||||||
|
value := c.GetFrom(toml, key)
|
||||||
|
if value != nil {
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tomlSection) GetFrom(toml *toml.Tree, key string) interface{} {
|
||||||
|
if !c.validKey(key) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return toml.Get(c.configKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tomlSection) validKey(key string) bool {
|
||||||
|
if c.keys == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := c.keys[key]
|
||||||
|
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tomlSection) configKey(key string) string {
|
||||||
|
if c.section == "" {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
return c.section + "." + key
|
||||||
|
}
|
||||||
259
cmd/nvidia-container-runtime.experimental/config/toml_test.go
Normal file
259
cmd/nvidia-container-runtime.experimental/config/toml_test.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/iotest"
|
||||||
|
|
||||||
|
testlog "github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateFromReader(t *testing.T) {
|
||||||
|
logger, _ := testlog.NewNullLogger()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
readerError bool
|
||||||
|
lines []string
|
||||||
|
expected *Config
|
||||||
|
expectedError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "reader error returns error",
|
||||||
|
readerError: true,
|
||||||
|
expectedError: true,
|
||||||
|
expected: getDefaultConfig(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "empty config returns defaults",
|
||||||
|
lines: []string{},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "/dev/null",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "debug output is set",
|
||||||
|
lines: []string{"nvidia-container-runtime.debug=\"nvidia-container-toolkit.log\""},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "nvidia-container-toolkit.log",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "debug output is set in section",
|
||||||
|
lines: []string{"[nvidia-container-runtime]", "debug=\"nvidia-container-toolkit.log\""},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "nvidia-container-toolkit.log",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "blank debug is set",
|
||||||
|
lines: []string{"nvidia-container-runtime.debug=\"\""},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "non-string debug is ignored",
|
||||||
|
lines: []string{"nvidia-container-runtime.debug=2"},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "/dev/null",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "log-level is set",
|
||||||
|
lines: []string{"nvidia-container-runtime.log-level=\"trace\""},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "/dev/null",
|
||||||
|
LogLevel: "trace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
cfg := getDefaultConfig()
|
||||||
|
|
||||||
|
c := tomlConfig{
|
||||||
|
logger: logger,
|
||||||
|
sections: []tomlSection{
|
||||||
|
{section: nvidiaContainerRuntimeConfigSection},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var reader io.Reader
|
||||||
|
if tc.readerError {
|
||||||
|
reader = iotest.ErrReader(fmt.Errorf("error"))
|
||||||
|
} else {
|
||||||
|
reader = strings.NewReader(strings.Join(tc.lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.updateFromReader(cfg, reader)
|
||||||
|
|
||||||
|
if tc.expectedError {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.EqualValues(t, tc.expected, cfg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateFromReaderExperimental(t *testing.T) {
|
||||||
|
logger, _ := testlog.NewNullLogger()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
readerError bool
|
||||||
|
lines []string
|
||||||
|
expected *Config
|
||||||
|
expectedError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
lines: []string{"nvidia-container-runtime.debug=\"nvidia-container-toolkit.log\""},
|
||||||
|
expected: getDefaultConfig(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lines: []string{"[nvidia-container-runtime]", "debug=\"nvidia-container-toolkit.log\""},
|
||||||
|
expected: getDefaultConfig(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lines: []string{"nvidia-container-runtime.experimental.debug=\"\""},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lines: []string{"nvidia-container-runtime.experimental.debug=2"},
|
||||||
|
expected: getDefaultConfig(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lines: []string{"nvidia-container-runtime.experimental.debug=\"nvidia-container-toolkit.log\""},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "nvidia-container-toolkit.log",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lines: []string{"[nvidia-container-runtime.experimental]", "debug=\"nvidia-container-toolkit.log\""},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "nvidia-container-toolkit.log",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lines: []string{
|
||||||
|
"nvidia-container-runtime.debug=\"nvidia-container-toolkit.log\"",
|
||||||
|
"nvidia-container-runtime.experimental.debug=\"nvidia-container-exp-toolkit.log\"",
|
||||||
|
},
|
||||||
|
expected: &Config{
|
||||||
|
DebugFilePath: "nvidia-container-exp-toolkit.log",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
cfg := getDefaultConfig()
|
||||||
|
|
||||||
|
c := tomlConfig{
|
||||||
|
logger: logger,
|
||||||
|
sections: []tomlSection{
|
||||||
|
{section: nvidiaContainerRuntimeExperimentalConfigSection},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var reader io.Reader
|
||||||
|
if tc.readerError {
|
||||||
|
reader = iotest.ErrReader(fmt.Errorf("error"))
|
||||||
|
} else {
|
||||||
|
reader = strings.NewReader(strings.Join(tc.lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.updateFromReader(cfg, reader)
|
||||||
|
|
||||||
|
if tc.expectedError {
|
||||||
|
require.Error(t, err, "%d: %v", i, tc)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err, "%d: %v", i, tc)
|
||||||
|
}
|
||||||
|
require.EqualValues(t, tc.expected, cfg, "%d: %v", i, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFromFile(t *testing.T) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// By default debug is disabled
|
||||||
|
lines := []string{
|
||||||
|
"[nvidia-container-cli]",
|
||||||
|
"root = \"/run/nvidia/driver\"",
|
||||||
|
"[nvidia-container-runtime]",
|
||||||
|
"#debug = \"/nvidia-container-toolkit.log\"",
|
||||||
|
"",
|
||||||
|
"[nvidia-container-runtime.experimental]",
|
||||||
|
"debug = \"/nvidia-container-toolkit.experimental.log\"",
|
||||||
|
}
|
||||||
|
|
||||||
|
contents := []byte(strings.Join(lines, "\n"))
|
||||||
|
testDir := path.Join(wd, "test")
|
||||||
|
filename := path.Join(testDir, configFileRelativePath)
|
||||||
|
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(filename), 0766))
|
||||||
|
require.NoError(t, ioutil.WriteFile(filename, contents, 0766))
|
||||||
|
defer func() { require.NoError(t, os.RemoveAll(testDir)) }()
|
||||||
|
|
||||||
|
logger, _ := testlog.NewNullLogger()
|
||||||
|
c := newConfigFromFileWithLogger(logger, filename)
|
||||||
|
|
||||||
|
cfg := getDefaultConfig()
|
||||||
|
|
||||||
|
err = c.Update(cfg)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "/nvidia-container-toolkit.experimental.log", cfg.DebugFilePath)
|
||||||
|
|
||||||
|
require.Equal(t, "/run/nvidia/driver", cfg.Root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFromNonexistentFileReturnsNoop(t *testing.T) {
|
||||||
|
logger, _ := testlog.NewNullLogger()
|
||||||
|
c := newConfigFromFileWithLogger(logger, "/does/not/exist")
|
||||||
|
|
||||||
|
n, ok := c.(*noop)
|
||||||
|
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotNil(t, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfigKey(t *testing.T) {
|
||||||
|
require.Equal(t, "key", tomlSection{}.configKey("key"))
|
||||||
|
require.Equal(t, "section.key", tomlSection{section: "section"}.configKey("key"))
|
||||||
|
}
|
||||||
144
cmd/nvidia-container-runtime.experimental/main.go
Normal file
144
cmd/nvidia-container-runtime.experimental/main.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/ensure"
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/filter"
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/modify"
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/runtime"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-container-runtime.experimental/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
visibleDevicesEnvvar = "NVIDIA_VISIBLE_DEVICES"
|
||||||
|
visibleDevicesVoid = "void"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = log.New()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.GetConfig(logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error loading config: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.DebugFilePath != "" && cfg.DebugFilePath != "/dev/null" {
|
||||||
|
logFile, err := os.OpenFile(cfg.DebugFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error opening debug log file: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer logFile.Close()
|
||||||
|
|
||||||
|
logger.SetOutput(logFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevel, err := log.ParseLevel(cfg.LogLevel)
|
||||||
|
if err == nil {
|
||||||
|
logger.SetLevel(logLevel)
|
||||||
|
} else {
|
||||||
|
logger.Warnf("Invalid log-level '%v'; using '%v'", cfg.LogLevel, logger.Level.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Starting nvidia-container-runtime: %v", os.Args)
|
||||||
|
logger.Debugf("Using config=%+v", cfg)
|
||||||
|
|
||||||
|
if err := run(cfg, os.Args); err != nil {
|
||||||
|
logger.Errorf("Error running runtime: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(cfg *config.Config, args []string) error {
|
||||||
|
logger.Debugf("running with args=%v", args)
|
||||||
|
|
||||||
|
// We create a low-level runtime
|
||||||
|
lowLevelRuntime, err := createLowLevelRuntime(logger, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error constructing low-level runtime: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !oci.HasCreateSubcommand(args) {
|
||||||
|
logger.Infof("No modification of OCI specification required")
|
||||||
|
logger.Infof("Forwarding command to runtime")
|
||||||
|
return lowLevelRuntime.Exec(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create the OCI spec that is to be modified
|
||||||
|
ociSpec, bundleDir, err := oci.NewSpecFromArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error constructing OCI spec: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ociSpec.Load()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading OCI specification: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleDevices, exists := ociSpec.LookupEnv(visibleDevicesEnvvar)
|
||||||
|
if !exists || visibleDevices == "" || visibleDevices == visibleDevicesVoid {
|
||||||
|
logger.Infof("Using low-level runtime: %v=%v (exists=%v)", visibleDevicesEnvvar, visibleDevices, exists)
|
||||||
|
return lowLevelRuntime.Exec(os.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create the modifier that will be applied by the Modifying Runtime Wrapper
|
||||||
|
modifier, err := createModifier(cfg.Root, bundleDir, visibleDevices, ociSpec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error constructing modifer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We construct the Modifying runtime
|
||||||
|
r := runtime.NewModifyingRuntimeWrapperWithLogger(logger, lowLevelRuntime, ociSpec, modifier)
|
||||||
|
|
||||||
|
return r.Exec(os.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createLowLevelRuntime(logger *log.Logger, cfg *config.Config) (oci.Runtime, error) {
|
||||||
|
if cfg.RuntimePath == "" {
|
||||||
|
return oci.NewLowLevelRuntimeWithLogger(logger, "docker-runc", "runc")
|
||||||
|
}
|
||||||
|
logger.Infof("Creating runtime with path %v", cfg.RuntimePath)
|
||||||
|
return oci.NewRuntimeForPathWithLogger(logger, cfg.RuntimePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createModifier(root string, bundleDir string, visibleDevices string, env filter.EnvLookup) (modify.Modifier, error) {
|
||||||
|
// We set up the modifier using discovery
|
||||||
|
discovered, err := discover.NewNVMLServerWithLogger(logger, root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error discovering devices: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We apply a filter to the discovered devices
|
||||||
|
selected := filter.NewSelectDevicesFromWithLogger(logger, discovered, visibleDevices, env)
|
||||||
|
|
||||||
|
// We ensure that the selected devices are available
|
||||||
|
available := ensure.NewEnsureDevicesWithLogger(logger, selected, root)
|
||||||
|
|
||||||
|
// We construct the modifer for the OCI spec
|
||||||
|
modifier := modify.NewModifierWithLoggerFor(logger, available, root, bundleDir)
|
||||||
|
|
||||||
|
return modifier, nil
|
||||||
|
}
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
# The NVIDIA Container Runtime
|
|
||||||
|
|
||||||
The NVIDIA Container Runtime is a shim for OCI-compliant low-level runtimes such as [runc](https://github.com/opencontainers/runc). When a `create` command is detected, the incoming [OCI runtime specification](https://github.com/opencontainers/runtime-spec) is modified in place and the command is forwarded to the low-level runtime.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
The NVIDIA Container Runtime uses file-based configuration, with the config stored in `/etc/nvidia-container-runtime/config.toml`. The `/etc` path can be overridden using the `XDG_CONFIG_HOME` environment variable with the `${XDG_CONFIG_HOME}/nvidia-container-runtime/config.toml` file used instead if this environment variable is set.
|
|
||||||
|
|
||||||
This config file may contain options for other components of the NVIDIA container stack and for the NVIDIA Container Runtime, the relevant config section is `nvidia-container-runtime`
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
|
|
||||||
The `log-level` config option (default: `"info"`) specifies the log level to use and the `debug` option, if set, specifies a log file to which logs for the NVIDIA Container Runtime must be written.
|
|
||||||
|
|
||||||
In addition to this, the NVIDIA Container Runtime considers the value of `--log` and `--log-format` flags that may be passed to it by a container runtime such as docker or containerd. If the `--debug` flag is present the log-level specified in the config file is overridden as `"debug"`.
|
|
||||||
|
|
||||||
### Low-level Runtime Path
|
|
||||||
|
|
||||||
The `runtimes` config option allows for the low-level runtime to be specified. The first entry in this list that is an existing executable file is used as the low-level runtime. If the entry is not a path, the `PATH` is searched for a matching executable. If the entry is a path this is checked instead.
|
|
||||||
|
|
||||||
The default value for this setting is:
|
|
||||||
```toml
|
|
||||||
runtimes = [
|
|
||||||
"docker-runc",
|
|
||||||
"runc",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
and if, for example, `crun` is to be used instead this can be changed to:
|
|
||||||
```toml
|
|
||||||
runtimes = [
|
|
||||||
"crun",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Runtime Mode
|
|
||||||
|
|
||||||
The `mode` config option (default `"auto"`) controls the high-level behaviour of the runtime.
|
|
||||||
|
|
||||||
#### Auto Mode
|
|
||||||
|
|
||||||
When `mode` is set to `"auto"`, the runtime employs heuristics to determine which mode to use based on, for example, the platform where the runtime is being run.
|
|
||||||
|
|
||||||
#### Legacy Mode
|
|
||||||
|
|
||||||
When `mode` is set to `"legacy"`, the NVIDIA Container Runtime adds a [`prestart` hook](https://github.com/opencontainers/runtime-spec/blob/master/config.md#prestart) to the incomming OCI specification that invokes the NVIDIA Container Runtime Hook for all containers created. This hook checks whether NVIDIA devices are requested and ensures GPU access is configured using the `nvidia-container-cli` from the [libnvidia-container](https://github.com/NVIDIA/libnvidia-container) project.
|
|
||||||
|
|
||||||
#### CSV Mode
|
|
||||||
|
|
||||||
When `mode` is set to `"csv"`, CSV files at `/etc/nvidia-container-runtime/host-files-for-container.d` define the devices and mounts that are to be injected into a container when it is created. The search path for the files can be overridden by modifying the `nvidia-container-runtime.modes.csv.mount-spec-path` in the config as below:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[nvidia-container-runtime]
|
|
||||||
[nvidia-container-runtime.modes.csv]
|
|
||||||
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"
|
|
||||||
```
|
|
||||||
|
|
||||||
This mode is primarily targeted at Tegra-based systems without NVML available.
|
|
||||||
|
|
||||||
### Notes on using the docker CLI
|
|
||||||
|
|
||||||
Note that only the `"legacy"` NVIDIA Container Runtime mode is directly compatible with the `--gpus` flag implemented by the `docker` CLI (assuming the NVIDIA Container Runtime is not used). The reason for this is that `docker` inserts the same NVIDIA Container Runtime Hook into the OCI runtime specification.
|
|
||||||
|
|
||||||
|
|
||||||
If a different mode is explicitly set or detected, the NVIDIA Container Runtime Hook will raise the following error when `--gpus` is set:
|
|
||||||
```
|
|
||||||
$ docker run --rm --gpus all ubuntu:18.04
|
|
||||||
docker: Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: Running hook #0:: error running hook: exit status 1, stdout: , stderr: Auto-detected mode as 'csv'
|
|
||||||
invoking the NVIDIA Container Runtime Hook directly (e.g. specifying the docker --gpus flag) is not supported. Please use the NVIDIA Container Runtime instead.: unknown.
|
|
||||||
```
|
|
||||||
Here NVIDIA Container Runtime must be used explicitly. The recommended way to do this is to specify the `--runtime=nvidia` command line argument as part of the `docker run` commmand as follows:
|
|
||||||
```
|
|
||||||
$ docker run --rm --gpus all --runtime=nvidia ubuntu:18.04
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively the NVIDIA Container Runtime can be set as the default runtime for docker. This can be done by modifying the `/etc/docker/daemon.json` file as follows:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"default-runtime": "nvidia",
|
|
||||||
"runtimes": {
|
|
||||||
"nvidia": {
|
|
||||||
"path": "nvidia-container-runtime",
|
|
||||||
"runtimeArgs": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment variables (OCI spec)
|
|
||||||
|
|
||||||
Each environment variable maps to an command-line argument for `nvidia-container-cli` from [libnvidia-container](https://github.com/NVIDIA/libnvidia-container).
|
|
||||||
These variables are already set in our [official CUDA images](https://hub.docker.com/r/nvidia/cuda/).
|
|
||||||
|
|
||||||
### `NVIDIA_VISIBLE_DEVICES`
|
|
||||||
This variable controls which GPUs will be made accessible inside the container.
|
|
||||||
|
|
||||||
#### Possible values
|
|
||||||
* `0,1,2`, `GPU-fef8089b` …: a comma-separated list of GPU UUID(s) or index(es).
|
|
||||||
* `all`: all GPUs will be accessible, this is the default value in our container images.
|
|
||||||
* `none`: no GPU will be accessible, but driver capabilities will be enabled.
|
|
||||||
* `void` or *empty* or *unset*: `nvidia-container-runtime` will have the same behavior as `runc`.
|
|
||||||
|
|
||||||
**Note**: When running on a MIG capable device, the following values will also be available:
|
|
||||||
* `0:0,0:1,1:0`, `MIG-GPU-fef8089b/0/1` …: a comma-separated list of MIG Device UUID(s) or index(es).
|
|
||||||
|
|
||||||
Where the MIG device indices have the form `<GPU Device Index>:<MIG Device Index>` as seen in the example output:
|
|
||||||
```
|
|
||||||
$ nvidia-smi -L
|
|
||||||
GPU 0: Graphics Device (UUID: GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5)
|
|
||||||
MIG Device 0: (UUID: MIG-GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5/1/0)
|
|
||||||
MIG Device 1: (UUID: MIG-GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5/1/1)
|
|
||||||
MIG Device 2: (UUID: MIG-GPU-b8ea3855-276c-c9cb-b366-c6fa655957c5/11/0)
|
|
||||||
```
|
|
||||||
|
|
||||||
### `NVIDIA_MIG_CONFIG_DEVICES`
|
|
||||||
This variable controls which of the visible GPUs can have their MIG
|
|
||||||
configuration managed from within the container. This includes enabling and
|
|
||||||
disabling MIG mode, creating and destroying GPU Instances and Compute
|
|
||||||
Instances, etc.
|
|
||||||
|
|
||||||
#### Possible values
|
|
||||||
* `all`: Allow all MIG-capable GPUs in the visible device list to have their
|
|
||||||
MIG configurations managed.
|
|
||||||
|
|
||||||
**Note**:
|
|
||||||
* This feature is only available on MIG capable devices (e.g. the A100).
|
|
||||||
* To use this feature, the container must be started with `CAP_SYS_ADMIN` privileges.
|
|
||||||
* When not running as `root`, the container user must have read access to the
|
|
||||||
`/proc/driver/nvidia/capabilities/mig/config` file on the host.
|
|
||||||
|
|
||||||
### `NVIDIA_MIG_MONITOR_DEVICES`
|
|
||||||
This variable controls which of the visible GPUs can have aggregate information
|
|
||||||
about all of their MIG devices monitored from within the container. This
|
|
||||||
includes inspecting the aggregate memory usage, listing the aggregate running
|
|
||||||
processes, etc.
|
|
||||||
|
|
||||||
#### Possible values
|
|
||||||
* `all`: Allow all MIG-capable GPUs in the visible device list to have their
|
|
||||||
MIG devices monitored.
|
|
||||||
|
|
||||||
**Note**:
|
|
||||||
* This feature is only available on MIG capable devices (e.g. the A100).
|
|
||||||
* To use this feature, the container must be started with `CAP_SYS_ADMIN` privileges.
|
|
||||||
* When not running as `root`, the container user must have read access to the
|
|
||||||
`/proc/driver/nvidia/capabilities/mig/monitor` file on the host.
|
|
||||||
|
|
||||||
### `NVIDIA_DRIVER_CAPABILITIES`
|
|
||||||
This option controls which driver libraries/binaries will be mounted inside the container.
|
|
||||||
|
|
||||||
#### Possible values
|
|
||||||
* `compute,video`, `graphics,utility` …: a comma-separated list of driver features the container needs.
|
|
||||||
* `all`: enable all available driver capabilities.
|
|
||||||
* *empty* or *unset*: use default driver capability: `utility,compute`.
|
|
||||||
|
|
||||||
#### Supported driver capabilities
|
|
||||||
* `compute`: required for CUDA and OpenCL applications.
|
|
||||||
* `compat32`: required for running 32-bit applications.
|
|
||||||
* `graphics`: required for running OpenGL and Vulkan applications.
|
|
||||||
* `utility`: required for using `nvidia-smi` and NVML.
|
|
||||||
* `video`: required for using the Video Codec SDK.
|
|
||||||
* `display`: required for leveraging X11 display.
|
|
||||||
|
|
||||||
### `NVIDIA_REQUIRE_*`
|
|
||||||
A logical expression to define constraints on the configurations supported by the container.
|
|
||||||
|
|
||||||
#### Supported constraints
|
|
||||||
* `cuda`: constraint on the CUDA driver version.
|
|
||||||
* `driver`: constraint on the driver version.
|
|
||||||
* `arch`: constraint on the compute architectures of the selected GPUs.
|
|
||||||
* `brand`: constraint on the brand of the selected GPUs (e.g. GeForce, Tesla, GRID).
|
|
||||||
|
|
||||||
#### Expressions
|
|
||||||
Multiple constraints can be expressed in a single environment variable: space-separated constraints are ORed, comma-separated constraints are ANDed.
|
|
||||||
Multiple environment variables of the form `NVIDIA_REQUIRE_*` are ANDed together.
|
|
||||||
|
|
||||||
### `NVIDIA_DISABLE_REQUIRE`
|
|
||||||
Single switch to disable all the constraints of the form `NVIDIA_REQUIRE_*`.
|
|
||||||
|
|
||||||
### `NVIDIA_REQUIRE_CUDA`
|
|
||||||
|
|
||||||
The version of the CUDA toolkit used by the container. It is an instance of the generic `NVIDIA_REQUIRE_*` case and it is set by official CUDA images.
|
|
||||||
If the version of the NVIDIA driver is insufficient to run this version of CUDA, the container will not be started.
|
|
||||||
|
|
||||||
#### Possible values
|
|
||||||
* `cuda>=7.5`, `cuda>=8.0`, `cuda>=9.0` …: any valid CUDA version in the form `major.minor`.
|
|
||||||
|
|
||||||
### `CUDA_VERSION`
|
|
||||||
Similar to `NVIDIA_REQUIRE_CUDA`, for legacy CUDA images.
|
|
||||||
In addition, if `NVIDIA_REQUIRE_CUDA` is not set, `NVIDIA_VISIBLE_DEVICES` and `NVIDIA_DRIVER_CAPABILITIES` will default to `all`.
|
|
||||||
|
|
||||||
## Usage example
|
|
||||||
|
|
||||||
**NOTE:** The use of the `nvidia-container-runtime` as CLI replacement for `runc` is uncommon and is only provided for completeness.
|
|
||||||
|
|
||||||
Although the `nvidia-container-runtime` is typically configured as a replacement for `runc` or `crun` in various container engines, it can also be
|
|
||||||
invoked from the command line as `runc` would. For example:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Setup a rootfs based on Ubuntu 16.04
|
|
||||||
cd $(mktemp -d) && mkdir rootfs
|
|
||||||
curl -sS http://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/ubuntu-base-16.04.6-base-amd64.tar.gz | tar --exclude 'dev/*' -C rootfs -xz
|
|
||||||
|
|
||||||
# Create an OCI runtime spec
|
|
||||||
nvidia-container-runtime spec
|
|
||||||
sed -i 's;"sh";"nvidia-smi";' config.json
|
|
||||||
sed -i 's;\("TERM=xterm"\);\1, "NVIDIA_VISIBLE_DEVICES=0";' config.json
|
|
||||||
|
|
||||||
# Run the container
|
|
||||||
sudo nvidia-container-runtime run nvidia_smi
|
|
||||||
```
|
|
||||||
79
cmd/nvidia-container-runtime/logger.go
Normal file
79
cmd/nvidia-container-runtime/logger.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/tsaikd/KDGoLib/logrusutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger adds a way to manage output to a log file to a logrus.Logger
|
||||||
|
type Logger struct {
|
||||||
|
*logrus.Logger
|
||||||
|
previousOutput io.Writer
|
||||||
|
logFile *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogger constructs a Logger with a preddefined formatter
|
||||||
|
func NewLogger() *Logger {
|
||||||
|
logrusLogger := logrus.New()
|
||||||
|
|
||||||
|
formatter := &logrusutil.ConsoleLogFormatter{
|
||||||
|
TimestampFormat: "2006/01/02 15:04:07",
|
||||||
|
Flag: logrusutil.Ltime,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := &Logger{
|
||||||
|
Logger: logrusLogger,
|
||||||
|
}
|
||||||
|
logger.SetFormatter(formatter)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogToFile opens the specified file for appending and sets the logger to
|
||||||
|
// output to the opened file. A reference to the file pointer is stored to
|
||||||
|
// allow this to be closed.
|
||||||
|
func (l *Logger) LogToFile(filename string) error {
|
||||||
|
logFile, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening debug log file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.logFile = logFile
|
||||||
|
l.previousOutput = l.Out
|
||||||
|
l.SetOutput(logFile)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseFile closes the log file (if any) and resets the logger output to what it
|
||||||
|
// was before LogToFile was called.
|
||||||
|
func (l *Logger) CloseFile() error {
|
||||||
|
if l.logFile == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logFile := l.logFile
|
||||||
|
l.SetOutput(l.previousOutput)
|
||||||
|
l.logFile = nil
|
||||||
|
|
||||||
|
return logFile.Close()
|
||||||
|
}
|
||||||
@@ -1,15 +1,89 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/runtime"
|
"github.com/pelletier/go-toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configOverride = "XDG_CONFIG_HOME"
|
||||||
|
configFilePath = "nvidia-container-runtime/config.toml"
|
||||||
|
|
||||||
|
hookDefaultFilePath = "/usr/bin/nvidia-container-runtime-hook"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configDir = "/etc/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = NewLogger()
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
r := runtime.New()
|
err := run(os.Args)
|
||||||
err := r.Run(os.Args)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Errorf("Error running %v: %v", os.Args, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run is an entry point that allows for idiomatic handling of errors
|
||||||
|
// when calling from the main function.
|
||||||
|
func run(argv []string) (err error) {
|
||||||
|
cfg, err := getConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = logger.LogToFile(cfg.debugFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening debug log file: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// We capture and log a returning error before closing the log file.
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error running %v: %v", argv, err)
|
||||||
|
}
|
||||||
|
logger.CloseFile()
|
||||||
|
}()
|
||||||
|
|
||||||
|
r, err := newRuntime(argv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating runtime: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Printf("Running %s\n", argv[0])
|
||||||
|
return r.Exec(argv)
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
debugFilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfig sets up the config struct. Values are read from a toml file
|
||||||
|
// or set via the environment.
|
||||||
|
func getConfig() (*config, error) {
|
||||||
|
cfg := &config{}
|
||||||
|
|
||||||
|
if XDGConfigDir := os.Getenv(configOverride); len(XDGConfigDir) != 0 {
|
||||||
|
configDir = XDGConfigDir
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilePath := path.Join(configDir, configFilePath)
|
||||||
|
|
||||||
|
tomlContent, err := os.ReadFile(configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
toml, err := toml.Load(string(tomlContent))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.debugFilePath = toml.GetDefault("nvidia-container-runtime.debug", "/dev/null").(string)
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,32 +3,25 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"fmt"
|
||||||
"log"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
testlog "github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/modifier"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/test"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nvidiaRuntime = "nvidia-container-runtime"
|
nvidiaRuntime = "nvidia-container-runtime"
|
||||||
nvidiaHook = "nvidia-container-runtime-hook"
|
nvidiaHook = "nvidia-container-runtime-hook"
|
||||||
bundlePathSuffix = "tests/output/bundle/"
|
bundlePathSuffix = "test/output/bundle/"
|
||||||
specFile = "config.json"
|
specFile = "config.json"
|
||||||
unmodifiedSpecFileSuffix = "tests/input/test_spec.json"
|
unmodifiedSpecFileSuffix = "test/input/test_spec.json"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
runcExecutableName = "runc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type testConfig struct {
|
type testConfig struct {
|
||||||
@@ -42,25 +35,25 @@ func TestMain(m *testing.M) {
|
|||||||
// TEST SETUP
|
// TEST SETUP
|
||||||
// Determine the module root and the test binary path
|
// Determine the module root and the test binary path
|
||||||
var err error
|
var err error
|
||||||
moduleRoot, err := test.GetModuleRoot()
|
moduleRoot, err := getModuleRoot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error in test setup: could not get module root: %v", err)
|
logger.Fatalf("error in test setup: could not get module root: %v", err)
|
||||||
}
|
}
|
||||||
testBinPath := filepath.Join(moduleRoot, "tests", "bin")
|
testBinPath := filepath.Join(moduleRoot, "test", "bin")
|
||||||
testInputPath := filepath.Join(moduleRoot, "tests", "input")
|
testInputPath := filepath.Join(moduleRoot, "test", "input")
|
||||||
|
|
||||||
// Set the environment variables for the test
|
// Set the environment variables for the test
|
||||||
os.Setenv("PATH", test.PrependToPath(testBinPath, moduleRoot))
|
os.Setenv("PATH", prependToPath(testBinPath, moduleRoot))
|
||||||
os.Setenv("XDG_CONFIG_HOME", testInputPath)
|
os.Setenv("XDG_CONFIG_HOME", testInputPath)
|
||||||
|
|
||||||
// Confirm that the environment is configured correctly
|
// Confirm that the environment is configured correctly
|
||||||
runcPath, err := exec.LookPath(runcExecutableName)
|
runcPath, err := exec.LookPath(runcExecutableName)
|
||||||
if err != nil || filepath.Join(testBinPath, runcExecutableName) != runcPath {
|
if err != nil || filepath.Join(testBinPath, runcExecutableName) != runcPath {
|
||||||
log.Fatalf("error in test setup: mock runc path set incorrectly in TestMain(): %v", err)
|
logger.Fatalf("error in test setup: mock runc path set incorrectly in TestMain(): %v", err)
|
||||||
}
|
}
|
||||||
hookPath, err := exec.LookPath(nvidiaHook)
|
hookPath, err := exec.LookPath(nvidiaHook)
|
||||||
if err != nil || filepath.Join(testBinPath, nvidiaHook) != hookPath {
|
if err != nil || filepath.Join(testBinPath, nvidiaHook) != hookPath {
|
||||||
log.Fatalf("error in test setup: mock hook path set incorrectly in TestMain(): %v", err)
|
logger.Fatalf("error in test setup: mock hook path set incorrectly in TestMain(): %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the root and binary paths in the test Config
|
// Store the root and binary paths in the test Config
|
||||||
@@ -78,16 +71,45 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getModuleRoot() (string, error) {
|
||||||
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
|
|
||||||
|
return hasGoMod(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasGoMod(dir string) (string, error) {
|
||||||
|
if dir == "" || dir == "/" {
|
||||||
|
return "", fmt.Errorf("module root not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Stat(filepath.Join(dir, "go.mod"))
|
||||||
|
if err != nil {
|
||||||
|
return hasGoMod(filepath.Dir(dir))
|
||||||
|
}
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prependToPath(additionalPaths ...string) string {
|
||||||
|
paths := strings.Split(os.Getenv("PATH"), ":")
|
||||||
|
paths = append(additionalPaths, paths...)
|
||||||
|
|
||||||
|
return strings.Join(paths, ":")
|
||||||
|
}
|
||||||
|
|
||||||
// case 1) nvidia-container-runtime run --bundle
|
// case 1) nvidia-container-runtime run --bundle
|
||||||
// case 2) nvidia-container-runtime create --bundle
|
// case 2) nvidia-container-runtime create --bundle
|
||||||
// - Confirm the runtime handles bad input correctly
|
// - Confirm the runtime handles bad input correctly
|
||||||
func TestBadInput(t *testing.T) {
|
func TestBadInput(t *testing.T) {
|
||||||
err := cfg.generateNewRuntimeSpec()
|
err := cfg.generateNewRuntimeSpec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
cmdRun := exec.Command(nvidiaRuntime, "run", "--bundle")
|
||||||
|
t.Logf("executing: %s\n", strings.Join(cmdRun.Args, " "))
|
||||||
|
output, err := cmdRun.CombinedOutput()
|
||||||
|
require.Errorf(t, err, "runtime should return an error", "output=%v", string(output))
|
||||||
|
|
||||||
cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle")
|
cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle")
|
||||||
t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " "))
|
t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " "))
|
||||||
err = cmdCreate.Run()
|
err = cmdCreate.Run()
|
||||||
@@ -95,17 +117,15 @@ func TestBadInput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// case 1) nvidia-container-runtime run --bundle <bundle-name> <ctr-name>
|
// case 1) nvidia-container-runtime run --bundle <bundle-name> <ctr-name>
|
||||||
// - Confirm the runtime runs with no errors
|
// - Confirm the runtime runs with no errors
|
||||||
//
|
|
||||||
// case 2) nvidia-container-runtime create --bundle <bundle-name> <ctr-name>
|
// case 2) nvidia-container-runtime create --bundle <bundle-name> <ctr-name>
|
||||||
// - Confirm the runtime inserts the NVIDIA prestart hook correctly
|
// - Confirm the runtime inserts the NVIDIA prestart hook correctly
|
||||||
func TestGoodInput(t *testing.T) {
|
func TestGoodInput(t *testing.T) {
|
||||||
err := cfg.generateNewRuntimeSpec()
|
err := cfg.generateNewRuntimeSpec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error generating runtime spec: %v", err)
|
t.Fatalf("error generating runtime spec: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
cmdRun := exec.Command(nvidiaRuntime, "run", "--bundle", cfg.bundlePath(), "testcontainer")
|
cmdRun := exec.Command(nvidiaRuntime, "run", "--bundle", cfg.bundlePath(), "testcontainer")
|
||||||
t.Logf("executing: %s\n", strings.Join(cmdRun.Args, " "))
|
t.Logf("executing: %s\n", strings.Join(cmdRun.Args, " "))
|
||||||
output, err := cmdRun.CombinedOutput()
|
output, err := cmdRun.CombinedOutput()
|
||||||
@@ -116,7 +136,6 @@ func TestGoodInput(t *testing.T) {
|
|||||||
require.NoError(t, err, "should be no errors when reading and parsing spec from config.json")
|
require.NoError(t, err, "should be no errors when reading and parsing spec from config.json")
|
||||||
require.Empty(t, spec.Hooks, "there should be no hooks in config.json")
|
require.Empty(t, spec.Hooks, "there should be no hooks in config.json")
|
||||||
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer")
|
cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer")
|
||||||
t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " "))
|
t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " "))
|
||||||
err = cmdCreate.Run()
|
err = cmdCreate.Run()
|
||||||
@@ -162,7 +181,6 @@ func TestDuplicateHook(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test how runtime handles already existing prestart hook in config.json
|
// Test how runtime handles already existing prestart hook in config.json
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer")
|
cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer")
|
||||||
t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " "))
|
t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " "))
|
||||||
output, err := cmdCreate.CombinedOutput()
|
output, err := cmdCreate.CombinedOutput()
|
||||||
@@ -175,12 +193,11 @@ func TestDuplicateHook(t *testing.T) {
|
|||||||
require.Equal(t, 1, nvidiaHookCount(spec.Hooks), "exactly one nvidia prestart hook should be inserted correctly into config.json")
|
require.Equal(t, 1, nvidiaHookCount(spec.Hooks), "exactly one nvidia prestart hook should be inserted correctly into config.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// addNVIDIAHook is a basic wrapper for an addHookModifier that is used for
|
// addNVIDIAHook is a basic wrapper for nvidiaContainerRunime.addNVIDIAHook that is used for
|
||||||
// testing.
|
// testing.
|
||||||
func addNVIDIAHook(spec *specs.Spec) error {
|
func addNVIDIAHook(spec *specs.Spec) error {
|
||||||
logger, _ := testlog.NewNullLogger()
|
r := nvidiaContainerRuntime{logger: logger.Logger}
|
||||||
m := modifier.NewStableRuntimeModifier(logger, nvidiaHook)
|
return r.addNVIDIAHook(spec)
|
||||||
return m.Modify(spec)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c testConfig) getRuntimeSpec() (specs.Spec, error) {
|
func (c testConfig) getRuntimeSpec() (specs.Spec, error) {
|
||||||
@@ -193,16 +210,15 @@ func (c testConfig) getRuntimeSpec() (specs.Spec, error) {
|
|||||||
}
|
}
|
||||||
defer jsonFile.Close()
|
defer jsonFile.Close()
|
||||||
|
|
||||||
jsonContent, err := io.ReadAll(jsonFile)
|
jsonContent, err := ioutil.ReadAll(jsonFile)
|
||||||
switch {
|
if err != nil {
|
||||||
case err != nil:
|
|
||||||
return spec, err
|
return spec, err
|
||||||
case json.Valid(jsonContent):
|
} else if json.Valid(jsonContent) {
|
||||||
err = json.Unmarshal(jsonContent, &spec)
|
err = json.Unmarshal(jsonContent, &spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return spec, err
|
return spec, err
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
err = json.NewDecoder(bytes.NewReader(jsonContent)).Decode(&spec)
|
err = json.NewDecoder(bytes.NewReader(jsonContent)).Decode(&spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return spec, err
|
return spec, err
|
||||||
@@ -232,7 +248,6 @@ func (c testConfig) generateNewRuntimeSpec() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
cmd := exec.Command("cp", c.unmodifiedSpecFile(), c.specFilePath())
|
cmd := exec.Command("cp", c.unmodifiedSpecFile(), c.specFilePath())
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -255,3 +270,24 @@ func nvidiaHookCount(hooks *specs.Hooks) int {
|
|||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetConfigWithCustomConfig(t *testing.T) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// By default debug is disabled
|
||||||
|
contents := []byte("[nvidia-container-runtime]\ndebug = \"/nvidia-container-toolkit.log\"")
|
||||||
|
testDir := filepath.Join(wd, "test")
|
||||||
|
filename := filepath.Join(testDir, configFilePath)
|
||||||
|
|
||||||
|
os.Setenv(configOverride, testDir)
|
||||||
|
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(filename), 0766))
|
||||||
|
require.NoError(t, ioutil.WriteFile(filename, contents, 0766))
|
||||||
|
|
||||||
|
defer func() { require.NoError(t, os.RemoveAll(testDir)) }()
|
||||||
|
|
||||||
|
cfg, err := getConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, cfg.debugFilePath, "/nvidia-container-toolkit.log")
|
||||||
|
}
|
||||||
|
|||||||
132
cmd/nvidia-container-runtime/nvcr.go
Normal file
132
cmd/nvidia-container-runtime/nvcr.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nvidiaContainerRuntime encapsulates the NVIDIA Container Runtime. It wraps the specified runtime, conditionally
|
||||||
|
// modifying the specified OCI specification before invoking the runtime.
|
||||||
|
type nvidiaContainerRuntime struct {
|
||||||
|
logger *log.Logger
|
||||||
|
runtime oci.Runtime
|
||||||
|
ociSpec oci.Spec
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ oci.Runtime = (*nvidiaContainerRuntime)(nil)
|
||||||
|
|
||||||
|
// newNvidiaContainerRuntime is a constructor for a standard runtime shim.
|
||||||
|
func newNvidiaContainerRuntimeWithLogger(logger *log.Logger, runtime oci.Runtime, ociSpec oci.Spec) (oci.Runtime, error) {
|
||||||
|
r := nvidiaContainerRuntime{
|
||||||
|
logger: logger,
|
||||||
|
runtime: runtime,
|
||||||
|
ociSpec: ociSpec,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec defines the entrypoint for the NVIDIA Container Runtime. A check is performed to see whether modifications
|
||||||
|
// to the OCI spec are required -- and applicable modifcations applied. The supplied arguments are then
|
||||||
|
// forwarded to the underlying runtime's Exec method.
|
||||||
|
func (r nvidiaContainerRuntime) Exec(args []string) error {
|
||||||
|
if r.modificationRequired(args) {
|
||||||
|
err := r.modifyOCISpec()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error modifying OCI spec: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.Println("Forwarding command to runtime")
|
||||||
|
return r.runtime.Exec(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// modificationRequired checks the intput arguments to determine whether a modification
|
||||||
|
// to the OCI spec is required.
|
||||||
|
func (r nvidiaContainerRuntime) modificationRequired(args []string) bool {
|
||||||
|
if oci.HasCreateSubcommand(args) {
|
||||||
|
r.logger.Infof("'create' command detected; modification required")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.Infof("No modification required")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifyOCISpec loads and modifies the OCI spec specified in the nvidiaContainerRuntime
|
||||||
|
// struct. The spec is modified in-place and written to the same file as the input after
|
||||||
|
// modifcationas are applied.
|
||||||
|
func (r nvidiaContainerRuntime) modifyOCISpec() error {
|
||||||
|
err := r.ociSpec.Load()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading OCI specification for modification: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.ociSpec.Modify(r.addNVIDIAHook)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error injecting NVIDIA Container Runtime hook: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.ociSpec.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing modified OCI specification: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNVIDIAHook modifies the specified OCI specification in-place, inserting a
|
||||||
|
// prestart hook.
|
||||||
|
func (r nvidiaContainerRuntime) addNVIDIAHook(spec *specs.Spec) error {
|
||||||
|
path, err := exec.LookPath("nvidia-container-runtime-hook")
|
||||||
|
if err != nil {
|
||||||
|
path = hookDefaultFilePath
|
||||||
|
_, err = os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.Printf("prestart hook path: %s\n", path)
|
||||||
|
|
||||||
|
args := []string{path}
|
||||||
|
if spec.Hooks == nil {
|
||||||
|
spec.Hooks = &specs.Hooks{}
|
||||||
|
} else if len(spec.Hooks.Prestart) != 0 {
|
||||||
|
for _, hook := range spec.Hooks.Prestart {
|
||||||
|
if !strings.Contains(hook.Path, "nvidia-container-runtime-hook") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.logger.Println("existing nvidia prestart hook in OCI spec file")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.Hooks.Prestart = append(spec.Hooks.Prestart, specs.Hook{
|
||||||
|
Path: path,
|
||||||
|
Args: append(args, "prestart"),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
203
cmd/nvidia-container-runtime/nvcr_test.go
Normal file
203
cmd/nvidia-container-runtime/nvcr_test.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
testlog "github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddNvidiaHook(t *testing.T) {
|
||||||
|
logger, logHook := testlog.NewNullLogger()
|
||||||
|
shim := nvidiaContainerRuntime{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
spec *specs.Spec
|
||||||
|
errorPrefix string
|
||||||
|
shouldNotAdd bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
spec: &specs.Spec{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: &specs.Spec{
|
||||||
|
Hooks: &specs.Hooks{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: &specs.Spec{
|
||||||
|
Hooks: &specs.Hooks{
|
||||||
|
Prestart: []specs.Hook{{
|
||||||
|
Path: "some-hook",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: &specs.Spec{
|
||||||
|
Hooks: &specs.Hooks{
|
||||||
|
Prestart: []specs.Hook{{
|
||||||
|
Path: "nvidia-container-runtime-hook",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldNotAdd: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
logHook.Reset()
|
||||||
|
|
||||||
|
var numPrestartHooks int
|
||||||
|
if tc.spec.Hooks != nil {
|
||||||
|
numPrestartHooks = len(tc.spec.Hooks.Prestart)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := shim.addNVIDIAHook(tc.spec)
|
||||||
|
|
||||||
|
if tc.errorPrefix == "" {
|
||||||
|
require.NoErrorf(t, err, "%d: %v", i, tc)
|
||||||
|
} else {
|
||||||
|
require.Truef(t, strings.HasPrefix(err.Error(), tc.errorPrefix), "%d: %v", i, tc)
|
||||||
|
|
||||||
|
require.NotNilf(t, tc.spec.Hooks, "%d: %v", i, tc)
|
||||||
|
require.Equalf(t, 1, nvidiaHookCount(tc.spec.Hooks), "%d: %v", i, tc)
|
||||||
|
|
||||||
|
if tc.shouldNotAdd {
|
||||||
|
require.Equal(t, numPrestartHooks+1, len(tc.spec.Hooks.Poststart), "%d: %v", i, tc)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, numPrestartHooks+1, len(tc.spec.Hooks.Poststart), "%d: %v", i, tc)
|
||||||
|
|
||||||
|
nvidiaHook := tc.spec.Hooks.Poststart[len(tc.spec.Hooks.Poststart)-1]
|
||||||
|
|
||||||
|
// TODO: This assumes that the hook has been set up in the makefile
|
||||||
|
expectedPath := "/usr/bin/nvidia-container-runtime-hook"
|
||||||
|
require.Equalf(t, expectedPath, nvidiaHook.Path, "%d: %v", i, tc)
|
||||||
|
require.Equalf(t, []string{expectedPath, "prestart"}, nvidiaHook.Args, "%d: %v", i, tc)
|
||||||
|
require.Emptyf(t, nvidiaHook.Env, "%d: %v", i, tc)
|
||||||
|
require.Nilf(t, nvidiaHook.Timeout, "%d: %v", i, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNvidiaContainerRuntime(t *testing.T) {
|
||||||
|
logger, hook := testlog.NewNullLogger()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
shim nvidiaContainerRuntime
|
||||||
|
shouldModify bool
|
||||||
|
args []string
|
||||||
|
modifyError error
|
||||||
|
writeError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
shim: nvidiaContainerRuntime{},
|
||||||
|
shouldModify: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shim: nvidiaContainerRuntime{},
|
||||||
|
args: []string{"create"},
|
||||||
|
shouldModify: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shim: nvidiaContainerRuntime{},
|
||||||
|
args: []string{"--bundle=create"},
|
||||||
|
shouldModify: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shim: nvidiaContainerRuntime{},
|
||||||
|
args: []string{"--bundle", "create"},
|
||||||
|
shouldModify: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shim: nvidiaContainerRuntime{},
|
||||||
|
args: []string{"create"},
|
||||||
|
shouldModify: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shim: nvidiaContainerRuntime{},
|
||||||
|
args: []string{"create"},
|
||||||
|
modifyError: fmt.Errorf("error modifying"),
|
||||||
|
shouldModify: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shim: nvidiaContainerRuntime{},
|
||||||
|
args: []string{"create"},
|
||||||
|
writeError: fmt.Errorf("error writing"),
|
||||||
|
shouldModify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
tc.shim.logger = logger
|
||||||
|
hook.Reset()
|
||||||
|
|
||||||
|
ociMock := &oci.SpecMock{
|
||||||
|
ModifyFunc: func(specModifier oci.SpecModifier) error {
|
||||||
|
return tc.modifyError
|
||||||
|
},
|
||||||
|
FlushFunc: func() error {
|
||||||
|
return tc.writeError
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, tc.shouldModify, tc.shim.modificationRequired(tc.args), "%d: %v", i, tc)
|
||||||
|
|
||||||
|
tc.shim.ociSpec = ociMock
|
||||||
|
tc.shim.runtime = &MockShim{}
|
||||||
|
|
||||||
|
err := tc.shim.Exec(tc.args)
|
||||||
|
if tc.modifyError != nil || tc.writeError != nil {
|
||||||
|
require.Error(t, err, "%d: %v", i, tc)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err, "%d: %v", i, tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.shouldModify {
|
||||||
|
require.Equal(t, 1, len(ociMock.ModifyCalls()), "%d: %v", i, tc)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, 0, len(ociMock.ModifyCalls()), "%d: %v", i, tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeExpected := tc.shouldModify && tc.modifyError == nil
|
||||||
|
if writeExpected {
|
||||||
|
require.Equal(t, 1, len(ociMock.FlushCalls()), "%d: %v", i, tc)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, 0, len(ociMock.FlushCalls()), "%d: %v", i, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockShim struct {
|
||||||
|
called bool
|
||||||
|
args []string
|
||||||
|
returnError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockShim) Exec(args []string) error {
|
||||||
|
m.called = true
|
||||||
|
m.args = args
|
||||||
|
return m.returnError
|
||||||
|
}
|
||||||
74
cmd/nvidia-container-runtime/runtime_factory.go
Normal file
74
cmd/nvidia-container-runtime/runtime_factory.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ociSpecFileName = "config.json"
|
||||||
|
dockerRuncExecutableName = "docker-runc"
|
||||||
|
runcExecutableName = "runc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newRuntime is a factory method that constructs a runtime based on the selected configuration.
|
||||||
|
func newRuntime(argv []string) (oci.Runtime, error) {
|
||||||
|
ociSpec, err := newOCISpec(argv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error constructing OCI specification: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runc, err := newRuncRuntime()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error constructing runc runtime: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := newNvidiaContainerRuntimeWithLogger(logger.Logger, runc, ociSpec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error constructing NVIDIA Container Runtime: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newOCISpec constructs an OCI spec for the provided arguments
|
||||||
|
func newOCISpec(argv []string) (oci.Spec, error) {
|
||||||
|
bundleDir, err := oci.GetBundleDir(argv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing command line arguments: %v", err)
|
||||||
|
}
|
||||||
|
logger.Infof("Using bundle directory: %v", bundleDir)
|
||||||
|
|
||||||
|
ociSpecPath := oci.GetSpecFilePath(bundleDir)
|
||||||
|
logger.Infof("Using OCI specification file path: %v", ociSpecPath)
|
||||||
|
|
||||||
|
ociSpec := oci.NewSpecFromFile(ociSpecPath)
|
||||||
|
|
||||||
|
return ociSpec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRuncRuntime locates the runc binary and wraps it in a SyscallExecRuntime
|
||||||
|
func newRuncRuntime() (oci.Runtime, error) {
|
||||||
|
return oci.NewLowLevelRuntimeWithLogger(
|
||||||
|
logger.Logger,
|
||||||
|
dockerRuncExecutableName,
|
||||||
|
runcExecutableName,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
//go:build tools
|
/*
|
||||||
// +build tools
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
|
||||||
/**
|
|
||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,12 +12,19 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
**/
|
*/
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// Define the tooling required to build the device plugin.
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
"testing"
|
||||||
_ "github.com/matryer/moq"
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestConstructor(t *testing.T) {
|
||||||
|
shim, err := newRuntime([]string{})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, shim)
|
||||||
|
}
|
||||||
83
cmd/nvidia-container-toolkit/capabilities.go
Normal file
83
cmd/nvidia-container-toolkit/capabilities.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
allDriverCapabilities = DriverCapabilities("compute,compat32,graphics,utility,video,display,ngx")
|
||||||
|
defaultDriverCapabilities = DriverCapabilities("utility,compute")
|
||||||
|
|
||||||
|
none = DriverCapabilities("")
|
||||||
|
all = DriverCapabilities("all")
|
||||||
|
)
|
||||||
|
|
||||||
|
func capabilityToCLI(cap string) string {
|
||||||
|
switch cap {
|
||||||
|
case "compute":
|
||||||
|
return "--compute"
|
||||||
|
case "compat32":
|
||||||
|
return "--compat32"
|
||||||
|
case "graphics":
|
||||||
|
return "--graphics"
|
||||||
|
case "utility":
|
||||||
|
return "--utility"
|
||||||
|
case "video":
|
||||||
|
return "--video"
|
||||||
|
case "display":
|
||||||
|
return "--display"
|
||||||
|
case "ngx":
|
||||||
|
return "--ngx"
|
||||||
|
default:
|
||||||
|
log.Panicln("unknown driver capability:", cap)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverCapabilities is used to process the NVIDIA_DRIVER_CAPABILITIES environment
|
||||||
|
// variable. Operations include default values, filtering, and handling meta values such as "all"
|
||||||
|
type DriverCapabilities string
|
||||||
|
|
||||||
|
// Intersection returns intersection between two sets of capabilities.
|
||||||
|
func (d DriverCapabilities) Intersection(capabilities DriverCapabilities) DriverCapabilities {
|
||||||
|
if capabilities == all {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
if d == all {
|
||||||
|
return capabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup := make(map[string]bool)
|
||||||
|
for _, c := range d.list() {
|
||||||
|
lookup[c] = true
|
||||||
|
}
|
||||||
|
var found []string
|
||||||
|
for _, c := range capabilities.list() {
|
||||||
|
if lookup[c] {
|
||||||
|
found = append(found, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
intersection := DriverCapabilities(strings.Join(found, ","))
|
||||||
|
return intersection
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the driver capabilities
|
||||||
|
func (d DriverCapabilities) String() string {
|
||||||
|
return string(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// list returns the driver capabilities as a list
|
||||||
|
func (d DriverCapabilities) list() []string {
|
||||||
|
var caps []string
|
||||||
|
for _, c := range strings.Split(string(d), ",") {
|
||||||
|
trimmed := strings.TrimSpace(c)
|
||||||
|
if len(trimmed) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
caps = append(caps, trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return caps
|
||||||
|
}
|
||||||
134
cmd/nvidia-container-toolkit/capabilities_test.go
Normal file
134
cmd/nvidia-container-toolkit/capabilities_test.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDriverCapabilitiesIntersection(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
capabilities DriverCapabilities
|
||||||
|
supportedCapabilities DriverCapabilities
|
||||||
|
expectedIntersection DriverCapabilities
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
capabilities: none,
|
||||||
|
supportedCapabilities: none,
|
||||||
|
expectedIntersection: none,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: all,
|
||||||
|
supportedCapabilities: none,
|
||||||
|
expectedIntersection: none,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: all,
|
||||||
|
supportedCapabilities: allDriverCapabilities,
|
||||||
|
expectedIntersection: allDriverCapabilities,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: allDriverCapabilities,
|
||||||
|
supportedCapabilities: all,
|
||||||
|
expectedIntersection: allDriverCapabilities,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: none,
|
||||||
|
supportedCapabilities: all,
|
||||||
|
expectedIntersection: none,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: none,
|
||||||
|
supportedCapabilities: DriverCapabilities("cap1"),
|
||||||
|
expectedIntersection: none,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities("cap0,cap1"),
|
||||||
|
supportedCapabilities: DriverCapabilities("cap1,cap0"),
|
||||||
|
expectedIntersection: DriverCapabilities("cap0,cap1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: defaultDriverCapabilities,
|
||||||
|
supportedCapabilities: allDriverCapabilities,
|
||||||
|
expectedIntersection: defaultDriverCapabilities,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities("compute,compat32,graphics,utility,video,display"),
|
||||||
|
supportedCapabilities: DriverCapabilities("compute,compat32,graphics,utility,video,display,ngx"),
|
||||||
|
expectedIntersection: DriverCapabilities("compute,compat32,graphics,utility,video,display"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities("cap1"),
|
||||||
|
supportedCapabilities: none,
|
||||||
|
expectedIntersection: none,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities("compute,compat32,graphics,utility,video,display,ngx"),
|
||||||
|
supportedCapabilities: DriverCapabilities("compute,compat32,graphics,utility,video,display"),
|
||||||
|
expectedIntersection: DriverCapabilities("compute,compat32,graphics,utility,video,display"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) {
|
||||||
|
intersection := tc.supportedCapabilities.Intersection(tc.capabilities)
|
||||||
|
require.EqualValues(t, tc.expectedIntersection, intersection)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDriverCapabilitiesList(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
capabilities DriverCapabilities
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities(" "),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities(","),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities(",cap"),
|
||||||
|
expected: []string{"cap"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities("cap,"),
|
||||||
|
expected: []string{"cap"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities("cap0,,cap1"),
|
||||||
|
expected: []string{"cap0", "cap1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: DriverCapabilities("cap1,cap0,cap3"),
|
||||||
|
expected: []string{"cap1", "cap0", "cap3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) {
|
||||||
|
require.EqualValues(t, tc.expected, tc.capabilities.list())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
421
cmd/nvidia-container-toolkit/container_config.go
Normal file
421
cmd/nvidia-container-toolkit/container_config.go
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var envSwarmGPU *string
|
||||||
|
|
||||||
|
const (
|
||||||
|
envCUDAVersion = "CUDA_VERSION"
|
||||||
|
envNVRequirePrefix = "NVIDIA_REQUIRE_"
|
||||||
|
envNVRequireCUDA = envNVRequirePrefix + "CUDA"
|
||||||
|
envNVDisableRequire = "NVIDIA_DISABLE_REQUIRE"
|
||||||
|
envNVVisibleDevices = "NVIDIA_VISIBLE_DEVICES"
|
||||||
|
envNVMigConfigDevices = "NVIDIA_MIG_CONFIG_DEVICES"
|
||||||
|
envNVMigMonitorDevices = "NVIDIA_MIG_MONITOR_DEVICES"
|
||||||
|
envNVDriverCapabilities = "NVIDIA_DRIVER_CAPABILITIES"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
capSysAdmin = "CAP_SYS_ADMIN"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
deviceListAsVolumeMountsRoot = "/var/run/nvidia-container-devices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nvidiaConfig struct {
|
||||||
|
Devices string
|
||||||
|
MigConfigDevices string
|
||||||
|
MigMonitorDevices string
|
||||||
|
DriverCapabilities string
|
||||||
|
Requirements []string
|
||||||
|
DisableRequire bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerConfig struct {
|
||||||
|
Pid int
|
||||||
|
Rootfs string
|
||||||
|
Env map[string]string
|
||||||
|
Nvidia *nvidiaConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root from OCI runtime spec
|
||||||
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L94-L100
|
||||||
|
type Root struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process from OCI runtime spec
|
||||||
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L57
|
||||||
|
type Process struct {
|
||||||
|
Env []string `json:"env,omitempty"`
|
||||||
|
Capabilities *json.RawMessage `json:"capabilities,omitempty" platform:"linux"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinuxCapabilities from OCI runtime spec
|
||||||
|
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L61
|
||||||
|
type LinuxCapabilities struct {
|
||||||
|
Bounding []string `json:"bounding,omitempty" platform:"linux"`
|
||||||
|
Effective []string `json:"effective,omitempty" platform:"linux"`
|
||||||
|
Inheritable []string `json:"inheritable,omitempty" platform:"linux"`
|
||||||
|
Permitted []string `json:"permitted,omitempty" platform:"linux"`
|
||||||
|
Ambient []string `json:"ambient,omitempty" platform:"linux"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount from OCI runtime spec
|
||||||
|
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L103
|
||||||
|
type Mount struct {
|
||||||
|
Destination string `json:"destination"`
|
||||||
|
Type string `json:"type,omitempty" platform:"linux,solaris"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
Options []string `json:"options,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spec from OCI runtime spec
|
||||||
|
// We use pointers to structs, similarly to the latest version of runtime-spec:
|
||||||
|
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L5-L28
|
||||||
|
type Spec struct {
|
||||||
|
Version *string `json:"ociVersion"`
|
||||||
|
Process *Process `json:"process,omitempty"`
|
||||||
|
Root *Root `json:"root,omitempty"`
|
||||||
|
Mounts []Mount `json:"mounts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HookState holds state information about the hook
|
||||||
|
type HookState struct {
|
||||||
|
Pid int `json:"pid,omitempty"`
|
||||||
|
// After 17.06, runc is using the runtime spec:
|
||||||
|
// github.com/docker/runc/blob/17.06/libcontainer/configs/config.go#L262-L263
|
||||||
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/state.go#L3-L17
|
||||||
|
Bundle string `json:"bundle"`
|
||||||
|
// Before 17.06, runc used a custom struct that didn't conform to the spec:
|
||||||
|
// github.com/docker/runc/blob/17.03.x/libcontainer/configs/config.go#L245-L252
|
||||||
|
BundlePath string `json:"bundlePath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCudaVersion(cudaVersion string) (vmaj, vmin, vpatch uint32) {
|
||||||
|
if _, err := fmt.Sscanf(cudaVersion, "%d.%d.%d\n", &vmaj, &vmin, &vpatch); err != nil {
|
||||||
|
vpatch = 0
|
||||||
|
if _, err := fmt.Sscanf(cudaVersion, "%d.%d\n", &vmaj, &vmin); err != nil {
|
||||||
|
vmin = 0
|
||||||
|
if _, err := fmt.Sscanf(cudaVersion, "%d\n", &vmaj); err != nil {
|
||||||
|
log.Panicln("invalid CUDA version:", cudaVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvMap(e []string) (m map[string]string) {
|
||||||
|
m = make(map[string]string)
|
||||||
|
for _, s := range e {
|
||||||
|
p := strings.SplitN(s, "=", 2)
|
||||||
|
if len(p) != 2 {
|
||||||
|
log.Panicln("environment error")
|
||||||
|
}
|
||||||
|
m[p[0]] = p[1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSpec(path string) (spec *Spec) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("could not open OCI spec:", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err = json.NewDecoder(f).Decode(&spec); err != nil {
|
||||||
|
log.Panicln("could not decode OCI spec:", err)
|
||||||
|
}
|
||||||
|
if spec.Version == nil {
|
||||||
|
log.Panicln("Version is empty in OCI spec")
|
||||||
|
}
|
||||||
|
if spec.Process == nil {
|
||||||
|
log.Panicln("Process is empty in OCI spec")
|
||||||
|
}
|
||||||
|
if spec.Root == nil {
|
||||||
|
log.Panicln("Root is empty in OCI spec")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPrivileged(s *Spec) bool {
|
||||||
|
if s.Process.Capabilities == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var caps []string
|
||||||
|
// If v1.1.0-rc1 <= OCI version < v1.0.0-rc5 parse s.Process.Capabilities as:
|
||||||
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/specs-go/config.go#L30-L54
|
||||||
|
rc1cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc1")
|
||||||
|
rc5cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc5")
|
||||||
|
if (rc1cmp == 1 || rc1cmp == 0) && (rc5cmp == -1) {
|
||||||
|
err := json.Unmarshal(*s.Process.Capabilities, &caps)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("could not decode Process.Capabilities in OCI spec:", err)
|
||||||
|
}
|
||||||
|
// Otherwise, parse s.Process.Capabilities as:
|
||||||
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L54
|
||||||
|
} else {
|
||||||
|
var lc LinuxCapabilities
|
||||||
|
err := json.Unmarshal(*s.Process.Capabilities, &lc)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("could not decode Process.Capabilities in OCI spec:", err)
|
||||||
|
}
|
||||||
|
// We only make sure that the bounding capabibility set has
|
||||||
|
// CAP_SYS_ADMIN. This allows us to make sure that the container was
|
||||||
|
// actually started as '--privileged', but also allow non-root users to
|
||||||
|
// access the privileged NVIDIA capabilities.
|
||||||
|
caps = lc.Bounding
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range caps {
|
||||||
|
if c == capSysAdmin {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLegacyCUDAImage(env map[string]string) bool {
|
||||||
|
legacyCudaVersion := env[envCUDAVersion]
|
||||||
|
cudaRequire := env[envNVRequireCUDA]
|
||||||
|
return len(legacyCudaVersion) > 0 && len(cudaRequire) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDevicesFromEnvvar(env map[string]string, legacyImage bool) *string {
|
||||||
|
// Build a list of envvars to consider.
|
||||||
|
envVars := []string{envNVVisibleDevices}
|
||||||
|
if envSwarmGPU != nil {
|
||||||
|
// The Swarm envvar has higher precedence.
|
||||||
|
envVars = append([]string{*envSwarmGPU}, envVars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab a reference to devices from the first envvar
|
||||||
|
// in the list that actually exists in the environment.
|
||||||
|
var devices *string
|
||||||
|
for _, envVar := range envVars {
|
||||||
|
if devs, ok := env[envVar]; ok {
|
||||||
|
devices = &devs
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment variable unset with legacy image: default to "all".
|
||||||
|
if devices == nil && legacyImage {
|
||||||
|
all := "all"
|
||||||
|
return &all
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment variable unset or empty or "void": return nil
|
||||||
|
if devices == nil || len(*devices) == 0 || *devices == "void" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment variable set to "none": reset to "".
|
||||||
|
if *devices == "none" {
|
||||||
|
empty := ""
|
||||||
|
return &empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other value.
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDevicesFromMounts(mounts []Mount) *string {
|
||||||
|
var devices []string
|
||||||
|
for _, m := range mounts {
|
||||||
|
root := filepath.Clean(deviceListAsVolumeMountsRoot)
|
||||||
|
source := filepath.Clean(m.Source)
|
||||||
|
destination := filepath.Clean(m.Destination)
|
||||||
|
|
||||||
|
// Only consider mounts who's host volume is /dev/null
|
||||||
|
if source != "/dev/null" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Only consider container mount points that begin with 'root'
|
||||||
|
if len(destination) < len(root) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if destination[:len(root)] != root {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Grab the full path beyond 'root' and add it to the list of devices
|
||||||
|
device := destination[len(root):]
|
||||||
|
if len(device) > 0 && device[0] == '/' {
|
||||||
|
device = device[1:]
|
||||||
|
}
|
||||||
|
if len(device) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
devices = append(devices, device)
|
||||||
|
}
|
||||||
|
|
||||||
|
if devices == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := strings.Join(devices, ",")
|
||||||
|
return &ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDevices(hookConfig *HookConfig, env map[string]string, mounts []Mount, privileged bool, legacyImage bool) *string {
|
||||||
|
// If enabled, try and get the device list from volume mounts first
|
||||||
|
if hookConfig.AcceptDeviceListAsVolumeMounts {
|
||||||
|
devices := getDevicesFromMounts(mounts)
|
||||||
|
if devices != nil {
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to reading from the environment variable if privileges are correct
|
||||||
|
devices := getDevicesFromEnvvar(env, legacyImage)
|
||||||
|
if devices == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if privileged || hookConfig.AcceptEnvvarUnprivileged {
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
|
||||||
|
configName := hookConfig.getConfigOption("AcceptEnvvarUnprivileged")
|
||||||
|
log.Printf("Ignoring devices specified in NVIDIA_VISIBLE_DEVICES (privileged=%v, %v=%v) ", privileged, configName, hookConfig.AcceptEnvvarUnprivileged)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMigConfigDevices(env map[string]string) *string {
|
||||||
|
if devices, ok := env[envNVMigConfigDevices]; ok {
|
||||||
|
return &devices
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMigMonitorDevices(env map[string]string) *string {
|
||||||
|
if devices, ok := env[envNVMigMonitorDevices]; ok {
|
||||||
|
return &devices
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDriverCapabilities(env map[string]string, supportedDriverCapabilities DriverCapabilities, legacyImage bool) DriverCapabilities {
|
||||||
|
// We use the default driver capabilities by default. This is filtered to only include the
|
||||||
|
// supported capabilities
|
||||||
|
capabilities := supportedDriverCapabilities.Intersection(defaultDriverCapabilities)
|
||||||
|
|
||||||
|
capsEnv, capsEnvSpecified := env[envNVDriverCapabilities]
|
||||||
|
|
||||||
|
if !capsEnvSpecified && legacyImage {
|
||||||
|
// Environment variable unset with legacy image: set all capabilities.
|
||||||
|
return supportedDriverCapabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
if capsEnvSpecified && len(capsEnv) > 0 {
|
||||||
|
// If the envvironment variable is specified and is non-empty, use the capabilities value
|
||||||
|
envCapabilities := DriverCapabilities(capsEnv)
|
||||||
|
capabilities = supportedDriverCapabilities.Intersection(envCapabilities)
|
||||||
|
if envCapabilities != all && capabilities != envCapabilities {
|
||||||
|
log.Panicln(fmt.Errorf("unsupported capabilities found in '%v' (allowed '%v')", envCapabilities, capabilities))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return capabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRequirements(env map[string]string, legacyImage bool) []string {
|
||||||
|
// All variables with the "NVIDIA_REQUIRE_" prefix are passed to nvidia-container-cli
|
||||||
|
var requirements []string
|
||||||
|
for name, value := range env {
|
||||||
|
if strings.HasPrefix(name, envNVRequirePrefix) {
|
||||||
|
requirements = append(requirements, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if legacyImage {
|
||||||
|
vmaj, vmin, _ := parseCudaVersion(env[envCUDAVersion])
|
||||||
|
cudaRequire := fmt.Sprintf("cuda>=%d.%d", vmaj, vmin)
|
||||||
|
requirements = append(requirements, cudaRequire)
|
||||||
|
}
|
||||||
|
return requirements
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNvidiaConfig(hookConfig *HookConfig, env map[string]string, mounts []Mount, privileged bool) *nvidiaConfig {
|
||||||
|
legacyImage := isLegacyCUDAImage(env)
|
||||||
|
|
||||||
|
var devices string
|
||||||
|
if d := getDevices(hookConfig, env, mounts, privileged, legacyImage); d != nil {
|
||||||
|
devices = *d
|
||||||
|
} else {
|
||||||
|
// 'nil' devices means this is not a GPU container.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var migConfigDevices string
|
||||||
|
if d := getMigConfigDevices(env); d != nil {
|
||||||
|
migConfigDevices = *d
|
||||||
|
}
|
||||||
|
if !privileged && migConfigDevices != "" {
|
||||||
|
log.Panicln("cannot set MIG_CONFIG_DEVICES in non privileged container")
|
||||||
|
}
|
||||||
|
|
||||||
|
var migMonitorDevices string
|
||||||
|
if d := getMigMonitorDevices(env); d != nil {
|
||||||
|
migMonitorDevices = *d
|
||||||
|
}
|
||||||
|
if !privileged && migMonitorDevices != "" {
|
||||||
|
log.Panicln("cannot set MIG_MONITOR_DEVICES in non privileged container")
|
||||||
|
}
|
||||||
|
|
||||||
|
driverCapabilities := getDriverCapabilities(env, hookConfig.SupportedDriverCapabilities, legacyImage).String()
|
||||||
|
|
||||||
|
requirements := getRequirements(env, legacyImage)
|
||||||
|
|
||||||
|
// Don't fail on invalid values.
|
||||||
|
disableRequire, _ := strconv.ParseBool(env[envNVDisableRequire])
|
||||||
|
|
||||||
|
return &nvidiaConfig{
|
||||||
|
Devices: devices,
|
||||||
|
MigConfigDevices: migConfigDevices,
|
||||||
|
MigMonitorDevices: migMonitorDevices,
|
||||||
|
DriverCapabilities: driverCapabilities,
|
||||||
|
Requirements: requirements,
|
||||||
|
DisableRequire: disableRequire,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContainerConfig(hook HookConfig) (config containerConfig) {
|
||||||
|
var h HookState
|
||||||
|
d := json.NewDecoder(os.Stdin)
|
||||||
|
if err := d.Decode(&h); err != nil {
|
||||||
|
log.Panicln("could not decode container state:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := h.Bundle
|
||||||
|
if len(b) == 0 {
|
||||||
|
b = h.BundlePath
|
||||||
|
}
|
||||||
|
|
||||||
|
s := loadSpec(path.Join(b, "config.json"))
|
||||||
|
|
||||||
|
env := getEnvMap(s.Process.Env)
|
||||||
|
privileged := isPrivileged(s)
|
||||||
|
envSwarmGPU = hook.SwarmResource
|
||||||
|
return containerConfig{
|
||||||
|
Pid: h.Pid,
|
||||||
|
Rootfs: s.Root.Path,
|
||||||
|
Env: env,
|
||||||
|
Nvidia: getNvidiaConfig(&hook, env, s.Mounts, privileged),
|
||||||
|
}
|
||||||
|
}
|
||||||
989
cmd/nvidia-container-toolkit/container_config_test.go
Normal file
989
cmd/nvidia-container-toolkit/container_config_test.go
Normal file
@@ -0,0 +1,989 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetNvidiaConfig(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
description string
|
||||||
|
env map[string]string
|
||||||
|
privileged bool
|
||||||
|
hookConfig *HookConfig
|
||||||
|
expectedConfig *nvidiaConfig
|
||||||
|
expectedPanic bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "No environment, unprivileged",
|
||||||
|
env: map[string]string{},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No environment, privileged",
|
||||||
|
env: map[string]string{},
|
||||||
|
privileged: true,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, no devices, no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
DriverCapabilities: allDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices 'all', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
DriverCapabilities: allDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices 'empty', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices 'void', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices 'none', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "none",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "",
|
||||||
|
DriverCapabilities: allDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices set, no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: allDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices set, capabilities 'empty', no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices set, capabilities 'all', no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "all",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: allDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices set, capabilities set, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "video,display",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices set, capabilities set, requirements set",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "video,display",
|
||||||
|
envNVRequirePrefix + "REQ0": "req0=true",
|
||||||
|
envNVRequirePrefix + "REQ1": "req1=false",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
Requirements: []string{"cuda>=9.0", "req0=true", "req1=false"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Legacy image, devices set, capabilities set, requirements set, disable requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "video,display",
|
||||||
|
envNVRequirePrefix + "REQ0": "req0=true",
|
||||||
|
envNVRequirePrefix + "REQ1": "req1=false",
|
||||||
|
envNVDisableRequire: "true",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
Requirements: []string{"cuda>=9.0", "req0=true", "req1=false"},
|
||||||
|
DisableRequire: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, no devices, no capabilities, no requirements, no envCUDAVersion",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, no devices, no capabilities, no requirement, envCUDAVersion set",
|
||||||
|
env: map[string]string{
|
||||||
|
envCUDAVersion: "9.0",
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'all', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'empty', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'void', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'none', no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "none",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices set, no capabilities, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices set, capabilities 'empty', no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices set, capabilities 'all', no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "all",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: allDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices set, capabilities set, no requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "video,display",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices set, capabilities set, requirements set",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "video,display",
|
||||||
|
envNVRequirePrefix + "REQ0": "req0=true",
|
||||||
|
envNVRequirePrefix + "REQ1": "req1=false",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
Requirements: []string{"cuda>=9.0", "req0=true", "req1=false"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices set, capabilities set, requirements set, disable requirements",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "gpu0,gpu1",
|
||||||
|
envNVDriverCapabilities: "video,display",
|
||||||
|
envNVRequirePrefix + "REQ0": "req0=true",
|
||||||
|
envNVRequirePrefix + "REQ1": "req1=false",
|
||||||
|
envNVDisableRequire: "true",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "gpu0,gpu1",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
Requirements: []string{"cuda>=9.0", "req0=true", "req1=false"},
|
||||||
|
DisableRequire: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No cuda envs, devices 'all'",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'all', migConfig set, privileged",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
envNVMigConfigDevices: "mig0,mig1",
|
||||||
|
},
|
||||||
|
privileged: true,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
MigConfigDevices: "mig0,mig1",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'all', migConfig set, unprivileged",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
envNVMigConfigDevices: "mig0,mig1",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedPanic: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'all', migMonitor set, privileged",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
envNVMigMonitorDevices: "mig0,mig1",
|
||||||
|
},
|
||||||
|
privileged: true,
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
MigMonitorDevices: "mig0,mig1",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
Requirements: []string{"cuda>=9.0"},
|
||||||
|
DisableRequire: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Modern image, devices 'all', migMonitor set, unprivileged",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVRequireCUDA: "cuda>=9.0",
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
envNVMigMonitorDevices: "mig0,mig1",
|
||||||
|
},
|
||||||
|
privileged: false,
|
||||||
|
expectedPanic: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Hook config set as driver-capabilities-all",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
envNVDriverCapabilities: "all",
|
||||||
|
},
|
||||||
|
privileged: true,
|
||||||
|
hookConfig: &HookConfig{
|
||||||
|
SupportedDriverCapabilities: "video,display",
|
||||||
|
},
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Hook config set, envvar sets driver-capabilities",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
envNVDriverCapabilities: "video,display",
|
||||||
|
},
|
||||||
|
privileged: true,
|
||||||
|
hookConfig: &HookConfig{
|
||||||
|
SupportedDriverCapabilities: "video,display,compute,utility",
|
||||||
|
},
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
DriverCapabilities: "video,display",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Hook config set, envvar unset sets default driver-capabilities",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "all",
|
||||||
|
},
|
||||||
|
privileged: true,
|
||||||
|
hookConfig: &HookConfig{
|
||||||
|
SupportedDriverCapabilities: "video,display,utility,compute",
|
||||||
|
},
|
||||||
|
expectedConfig: &nvidiaConfig{
|
||||||
|
Devices: "all",
|
||||||
|
DriverCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
// Wrap the call to getNvidiaConfig() in a closure.
|
||||||
|
var config *nvidiaConfig
|
||||||
|
getConfig := func() {
|
||||||
|
hookConfig := tc.hookConfig
|
||||||
|
if hookConfig == nil {
|
||||||
|
defaultConfig := getDefaultHookConfig()
|
||||||
|
hookConfig = &defaultConfig
|
||||||
|
}
|
||||||
|
config = getNvidiaConfig(hookConfig, tc.env, nil, tc.privileged)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For any tests that are expected to panic, make sure they do.
|
||||||
|
if tc.expectedPanic {
|
||||||
|
require.Panics(t, getConfig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all other tests, just grab the config
|
||||||
|
getConfig()
|
||||||
|
|
||||||
|
// And start comparing the test results to the expected results.
|
||||||
|
if tc.expectedConfig == nil {
|
||||||
|
require.Nil(t, config, tc.description)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotNil(t, config, tc.description)
|
||||||
|
|
||||||
|
require.Equal(t, tc.expectedConfig.Devices, config.Devices)
|
||||||
|
require.Equal(t, tc.expectedConfig.MigConfigDevices, config.MigConfigDevices)
|
||||||
|
require.Equal(t, tc.expectedConfig.MigMonitorDevices, config.MigMonitorDevices)
|
||||||
|
require.Equal(t, tc.expectedConfig.DriverCapabilities, config.DriverCapabilities)
|
||||||
|
|
||||||
|
require.ElementsMatch(t, tc.expectedConfig.Requirements, config.Requirements)
|
||||||
|
require.Equal(t, tc.expectedConfig.DisableRequire, config.DisableRequire)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDevicesFromMounts(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
description string
|
||||||
|
mounts []Mount
|
||||||
|
expectedDevices *string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "No mounts",
|
||||||
|
mounts: nil,
|
||||||
|
expectedDevices: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Host path is not /dev/null",
|
||||||
|
mounts: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/not/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU0"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDevices: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Container path is not prefixed by 'root'",
|
||||||
|
mounts: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join("/other/prefix", "GPU0"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDevices: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Container path is only 'root'",
|
||||||
|
mounts: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: deviceListAsVolumeMountsRoot,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDevices: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Discover 2 devices",
|
||||||
|
mounts: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU0"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDevices: &[]string{"GPU0,GPU1"}[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Discover 2 devices with slashes in the name",
|
||||||
|
mounts: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU0-MIG0/0/1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU1-MIG0/0/1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDevices: &[]string{"GPU0-MIG0/0/1,GPU1-MIG0/0/1"}[0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
devices := getDevicesFromMounts(tc.mounts)
|
||||||
|
require.Equal(t, tc.expectedDevices, devices)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeviceListSourcePriority(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
description string
|
||||||
|
mountDevices []Mount
|
||||||
|
envvarDevices string
|
||||||
|
privileged bool
|
||||||
|
acceptUnprivileged bool
|
||||||
|
acceptMounts bool
|
||||||
|
expectedDevices *string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "Mount devices, unprivileged, no accept unprivileged",
|
||||||
|
mountDevices: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU0"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
envvarDevices: "GPU2,GPU3",
|
||||||
|
privileged: false,
|
||||||
|
acceptUnprivileged: false,
|
||||||
|
acceptMounts: true,
|
||||||
|
expectedDevices: &[]string{"GPU0,GPU1"}[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No mount devices, unprivileged, no accept unprivileged",
|
||||||
|
mountDevices: nil,
|
||||||
|
envvarDevices: "GPU0,GPU1",
|
||||||
|
privileged: false,
|
||||||
|
acceptUnprivileged: false,
|
||||||
|
acceptMounts: true,
|
||||||
|
expectedDevices: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No mount devices, privileged, no accept unprivileged",
|
||||||
|
mountDevices: nil,
|
||||||
|
envvarDevices: "GPU0,GPU1",
|
||||||
|
privileged: true,
|
||||||
|
acceptUnprivileged: false,
|
||||||
|
acceptMounts: true,
|
||||||
|
expectedDevices: &[]string{"GPU0,GPU1"}[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No mount devices, unprivileged, accept unprivileged",
|
||||||
|
mountDevices: nil,
|
||||||
|
envvarDevices: "GPU0,GPU1",
|
||||||
|
privileged: false,
|
||||||
|
acceptUnprivileged: true,
|
||||||
|
acceptMounts: true,
|
||||||
|
expectedDevices: &[]string{"GPU0,GPU1"}[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Mount devices, unprivileged, accept unprivileged, no accept mounts",
|
||||||
|
mountDevices: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU0"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
envvarDevices: "GPU2,GPU3",
|
||||||
|
privileged: false,
|
||||||
|
acceptUnprivileged: true,
|
||||||
|
acceptMounts: false,
|
||||||
|
expectedDevices: &[]string{"GPU2,GPU3"}[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Mount devices, unprivileged, no accept unprivileged, no accept mounts",
|
||||||
|
mountDevices: []Mount{
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU0"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: "/dev/null",
|
||||||
|
Destination: filepath.Join(deviceListAsVolumeMountsRoot, "GPU1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
envvarDevices: "GPU2,GPU3",
|
||||||
|
privileged: false,
|
||||||
|
acceptUnprivileged: false,
|
||||||
|
acceptMounts: false,
|
||||||
|
expectedDevices: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
// Wrap the call to getDevices() in a closure.
|
||||||
|
var devices *string
|
||||||
|
getDevices := func() {
|
||||||
|
env := map[string]string{
|
||||||
|
envNVVisibleDevices: tc.envvarDevices,
|
||||||
|
}
|
||||||
|
hookConfig := getDefaultHookConfig()
|
||||||
|
hookConfig.AcceptEnvvarUnprivileged = tc.acceptUnprivileged
|
||||||
|
hookConfig.AcceptDeviceListAsVolumeMounts = tc.acceptMounts
|
||||||
|
devices = getDevices(&hookConfig, env, tc.mountDevices, tc.privileged, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all other tests, just grab the devices and check the results
|
||||||
|
getDevices()
|
||||||
|
|
||||||
|
require.Equal(t, tc.expectedDevices, devices)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDevicesFromEnvvar(t *testing.T) {
|
||||||
|
all := "all"
|
||||||
|
empty := ""
|
||||||
|
envDockerResourceGPUs := "DOCKER_RESOURCE_GPUS"
|
||||||
|
gpuID := "GPU-12345"
|
||||||
|
anotherGPUID := "GPU-67890"
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
description string
|
||||||
|
envSwarmGPU *string
|
||||||
|
env map[string]string
|
||||||
|
legacyImage bool
|
||||||
|
expectedDevices *string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "empty env returns nil for non-legacy image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "blank NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "'void' NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "void",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "'none' NVIDIA_VISIBLE_DEVICES returns empty for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "none",
|
||||||
|
},
|
||||||
|
expectedDevices: &empty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "NVIDIA_VISIBLE_DEVICES set returns value for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: gpuID,
|
||||||
|
},
|
||||||
|
expectedDevices: &gpuID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "NVIDIA_VISIBLE_DEVICES set returns value for legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: gpuID,
|
||||||
|
},
|
||||||
|
legacyImage: true,
|
||||||
|
expectedDevices: &gpuID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "empty env returns all for legacy image",
|
||||||
|
legacyImage: true,
|
||||||
|
expectedDevices: &all,
|
||||||
|
},
|
||||||
|
// Add the `DOCKER_RESOURCE_GPUS` envvar and ensure that this is ignored when
|
||||||
|
// not enabled
|
||||||
|
{
|
||||||
|
description: "missing NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "blank NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "",
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "'void' NVIDIA_VISIBLE_DEVICES returns nil for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "void",
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "'none' NVIDIA_VISIBLE_DEVICES returns empty for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: "none",
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
expectedDevices: &empty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "NVIDIA_VISIBLE_DEVICES set returns value for non-legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: gpuID,
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
expectedDevices: &gpuID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "NVIDIA_VISIBLE_DEVICES set returns value for legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: gpuID,
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
legacyImage: true,
|
||||||
|
expectedDevices: &gpuID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "empty env returns all for legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
legacyImage: true,
|
||||||
|
expectedDevices: &all,
|
||||||
|
},
|
||||||
|
// Add the `DOCKER_RESOURCE_GPUS` envvar and ensure that this is selected when
|
||||||
|
// enabled
|
||||||
|
{
|
||||||
|
description: "empty env returns nil for non-legacy image",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "blank DOCKER_RESOURCE_GPUS returns nil for non-legacy image",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "'void' DOCKER_RESOURCE_GPUS returns nil for non-legacy image",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: "void",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "'none' DOCKER_RESOURCE_GPUS returns empty for non-legacy image",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: "none",
|
||||||
|
},
|
||||||
|
expectedDevices: &empty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "DOCKER_RESOURCE_GPUS set returns value for non-legacy image",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: gpuID,
|
||||||
|
},
|
||||||
|
expectedDevices: &gpuID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "DOCKER_RESOURCE_GPUS set returns value for legacy image",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: gpuID,
|
||||||
|
},
|
||||||
|
legacyImage: true,
|
||||||
|
expectedDevices: &gpuID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "DOCKER_RESOURCE_GPUS is selected if present",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
env: map[string]string{
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
expectedDevices: &anotherGPUID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "DOCKER_RESOURCE_GPUS overrides NVIDIA_VISIBLE_DEVICES if present",
|
||||||
|
envSwarmGPU: &envDockerResourceGPUs,
|
||||||
|
env: map[string]string{
|
||||||
|
envNVVisibleDevices: gpuID,
|
||||||
|
envDockerResourceGPUs: anotherGPUID,
|
||||||
|
},
|
||||||
|
expectedDevices: &anotherGPUID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range tests {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
envSwarmGPU = tc.envSwarmGPU
|
||||||
|
devices := getDevicesFromEnvvar(tc.env, tc.legacyImage)
|
||||||
|
if tc.expectedDevices == nil {
|
||||||
|
require.Nil(t, devices, "%d: %v", i, tc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotNil(t, devices, "%d: %v", i, tc)
|
||||||
|
require.Equal(t, *tc.expectedDevices, *devices, "%d: %v", i, tc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDriverCapabilities(t *testing.T) {
|
||||||
|
|
||||||
|
supportedCapabilities := "compute,utility,display,video"
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
env map[string]string
|
||||||
|
legacyImage bool
|
||||||
|
supportedCapabilities string
|
||||||
|
expectedPanic bool
|
||||||
|
expectedCapabilities string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "Env is set for legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVDriverCapabilities: "display,video",
|
||||||
|
},
|
||||||
|
legacyImage: true,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: "display,video",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Env is all for legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVDriverCapabilities: "all",
|
||||||
|
},
|
||||||
|
legacyImage: true,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: supportedCapabilities,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Env is empty for legacy image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVDriverCapabilities: "",
|
||||||
|
},
|
||||||
|
legacyImage: true,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Env unset for legacy image is 'all'",
|
||||||
|
env: map[string]string{},
|
||||||
|
legacyImage: true,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: supportedCapabilities,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Env is set for modern image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVDriverCapabilities: "display,video",
|
||||||
|
},
|
||||||
|
legacyImage: false,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: "display,video",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Env unset for modern image is default",
|
||||||
|
env: map[string]string{},
|
||||||
|
legacyImage: false,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Env is all for modern image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVDriverCapabilities: "all",
|
||||||
|
},
|
||||||
|
legacyImage: false,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: supportedCapabilities,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Env is empty for modern image",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVDriverCapabilities: "",
|
||||||
|
},
|
||||||
|
legacyImage: false,
|
||||||
|
supportedCapabilities: supportedCapabilities,
|
||||||
|
expectedCapabilities: defaultDriverCapabilities.String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid capabilities panic",
|
||||||
|
env: map[string]string{
|
||||||
|
envNVDriverCapabilities: "compute,utility",
|
||||||
|
},
|
||||||
|
supportedCapabilities: "not-compute,not-utility",
|
||||||
|
expectedPanic: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Default is restricted for modern image",
|
||||||
|
legacyImage: false,
|
||||||
|
supportedCapabilities: "compute",
|
||||||
|
expectedCapabilities: "compute",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
var capabilites DriverCapabilities
|
||||||
|
|
||||||
|
getDriverCapabilities := func() {
|
||||||
|
supportedCapabilities := DriverCapabilities(tc.supportedCapabilities)
|
||||||
|
capabilites = getDriverCapabilities(tc.env, supportedCapabilities, tc.legacyImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedPanic {
|
||||||
|
require.Panics(t, getDriverCapabilities)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
getDriverCapabilities()
|
||||||
|
require.EqualValues(t, tc.expectedCapabilities, capabilites)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
115
cmd/nvidia-container-toolkit/hook_config.go
Normal file
115
cmd/nvidia-container-toolkit/hook_config.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configPath = "/etc/nvidia-container-runtime/config.toml"
|
||||||
|
driverPath = "/run/nvidia/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultPaths = [...]string{
|
||||||
|
path.Join(driverPath, configPath),
|
||||||
|
configPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLIConfig : options for nvidia-container-cli.
|
||||||
|
type CLIConfig struct {
|
||||||
|
Root *string `toml:"root"`
|
||||||
|
Path *string `toml:"path"`
|
||||||
|
Environment []string `toml:"environment"`
|
||||||
|
Debug *string `toml:"debug"`
|
||||||
|
Ldcache *string `toml:"ldcache"`
|
||||||
|
LoadKmods bool `toml:"load-kmods"`
|
||||||
|
NoPivot bool `toml:"no-pivot"`
|
||||||
|
NoCgroups bool `toml:"no-cgroups"`
|
||||||
|
User *string `toml:"user"`
|
||||||
|
Ldconfig *string `toml:"ldconfig"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HookConfig : options for the nvidia-container-toolkit.
|
||||||
|
type HookConfig struct {
|
||||||
|
DisableRequire bool `toml:"disable-require"`
|
||||||
|
SwarmResource *string `toml:"swarm-resource"`
|
||||||
|
AcceptEnvvarUnprivileged bool `toml:"accept-nvidia-visible-devices-envvar-when-unprivileged"`
|
||||||
|
AcceptDeviceListAsVolumeMounts bool `toml:"accept-nvidia-visible-devices-as-volume-mounts"`
|
||||||
|
SupportedDriverCapabilities DriverCapabilities `toml:"supported-driver-capabilities"`
|
||||||
|
|
||||||
|
NvidiaContainerCLI CLIConfig `toml:"nvidia-container-cli"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultHookConfig() (config HookConfig) {
|
||||||
|
return HookConfig{
|
||||||
|
DisableRequire: false,
|
||||||
|
SwarmResource: nil,
|
||||||
|
AcceptEnvvarUnprivileged: true,
|
||||||
|
AcceptDeviceListAsVolumeMounts: false,
|
||||||
|
SupportedDriverCapabilities: allDriverCapabilities,
|
||||||
|
NvidiaContainerCLI: CLIConfig{
|
||||||
|
Root: nil,
|
||||||
|
Path: nil,
|
||||||
|
Environment: []string{},
|
||||||
|
Debug: nil,
|
||||||
|
Ldcache: nil,
|
||||||
|
LoadKmods: true,
|
||||||
|
NoPivot: false,
|
||||||
|
NoCgroups: false,
|
||||||
|
User: nil,
|
||||||
|
Ldconfig: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHookConfig() (config HookConfig) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(*configflag) > 0 {
|
||||||
|
config = getDefaultHookConfig()
|
||||||
|
_, err = toml.DecodeFile(*configflag, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("couldn't open configuration file:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, p := range defaultPaths {
|
||||||
|
config = getDefaultHookConfig()
|
||||||
|
_, err = toml.DecodeFile(p, &config)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
log.Panicln("couldn't open default configuration file:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SupportedDriverCapabilities == all {
|
||||||
|
config.SupportedDriverCapabilities = allDriverCapabilities
|
||||||
|
}
|
||||||
|
// We ensure that the supported-driver-capabilites option is a subset of allDriverCapabilities
|
||||||
|
if intersection := allDriverCapabilities.Intersection(config.SupportedDriverCapabilities); intersection != config.SupportedDriverCapabilities {
|
||||||
|
configName := config.getConfigOption("SupportedDriverCapabilities")
|
||||||
|
log.Panicf("Invalid value for config option '%v'; %v (supported: %v)\n", configName, config.SupportedDriverCapabilities, allDriverCapabilities)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfigOption returns the toml config option associated with the
|
||||||
|
// specified struct field.
|
||||||
|
func (c HookConfig) getConfigOption(fieldName string) string {
|
||||||
|
t := reflect.TypeOf(c)
|
||||||
|
f, ok := t.FieldByName(fieldName)
|
||||||
|
if !ok {
|
||||||
|
return fieldName
|
||||||
|
}
|
||||||
|
v, ok := f.Tag.Lookup("toml")
|
||||||
|
if !ok {
|
||||||
|
return fieldName
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
@@ -22,25 +22,22 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/image"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetHookConfig(t *testing.T) {
|
func TestGetHookConfig(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
lines []string
|
lines []string
|
||||||
expectedPanic bool
|
expectedPanic bool
|
||||||
expectedDriverCapabilities string
|
expectedDriverCapabilities DriverCapabilities
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
expectedDriverCapabilities: image.SupportedDriverCapabilities.String(),
|
expectedDriverCapabilities: allDriverCapabilities,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lines: []string{
|
lines: []string{
|
||||||
"supported-driver-capabilities = \"all\"",
|
"supported-driver-capabilities = \"all\"",
|
||||||
},
|
},
|
||||||
expectedDriverCapabilities: image.SupportedDriverCapabilities.String(),
|
expectedDriverCapabilities: allDriverCapabilities,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lines: []string{
|
lines: []string{
|
||||||
@@ -50,19 +47,19 @@ func TestGetHookConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
lines: []string{},
|
lines: []string{},
|
||||||
expectedDriverCapabilities: image.SupportedDriverCapabilities.String(),
|
expectedDriverCapabilities: allDriverCapabilities,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lines: []string{
|
lines: []string{
|
||||||
"supported-driver-capabilities = \"\"",
|
"supported-driver-capabilities = \"\"",
|
||||||
},
|
},
|
||||||
expectedDriverCapabilities: "",
|
expectedDriverCapabilities: none,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lines: []string{
|
lines: []string{
|
||||||
"supported-driver-capabilities = \"compute,utility\"",
|
"supported-driver-capabilities = \"utility,compute\"",
|
||||||
},
|
},
|
||||||
expectedDriverCapabilities: "compute,utility",
|
expectedDriverCapabilities: DriverCapabilities("utility,compute"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,15 +82,14 @@ func TestGetHookConfig(t *testing.T) {
|
|||||||
configflag = &filename
|
configflag = &filename
|
||||||
|
|
||||||
for _, line := range tc.lines {
|
for _, line := range tc.lines {
|
||||||
_, err := fmt.Fprintf(configFile, "%s\n", line)
|
_, err := configFile.WriteString(fmt.Sprintf("%s\n", line))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg hookConfig
|
var config HookConfig
|
||||||
getHookConfig := func() {
|
getHookConfig := func() {
|
||||||
c, _ := getHookConfig()
|
config = getHookConfig()
|
||||||
cfg = *c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.expectedPanic {
|
if tc.expectedPanic {
|
||||||
@@ -103,56 +99,7 @@ func TestGetHookConfig(t *testing.T) {
|
|||||||
|
|
||||||
getHookConfig()
|
getHookConfig()
|
||||||
|
|
||||||
require.EqualValues(t, tc.expectedDriverCapabilities, cfg.SupportedDriverCapabilities)
|
require.EqualValues(t, tc.expectedDriverCapabilities, config.SupportedDriverCapabilities)
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSwarmResourceEnvvars(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
value string
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
value: "",
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: " ",
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "single",
|
|
||||||
expected: []string{"single"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "single ",
|
|
||||||
expected: []string{"single"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "one,two",
|
|
||||||
expected: []string{"one", "two"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "one ,two",
|
|
||||||
expected: []string{"one", "two"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "one, two",
|
|
||||||
expected: []string{"one", "two"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
c := &hookConfig{
|
|
||||||
Config: &config.Config{
|
|
||||||
SwarmResource: tc.value,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
envvars := c.getSwarmResourceEnvvars()
|
|
||||||
require.EqualValues(t, tc.expected, envvars)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,51 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestParseCudaVersionValid(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
version string
|
||||||
|
expected [3]uint32
|
||||||
|
}{
|
||||||
|
{"0", [3]uint32{0, 0, 0}},
|
||||||
|
{"8", [3]uint32{8, 0, 0}},
|
||||||
|
{"7.5", [3]uint32{7, 5, 0}},
|
||||||
|
{"9.0.116", [3]uint32{9, 0, 116}},
|
||||||
|
{"4294967295.4294967295.4294967295", [3]uint32{4294967295, 4294967295, 4294967295}},
|
||||||
|
}
|
||||||
|
for i, c := range tests {
|
||||||
|
vmaj, vmin, vpatch := parseCudaVersion(c.version)
|
||||||
|
|
||||||
|
version := [3]uint32{vmaj, vmin, vpatch}
|
||||||
|
|
||||||
|
require.Equal(t, c.expected, version, "%d: %v", i, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseCudaVersionInvalid(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"foo",
|
||||||
|
"foo.5.10",
|
||||||
|
"9.0.116.50",
|
||||||
|
"9.0.116foo",
|
||||||
|
"7.foo",
|
||||||
|
"9.0.bar",
|
||||||
|
"9.4294967296",
|
||||||
|
"9.0.116.",
|
||||||
|
"9..0",
|
||||||
|
"9.",
|
||||||
|
".5.10",
|
||||||
|
"-9",
|
||||||
|
"+9",
|
||||||
|
"-9.1.116",
|
||||||
|
"-9.-1.-116",
|
||||||
|
}
|
||||||
|
for _, c := range tests {
|
||||||
|
require.Panics(t, func() {
|
||||||
|
parseCudaVersion(c)
|
||||||
|
}, "parseCudaVersion(%v)", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsPrivileged(t *testing.T) {
|
func TestIsPrivileged(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
spec string
|
spec string
|
||||||
188
cmd/nvidia-container-toolkit/main.go
Normal file
188
cmd/nvidia-container-toolkit/main.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debugflag = flag.Bool("debug", false, "enable debug output")
|
||||||
|
configflag = flag.String("config", "", "configuration file")
|
||||||
|
|
||||||
|
defaultPATH = []string{"/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func exit() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if _, ok := err.(runtime.Error); ok {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
if *debugflag {
|
||||||
|
log.Printf("%s", debug.Stack())
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPATH(config CLIConfig) string {
|
||||||
|
dirs := filepath.SplitList(os.Getenv("PATH"))
|
||||||
|
// directories from the hook environment have higher precedence
|
||||||
|
dirs = append(dirs, defaultPATH...)
|
||||||
|
|
||||||
|
if config.Root != nil {
|
||||||
|
rootDirs := []string{}
|
||||||
|
for _, dir := range dirs {
|
||||||
|
rootDirs = append(rootDirs, path.Join(*config.Root, dir))
|
||||||
|
}
|
||||||
|
// directories with the root prefix have higher precedence
|
||||||
|
dirs = append(rootDirs, dirs...)
|
||||||
|
}
|
||||||
|
return strings.Join(dirs, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCLIPath(config CLIConfig) string {
|
||||||
|
if config.Path != nil {
|
||||||
|
return *config.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Setenv("PATH", getPATH(config)); err != nil {
|
||||||
|
log.Panicln("couldn't set PATH variable:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := exec.LookPath("nvidia-container-cli")
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("couldn't find binary nvidia-container-cli in", os.Getenv("PATH"), ":", err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRootfsPath returns an absolute path. We don't need to resolve symlinks for now.
|
||||||
|
func getRootfsPath(config containerConfig) string {
|
||||||
|
rootfs, err := filepath.Abs(config.Rootfs)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
return rootfs
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPrestart() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
defer exit()
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
hook := getHookConfig()
|
||||||
|
cli := hook.NvidiaContainerCLI
|
||||||
|
|
||||||
|
container := getContainerConfig(hook)
|
||||||
|
nvidia := container.Nvidia
|
||||||
|
if nvidia == nil {
|
||||||
|
// Not a GPU container, nothing to do.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs := getRootfsPath(container)
|
||||||
|
|
||||||
|
args := []string{getCLIPath(cli)}
|
||||||
|
if cli.Root != nil {
|
||||||
|
args = append(args, fmt.Sprintf("--root=%s", *cli.Root))
|
||||||
|
}
|
||||||
|
if cli.LoadKmods {
|
||||||
|
args = append(args, "--load-kmods")
|
||||||
|
}
|
||||||
|
if cli.NoPivot {
|
||||||
|
args = append(args, "--no-pivot")
|
||||||
|
}
|
||||||
|
if *debugflag {
|
||||||
|
args = append(args, "--debug=/dev/stderr")
|
||||||
|
} else if cli.Debug != nil {
|
||||||
|
args = append(args, fmt.Sprintf("--debug=%s", *cli.Debug))
|
||||||
|
}
|
||||||
|
if cli.Ldcache != nil {
|
||||||
|
args = append(args, fmt.Sprintf("--ldcache=%s", *cli.Ldcache))
|
||||||
|
}
|
||||||
|
if cli.User != nil {
|
||||||
|
args = append(args, fmt.Sprintf("--user=%s", *cli.User))
|
||||||
|
}
|
||||||
|
args = append(args, "configure")
|
||||||
|
|
||||||
|
if cli.Ldconfig != nil {
|
||||||
|
args = append(args, fmt.Sprintf("--ldconfig=%s", *cli.Ldconfig))
|
||||||
|
}
|
||||||
|
if cli.NoCgroups {
|
||||||
|
args = append(args, "--no-cgroups")
|
||||||
|
}
|
||||||
|
if len(nvidia.Devices) > 0 {
|
||||||
|
args = append(args, fmt.Sprintf("--device=%s", nvidia.Devices))
|
||||||
|
}
|
||||||
|
if len(nvidia.MigConfigDevices) > 0 {
|
||||||
|
args = append(args, fmt.Sprintf("--mig-config=%s", nvidia.MigConfigDevices))
|
||||||
|
}
|
||||||
|
if len(nvidia.MigMonitorDevices) > 0 {
|
||||||
|
args = append(args, fmt.Sprintf("--mig-monitor=%s", nvidia.MigMonitorDevices))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cap := range strings.Split(nvidia.DriverCapabilities, ",") {
|
||||||
|
if len(cap) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
args = append(args, capabilityToCLI(cap))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hook.DisableRequire && !nvidia.DisableRequire {
|
||||||
|
for _, req := range nvidia.Requirements {
|
||||||
|
args = append(args, fmt.Sprintf("--require=%s", req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, fmt.Sprintf("--pid=%s", strconv.FormatUint(uint64(container.Pid), 10)))
|
||||||
|
args = append(args, rootfs)
|
||||||
|
|
||||||
|
env := append(os.Environ(), cli.Environment...)
|
||||||
|
err = syscall.Exec(args[0], args, env)
|
||||||
|
log.Panicln("exec failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Fprintf(os.Stderr, "\nCommands:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " prestart\n run the prestart hook\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " poststart\n no-op\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " poststop\n no-op\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Usage = usage
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "prestart":
|
||||||
|
doPrestart()
|
||||||
|
os.Exit(0)
|
||||||
|
case "poststart":
|
||||||
|
fallthrough
|
||||||
|
case "poststop":
|
||||||
|
os.Exit(0)
|
||||||
|
default:
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container/operator"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
restartModeNone = "none"
|
|
||||||
restartModeSignal = "signal"
|
|
||||||
restartModeSystemd = "systemd"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options defines the shared options for the CLIs to configure containers runtimes.
|
|
||||||
type Options struct {
|
|
||||||
Config string
|
|
||||||
Socket string
|
|
||||||
// ExecutablePath specifies the path to the container runtime executable.
|
|
||||||
// This is used to extract the current config, for example.
|
|
||||||
// If a HostRootMount is specified, this path is relative to the host root
|
|
||||||
// mount.
|
|
||||||
ExecutablePath string
|
|
||||||
// EnabledCDI indicates whether CDI should be enabled.
|
|
||||||
EnableCDI bool
|
|
||||||
RuntimeName string
|
|
||||||
RuntimeDir string
|
|
||||||
SetAsDefault bool
|
|
||||||
RestartMode string
|
|
||||||
HostRootMount string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseArgs parses the command line arguments to the CLI
|
|
||||||
func ParseArgs(c *cli.Context, o *Options) error {
|
|
||||||
if o.RuntimeDir != "" {
|
|
||||||
logrus.Debug("Runtime directory already set; ignoring arguments")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args := c.Args()
|
|
||||||
|
|
||||||
logrus.Infof("Parsing arguments: %v", args.Slice())
|
|
||||||
if c.NArg() != 1 {
|
|
||||||
return fmt.Errorf("incorrect number of arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
o.RuntimeDir = args.Get(0)
|
|
||||||
|
|
||||||
logrus.Infof("Successfully parsed arguments")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure applies the options to the specified config
|
|
||||||
func (o Options) Configure(cfg engine.Interface) error {
|
|
||||||
err := o.UpdateConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to update config: %v", err)
|
|
||||||
}
|
|
||||||
return o.flush(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unconfigure removes the options from the specified config
|
|
||||||
func (o Options) Unconfigure(cfg engine.Interface) error {
|
|
||||||
err := o.RevertConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to update config: %v", err)
|
|
||||||
}
|
|
||||||
return o.flush(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush flushes the specified config to disk
|
|
||||||
func (o Options) flush(cfg engine.Interface) error {
|
|
||||||
logrus.Infof("Flushing config to %v", o.Config)
|
|
||||||
n, err := cfg.Save(o.Config)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to flush config: %v", err)
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
logrus.Infof("Config file is empty, removed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConfig updates the specified config to include the nvidia runtimes
|
|
||||||
func (o Options) UpdateConfig(cfg engine.Interface) error {
|
|
||||||
runtimes := operator.GetRuntimes(
|
|
||||||
operator.WithNvidiaRuntimeName(o.RuntimeName),
|
|
||||||
operator.WithSetAsDefault(o.SetAsDefault),
|
|
||||||
operator.WithRoot(o.RuntimeDir),
|
|
||||||
)
|
|
||||||
for name, runtime := range runtimes {
|
|
||||||
err := cfg.AddRuntime(name, runtime.Path, runtime.SetAsDefault)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update runtime %q: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.EnableCDI {
|
|
||||||
cfg.EnableCDI()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevertConfig reverts the specified config to remove the nvidia runtimes
|
|
||||||
func (o Options) RevertConfig(cfg engine.Interface) error {
|
|
||||||
runtimes := operator.GetRuntimes(
|
|
||||||
operator.WithNvidiaRuntimeName(o.RuntimeName),
|
|
||||||
operator.WithSetAsDefault(o.SetAsDefault),
|
|
||||||
operator.WithRoot(o.RuntimeDir),
|
|
||||||
)
|
|
||||||
for name := range runtimes {
|
|
||||||
err := cfg.RemoveRuntime(name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to remove runtime %q: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart restarts the specified service
|
|
||||||
func (o Options) Restart(service string, withSignal func(string) error) error {
|
|
||||||
switch o.RestartMode {
|
|
||||||
case restartModeNone:
|
|
||||||
logrus.Warningf("Skipping restart of %v due to --restart-mode=%v", service, o.RestartMode)
|
|
||||||
return nil
|
|
||||||
case restartModeSignal:
|
|
||||||
return withSignal(o.Socket)
|
|
||||||
case restartModeSystemd:
|
|
||||||
return o.SystemdRestart(service)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("invalid restart mode specified: %v", o.RestartMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemdRestart restarts the specified service using systemd
|
|
||||||
func (o Options) SystemdRestart(service string) error {
|
|
||||||
var args []string
|
|
||||||
var msg string
|
|
||||||
if o.HostRootMount != "" {
|
|
||||||
msg = " on host"
|
|
||||||
args = append(args, "chroot", o.HostRootMount)
|
|
||||||
}
|
|
||||||
args = append(args, "systemctl", "restart", service)
|
|
||||||
|
|
||||||
logrus.Infof("Restarting %v%v using systemd: %v", service, msg, args)
|
|
||||||
|
|
||||||
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error restarting %v using systemd: %v", service, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package operator
|
|
||||||
|
|
||||||
import "path/filepath"
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultRuntimeName = "nvidia"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Runtime defines a runtime to be configured.
|
|
||||||
// The path and whether the runtime is the default runtime can be specified
|
|
||||||
type Runtime struct {
|
|
||||||
name string
|
|
||||||
Path string
|
|
||||||
SetAsDefault bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runtimes defines a set of runtimes to be configured for use in the GPU Operator
|
|
||||||
type Runtimes map[string]Runtime
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
root string
|
|
||||||
nvidiaRuntimeName string
|
|
||||||
setAsDefault bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRuntimes returns the set of runtimes to be configured for use with the GPU Operator.
|
|
||||||
func GetRuntimes(opts ...Option) Runtimes {
|
|
||||||
c := &config{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.nvidiaRuntimeName == "" {
|
|
||||||
c.nvidiaRuntimeName = defaultRuntimeName
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimes := make(Runtimes)
|
|
||||||
runtimes.add(c.nvidiaRuntime())
|
|
||||||
|
|
||||||
modes := []string{"cdi", "legacy"}
|
|
||||||
for _, mode := range modes {
|
|
||||||
runtimes.add(c.modeRuntime(mode))
|
|
||||||
}
|
|
||||||
return runtimes
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultRuntimeName returns the name of the default runtime.
|
|
||||||
func (r Runtimes) DefaultRuntimeName() string {
|
|
||||||
for _, runtime := range r {
|
|
||||||
if runtime.SetAsDefault {
|
|
||||||
return runtime.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a runtime to the set of runtimes.
|
|
||||||
func (r Runtimes) add(runtime Runtime) {
|
|
||||||
r[runtime.name] = runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
// nvidiaRuntime creates a runtime that corresponds to the nvidia runtime.
|
|
||||||
// If name is equal to one of the predefined runtimes, `nvidia` is used as the runtime name instead.
|
|
||||||
func (c config) nvidiaRuntime() Runtime {
|
|
||||||
predefinedRuntimes := map[string]struct{}{
|
|
||||||
"nvidia-cdi": {},
|
|
||||||
"nvidia-legacy": {},
|
|
||||||
}
|
|
||||||
name := c.nvidiaRuntimeName
|
|
||||||
if _, isPredefinedRuntime := predefinedRuntimes[name]; isPredefinedRuntime {
|
|
||||||
name = defaultRuntimeName
|
|
||||||
}
|
|
||||||
return c.newRuntime(name, "nvidia-container-runtime")
|
|
||||||
}
|
|
||||||
|
|
||||||
// modeRuntime creates a runtime for the specified mode.
|
|
||||||
func (c config) modeRuntime(mode string) Runtime {
|
|
||||||
return c.newRuntime("nvidia-"+mode, "nvidia-container-runtime."+mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newRuntime creates a runtime based on the configuration
|
|
||||||
func (c config) newRuntime(name string, binary string) Runtime {
|
|
||||||
return Runtime{
|
|
||||||
name: name,
|
|
||||||
Path: filepath.Join(c.root, binary),
|
|
||||||
SetAsDefault: c.setAsDefault && name == c.nvidiaRuntimeName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option is a functional option for configuring set of runtimes.
|
|
||||||
type Option func(*config)
|
|
||||||
|
|
||||||
// WithRoot sets the root directory for the runtime binaries.
|
|
||||||
func WithRoot(root string) Option {
|
|
||||||
return func(c *config) {
|
|
||||||
c.root = root
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNvidiaRuntimeName sets the name of the nvidia runtime.
|
|
||||||
func WithNvidiaRuntimeName(name string) Option {
|
|
||||||
return func(c *config) {
|
|
||||||
c.nvidiaRuntimeName = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSetAsDefault sets the default runtime to the nvidia runtime.
|
|
||||||
func WithSetAsDefault(set bool) Option {
|
|
||||||
return func(c *config) {
|
|
||||||
c.setAsDefault = set
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package operator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOptions(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
setAsDefault bool
|
|
||||||
nvidiaRuntimeName string
|
|
||||||
expectedDefaultRuntime string
|
|
||||||
expectedRuntimes Runtimes
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
expectedRuntimes: Runtimes{
|
|
||||||
"nvidia": Runtime{
|
|
||||||
name: "nvidia",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
"nvidia-cdi": Runtime{
|
|
||||||
name: "nvidia-cdi",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
"nvidia-legacy": Runtime{
|
|
||||||
name: "nvidia-legacy",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setAsDefault: true,
|
|
||||||
expectedDefaultRuntime: "nvidia",
|
|
||||||
expectedRuntimes: Runtimes{
|
|
||||||
"nvidia": Runtime{
|
|
||||||
name: "nvidia",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime",
|
|
||||||
SetAsDefault: true,
|
|
||||||
},
|
|
||||||
"nvidia-cdi": Runtime{
|
|
||||||
name: "nvidia-cdi",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
"nvidia-legacy": Runtime{
|
|
||||||
name: "nvidia-legacy",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setAsDefault: true,
|
|
||||||
nvidiaRuntimeName: "nvidia",
|
|
||||||
expectedDefaultRuntime: "nvidia",
|
|
||||||
expectedRuntimes: Runtimes{
|
|
||||||
"nvidia": Runtime{
|
|
||||||
name: "nvidia",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime",
|
|
||||||
SetAsDefault: true,
|
|
||||||
},
|
|
||||||
"nvidia-cdi": Runtime{
|
|
||||||
name: "nvidia-cdi",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
"nvidia-legacy": Runtime{
|
|
||||||
name: "nvidia-legacy",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setAsDefault: true,
|
|
||||||
nvidiaRuntimeName: "NAME",
|
|
||||||
expectedDefaultRuntime: "NAME",
|
|
||||||
expectedRuntimes: Runtimes{
|
|
||||||
"NAME": Runtime{
|
|
||||||
name: "NAME",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime",
|
|
||||||
SetAsDefault: true,
|
|
||||||
},
|
|
||||||
"nvidia-cdi": Runtime{
|
|
||||||
name: "nvidia-cdi",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
"nvidia-legacy": Runtime{
|
|
||||||
name: "nvidia-legacy",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setAsDefault: false,
|
|
||||||
nvidiaRuntimeName: "NAME",
|
|
||||||
expectedRuntimes: Runtimes{
|
|
||||||
"NAME": Runtime{
|
|
||||||
name: "NAME",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
"nvidia-cdi": Runtime{
|
|
||||||
name: "nvidia-cdi",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
"nvidia-legacy": Runtime{
|
|
||||||
name: "nvidia-legacy",
|
|
||||||
Path: "/usr/bin/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
runtimes := GetRuntimes(
|
|
||||||
WithNvidiaRuntimeName(tc.nvidiaRuntimeName),
|
|
||||||
WithSetAsDefault(tc.setAsDefault),
|
|
||||||
WithRoot("/usr/bin"),
|
|
||||||
)
|
|
||||||
|
|
||||||
require.EqualValues(t, tc.expectedRuntimes, runtimes)
|
|
||||||
require.Equal(t, tc.expectedDefaultRuntime, runtimes.DefaultRuntimeName())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,584 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
testlog "github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/containerd"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpdateV1ConfigDefaultRuntime(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
legacyConfig bool
|
|
||||||
setAsDefault bool
|
|
||||||
runtimeName string
|
|
||||||
expectedDefaultRuntimeName interface{}
|
|
||||||
expectedDefaultRuntimeBinary interface{}
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
legacyConfig: true,
|
|
||||||
setAsDefault: false,
|
|
||||||
expectedDefaultRuntimeName: nil,
|
|
||||||
expectedDefaultRuntimeBinary: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
legacyConfig: true,
|
|
||||||
setAsDefault: true,
|
|
||||||
expectedDefaultRuntimeName: nil,
|
|
||||||
expectedDefaultRuntimeBinary: "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
legacyConfig: true,
|
|
||||||
setAsDefault: true,
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedDefaultRuntimeName: nil,
|
|
||||||
expectedDefaultRuntimeBinary: "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
legacyConfig: false,
|
|
||||||
setAsDefault: false,
|
|
||||||
expectedDefaultRuntimeName: nil,
|
|
||||||
expectedDefaultRuntimeBinary: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
legacyConfig: false,
|
|
||||||
setAsDefault: true,
|
|
||||||
expectedDefaultRuntimeName: "nvidia",
|
|
||||||
expectedDefaultRuntimeBinary: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
legacyConfig: false,
|
|
||||||
setAsDefault: true,
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedDefaultRuntimeName: "NAME",
|
|
||||||
expectedDefaultRuntimeBinary: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: tc.runtimeName,
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
SetAsDefault: tc.setAsDefault,
|
|
||||||
}
|
|
||||||
|
|
||||||
v1, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.Empty),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
containerd.WithUseLegacyConfig(tc.legacyConfig),
|
|
||||||
containerd.WithConfigVersion(1),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := v1.(*containerd.ConfigV1)
|
|
||||||
defaultRuntimeName := cfg.GetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"})
|
|
||||||
require.EqualValues(t, tc.expectedDefaultRuntimeName, defaultRuntimeName)
|
|
||||||
|
|
||||||
defaultRuntime := cfg.GetPath([]string{"plugins", "cri", "containerd", "default_runtime"})
|
|
||||||
if tc.expectedDefaultRuntimeBinary == nil {
|
|
||||||
require.Nil(t, defaultRuntime)
|
|
||||||
} else {
|
|
||||||
require.NotNil(t, defaultRuntime)
|
|
||||||
|
|
||||||
expected, err := defaultRuntimeTomlConfigV1(tc.expectedDefaultRuntimeBinary.(string))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
configContents, _ := toml.Marshal(defaultRuntime.(*toml.Tree))
|
|
||||||
expectedContents, _ := toml.Marshal(expected)
|
|
||||||
|
|
||||||
require.Equal(t, string(expectedContents), string(configContents), "%d: %v: %v", i, tc)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateV1Config(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
runtimeName string
|
|
||||||
expectedConfig map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
runtimeName: "nvidia",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(1),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"nvidia": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(1),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"NAME": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: tc.runtimeName,
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
v1, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.Empty),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
containerd.WithConfigVersion(1),
|
|
||||||
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected, err := toml.TreeFromMap(tc.expectedConfig)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expected.String(), v1.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateV1ConfigWithRuncPresent(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
runtimeName string
|
|
||||||
expectedConfig map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
runtimeName: "nvidia",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(1),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"runc": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/runc-binary",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(1),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"runc": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/runc-binary",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"NAME": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
"Runtime": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: tc.runtimeName,
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
v1, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.FromMap(runcConfigMapV1("/runc-binary"))),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
containerd.WithConfigVersion(1),
|
|
||||||
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected, err := toml.TreeFromMap(tc.expectedConfig)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expected.String(), v1.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateV1EnableCDI(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
enableCDI bool
|
|
||||||
expectedEnableCDIValue interface{}
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
enableCDI: false,
|
|
||||||
expectedEnableCDIValue: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enableCDI: true,
|
|
||||||
expectedEnableCDIValue: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%v", tc.enableCDI), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
EnableCDI: tc.enableCDI,
|
|
||||||
RuntimeName: "nvidia",
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := toml.Empty.Load()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
v1 := &containerd.ConfigV1{
|
|
||||||
Logger: logger,
|
|
||||||
Tree: cfg,
|
|
||||||
RuntimeType: runtimeType,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
enableCDIValue := v1.GetPath([]string{"plugins", "cri", "containerd", "enable_cdi"})
|
|
||||||
require.EqualValues(t, tc.expectedEnableCDIValue, enableCDIValue)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRevertV1Config(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
testCases := []struct {
|
|
||||||
config map[string]interface {
|
|
||||||
}
|
|
||||||
expected map[string]interface{}
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
config: map[string]interface{}{
|
|
||||||
"version": int64(1),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: map[string]interface{}{
|
|
||||||
"version": int64(1),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"nvidia": runtimeMapV1("/test/runtime/dir/nvidia-container-runtime"),
|
|
||||||
"nvidia-cdi": runtimeMapV1("/test/runtime/dir/nvidia-container-runtime.cdi"),
|
|
||||||
"nvidia-legacy": runtimeMapV1("/test/runtime/dir/nvidia-container-runtime.legacy"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: map[string]interface{}{
|
|
||||||
"version": int64(1),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"nvidia": runtimeMapV1("/test/runtime/dir/nvidia-container-runtime"),
|
|
||||||
"nvidia-cdi": runtimeMapV1("/test/runtime/dir/nvidia-container-runtime.cdi"),
|
|
||||||
"nvidia-legacy": runtimeMapV1("/test/runtime/dir/nvidia-container-runtime.legacy"),
|
|
||||||
},
|
|
||||||
"default_runtime": defaultRuntimeV1("/test/runtime/dir/nvidia-container-runtime"),
|
|
||||||
"default_runtime_name": "nvidia",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: "nvidia",
|
|
||||||
}
|
|
||||||
|
|
||||||
expected, err := toml.TreeFromMap(tc.expected)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
v1, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.FromMap(tc.config)),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
|
|
||||||
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.RevertConfig(v1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expected.String(), v1.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultRuntimeTomlConfigV1(binary string) (*toml.Tree, error) {
|
|
||||||
return toml.TreeFromMap(defaultRuntimeV1(binary))
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultRuntimeV1(binary string) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"runtime_type": runtimeType,
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": binary,
|
|
||||||
"Runtime": binary,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runtimeMapV1(binary string) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"runtime_type": runtimeType,
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": binary,
|
|
||||||
"Runtime": binary,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runcConfigMapV1(binary string) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"runc": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": binary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,520 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
testlog "github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/containerd"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
runtimeType = "runtime_type"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpdateV2ConfigDefaultRuntime(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
setAsDefault bool
|
|
||||||
runtimeName string
|
|
||||||
expectedDefaultRuntimeName interface{}
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
setAsDefault: false,
|
|
||||||
runtimeName: "nvidia",
|
|
||||||
expectedDefaultRuntimeName: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setAsDefault: false,
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedDefaultRuntimeName: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setAsDefault: true,
|
|
||||||
runtimeName: "nvidia",
|
|
||||||
expectedDefaultRuntimeName: "nvidia",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setAsDefault: true,
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedDefaultRuntimeName: "NAME",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: tc.runtimeName,
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
SetAsDefault: tc.setAsDefault,
|
|
||||||
}
|
|
||||||
|
|
||||||
v2, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.Empty),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := v2.(*containerd.Config)
|
|
||||||
|
|
||||||
defaultRuntimeName := cfg.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"})
|
|
||||||
require.EqualValues(t, tc.expectedDefaultRuntimeName, defaultRuntimeName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateV2Config(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
runtimeName string
|
|
||||||
expectedConfig map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
runtimeName: "nvidia",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(2),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"io.containerd.grpc.v1.cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"nvidia": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(2),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"io.containerd.grpc.v1.cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"NAME": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runtime_type",
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: tc.runtimeName,
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
v2, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.Empty),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected, err := toml.TreeFromMap(tc.expectedConfig)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expected.String(), v2.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateV2ConfigWithRuncPresent(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
runtimeName string
|
|
||||||
expectedConfig map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
runtimeName: "nvidia",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(2),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"io.containerd.grpc.v1.cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"runc": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/runc-binary",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
runtimeName: "NAME",
|
|
||||||
expectedConfig: map[string]interface{}{
|
|
||||||
"version": int64(2),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"io.containerd.grpc.v1.cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"runc": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/runc-binary",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"NAME": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-cdi": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.cdi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nvidia-legacy": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"container_annotations": []string{"cdi.k8s.io/*"},
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": "/test/runtime/dir/nvidia-container-runtime.legacy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: tc.runtimeName,
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
v2, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.FromMap(runcConfigMapV2("/runc-binary"))),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected, err := toml.TreeFromMap(tc.expectedConfig)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expected.String(), v2.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateV2ConfigEnableCDI(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
const runtimeDir = "/test/runtime/dir"
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
enableCDI bool
|
|
||||||
expectedEnableCDIValue interface{}
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
enableCDI: false,
|
|
||||||
expectedEnableCDIValue: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enableCDI: true,
|
|
||||||
expectedEnableCDIValue: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%v", tc.enableCDI), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
EnableCDI: tc.enableCDI,
|
|
||||||
RuntimeName: "nvidia",
|
|
||||||
RuntimeDir: runtimeDir,
|
|
||||||
SetAsDefault: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := toml.LoadMap(map[string]interface{}{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
v2 := &containerd.Config{
|
|
||||||
Logger: logger,
|
|
||||||
Tree: cfg,
|
|
||||||
RuntimeType: runtimeType,
|
|
||||||
CRIRuntimePluginName: "io.containerd.grpc.v1.cri",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.UpdateConfig(v2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
enableCDIValue := cfg.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "enable_cdi"})
|
|
||||||
require.EqualValues(t, tc.expectedEnableCDIValue, enableCDIValue)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRevertV2Config(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
config map[string]interface {
|
|
||||||
}
|
|
||||||
expected map[string]interface{}
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
config: map[string]interface{}{
|
|
||||||
"version": int64(2),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: map[string]interface{}{
|
|
||||||
"version": int64(2),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"io.containerd.grpc.v1.cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"nvidia": runtimeMapV2("/test/runtime/dir/nvidia-container-runtime"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: map[string]interface{}{
|
|
||||||
"version": int64(2),
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"io.containerd.grpc.v1.cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"nvidia": runtimeMapV2("/test/runtime/dir/nvidia-container-runtime"),
|
|
||||||
},
|
|
||||||
"default_runtime_name": "nvidia",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
||||||
o := &container.Options{
|
|
||||||
RuntimeName: "nvidia",
|
|
||||||
}
|
|
||||||
|
|
||||||
expected, err := toml.TreeFromMap(tc.expected)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
v2, err := containerd.New(
|
|
||||||
containerd.WithLogger(logger),
|
|
||||||
containerd.WithConfigSource(toml.FromMap(tc.config)),
|
|
||||||
containerd.WithRuntimeType(runtimeType),
|
|
||||||
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = o.RevertConfig(v2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expected.String(), v2.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runtimeMapV2(binary string) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"runtime_type": runtimeType,
|
|
||||||
"runtime_root": "",
|
|
||||||
"runtime_engine": "",
|
|
||||||
"privileged_without_host_devices": false,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"BinaryName": binary,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runcConfigMapV2(binary string) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"version": 2,
|
|
||||||
"plugins": map[string]interface{}{
|
|
||||||
"io.containerd.grpc.v1.cri": map[string]interface{}{
|
|
||||||
"containerd": map[string]interface{}{
|
|
||||||
"runtimes": map[string]interface{}{
|
|
||||||
"runc": map[string]interface{}{
|
|
||||||
"runtime_type": "runc_runtime_type",
|
|
||||||
"runtime_root": "runc_runtime_root",
|
|
||||||
"runtime_engine": "runc_runtime_engine",
|
|
||||||
"privileged_without_host_devices": true,
|
|
||||||
"options": map[string]interface{}{
|
|
||||||
"runc-option": "value",
|
|
||||||
"BinaryName": binary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2020-2021, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
cli "github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/containerd"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Name = "containerd"
|
|
||||||
|
|
||||||
DefaultConfig = "/etc/containerd/config.toml"
|
|
||||||
DefaultSocket = "/run/containerd/containerd.sock"
|
|
||||||
DefaultRestartMode = "signal"
|
|
||||||
|
|
||||||
defaultRuntmeType = "io.containerd.runc.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options stores the containerd-specific options
|
|
||||||
type Options struct {
|
|
||||||
useLegacyConfig bool
|
|
||||||
runtimeType string
|
|
||||||
|
|
||||||
ContainerRuntimeModesCDIAnnotationPrefixes cli.StringSlice
|
|
||||||
|
|
||||||
runtimeConfigOverrideJSON string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flags(opts *Options) []cli.Flag {
|
|
||||||
flags := []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "use-legacy-config",
|
|
||||||
Usage: "Specify whether a legacy (pre v1.3) config should be used. " +
|
|
||||||
"This ensures that a version 1 container config is created by default and that the " +
|
|
||||||
"containerd.runtimes.default_runtime config section is used to define the default " +
|
|
||||||
"runtime instead of container.default_runtime_name.",
|
|
||||||
Destination: &opts.useLegacyConfig,
|
|
||||||
EnvVars: []string{"CONTAINERD_USE_LEGACY_CONFIG"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "runtime-type",
|
|
||||||
Usage: "The runtime_type to use for the configured runtime classes",
|
|
||||||
Value: defaultRuntmeType,
|
|
||||||
Destination: &opts.runtimeType,
|
|
||||||
EnvVars: []string{"CONTAINERD_RUNTIME_TYPE"},
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "nvidia-container-runtime-modes.cdi.annotation-prefixes",
|
|
||||||
Destination: &opts.ContainerRuntimeModesCDIAnnotationPrefixes,
|
|
||||||
EnvVars: []string{"NVIDIA_CONTAINER_RUNTIME_MODES_CDI_ANNOTATION_PREFIXES"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "runtime-config-override",
|
|
||||||
Destination: &opts.runtimeConfigOverrideJSON,
|
|
||||||
Usage: "specify additional runtime options as a JSON string. The paths are relative to the runtime config.",
|
|
||||||
Value: "{}",
|
|
||||||
EnvVars: []string{"RUNTIME_CONFIG_OVERRIDE", "CONTAINERD_RUNTIME_CONFIG_OVERRIDE"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup updates a containerd configuration to include the nvidia-containerd-runtime and reloads it
|
|
||||||
func Setup(c *cli.Context, o *container.Options, co *Options) error {
|
|
||||||
log.Infof("Starting 'setup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
cfg, err := getRuntimeConfig(o, co)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.Configure(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to configure containerd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = RestartContainerd(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to restart containerd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Completed 'setup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup reverts a containerd configuration to remove the nvidia-containerd-runtime and reloads it
|
|
||||||
func Cleanup(c *cli.Context, o *container.Options, co *Options) error {
|
|
||||||
log.Infof("Starting 'cleanup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
cfg, err := getRuntimeConfig(o, co)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.Unconfigure(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unconfigure containerd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = RestartContainerd(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to restart containerd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Completed 'cleanup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestartContainerd restarts containerd depending on the value of restartModeFlag
|
|
||||||
func RestartContainerd(o *container.Options) error {
|
|
||||||
return o.Restart("containerd", SignalContainerd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// containerAnnotationsFromCDIPrefixes returns the container annotations to set for the given CDI prefixes.
|
|
||||||
func (o *Options) containerAnnotationsFromCDIPrefixes() []string {
|
|
||||||
var annotations []string
|
|
||||||
for _, prefix := range o.ContainerRuntimeModesCDIAnnotationPrefixes.Value() {
|
|
||||||
annotations = append(annotations, prefix+"*")
|
|
||||||
}
|
|
||||||
|
|
||||||
return annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Options) runtimeConfigOverride() (map[string]interface{}, error) {
|
|
||||||
if o.runtimeConfigOverrideJSON == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeOptions := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal([]byte(o.runtimeConfigOverrideJSON), &runtimeOptions); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read %v as JSON: %w", o.runtimeConfigOverrideJSON, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return runtimeOptions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLowlevelRuntimePaths(o *container.Options, co *Options) ([]string, error) {
|
|
||||||
cfg, err := getRuntimeConfig(o, co)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to load containerd config: %w", err)
|
|
||||||
}
|
|
||||||
return engine.GetBinaryPathsForRuntimes(cfg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRuntimeConfig(o *container.Options, co *Options) (engine.Interface, error) {
|
|
||||||
return containerd.New(
|
|
||||||
containerd.WithPath(o.Config),
|
|
||||||
containerd.WithConfigSource(
|
|
||||||
toml.LoadFirst(
|
|
||||||
containerd.CommandLineSource(o.HostRootMount, o.ExecutablePath),
|
|
||||||
toml.FromFile(o.Config),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
containerd.WithRuntimeType(co.runtimeType),
|
|
||||||
containerd.WithUseLegacyConfig(co.useLegacyConfig),
|
|
||||||
containerd.WithContainerAnnotations(co.containerAnnotationsFromCDIPrefixes()...),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright 2020-2023 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
reloadBackoff = 5 * time.Second
|
|
||||||
maxReloadAttempts = 6
|
|
||||||
|
|
||||||
socketMessageToGetPID = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// SignalContainerd sends a SIGHUP signal to the containerd daemon
|
|
||||||
func SignalContainerd(socket string) error {
|
|
||||||
log.Infof("Sending SIGHUP signal to containerd")
|
|
||||||
|
|
||||||
// Wrap the logic to perform the SIGHUP in a function so we can retry it on failure
|
|
||||||
retriable := func() error {
|
|
||||||
conn, err := net.Dial("unix", socket)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to dial: %v", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
sconn, err := conn.(*net.UnixConn).SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get syscall connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err1 := sconn.Control(func(fd uintptr) {
|
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
|
|
||||||
})
|
|
||||||
if err1 != nil {
|
|
||||||
return fmt.Errorf("unable to issue call on socket fd: %v", err1)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to SetsockoptInt on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = conn.(*net.UnixConn).WriteMsgUnix([]byte(socketMessageToGetPID), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to WriteMsgUnix on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
oob := make([]byte, 1024)
|
|
||||||
_, oobn, _, _, err := conn.(*net.UnixConn).ReadMsgUnix(nil, oob)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to ReadMsgUnix on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
oob = oob[:oobn]
|
|
||||||
scm, err := syscall.ParseSocketControlMessage(oob)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to ParseSocketControlMessage from message received on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ucred, err := syscall.ParseUnixCredentials(&scm[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to ParseUnixCredentials from message received on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = syscall.Kill(int(ucred.Pid), syscall.SIGHUP)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to send SIGHUP to 'containerd' process: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to send a SIGHUP up to maxReloadAttempts times
|
|
||||||
var err error
|
|
||||||
for i := 0; i < maxReloadAttempts; i++ {
|
|
||||||
err = retriable()
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i == maxReloadAttempts-1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.Warningf("Error signaling containerd, attempt %v/%v: %v", i+1, maxReloadAttempts, err)
|
|
||||||
time.Sleep(reloadBackoff)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("Max retries reached %v/%v, aborting", maxReloadAttempts, maxReloadAttempts)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Successfully signaled containerd")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
/**
|
|
||||||
# Copyright 2023 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SignalContainerd is unsupported on non-linux platforms.
|
|
||||||
func SignalContainerd(socket string) error {
|
|
||||||
return errors.New("SignalContainerd is unsupported on non-linux platforms")
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRuntimeOptions(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
options Options
|
|
||||||
expected map[string]interface{}
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "empty is nil",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "empty json",
|
|
||||||
options: Options{
|
|
||||||
runtimeConfigOverrideJSON: "{}",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "SystemdCgroup is true",
|
|
||||||
options: Options{
|
|
||||||
runtimeConfigOverrideJSON: "{\"SystemdCgroup\": true}",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"SystemdCgroup": true,
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "SystemdCgroup is false",
|
|
||||||
options: Options{
|
|
||||||
runtimeConfigOverrideJSON: "{\"SystemdCgroup\": false}",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"SystemdCgroup": false,
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
runtimeOptions, err := tc.options.runtimeConfigOverride()
|
|
||||||
require.ErrorIs(t, tc.expectedError, err)
|
|
||||||
require.EqualValues(t, tc.expected, runtimeOptions)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2021-2022, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package crio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
cli "github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/crio"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/ocihook"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Name = "crio"
|
|
||||||
|
|
||||||
defaultConfigMode = "hook"
|
|
||||||
|
|
||||||
// Hook-based settings
|
|
||||||
defaultHooksDir = "/usr/share/containers/oci/hooks.d"
|
|
||||||
defaultHookFilename = "oci-nvidia-hook.json"
|
|
||||||
|
|
||||||
// Config-based settings
|
|
||||||
DefaultConfig = "/etc/crio/crio.conf"
|
|
||||||
DefaultSocket = "/var/run/crio/crio.sock"
|
|
||||||
DefaultRestartMode = "systemd"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options defines the cri-o specific options.
|
|
||||||
type Options struct {
|
|
||||||
configMode string
|
|
||||||
|
|
||||||
// hook-specific options
|
|
||||||
hooksDir string
|
|
||||||
hookFilename string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flags(opts *Options) []cli.Flag {
|
|
||||||
flags := []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "hooks-dir",
|
|
||||||
Usage: "path to the cri-o hooks directory",
|
|
||||||
Value: defaultHooksDir,
|
|
||||||
Destination: &opts.hooksDir,
|
|
||||||
EnvVars: []string{"CRIO_HOOKS_DIR"},
|
|
||||||
DefaultText: defaultHooksDir,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "hook-filename",
|
|
||||||
Usage: "filename of the cri-o hook that will be created / removed in the hooks directory",
|
|
||||||
Value: defaultHookFilename,
|
|
||||||
Destination: &opts.hookFilename,
|
|
||||||
EnvVars: []string{"CRIO_HOOK_FILENAME"},
|
|
||||||
DefaultText: defaultHookFilename,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "config-mode",
|
|
||||||
Usage: "the configuration mode to use. One of [hook | config]",
|
|
||||||
Value: defaultConfigMode,
|
|
||||||
Destination: &opts.configMode,
|
|
||||||
EnvVars: []string{"CRIO_CONFIG_MODE"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup installs the prestart hook required to launch GPU-enabled containers
|
|
||||||
func Setup(c *cli.Context, o *container.Options, co *Options) error {
|
|
||||||
log.Infof("Starting 'setup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
switch co.configMode {
|
|
||||||
case "hook":
|
|
||||||
return setupHook(o, co)
|
|
||||||
case "config":
|
|
||||||
return setupConfig(o)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid config-mode '%v'", co.configMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupHook installs the prestart hook required to launch GPU-enabled containers
|
|
||||||
func setupHook(o *container.Options, co *Options) error {
|
|
||||||
log.Infof("Installing prestart hook")
|
|
||||||
|
|
||||||
hookPath := filepath.Join(co.hooksDir, co.hookFilename)
|
|
||||||
err := ocihook.CreateHook(hookPath, filepath.Join(o.RuntimeDir, config.NVIDIAContainerRuntimeHookExecutable))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating hook: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupConfig updates the cri-o config for the NVIDIA container runtime
|
|
||||||
func setupConfig(o *container.Options) error {
|
|
||||||
log.Infof("Updating config file")
|
|
||||||
|
|
||||||
cfg, err := getRuntimeConfig(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.Configure(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to configure cri-o: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = RestartCrio(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to restart crio: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup removes the specified prestart hook
|
|
||||||
func Cleanup(c *cli.Context, o *container.Options, co *Options) error {
|
|
||||||
log.Infof("Starting 'cleanup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
switch co.configMode {
|
|
||||||
case "hook":
|
|
||||||
return cleanupHook(co)
|
|
||||||
case "config":
|
|
||||||
return cleanupConfig(o)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid config-mode '%v'", co.configMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupHook removes the prestart hook
|
|
||||||
func cleanupHook(co *Options) error {
|
|
||||||
log.Infof("Removing prestart hook")
|
|
||||||
|
|
||||||
hookPath := filepath.Join(co.hooksDir, co.hookFilename)
|
|
||||||
err := os.Remove(hookPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error removing hook '%v': %v", hookPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupConfig removes the NVIDIA container runtime from the cri-o config
|
|
||||||
func cleanupConfig(o *container.Options) error {
|
|
||||||
log.Infof("Reverting config file modifications")
|
|
||||||
|
|
||||||
cfg, err := getRuntimeConfig(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.Unconfigure(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unconfigure cri-o: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = RestartCrio(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to restart crio: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestartCrio restarts crio depending on the value of restartModeFlag
|
|
||||||
func RestartCrio(o *container.Options) error {
|
|
||||||
return o.Restart("crio", func(string) error { return fmt.Errorf("supporting crio via signal is unsupported") })
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLowlevelRuntimePaths(o *container.Options) ([]string, error) {
|
|
||||||
cfg, err := getRuntimeConfig(o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to load crio config: %w", err)
|
|
||||||
}
|
|
||||||
return engine.GetBinaryPathsForRuntimes(cfg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRuntimeConfig(o *container.Options) (engine.Interface, error) {
|
|
||||||
return crio.New(
|
|
||||||
crio.WithPath(o.Config),
|
|
||||||
crio.WithConfigSource(
|
|
||||||
toml.LoadFirst(
|
|
||||||
crio.CommandLineSource(o.HostRootMount, o.ExecutablePath),
|
|
||||||
toml.FromFile(o.Config),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
cli "github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/docker"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Name = "docker"
|
|
||||||
|
|
||||||
DefaultConfig = "/etc/docker/daemon.json"
|
|
||||||
DefaultSocket = "/var/run/docker.sock"
|
|
||||||
DefaultRestartMode = "signal"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Options struct{}
|
|
||||||
|
|
||||||
func Flags(opts *Options) []cli.Flag {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup updates docker configuration to include the nvidia runtime and reloads it
|
|
||||||
func Setup(c *cli.Context, o *container.Options) error {
|
|
||||||
log.Infof("Starting 'setup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
cfg, err := getRuntimeConfig(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.Configure(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to configure docker: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = RestartDocker(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to restart docker: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Completed 'setup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup reverts docker configuration to remove the nvidia runtime and reloads it
|
|
||||||
func Cleanup(c *cli.Context, o *container.Options) error {
|
|
||||||
log.Infof("Starting 'cleanup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
cfg, err := getRuntimeConfig(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.Unconfigure(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unconfigure docker: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = RestartDocker(o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to signal docker: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Completed 'cleanup' for %v", c.App.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestartDocker restarts docker depending on the value of restartModeFlag
|
|
||||||
func RestartDocker(o *container.Options) error {
|
|
||||||
return o.Restart("docker", SignalDocker)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLowlevelRuntimePaths(o *container.Options) ([]string, error) {
|
|
||||||
cfg, err := docker.New(
|
|
||||||
docker.WithPath(o.Config),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to load docker config: %w", err)
|
|
||||||
}
|
|
||||||
return engine.GetBinaryPathsForRuntimes(cfg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRuntimeConfig(o *container.Options) (engine.Interface, error) {
|
|
||||||
return docker.New(
|
|
||||||
docker.WithPath(o.Config),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright 2021-2023 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
reloadBackoff = 5 * time.Second
|
|
||||||
maxReloadAttempts = 6
|
|
||||||
|
|
||||||
socketMessageToGetPID = "GET /info HTTP/1.0\r\n\r\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SignalDocker sends a SIGHUP signal to docker daemon
|
|
||||||
func SignalDocker(socket string) error {
|
|
||||||
log.Infof("Sending SIGHUP signal to docker")
|
|
||||||
|
|
||||||
// Wrap the logic to perform the SIGHUP in a function so we can retry it on failure
|
|
||||||
retriable := func() error {
|
|
||||||
conn, err := net.Dial("unix", socket)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to dial: %v", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
sconn, err := conn.(*net.UnixConn).SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get syscall connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err1 := sconn.Control(func(fd uintptr) {
|
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
|
|
||||||
})
|
|
||||||
if err1 != nil {
|
|
||||||
return fmt.Errorf("unable to issue call on socket fd: %v", err1)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to SetsockoptInt on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = conn.(*net.UnixConn).WriteMsgUnix([]byte(socketMessageToGetPID), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to WriteMsgUnix on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
oob := make([]byte, 1024)
|
|
||||||
_, oobn, _, _, err := conn.(*net.UnixConn).ReadMsgUnix(nil, oob)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to ReadMsgUnix on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
oob = oob[:oobn]
|
|
||||||
scm, err := syscall.ParseSocketControlMessage(oob)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to ParseSocketControlMessage from message received on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ucred, err := syscall.ParseUnixCredentials(&scm[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to ParseUnixCredentials from message received on socket fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = syscall.Kill(int(ucred.Pid), syscall.SIGHUP)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to send SIGHUP to 'docker' process: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to send a SIGHUP up to maxReloadAttempts times
|
|
||||||
var err error
|
|
||||||
for i := 0; i < maxReloadAttempts; i++ {
|
|
||||||
err = retriable()
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i == maxReloadAttempts-1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.Warningf("Error signaling docker, attempt %v/%v: %v", i+1, maxReloadAttempts, err)
|
|
||||||
time.Sleep(reloadBackoff)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("Max retries reached %v/%v, aborting", maxReloadAttempts, maxReloadAttempts)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Successfully signaled docker")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright 2024 NVIDIA CORPORATION
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container/runtime/containerd"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container/runtime/crio"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container/runtime/docker"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/toolkit"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultSetAsDefault = true
|
|
||||||
// defaultRuntimeName specifies the NVIDIA runtime to be use as the default runtime if setting the default runtime is enabled
|
|
||||||
defaultRuntimeName = "nvidia"
|
|
||||||
defaultHostRootMount = "/host"
|
|
||||||
|
|
||||||
runtimeSpecificDefault = "RUNTIME_SPECIFIC_DEFAULT"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
container.Options
|
|
||||||
|
|
||||||
containerdOptions containerd.Options
|
|
||||||
crioOptions crio.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flags(opts *Options) []cli.Flag {
|
|
||||||
flags := []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "config",
|
|
||||||
Usage: "Path to the runtime config file",
|
|
||||||
Value: runtimeSpecificDefault,
|
|
||||||
Destination: &opts.Config,
|
|
||||||
EnvVars: []string{"RUNTIME_CONFIG", "CONTAINERD_CONFIG", "DOCKER_CONFIG"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "executable-path",
|
|
||||||
Usage: "The path to the runtime executable. This is used to extract the current config",
|
|
||||||
Destination: &opts.ExecutablePath,
|
|
||||||
EnvVars: []string{"RUNTIME_EXECUTABLE_PATH"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "socket",
|
|
||||||
Usage: "Path to the runtime socket file",
|
|
||||||
Value: runtimeSpecificDefault,
|
|
||||||
Destination: &opts.Socket,
|
|
||||||
EnvVars: []string{"RUNTIME_SOCKET", "CONTAINERD_SOCKET", "DOCKER_SOCKET"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "restart-mode",
|
|
||||||
Usage: "Specify how the runtime should be restarted; If 'none' is selected it will not be restarted [signal | systemd | none ]",
|
|
||||||
Value: runtimeSpecificDefault,
|
|
||||||
Destination: &opts.RestartMode,
|
|
||||||
EnvVars: []string{"RUNTIME_RESTART_MODE"},
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "enable-cdi-in-runtime",
|
|
||||||
Usage: "Enable CDI in the configured runt ime",
|
|
||||||
Destination: &opts.EnableCDI,
|
|
||||||
EnvVars: []string{"RUNTIME_ENABLE_CDI"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "host-root",
|
|
||||||
Usage: "Specify the path to the host root to be used when restarting the runtime using systemd",
|
|
||||||
Value: defaultHostRootMount,
|
|
||||||
Destination: &opts.HostRootMount,
|
|
||||||
EnvVars: []string{"HOST_ROOT_MOUNT"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "runtime-name",
|
|
||||||
Aliases: []string{"nvidia-runtime-name", "runtime-class"},
|
|
||||||
Usage: "Specify the name of the `nvidia` runtime. If set-as-default is selected, the runtime is used as the default runtime.",
|
|
||||||
Value: defaultRuntimeName,
|
|
||||||
Destination: &opts.RuntimeName,
|
|
||||||
EnvVars: []string{"NVIDIA_RUNTIME_NAME", "CONTAINERD_RUNTIME_CLASS", "DOCKER_RUNTIME_NAME"},
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "set-as-default",
|
|
||||||
Usage: "Set the `nvidia` runtime as the default runtime.",
|
|
||||||
Value: defaultSetAsDefault,
|
|
||||||
Destination: &opts.SetAsDefault,
|
|
||||||
EnvVars: []string{"NVIDIA_RUNTIME_SET_AS_DEFAULT", "CONTAINERD_SET_AS_DEFAULT", "DOCKER_SET_AS_DEFAULT"},
|
|
||||||
Hidden: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = append(flags, containerd.Flags(&opts.containerdOptions)...)
|
|
||||||
flags = append(flags, crio.Flags(&opts.crioOptions)...)
|
|
||||||
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks whether the specified options are valid
|
|
||||||
func (opts *Options) Validate(logger logger.Interface, c *cli.Context, runtime string, toolkitRoot string, to *toolkit.Options) error {
|
|
||||||
// We set this option here to ensure that it is available in future calls.
|
|
||||||
opts.RuntimeDir = toolkitRoot
|
|
||||||
|
|
||||||
if !c.IsSet("enable-cdi-in-runtime") {
|
|
||||||
opts.EnableCDI = to.CDI.Enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.ExecutablePath != "" && opts.RuntimeName == docker.Name {
|
|
||||||
logger.Warningf("Ignoring executable-path=%q flag for %v", opts.ExecutablePath, opts.RuntimeName)
|
|
||||||
opts.ExecutablePath = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the runtime-specific config changes.
|
|
||||||
switch runtime {
|
|
||||||
case containerd.Name:
|
|
||||||
if opts.Config == runtimeSpecificDefault {
|
|
||||||
opts.Config = containerd.DefaultConfig
|
|
||||||
}
|
|
||||||
if opts.Socket == runtimeSpecificDefault {
|
|
||||||
opts.Socket = containerd.DefaultSocket
|
|
||||||
}
|
|
||||||
if opts.RestartMode == runtimeSpecificDefault {
|
|
||||||
opts.RestartMode = containerd.DefaultRestartMode
|
|
||||||
}
|
|
||||||
case crio.Name:
|
|
||||||
if opts.Config == runtimeSpecificDefault {
|
|
||||||
opts.Config = crio.DefaultConfig
|
|
||||||
}
|
|
||||||
if opts.Socket == runtimeSpecificDefault {
|
|
||||||
opts.Socket = crio.DefaultSocket
|
|
||||||
}
|
|
||||||
if opts.RestartMode == runtimeSpecificDefault {
|
|
||||||
opts.RestartMode = crio.DefaultRestartMode
|
|
||||||
}
|
|
||||||
case docker.Name:
|
|
||||||
if opts.Config == runtimeSpecificDefault {
|
|
||||||
opts.Config = docker.DefaultConfig
|
|
||||||
}
|
|
||||||
if opts.Socket == runtimeSpecificDefault {
|
|
||||||
opts.Socket = docker.DefaultSocket
|
|
||||||
}
|
|
||||||
if opts.RestartMode == runtimeSpecificDefault {
|
|
||||||
opts.RestartMode = docker.DefaultRestartMode
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("undefined runtime %v", runtime)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Setup(c *cli.Context, opts *Options, runtime string) error {
|
|
||||||
switch runtime {
|
|
||||||
case containerd.Name:
|
|
||||||
return containerd.Setup(c, &opts.Options, &opts.containerdOptions)
|
|
||||||
case crio.Name:
|
|
||||||
return crio.Setup(c, &opts.Options, &opts.crioOptions)
|
|
||||||
case docker.Name:
|
|
||||||
return docker.Setup(c, &opts.Options)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("undefined runtime %v", runtime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Cleanup(c *cli.Context, opts *Options, runtime string) error {
|
|
||||||
switch runtime {
|
|
||||||
case containerd.Name:
|
|
||||||
return containerd.Cleanup(c, &opts.Options, &opts.containerdOptions)
|
|
||||||
case crio.Name:
|
|
||||||
return crio.Cleanup(c, &opts.Options, &opts.crioOptions)
|
|
||||||
case docker.Name:
|
|
||||||
return docker.Cleanup(c, &opts.Options)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("undefined runtime %v", runtime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLowlevelRuntimePaths(opts *Options, runtime string) ([]string, error) {
|
|
||||||
switch runtime {
|
|
||||||
case containerd.Name:
|
|
||||||
return containerd.GetLowlevelRuntimePaths(&opts.Options, &opts.containerdOptions)
|
|
||||||
case crio.Name:
|
|
||||||
return crio.GetLowlevelRuntimePaths(&opts.Options)
|
|
||||||
case docker.Name:
|
|
||||||
return docker.GetLowlevelRuntimePaths(&opts.Options)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("undefined runtime %v", runtime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container/runtime"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/toolkit"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/info"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
toolkitPidFilename = "toolkit.pid"
|
|
||||||
defaultPidFile = "/run/nvidia/toolkit/" + toolkitPidFilename
|
|
||||||
|
|
||||||
defaultToolkitInstallDir = "/usr/local/nvidia"
|
|
||||||
toolkitSubDir = "toolkit"
|
|
||||||
|
|
||||||
defaultRuntime = "docker"
|
|
||||||
)
|
|
||||||
|
|
||||||
var availableRuntimes = map[string]struct{}{"docker": {}, "crio": {}, "containerd": {}}
|
|
||||||
var defaultLowLevelRuntimes = []string{"docker-runc", "runc", "crun"}
|
|
||||||
|
|
||||||
var waitingForSignal = make(chan bool, 1)
|
|
||||||
var signalReceived = make(chan bool, 1)
|
|
||||||
|
|
||||||
// options stores the command line arguments
|
|
||||||
type options struct {
|
|
||||||
toolkitInstallDir string
|
|
||||||
|
|
||||||
noDaemon bool
|
|
||||||
runtime string
|
|
||||||
pidFile string
|
|
||||||
sourceRoot string
|
|
||||||
|
|
||||||
toolkitOptions toolkit.Options
|
|
||||||
runtimeOptions runtime.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o options) toolkitRoot() string {
|
|
||||||
return filepath.Join(o.toolkitInstallDir, toolkitSubDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
logger := logger.New()
|
|
||||||
c := NewApp(logger)
|
|
||||||
|
|
||||||
// Run the CLI
|
|
||||||
logger.Infof("Starting %v", c.Name)
|
|
||||||
if err := c.Run(os.Args); err != nil {
|
|
||||||
logger.Errorf("error running %v: %v", c.Name, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Infof("Completed %v", c.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An app represents the nvidia-ctk-installer.
|
|
||||||
type app struct {
|
|
||||||
logger logger.Interface
|
|
||||||
|
|
||||||
toolkit *toolkit.Installer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApp creates the CLI app fro the specified options.
|
|
||||||
func NewApp(logger logger.Interface) *cli.App {
|
|
||||||
a := app{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
return a.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a app) build() *cli.App {
|
|
||||||
options := options{
|
|
||||||
toolkitOptions: toolkit.Options{},
|
|
||||||
}
|
|
||||||
// Create the top-level CLI
|
|
||||||
c := cli.NewApp()
|
|
||||||
c.Name = "nvidia-ctk-installer"
|
|
||||||
c.Usage = "Install the NVIDIA Container Toolkit and configure the specified runtime to use the `nvidia` runtime."
|
|
||||||
c.Version = info.GetVersionString()
|
|
||||||
c.Before = func(ctx *cli.Context) error {
|
|
||||||
return a.Before(ctx, &options)
|
|
||||||
}
|
|
||||||
c.Action = func(ctx *cli.Context) error {
|
|
||||||
return a.Run(ctx, &options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup flags for the CLI
|
|
||||||
c.Flags = []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "no-daemon",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "terminate immediately after setting up the runtime. Note that no cleanup will be performed",
|
|
||||||
Destination: &options.noDaemon,
|
|
||||||
EnvVars: []string{"NO_DAEMON"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "runtime",
|
|
||||||
Aliases: []string{"r"},
|
|
||||||
Usage: "the runtime to setup on this node. One of {'docker', 'crio', 'containerd'}",
|
|
||||||
Value: defaultRuntime,
|
|
||||||
Destination: &options.runtime,
|
|
||||||
EnvVars: []string{"RUNTIME"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "toolkit-install-dir",
|
|
||||||
Aliases: []string{"root"},
|
|
||||||
Usage: "The directory where the NVIDIA Container Toolkit is to be installed. " +
|
|
||||||
"The components of the toolkit will be installed to `ROOT`/toolkit. " +
|
|
||||||
"Note that in the case of a containerized installer, this is the path in the container and it is " +
|
|
||||||
"recommended that this match the path on the host.",
|
|
||||||
Value: defaultToolkitInstallDir,
|
|
||||||
Destination: &options.toolkitInstallDir,
|
|
||||||
EnvVars: []string{"TOOLKIT_INSTALL_DIR", "ROOT"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "source-root",
|
|
||||||
Value: "/",
|
|
||||||
Usage: "The folder where the required toolkit artifacts can be found",
|
|
||||||
Destination: &options.sourceRoot,
|
|
||||||
EnvVars: []string{"SOURCE_ROOT"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "pid-file",
|
|
||||||
Value: defaultPidFile,
|
|
||||||
Usage: "the path to a toolkit.pid file to ensure that only a single configuration instance is running",
|
|
||||||
Destination: &options.pidFile,
|
|
||||||
EnvVars: []string{"TOOLKIT_PID_FILE", "PID_FILE"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Flags = append(c.Flags, toolkit.Flags(&options.toolkitOptions)...)
|
|
||||||
c.Flags = append(c.Flags, runtime.Flags(&options.runtimeOptions)...)
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) Before(c *cli.Context, o *options) error {
|
|
||||||
a.toolkit = toolkit.NewInstaller(
|
|
||||||
toolkit.WithLogger(a.logger),
|
|
||||||
toolkit.WithSourceRoot(o.sourceRoot),
|
|
||||||
toolkit.WithToolkitRoot(o.toolkitRoot()),
|
|
||||||
)
|
|
||||||
return a.validateFlags(c, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) validateFlags(c *cli.Context, o *options) error {
|
|
||||||
if o.toolkitInstallDir == "" {
|
|
||||||
return fmt.Errorf("the install root must be specified")
|
|
||||||
}
|
|
||||||
if _, exists := availableRuntimes[o.runtime]; !exists {
|
|
||||||
return fmt.Errorf("unknown runtime: %v", o.runtime)
|
|
||||||
}
|
|
||||||
if filepath.Base(o.pidFile) != toolkitPidFilename {
|
|
||||||
return fmt.Errorf("invalid toolkit.pid path %v", o.pidFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.toolkit.ValidateOptions(&o.toolkitOptions); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := o.runtimeOptions.Validate(a.logger, c, o.runtime, o.toolkitRoot(), &o.toolkitOptions); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run installs the NVIDIA Container Toolkit and updates the requested runtime.
|
|
||||||
// If the application is run as a daemon, the application waits and unconfigures
|
|
||||||
// the runtime on termination.
|
|
||||||
func (a *app) Run(c *cli.Context, o *options) error {
|
|
||||||
err := a.initialize(o.pidFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to initialize: %v", err)
|
|
||||||
}
|
|
||||||
defer a.shutdown(o.pidFile)
|
|
||||||
|
|
||||||
if len(o.toolkitOptions.ContainerRuntimeRuntimes.Value()) == 0 {
|
|
||||||
lowlevelRuntimePaths, err := runtime.GetLowlevelRuntimePaths(&o.runtimeOptions, o.runtime)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to determine runtime options: %w", err)
|
|
||||||
}
|
|
||||||
lowlevelRuntimePaths = append(lowlevelRuntimePaths, defaultLowLevelRuntimes...)
|
|
||||||
|
|
||||||
o.toolkitOptions.ContainerRuntimeRuntimes = *cli.NewStringSlice(lowlevelRuntimePaths...)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.toolkit.Install(c, &o.toolkitOptions)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to install toolkit: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runtime.Setup(c, &o.runtimeOptions, o.runtime)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to setup runtime: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !o.noDaemon {
|
|
||||||
err = a.waitForSignal()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to wait for signal: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runtime.Cleanup(c, &o.runtimeOptions, o.runtime)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to cleanup runtime: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) initialize(pidFile string) error {
|
|
||||||
a.logger.Infof("Initializing")
|
|
||||||
|
|
||||||
if dir := filepath.Dir(pidFile); dir != "" {
|
|
||||||
err := os.MkdirAll(dir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create folder for pidfile: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(pidFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create pidfile: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Warningf("Unable to get exclusive lock on '%v'", pidFile)
|
|
||||||
a.logger.Warningf("This normally means an instance of the NVIDIA toolkit Container is already running, aborting")
|
|
||||||
return fmt.Errorf("unable to get flock on pidfile: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintf(f, "%v\n", os.Getpid())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write PID to pidfile: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sigs := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGPIPE, syscall.SIGTERM)
|
|
||||||
go func() {
|
|
||||||
<-sigs
|
|
||||||
select {
|
|
||||||
case <-waitingForSignal:
|
|
||||||
signalReceived <- true
|
|
||||||
default:
|
|
||||||
a.logger.Infof("Signal received, exiting early")
|
|
||||||
a.shutdown(pidFile)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) waitForSignal() error {
|
|
||||||
a.logger.Infof("Waiting for signal")
|
|
||||||
waitingForSignal <- true
|
|
||||||
<-signalReceived
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) shutdown(pidFile string) {
|
|
||||||
a.logger.Infof("Shutting Down")
|
|
||||||
|
|
||||||
err := os.Remove(pidFile)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Warningf("Unable to remove pidfile: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,455 +0,0 @@
|
|||||||
/**
|
|
||||||
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
testlog "github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestApp(t *testing.T) {
|
|
||||||
t.Setenv("__NVCT_TESTING_DEVICES_ARE_FILES", "true")
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
|
|
||||||
moduleRoot, err := test.GetModuleRoot()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
artifactRoot := filepath.Join(moduleRoot, "testdata", "installer", "artifacts")
|
|
||||||
hostRoot := filepath.Join(moduleRoot, "testdata", "lookup", "rootfs-1")
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
args []string
|
|
||||||
expectedToolkitConfig string
|
|
||||||
expectedRuntimeConfig string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "no args",
|
|
||||||
expectedToolkitConfig: `accept-nvidia-visible-devices-as-volume-mounts = false
|
|
||||||
accept-nvidia-visible-devices-envvar-when-unprivileged = true
|
|
||||||
disable-require = false
|
|
||||||
supported-driver-capabilities = "compat32,compute,display,graphics,ngx,utility,video"
|
|
||||||
swarm-resource = ""
|
|
||||||
|
|
||||||
[nvidia-container-cli]
|
|
||||||
debug = ""
|
|
||||||
environment = []
|
|
||||||
ldcache = ""
|
|
||||||
ldconfig = "@/run/nvidia/driver/sbin/ldconfig"
|
|
||||||
load-kmods = true
|
|
||||||
no-cgroups = false
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-cli"
|
|
||||||
root = "/run/nvidia/driver"
|
|
||||||
user = ""
|
|
||||||
|
|
||||||
[nvidia-container-runtime]
|
|
||||||
debug = "/dev/null"
|
|
||||||
log-level = "info"
|
|
||||||
mode = "auto"
|
|
||||||
runtimes = ["docker-runc", "runc", "crun"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.cdi]
|
|
||||||
annotation-prefixes = ["cdi.k8s.io/"]
|
|
||||||
default-kind = "nvidia.com/gpu"
|
|
||||||
spec-dirs = ["/etc/cdi", "/var/run/cdi"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.csv]
|
|
||||||
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.legacy]
|
|
||||||
cuda-compat-mode = "ldconfig"
|
|
||||||
|
|
||||||
[nvidia-container-runtime-hook]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime-hook"
|
|
||||||
skip-mode-detection = true
|
|
||||||
|
|
||||||
[nvidia-ctk]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-ctk"
|
|
||||||
`,
|
|
||||||
expectedRuntimeConfig: `{
|
|
||||||
"default-runtime": "nvidia",
|
|
||||||
"runtimes": {
|
|
||||||
"nvidia": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime"
|
|
||||||
},
|
|
||||||
"nvidia-cdi": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.cdi"
|
|
||||||
},
|
|
||||||
"nvidia-legacy": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.legacy"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "CDI enabled enables CDI in docker",
|
|
||||||
args: []string{"--cdi-enabled", "--create-device-nodes=none"},
|
|
||||||
expectedToolkitConfig: `accept-nvidia-visible-devices-as-volume-mounts = false
|
|
||||||
accept-nvidia-visible-devices-envvar-when-unprivileged = true
|
|
||||||
disable-require = false
|
|
||||||
supported-driver-capabilities = "compat32,compute,display,graphics,ngx,utility,video"
|
|
||||||
swarm-resource = ""
|
|
||||||
|
|
||||||
[nvidia-container-cli]
|
|
||||||
debug = ""
|
|
||||||
environment = []
|
|
||||||
ldcache = ""
|
|
||||||
ldconfig = "@/run/nvidia/driver/sbin/ldconfig"
|
|
||||||
load-kmods = true
|
|
||||||
no-cgroups = false
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-cli"
|
|
||||||
root = "/run/nvidia/driver"
|
|
||||||
user = ""
|
|
||||||
|
|
||||||
[nvidia-container-runtime]
|
|
||||||
debug = "/dev/null"
|
|
||||||
log-level = "info"
|
|
||||||
mode = "auto"
|
|
||||||
runtimes = ["docker-runc", "runc", "crun"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.cdi]
|
|
||||||
annotation-prefixes = ["cdi.k8s.io/"]
|
|
||||||
default-kind = "nvidia.com/gpu"
|
|
||||||
spec-dirs = ["/etc/cdi", "/var/run/cdi"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.csv]
|
|
||||||
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.legacy]
|
|
||||||
cuda-compat-mode = "ldconfig"
|
|
||||||
|
|
||||||
[nvidia-container-runtime-hook]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime-hook"
|
|
||||||
skip-mode-detection = true
|
|
||||||
|
|
||||||
[nvidia-ctk]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-ctk"
|
|
||||||
`,
|
|
||||||
expectedRuntimeConfig: `{
|
|
||||||
"default-runtime": "nvidia",
|
|
||||||
"features": {
|
|
||||||
"cdi": true
|
|
||||||
},
|
|
||||||
"runtimes": {
|
|
||||||
"nvidia": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime"
|
|
||||||
},
|
|
||||||
"nvidia-cdi": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.cdi"
|
|
||||||
},
|
|
||||||
"nvidia-legacy": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.legacy"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "--enable-cdi-in-runtime=false overrides --cdi-enabled in Docker",
|
|
||||||
args: []string{"--cdi-enabled", "--create-device-nodes=none", "--enable-cdi-in-runtime=false"},
|
|
||||||
expectedToolkitConfig: `accept-nvidia-visible-devices-as-volume-mounts = false
|
|
||||||
accept-nvidia-visible-devices-envvar-when-unprivileged = true
|
|
||||||
disable-require = false
|
|
||||||
supported-driver-capabilities = "compat32,compute,display,graphics,ngx,utility,video"
|
|
||||||
swarm-resource = ""
|
|
||||||
|
|
||||||
[nvidia-container-cli]
|
|
||||||
debug = ""
|
|
||||||
environment = []
|
|
||||||
ldcache = ""
|
|
||||||
ldconfig = "@/run/nvidia/driver/sbin/ldconfig"
|
|
||||||
load-kmods = true
|
|
||||||
no-cgroups = false
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-cli"
|
|
||||||
root = "/run/nvidia/driver"
|
|
||||||
user = ""
|
|
||||||
|
|
||||||
[nvidia-container-runtime]
|
|
||||||
debug = "/dev/null"
|
|
||||||
log-level = "info"
|
|
||||||
mode = "auto"
|
|
||||||
runtimes = ["docker-runc", "runc", "crun"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.cdi]
|
|
||||||
annotation-prefixes = ["cdi.k8s.io/"]
|
|
||||||
default-kind = "nvidia.com/gpu"
|
|
||||||
spec-dirs = ["/etc/cdi", "/var/run/cdi"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.csv]
|
|
||||||
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.legacy]
|
|
||||||
cuda-compat-mode = "ldconfig"
|
|
||||||
|
|
||||||
[nvidia-container-runtime-hook]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime-hook"
|
|
||||||
skip-mode-detection = true
|
|
||||||
|
|
||||||
[nvidia-ctk]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-ctk"
|
|
||||||
`,
|
|
||||||
expectedRuntimeConfig: `{
|
|
||||||
"default-runtime": "nvidia",
|
|
||||||
"runtimes": {
|
|
||||||
"nvidia": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime"
|
|
||||||
},
|
|
||||||
"nvidia-cdi": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.cdi"
|
|
||||||
},
|
|
||||||
"nvidia-legacy": {
|
|
||||||
"args": [],
|
|
||||||
"path": "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.legacy"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "CDI enabled enables CDI in containerd",
|
|
||||||
args: []string{"--cdi-enabled", "--runtime=containerd"},
|
|
||||||
expectedToolkitConfig: `accept-nvidia-visible-devices-as-volume-mounts = false
|
|
||||||
accept-nvidia-visible-devices-envvar-when-unprivileged = true
|
|
||||||
disable-require = false
|
|
||||||
supported-driver-capabilities = "compat32,compute,display,graphics,ngx,utility,video"
|
|
||||||
swarm-resource = ""
|
|
||||||
|
|
||||||
[nvidia-container-cli]
|
|
||||||
debug = ""
|
|
||||||
environment = []
|
|
||||||
ldcache = ""
|
|
||||||
ldconfig = "@/run/nvidia/driver/sbin/ldconfig"
|
|
||||||
load-kmods = true
|
|
||||||
no-cgroups = false
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-cli"
|
|
||||||
root = "/run/nvidia/driver"
|
|
||||||
user = ""
|
|
||||||
|
|
||||||
[nvidia-container-runtime]
|
|
||||||
debug = "/dev/null"
|
|
||||||
log-level = "info"
|
|
||||||
mode = "auto"
|
|
||||||
runtimes = ["docker-runc", "runc", "crun"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.cdi]
|
|
||||||
annotation-prefixes = ["cdi.k8s.io/"]
|
|
||||||
default-kind = "nvidia.com/gpu"
|
|
||||||
spec-dirs = ["/etc/cdi", "/var/run/cdi"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.csv]
|
|
||||||
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.legacy]
|
|
||||||
cuda-compat-mode = "ldconfig"
|
|
||||||
|
|
||||||
[nvidia-container-runtime-hook]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime-hook"
|
|
||||||
skip-mode-detection = true
|
|
||||||
|
|
||||||
[nvidia-ctk]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-ctk"
|
|
||||||
`,
|
|
||||||
expectedRuntimeConfig: `version = 2
|
|
||||||
|
|
||||||
[plugins]
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri"]
|
|
||||||
enable_cdi = true
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd]
|
|
||||||
default_runtime_name = "nvidia"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
|
|
||||||
privileged_without_host_devices = false
|
|
||||||
runtime_engine = ""
|
|
||||||
runtime_root = ""
|
|
||||||
runtime_type = "io.containerd.runc.v2"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
|
|
||||||
BinaryName = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-cdi]
|
|
||||||
privileged_without_host_devices = false
|
|
||||||
runtime_engine = ""
|
|
||||||
runtime_root = ""
|
|
||||||
runtime_type = "io.containerd.runc.v2"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-cdi.options]
|
|
||||||
BinaryName = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.cdi"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-legacy]
|
|
||||||
privileged_without_host_devices = false
|
|
||||||
runtime_engine = ""
|
|
||||||
runtime_root = ""
|
|
||||||
runtime_type = "io.containerd.runc.v2"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-legacy.options]
|
|
||||||
BinaryName = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.legacy"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "--enable-cdi-in-runtime=false overrides --cdi-enabled in containerd",
|
|
||||||
args: []string{"--cdi-enabled", "--create-device-nodes=none", "--enable-cdi-in-runtime=false", "--runtime=containerd"},
|
|
||||||
expectedToolkitConfig: `accept-nvidia-visible-devices-as-volume-mounts = false
|
|
||||||
accept-nvidia-visible-devices-envvar-when-unprivileged = true
|
|
||||||
disable-require = false
|
|
||||||
supported-driver-capabilities = "compat32,compute,display,graphics,ngx,utility,video"
|
|
||||||
swarm-resource = ""
|
|
||||||
|
|
||||||
[nvidia-container-cli]
|
|
||||||
debug = ""
|
|
||||||
environment = []
|
|
||||||
ldcache = ""
|
|
||||||
ldconfig = "@/run/nvidia/driver/sbin/ldconfig"
|
|
||||||
load-kmods = true
|
|
||||||
no-cgroups = false
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-cli"
|
|
||||||
root = "/run/nvidia/driver"
|
|
||||||
user = ""
|
|
||||||
|
|
||||||
[nvidia-container-runtime]
|
|
||||||
debug = "/dev/null"
|
|
||||||
log-level = "info"
|
|
||||||
mode = "auto"
|
|
||||||
runtimes = ["docker-runc", "runc", "crun"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.cdi]
|
|
||||||
annotation-prefixes = ["cdi.k8s.io/"]
|
|
||||||
default-kind = "nvidia.com/gpu"
|
|
||||||
spec-dirs = ["/etc/cdi", "/var/run/cdi"]
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.csv]
|
|
||||||
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"
|
|
||||||
|
|
||||||
[nvidia-container-runtime.modes.legacy]
|
|
||||||
cuda-compat-mode = "ldconfig"
|
|
||||||
|
|
||||||
[nvidia-container-runtime-hook]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime-hook"
|
|
||||||
skip-mode-detection = true
|
|
||||||
|
|
||||||
[nvidia-ctk]
|
|
||||||
path = "{{ .toolkitRoot }}/toolkit/nvidia-ctk"
|
|
||||||
`,
|
|
||||||
expectedRuntimeConfig: `version = 2
|
|
||||||
|
|
||||||
[plugins]
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri"]
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd]
|
|
||||||
default_runtime_name = "nvidia"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
|
|
||||||
privileged_without_host_devices = false
|
|
||||||
runtime_engine = ""
|
|
||||||
runtime_root = ""
|
|
||||||
runtime_type = "io.containerd.runc.v2"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
|
|
||||||
BinaryName = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-cdi]
|
|
||||||
privileged_without_host_devices = false
|
|
||||||
runtime_engine = ""
|
|
||||||
runtime_root = ""
|
|
||||||
runtime_type = "io.containerd.runc.v2"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-cdi.options]
|
|
||||||
BinaryName = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.cdi"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-legacy]
|
|
||||||
privileged_without_host_devices = false
|
|
||||||
runtime_engine = ""
|
|
||||||
runtime_root = ""
|
|
||||||
runtime_type = "io.containerd.runc.v2"
|
|
||||||
|
|
||||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia-legacy.options]
|
|
||||||
BinaryName = "{{ .toolkitRoot }}/toolkit/nvidia-container-runtime.legacy"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
testRoot := t.TempDir()
|
|
||||||
|
|
||||||
cdiOutputDir := filepath.Join(testRoot, "/var/run/cdi")
|
|
||||||
runtimeConfigFile := filepath.Join(testRoot, "config.file")
|
|
||||||
|
|
||||||
toolkitRoot := filepath.Join(testRoot, "toolkit-test")
|
|
||||||
toolkitConfigFile := filepath.Join(toolkitRoot, "toolkit/.config/nvidia-container-runtime/config.toml")
|
|
||||||
|
|
||||||
app := NewApp(logger)
|
|
||||||
|
|
||||||
testArgs := []string{
|
|
||||||
"nvidia-ctk-installer",
|
|
||||||
"--toolkit-install-dir=" + toolkitRoot,
|
|
||||||
"--no-daemon",
|
|
||||||
"--cdi-output-dir=" + cdiOutputDir,
|
|
||||||
"--config=" + runtimeConfigFile,
|
|
||||||
"--create-device-nodes=none",
|
|
||||||
"--driver-root-ctr-path=" + hostRoot,
|
|
||||||
"--pid-file=" + filepath.Join(testRoot, "toolkit.pid"),
|
|
||||||
"--restart-mode=none",
|
|
||||||
"--source-root=" + filepath.Join(artifactRoot, "deb"),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := app.Run(append(testArgs, tc.args...))
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.FileExists(t, toolkitConfigFile)
|
|
||||||
toolkitConfigFileContents, err := os.ReadFile(toolkitConfigFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, strings.ReplaceAll(tc.expectedToolkitConfig, "{{ .toolkitRoot }}", toolkitRoot), string(toolkitConfigFileContents))
|
|
||||||
|
|
||||||
require.FileExists(t, runtimeConfigFile)
|
|
||||||
runtimeConfigFileContents, err := os.ReadFile(runtimeConfigFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, strings.ReplaceAll(tc.expectedRuntimeConfig, "{{ .toolkitRoot }}", toolkitRoot), string(runtimeConfigFileContents))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An artifactRoot is used as a source for installed artifacts.
|
|
||||||
// It is refined by a directory path, a library locator, and an executable locator.
|
|
||||||
type artifactRoot struct {
|
|
||||||
path string
|
|
||||||
libraries lookup.Locator
|
|
||||||
executables lookup.Locator
|
|
||||||
}
|
|
||||||
|
|
||||||
func newArtifactRoot(logger logger.Interface, rootDirectoryPath string) (*artifactRoot, error) {
|
|
||||||
relativeLibrarySearchPaths := []string{
|
|
||||||
"/usr/lib64",
|
|
||||||
"/usr/lib/x86_64-linux-gnu",
|
|
||||||
"/usr/lib/aarch64-linux-gnu",
|
|
||||||
}
|
|
||||||
var librarySearchPaths []string
|
|
||||||
for _, l := range relativeLibrarySearchPaths {
|
|
||||||
librarySearchPaths = append(librarySearchPaths, filepath.Join(rootDirectoryPath, l))
|
|
||||||
}
|
|
||||||
|
|
||||||
a := artifactRoot{
|
|
||||||
path: rootDirectoryPath,
|
|
||||||
libraries: lookup.NewLibraryLocator(
|
|
||||||
lookup.WithLogger(logger),
|
|
||||||
lookup.WithCount(1),
|
|
||||||
lookup.WithSearchPaths(librarySearchPaths...),
|
|
||||||
),
|
|
||||||
executables: lookup.NewExecutableLocator(
|
|
||||||
logger,
|
|
||||||
rootDirectoryPath,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *artifactRoot) findLibrary(name string) (string, error) {
|
|
||||||
candidates, err := r.libraries.Locate(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error locating library: %w", err)
|
|
||||||
}
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
return "", fmt.Errorf("library %v not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidates[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *artifactRoot) findExecutable(name string) (string, error) {
|
|
||||||
candidates, err := r.executables.Locate(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error locating executable: %w", err)
|
|
||||||
}
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
return "", fmt.Errorf("executable %v not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidates[0], nil
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type createDirectory struct {
|
|
||||||
logger logger.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *toolkitInstaller) createDirectory() Installer {
|
|
||||||
return &createDirectory{
|
|
||||||
logger: t.logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *createDirectory) Install(dir string) error {
|
|
||||||
if dir == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
d.logger.Infof("Creating directory '%v'", dir)
|
|
||||||
err := os.MkdirAll(dir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating directory: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk-installer/container/operator"
|
|
||||||
)
|
|
||||||
|
|
||||||
type executable struct {
|
|
||||||
requiresKernelModule bool
|
|
||||||
path string
|
|
||||||
symlink string
|
|
||||||
args []string
|
|
||||||
env map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *toolkitInstaller) collectExecutables(destDir string) ([]Installer, error) {
|
|
||||||
configHome := filepath.Join(destDir, ".config")
|
|
||||||
configDir := filepath.Join(configHome, "nvidia-container-runtime")
|
|
||||||
configPath := filepath.Join(configDir, "config.toml")
|
|
||||||
|
|
||||||
executables := []executable{
|
|
||||||
{
|
|
||||||
path: "nvidia-ctk",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "nvidia-cdi-hook",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, runtime := range operator.GetRuntimes() {
|
|
||||||
e := executable{
|
|
||||||
path: runtime.Path,
|
|
||||||
requiresKernelModule: true,
|
|
||||||
env: map[string]string{
|
|
||||||
"XDG_CONFIG_HOME": configHome,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executables = append(executables, e)
|
|
||||||
}
|
|
||||||
executables = append(executables,
|
|
||||||
executable{
|
|
||||||
path: "nvidia-container-cli",
|
|
||||||
env: map[string]string{"LD_LIBRARY_PATH": destDir + ":$LD_LIBRARY_PATH"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
executables = append(executables,
|
|
||||||
executable{
|
|
||||||
path: "nvidia-container-runtime-hook",
|
|
||||||
symlink: "nvidia-container-toolkit",
|
|
||||||
args: []string{fmt.Sprintf("-config %s", configPath)},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
var installers []Installer
|
|
||||||
for _, executable := range executables {
|
|
||||||
executablePath, err := t.artifactRoot.findExecutable(executable.path)
|
|
||||||
if err != nil {
|
|
||||||
if t.ignoreErrors {
|
|
||||||
log.Errorf("Ignoring error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappedExecutableFilename := filepath.Base(executablePath)
|
|
||||||
dotRealFilename := wrappedExecutableFilename + ".real"
|
|
||||||
|
|
||||||
w := &wrapper{
|
|
||||||
Source: executablePath,
|
|
||||||
WrappedExecutable: dotRealFilename,
|
|
||||||
CheckModules: executable.requiresKernelModule,
|
|
||||||
Args: executable.args,
|
|
||||||
Envvars: map[string]string{
|
|
||||||
"PATH": strings.Join([]string{destDir, "$PATH"}, ":"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for k, v := range executable.env {
|
|
||||||
w.Envvars[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
installers = append(installers, w)
|
|
||||||
|
|
||||||
if executable.symlink == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
link := symlink{
|
|
||||||
linkname: executable.symlink,
|
|
||||||
target: filepath.Base(executablePath),
|
|
||||||
}
|
|
||||||
installers = append(installers, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
return installers, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrapper struct {
|
|
||||||
Source string
|
|
||||||
Envvars map[string]string
|
|
||||||
WrappedExecutable string
|
|
||||||
CheckModules bool
|
|
||||||
Args []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type render struct {
|
|
||||||
*wrapper
|
|
||||||
DestDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrapper) Install(destDir string) error {
|
|
||||||
// Copy the executable with a .real extension.
|
|
||||||
mode, err := installFile(w.Source, filepath.Join(destDir, w.WrappedExecutable))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a wrapper file.
|
|
||||||
r := render{
|
|
||||||
wrapper: w,
|
|
||||||
DestDir: destDir,
|
|
||||||
}
|
|
||||||
content, err := r.render()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to render wrapper: %w", err)
|
|
||||||
}
|
|
||||||
wrapperFile := filepath.Join(destDir, filepath.Base(w.Source))
|
|
||||||
return installContent(content, wrapperFile, mode|0111)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *render) render() (io.Reader, error) {
|
|
||||||
wrapperTemplate := `#! /bin/sh
|
|
||||||
{{- if (.CheckModules) }}
|
|
||||||
cat /proc/modules | grep -e "^nvidia " >/dev/null 2>&1
|
|
||||||
if [ "${?}" != "0" ]; then
|
|
||||||
echo "nvidia driver modules are not yet loaded, invoking runc directly"
|
|
||||||
exec runc "$@"
|
|
||||||
fi
|
|
||||||
{{- end }}
|
|
||||||
{{- range $key, $value := .Envvars }}
|
|
||||||
{{$key}}={{$value}} \
|
|
||||||
{{- end }}
|
|
||||||
{{ .DestDir }}/{{ .WrappedExecutable }} \
|
|
||||||
{{- range $arg := .Args }}
|
|
||||||
{{$arg}} \
|
|
||||||
{{- end }}
|
|
||||||
"$@"
|
|
||||||
`
|
|
||||||
|
|
||||||
var content bytes.Buffer
|
|
||||||
tmpl, err := template.New("wrapper").Parse(wrapperTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := tmpl.Execute(&content, w); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &content, nil
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWrapperRender(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
w *wrapper
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "executable is added",
|
|
||||||
w: &wrapper{
|
|
||||||
WrappedExecutable: "some-runtime",
|
|
||||||
},
|
|
||||||
expected: `#! /bin/sh
|
|
||||||
/dest-dir/some-runtime \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "module check is added",
|
|
||||||
w: &wrapper{
|
|
||||||
WrappedExecutable: "some-runtime",
|
|
||||||
CheckModules: true,
|
|
||||||
},
|
|
||||||
expected: `#! /bin/sh
|
|
||||||
cat /proc/modules | grep -e "^nvidia " >/dev/null 2>&1
|
|
||||||
if [ "${?}" != "0" ]; then
|
|
||||||
echo "nvidia driver modules are not yet loaded, invoking runc directly"
|
|
||||||
exec runc "$@"
|
|
||||||
fi
|
|
||||||
/dest-dir/some-runtime \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "environment is added",
|
|
||||||
w: &wrapper{
|
|
||||||
WrappedExecutable: "some-runtime",
|
|
||||||
Envvars: map[string]string{
|
|
||||||
"PATH": "/foo/bar/baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: `#! /bin/sh
|
|
||||||
PATH=/foo/bar/baz \
|
|
||||||
/dest-dir/some-runtime \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "args are added",
|
|
||||||
w: &wrapper{
|
|
||||||
WrappedExecutable: "some-runtime",
|
|
||||||
Args: []string{"--config foo", "bar"},
|
|
||||||
},
|
|
||||||
expected: `#! /bin/sh
|
|
||||||
/dest-dir/some-runtime \
|
|
||||||
--config foo \
|
|
||||||
bar \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
r := render{
|
|
||||||
wrapper: tc.w,
|
|
||||||
DestDir: "/dest-dir",
|
|
||||||
}
|
|
||||||
reader, err := r.render()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var content bytes.Buffer
|
|
||||||
_, err = content.ReadFrom(reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, tc.expected, content.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
// Code generated by moq; DO NOT EDIT.
|
|
||||||
// github.com/matryer/moq
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure, that fileInstallerMock does implement fileInstaller.
|
|
||||||
// If this is not the case, regenerate this file with moq.
|
|
||||||
var _ fileInstaller = &fileInstallerMock{}
|
|
||||||
|
|
||||||
// fileInstallerMock is a mock implementation of fileInstaller.
|
|
||||||
//
|
|
||||||
// func TestSomethingThatUsesfileInstaller(t *testing.T) {
|
|
||||||
//
|
|
||||||
// // make and configure a mocked fileInstaller
|
|
||||||
// mockedfileInstaller := &fileInstallerMock{
|
|
||||||
// installContentFunc: func(reader io.Reader, s string, v os.FileMode) error {
|
|
||||||
// panic("mock out the installContent method")
|
|
||||||
// },
|
|
||||||
// installFileFunc: func(s1 string, s2 string) (os.FileMode, error) {
|
|
||||||
// panic("mock out the installFile method")
|
|
||||||
// },
|
|
||||||
// installSymlinkFunc: func(s1 string, s2 string) error {
|
|
||||||
// panic("mock out the installSymlink method")
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // use mockedfileInstaller in code that requires fileInstaller
|
|
||||||
// // and then make assertions.
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
type fileInstallerMock struct {
|
|
||||||
// installContentFunc mocks the installContent method.
|
|
||||||
installContentFunc func(reader io.Reader, s string, v os.FileMode) error
|
|
||||||
|
|
||||||
// installFileFunc mocks the installFile method.
|
|
||||||
installFileFunc func(s1 string, s2 string) (os.FileMode, error)
|
|
||||||
|
|
||||||
// installSymlinkFunc mocks the installSymlink method.
|
|
||||||
installSymlinkFunc func(s1 string, s2 string) error
|
|
||||||
|
|
||||||
// calls tracks calls to the methods.
|
|
||||||
calls struct {
|
|
||||||
// installContent holds details about calls to the installContent method.
|
|
||||||
installContent []struct {
|
|
||||||
// Reader is the reader argument value.
|
|
||||||
Reader io.Reader
|
|
||||||
// S is the s argument value.
|
|
||||||
S string
|
|
||||||
// V is the v argument value.
|
|
||||||
V os.FileMode
|
|
||||||
}
|
|
||||||
// installFile holds details about calls to the installFile method.
|
|
||||||
installFile []struct {
|
|
||||||
// S1 is the s1 argument value.
|
|
||||||
S1 string
|
|
||||||
// S2 is the s2 argument value.
|
|
||||||
S2 string
|
|
||||||
}
|
|
||||||
// installSymlink holds details about calls to the installSymlink method.
|
|
||||||
installSymlink []struct {
|
|
||||||
// S1 is the s1 argument value.
|
|
||||||
S1 string
|
|
||||||
// S2 is the s2 argument value.
|
|
||||||
S2 string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lockinstallContent sync.RWMutex
|
|
||||||
lockinstallFile sync.RWMutex
|
|
||||||
lockinstallSymlink sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// installContent calls installContentFunc.
|
|
||||||
func (mock *fileInstallerMock) installContent(reader io.Reader, s string, v os.FileMode) error {
|
|
||||||
if mock.installContentFunc == nil {
|
|
||||||
panic("fileInstallerMock.installContentFunc: method is nil but fileInstaller.installContent was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Reader io.Reader
|
|
||||||
S string
|
|
||||||
V os.FileMode
|
|
||||||
}{
|
|
||||||
Reader: reader,
|
|
||||||
S: s,
|
|
||||||
V: v,
|
|
||||||
}
|
|
||||||
mock.lockinstallContent.Lock()
|
|
||||||
mock.calls.installContent = append(mock.calls.installContent, callInfo)
|
|
||||||
mock.lockinstallContent.Unlock()
|
|
||||||
return mock.installContentFunc(reader, s, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// installContentCalls gets all the calls that were made to installContent.
|
|
||||||
// Check the length with:
|
|
||||||
//
|
|
||||||
// len(mockedfileInstaller.installContentCalls())
|
|
||||||
func (mock *fileInstallerMock) installContentCalls() []struct {
|
|
||||||
Reader io.Reader
|
|
||||||
S string
|
|
||||||
V os.FileMode
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Reader io.Reader
|
|
||||||
S string
|
|
||||||
V os.FileMode
|
|
||||||
}
|
|
||||||
mock.lockinstallContent.RLock()
|
|
||||||
calls = mock.calls.installContent
|
|
||||||
mock.lockinstallContent.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// installFile calls installFileFunc.
|
|
||||||
func (mock *fileInstallerMock) installFile(s1 string, s2 string) (os.FileMode, error) {
|
|
||||||
if mock.installFileFunc == nil {
|
|
||||||
panic("fileInstallerMock.installFileFunc: method is nil but fileInstaller.installFile was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
}{
|
|
||||||
S1: s1,
|
|
||||||
S2: s2,
|
|
||||||
}
|
|
||||||
mock.lockinstallFile.Lock()
|
|
||||||
mock.calls.installFile = append(mock.calls.installFile, callInfo)
|
|
||||||
mock.lockinstallFile.Unlock()
|
|
||||||
return mock.installFileFunc(s1, s2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// installFileCalls gets all the calls that were made to installFile.
|
|
||||||
// Check the length with:
|
|
||||||
//
|
|
||||||
// len(mockedfileInstaller.installFileCalls())
|
|
||||||
func (mock *fileInstallerMock) installFileCalls() []struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
}
|
|
||||||
mock.lockinstallFile.RLock()
|
|
||||||
calls = mock.calls.installFile
|
|
||||||
mock.lockinstallFile.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// installSymlink calls installSymlinkFunc.
|
|
||||||
func (mock *fileInstallerMock) installSymlink(s1 string, s2 string) error {
|
|
||||||
if mock.installSymlinkFunc == nil {
|
|
||||||
panic("fileInstallerMock.installSymlinkFunc: method is nil but fileInstaller.installSymlink was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
}{
|
|
||||||
S1: s1,
|
|
||||||
S2: s2,
|
|
||||||
}
|
|
||||||
mock.lockinstallSymlink.Lock()
|
|
||||||
mock.calls.installSymlink = append(mock.calls.installSymlink, callInfo)
|
|
||||||
mock.lockinstallSymlink.Unlock()
|
|
||||||
return mock.installSymlinkFunc(s1, s2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// installSymlinkCalls gets all the calls that were made to installSymlink.
|
|
||||||
// Check the length with:
|
|
||||||
//
|
|
||||||
// len(mockedfileInstaller.installSymlinkCalls())
|
|
||||||
func (mock *fileInstallerMock) installSymlinkCalls() []struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
}
|
|
||||||
mock.lockinstallSymlink.RLock()
|
|
||||||
calls = mock.calls.installSymlink
|
|
||||||
mock.lockinstallSymlink.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate moq -rm -fmt=goimports -out installer_mock.go . Installer
|
|
||||||
type Installer interface {
|
|
||||||
Install(string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type toolkitInstaller struct {
|
|
||||||
logger logger.Interface
|
|
||||||
ignoreErrors bool
|
|
||||||
sourceRoot string
|
|
||||||
|
|
||||||
artifactRoot *artifactRoot
|
|
||||||
|
|
||||||
ensureTargetDirectory Installer
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Installer = (*toolkitInstaller)(nil)
|
|
||||||
|
|
||||||
// New creates a toolkit installer with the specified options.
|
|
||||||
func New(opts ...Option) (Installer, error) {
|
|
||||||
t := &toolkitInstaller{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.logger == nil {
|
|
||||||
t.logger = logger.New()
|
|
||||||
}
|
|
||||||
if t.sourceRoot == "" {
|
|
||||||
t.sourceRoot = "/"
|
|
||||||
}
|
|
||||||
if t.artifactRoot == nil {
|
|
||||||
artifactRoot, err := newArtifactRoot(t.logger, t.sourceRoot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.artifactRoot = artifactRoot
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.ensureTargetDirectory == nil {
|
|
||||||
t.ensureTargetDirectory = t.createDirectory()
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install ensures that the required toolkit files are installed in the specified directory.
|
|
||||||
func (t *toolkitInstaller) Install(destDir string) error {
|
|
||||||
var installers []Installer
|
|
||||||
|
|
||||||
installers = append(installers, t.ensureTargetDirectory)
|
|
||||||
|
|
||||||
libraries, err := t.collectLibraries()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to collect libraries: %w", err)
|
|
||||||
}
|
|
||||||
installers = append(installers, libraries...)
|
|
||||||
|
|
||||||
executables, err := t.collectExecutables(destDir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to collect executables: %w", err)
|
|
||||||
}
|
|
||||||
installers = append(installers, executables...)
|
|
||||||
|
|
||||||
var errs error
|
|
||||||
for _, i := range installers {
|
|
||||||
errs = errors.Join(errs, i.Install(destDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
type symlink struct {
|
|
||||||
linkname string
|
|
||||||
target string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s symlink) Install(destDir string) error {
|
|
||||||
symlinkPath := filepath.Join(destDir, s.linkname)
|
|
||||||
return installSymlink(s.target, symlinkPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate moq -rm -fmt=goimports -out file-installer_mock.go . fileInstaller
|
|
||||||
type fileInstaller interface {
|
|
||||||
installContent(io.Reader, string, os.FileMode) error
|
|
||||||
installFile(string, string) (os.FileMode, error)
|
|
||||||
installSymlink(string, string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var installSymlink = installSymlinkStub
|
|
||||||
|
|
||||||
func installSymlinkStub(target string, link string) error {
|
|
||||||
err := os.Symlink(target, link)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating symlink '%v' => '%v': %v", link, target, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var installFile = installFileStub
|
|
||||||
|
|
||||||
func installFileStub(src string, dest string) (os.FileMode, error) {
|
|
||||||
sourceInfo, err := os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("error getting file info for '%v': %v", src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
source, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("error opening source: %w", err)
|
|
||||||
}
|
|
||||||
defer source.Close()
|
|
||||||
|
|
||||||
mode := sourceInfo.Mode()
|
|
||||||
if err := installContent(source, dest, mode); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return mode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var installContent = installContentStub
|
|
||||||
|
|
||||||
func installContentStub(content io.Reader, dest string, mode fs.FileMode) error {
|
|
||||||
destination, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating destination: %w", err)
|
|
||||||
}
|
|
||||||
defer destination.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(destination, content)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error copying file: %w", err)
|
|
||||||
}
|
|
||||||
err = os.Chmod(dest, mode)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error setting mode for '%v': %v", dest, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// Code generated by moq; DO NOT EDIT.
|
|
||||||
// github.com/matryer/moq
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure, that InstallerMock does implement Installer.
|
|
||||||
// If this is not the case, regenerate this file with moq.
|
|
||||||
var _ Installer = &InstallerMock{}
|
|
||||||
|
|
||||||
// InstallerMock is a mock implementation of Installer.
|
|
||||||
//
|
|
||||||
// func TestSomethingThatUsesInstaller(t *testing.T) {
|
|
||||||
//
|
|
||||||
// // make and configure a mocked Installer
|
|
||||||
// mockedInstaller := &InstallerMock{
|
|
||||||
// InstallFunc: func(s string) error {
|
|
||||||
// panic("mock out the Install method")
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // use mockedInstaller in code that requires Installer
|
|
||||||
// // and then make assertions.
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
type InstallerMock struct {
|
|
||||||
// InstallFunc mocks the Install method.
|
|
||||||
InstallFunc func(s string) error
|
|
||||||
|
|
||||||
// calls tracks calls to the methods.
|
|
||||||
calls struct {
|
|
||||||
// Install holds details about calls to the Install method.
|
|
||||||
Install []struct {
|
|
||||||
// S is the s argument value.
|
|
||||||
S string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lockInstall sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install calls InstallFunc.
|
|
||||||
func (mock *InstallerMock) Install(s string) error {
|
|
||||||
if mock.InstallFunc == nil {
|
|
||||||
panic("InstallerMock.InstallFunc: method is nil but Installer.Install was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
S string
|
|
||||||
}{
|
|
||||||
S: s,
|
|
||||||
}
|
|
||||||
mock.lockInstall.Lock()
|
|
||||||
mock.calls.Install = append(mock.calls.Install, callInfo)
|
|
||||||
mock.lockInstall.Unlock()
|
|
||||||
return mock.InstallFunc(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallCalls gets all the calls that were made to Install.
|
|
||||||
// Check the length with:
|
|
||||||
//
|
|
||||||
// len(mockedInstaller.InstallCalls())
|
|
||||||
func (mock *InstallerMock) InstallCalls() []struct {
|
|
||||||
S string
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
S string
|
|
||||||
}
|
|
||||||
mock.lockInstall.RLock()
|
|
||||||
calls = mock.calls.Install
|
|
||||||
mock.lockInstall.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
testlog "github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestToolkitInstaller(t *testing.T) {
|
|
||||||
logger, _ := testlog.NewNullLogger()
|
|
||||||
|
|
||||||
type contentCall struct {
|
|
||||||
wrapper string
|
|
||||||
path string
|
|
||||||
mode fs.FileMode
|
|
||||||
}
|
|
||||||
var contentCalls []contentCall
|
|
||||||
|
|
||||||
installer := &fileInstallerMock{
|
|
||||||
installFileFunc: func(s1, s2 string) (os.FileMode, error) {
|
|
||||||
return 0666, nil
|
|
||||||
},
|
|
||||||
installContentFunc: func(reader io.Reader, s string, fileMode fs.FileMode) error {
|
|
||||||
var b bytes.Buffer
|
|
||||||
if _, err := b.ReadFrom(reader); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
contents := contentCall{
|
|
||||||
wrapper: b.String(),
|
|
||||||
path: s,
|
|
||||||
mode: fileMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
contentCalls = append(contentCalls, contents)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
installSymlinkFunc: func(s1, s2 string) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
installFile = installer.installFile
|
|
||||||
installContent = installer.installContent
|
|
||||||
installSymlink = installer.installSymlink
|
|
||||||
|
|
||||||
root := "/artifacts/test"
|
|
||||||
libraries := &lookup.LocatorMock{
|
|
||||||
LocateFunc: func(s string) ([]string, error) {
|
|
||||||
switch s {
|
|
||||||
case "libnvidia-container.so.1":
|
|
||||||
return []string{filepath.Join(root, "libnvidia-container.so.987.65.43")}, nil
|
|
||||||
case "libnvidia-container-go.so.1":
|
|
||||||
return []string{filepath.Join(root, "libnvidia-container-go.so.1.23.4")}, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%v not found", s)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executables := &lookup.LocatorMock{
|
|
||||||
LocateFunc: func(s string) ([]string, error) {
|
|
||||||
switch s {
|
|
||||||
case "nvidia-container-runtime.cdi":
|
|
||||||
fallthrough
|
|
||||||
case "nvidia-container-runtime.legacy":
|
|
||||||
fallthrough
|
|
||||||
case "nvidia-container-runtime":
|
|
||||||
fallthrough
|
|
||||||
case "nvidia-ctk":
|
|
||||||
fallthrough
|
|
||||||
case "nvidia-container-cli":
|
|
||||||
fallthrough
|
|
||||||
case "nvidia-container-runtime-hook":
|
|
||||||
fallthrough
|
|
||||||
case "nvidia-cdi-hook":
|
|
||||||
return []string{filepath.Join(root, "usr/bin", s)}, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%v not found", s)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &artifactRoot{
|
|
||||||
libraries: libraries,
|
|
||||||
executables: executables,
|
|
||||||
}
|
|
||||||
|
|
||||||
createDirectory := &InstallerMock{
|
|
||||||
InstallFunc: func(c string) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
i := toolkitInstaller{
|
|
||||||
logger: logger,
|
|
||||||
artifactRoot: r,
|
|
||||||
ensureTargetDirectory: createDirectory,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := i.Install("/foo/bar/baz")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.ElementsMatch(t,
|
|
||||||
[]struct {
|
|
||||||
S string
|
|
||||||
}{
|
|
||||||
{"/foo/bar/baz"},
|
|
||||||
},
|
|
||||||
createDirectory.InstallCalls(),
|
|
||||||
)
|
|
||||||
|
|
||||||
require.ElementsMatch(t,
|
|
||||||
installer.installFileCalls(),
|
|
||||||
[]struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
}{
|
|
||||||
{"/artifacts/test/libnvidia-container-go.so.1.23.4", "/foo/bar/baz/libnvidia-container-go.so.1.23.4"},
|
|
||||||
{"/artifacts/test/libnvidia-container.so.987.65.43", "/foo/bar/baz/libnvidia-container.so.987.65.43"},
|
|
||||||
{"/artifacts/test/usr/bin/nvidia-container-runtime.cdi", "/foo/bar/baz/nvidia-container-runtime.cdi.real"},
|
|
||||||
{"/artifacts/test/usr/bin/nvidia-container-runtime.legacy", "/foo/bar/baz/nvidia-container-runtime.legacy.real"},
|
|
||||||
{"/artifacts/test/usr/bin/nvidia-container-runtime", "/foo/bar/baz/nvidia-container-runtime.real"},
|
|
||||||
{"/artifacts/test/usr/bin/nvidia-ctk", "/foo/bar/baz/nvidia-ctk.real"},
|
|
||||||
{"/artifacts/test/usr/bin/nvidia-cdi-hook", "/foo/bar/baz/nvidia-cdi-hook.real"},
|
|
||||||
{"/artifacts/test/usr/bin/nvidia-container-cli", "/foo/bar/baz/nvidia-container-cli.real"},
|
|
||||||
{"/artifacts/test/usr/bin/nvidia-container-runtime-hook", "/foo/bar/baz/nvidia-container-runtime-hook.real"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
require.ElementsMatch(t,
|
|
||||||
installer.installSymlinkCalls(),
|
|
||||||
[]struct {
|
|
||||||
S1 string
|
|
||||||
S2 string
|
|
||||||
}{
|
|
||||||
{"libnvidia-container-go.so.1.23.4", "/foo/bar/baz/libnvidia-container-go.so.1"},
|
|
||||||
{"libnvidia-container.so.987.65.43", "/foo/bar/baz/libnvidia-container.so.1"},
|
|
||||||
{"nvidia-container-runtime-hook", "/foo/bar/baz/nvidia-container-toolkit"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
require.ElementsMatch(t,
|
|
||||||
contentCalls,
|
|
||||||
[]contentCall{
|
|
||||||
{
|
|
||||||
path: "/foo/bar/baz/nvidia-container-runtime",
|
|
||||||
mode: 0777,
|
|
||||||
wrapper: `#! /bin/sh
|
|
||||||
cat /proc/modules | grep -e "^nvidia " >/dev/null 2>&1
|
|
||||||
if [ "${?}" != "0" ]; then
|
|
||||||
echo "nvidia driver modules are not yet loaded, invoking runc directly"
|
|
||||||
exec runc "$@"
|
|
||||||
fi
|
|
||||||
PATH=/foo/bar/baz:$PATH \
|
|
||||||
XDG_CONFIG_HOME=/foo/bar/baz/.config \
|
|
||||||
/foo/bar/baz/nvidia-container-runtime.real \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/foo/bar/baz/nvidia-container-runtime.cdi",
|
|
||||||
mode: 0777,
|
|
||||||
wrapper: `#! /bin/sh
|
|
||||||
cat /proc/modules | grep -e "^nvidia " >/dev/null 2>&1
|
|
||||||
if [ "${?}" != "0" ]; then
|
|
||||||
echo "nvidia driver modules are not yet loaded, invoking runc directly"
|
|
||||||
exec runc "$@"
|
|
||||||
fi
|
|
||||||
PATH=/foo/bar/baz:$PATH \
|
|
||||||
XDG_CONFIG_HOME=/foo/bar/baz/.config \
|
|
||||||
/foo/bar/baz/nvidia-container-runtime.cdi.real \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/foo/bar/baz/nvidia-container-runtime.legacy",
|
|
||||||
mode: 0777,
|
|
||||||
wrapper: `#! /bin/sh
|
|
||||||
cat /proc/modules | grep -e "^nvidia " >/dev/null 2>&1
|
|
||||||
if [ "${?}" != "0" ]; then
|
|
||||||
echo "nvidia driver modules are not yet loaded, invoking runc directly"
|
|
||||||
exec runc "$@"
|
|
||||||
fi
|
|
||||||
PATH=/foo/bar/baz:$PATH \
|
|
||||||
XDG_CONFIG_HOME=/foo/bar/baz/.config \
|
|
||||||
/foo/bar/baz/nvidia-container-runtime.legacy.real \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/foo/bar/baz/nvidia-ctk",
|
|
||||||
mode: 0777,
|
|
||||||
wrapper: `#! /bin/sh
|
|
||||||
PATH=/foo/bar/baz:$PATH \
|
|
||||||
/foo/bar/baz/nvidia-ctk.real \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/foo/bar/baz/nvidia-cdi-hook",
|
|
||||||
mode: 0777,
|
|
||||||
wrapper: `#! /bin/sh
|
|
||||||
PATH=/foo/bar/baz:$PATH \
|
|
||||||
/foo/bar/baz/nvidia-cdi-hook.real \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/foo/bar/baz/nvidia-container-cli",
|
|
||||||
mode: 0777,
|
|
||||||
wrapper: `#! /bin/sh
|
|
||||||
LD_LIBRARY_PATH=/foo/bar/baz:$LD_LIBRARY_PATH \
|
|
||||||
PATH=/foo/bar/baz:$PATH \
|
|
||||||
/foo/bar/baz/nvidia-container-cli.real \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/foo/bar/baz/nvidia-container-runtime-hook",
|
|
||||||
mode: 0777,
|
|
||||||
wrapper: `#! /bin/sh
|
|
||||||
PATH=/foo/bar/baz:$PATH \
|
|
||||||
/foo/bar/baz/nvidia-container-runtime-hook.real \
|
|
||||||
-config /foo/bar/baz/.config/nvidia-container-runtime/config.toml \
|
|
||||||
"$@"
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/**
|
|
||||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
package installer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// collectLibraries locates and installs the libraries that are part of
|
|
||||||
// the nvidia-container-toolkit.
|
|
||||||
// A predefined set of library candidates are considered, with the first one
|
|
||||||
// resulting in success being installed to the toolkit folder. The install process
|
|
||||||
// resolves the symlink for the library and copies the versioned library itself.
|
|
||||||
func (t *toolkitInstaller) collectLibraries() ([]Installer, error) {
|
|
||||||
requiredLibraries := []string{
|
|
||||||
"libnvidia-container.so.1",
|
|
||||||
"libnvidia-container-go.so.1",
|
|
||||||
}
|
|
||||||
|
|
||||||
var installers []Installer
|
|
||||||
for _, l := range requiredLibraries {
|
|
||||||
libraryPath, err := t.artifactRoot.findLibrary(l)
|
|
||||||
if err != nil {
|
|
||||||
if t.ignoreErrors {
|
|
||||||
log.Errorf("Ignoring error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
installers = append(installers, library(libraryPath))
|
|
||||||
|
|
||||||
if filepath.Base(libraryPath) == l {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
link := symlink{
|
|
||||||
linkname: l,
|
|
||||||
target: filepath.Base(libraryPath),
|
|
||||||
}
|
|
||||||
installers = append(installers, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
return installers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type library string
|
|
||||||
|
|
||||||
// Install copies the library l to the destination folder.
|
|
||||||
// The same basename is used in the destination folder.
|
|
||||||
func (l library) Install(destinationDir string) error {
|
|
||||||
dest := filepath.Join(destinationDir, filepath.Base(string(l)))
|
|
||||||
|
|
||||||
_, err := installFile(string(l), dest)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user