Skip to content

Commit 100c111

Browse files
committed
Add support for feature flags
This change adds an nvidia-container-runtime.features config that allows individual features to be toggled at a global level. Each feature can (by default) be controlled by an environment variable. The GDS, MOFED, NVSWITCH, and GDRCOPY features are examples of such features. Signed-off-by: Evan Lezar <[email protected]>
1 parent 355997d commit 100c111

File tree

5 files changed

+102
-16
lines changed

5 files changed

+102
-16
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ licenses:
9595

9696
COVERAGE_FILE := coverage.out
9797
test: build cmds
98-
go test -v -coverprofile=$(COVERAGE_FILE) $(MODULE)/...
98+
go test -coverprofile=$(COVERAGE_FILE) $(MODULE)/...
9999

100100
coverage: test
101101
cat $(COVERAGE_FILE) | grep -v "_mock.go" > $(COVERAGE_FILE).no-mocks

cmd/nvidia-ctk/config/config.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ func run(c *cli.Context, opts *options) error {
109109
if err != nil {
110110
return fmt.Errorf("invalid --set option %v: %w", set, err)
111111
}
112-
cfgToml.Set(key, value)
112+
if value == nil {
113+
_ = cfgToml.Delete(key)
114+
} else {
115+
cfgToml.Set(key, value)
116+
}
113117
}
114118

115119
if err := opts.EnsureOutputFolder(); err != nil {
@@ -146,20 +150,25 @@ func setFlagToKeyValue(setFlag string) (string, interface{}, error) {
146150

147151
kind := field.Kind()
148152
if len(setParts) != 2 {
149-
if kind == reflect.Bool {
153+
if kind == reflect.Bool || (kind == reflect.Pointer && field.Elem().Kind() == reflect.Bool) {
150154
return key, true, nil
151155
}
152156
return key, nil, fmt.Errorf("%w: expected key=value; got %v", errInvalidFormat, setFlag)
153157
}
154158

155159
value := setParts[1]
160+
if kind == reflect.Pointer && value != "nil" {
161+
kind = field.Elem().Kind()
162+
}
156163
switch kind {
164+
case reflect.Pointer:
165+
return key, nil, nil
157166
case reflect.Bool:
158167
b, err := strconv.ParseBool(value)
159168
if err != nil {
160169
return key, value, fmt.Errorf("%w: %w", errInvalidFormat, err)
161170
}
162-
return key, b, err
171+
return key, b, nil
163172
case reflect.String:
164173
return key, value, nil
165174
case reflect.Slice:
@@ -201,7 +210,7 @@ func getStruct(current reflect.Type, paths ...string) (reflect.StructField, erro
201210
if !ok {
202211
continue
203212
}
204-
if v != tomlField {
213+
if strings.SplitN(v, ",", 2)[0] != tomlField {
205214
continue
206215
}
207216
if len(paths) == 1 {

internal/config/features.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
# Copyright 2024 NVIDIA CORPORATION
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
**/
16+
17+
package config
18+
19+
type featureName string
20+
21+
const (
22+
FeatureGDS = featureName("gds")
23+
FeatureMOFED = featureName("mofed")
24+
FeatureNVSWITCH = featureName("nvswitch")
25+
FeatureGDRCopy = featureName("gdrcopy")
26+
)
27+
28+
// features specifies a set of named features.
29+
type features struct {
30+
GDS *feature `toml:"gds,omitempty"`
31+
MOFED *feature `toml:"mofed,omitempty"`
32+
NVSWITCH *feature `toml:"nvswitch,omitempty"`
33+
GDRCopy *feature `toml:"gdrcopy,omitempty"`
34+
}
35+
36+
type feature bool
37+
38+
func (fs features) IsEnabled(n featureName, in ...getenver) bool {
39+
featureEnvvars := map[featureName]string{
40+
FeatureGDS: "NVIDIA_GDS",
41+
FeatureMOFED: "NVIDIA_MOFED",
42+
FeatureNVSWITCH: "NVIDIA_NVSWITCH",
43+
FeatureGDRCopy: "NVIDIA_GDRCOPY",
44+
}
45+
46+
envvar := featureEnvvars[n]
47+
switch n {
48+
case FeatureGDS:
49+
return fs.GDS.isEnabled(envvar, in...)
50+
case FeatureMOFED:
51+
return fs.MOFED.isEnabled(envvar, in...)
52+
case FeatureNVSWITCH:
53+
return fs.NVSWITCH.isEnabled(envvar, in...)
54+
case FeatureGDRCopy:
55+
return fs.GDRCopy.isEnabled(envvar, in...)
56+
default:
57+
return false
58+
}
59+
}
60+
61+
// isEnabled checks whether a feature is enabled.
62+
// If the enabled value is explicitly set, this is returned, otherwise the
63+
// associated envvar is checked in the specified getenver for the string "enabled"
64+
// A CUDA container / image can be passed here.
65+
func (f *feature) isEnabled(envvar string, ins ...getenver) bool {
66+
if f != nil {
67+
return bool(*f)
68+
}
69+
if envvar == "" {
70+
return false
71+
}
72+
for _, in := range ins {
73+
if in.Getenv(envvar) == "enabled" {
74+
return true
75+
}
76+
}
77+
return false
78+
}
79+
80+
type getenver interface {
81+
Getenv(string) string
82+
}

internal/config/runtime.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type RuntimeConfig struct {
2525
Runtimes []string `toml:"runtimes"`
2626
Mode string `toml:"mode"`
2727
Modes modesConfig `toml:"modes"`
28+
// Features allows for finer control over optional features.
29+
Features features `toml:"features,omitempty"`
2830
}
2931

3032
// modesConfig defines (optional) per-mode configs

internal/modifier/gated.go

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,6 @@ import (
2626
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
2727
)
2828

29-
const (
30-
nvidiaGDSEnvvar = "NVIDIA_GDS"
31-
nvidiaMOFEDEnvvar = "NVIDIA_MOFED"
32-
nvidiaNVSWITCHEnvvar = "NVIDIA_NVSWITCH"
33-
nvidiaGDRCOPYEnvvar = "NVIDIA_GDRCOPY"
34-
)
35-
3629
// NewFeatureGatedModifier creates the modifiers for optional features.
3730
// These include:
3831
//
@@ -53,31 +46,31 @@ func NewFeatureGatedModifier(logger logger.Interface, cfg *config.Config, image
5346
driverRoot := cfg.NVIDIAContainerCLIConfig.Root
5447
devRoot := cfg.NVIDIAContainerCLIConfig.Root
5548

56-
if image.Getenv(nvidiaGDSEnvvar) == "enabled" {
49+
if cfg.NVIDIAContainerRuntimeConfig.Features.IsEnabled(config.FeatureGDS, image) {
5750
d, err := discover.NewGDSDiscoverer(logger, driverRoot, devRoot)
5851
if err != nil {
5952
return nil, fmt.Errorf("failed to construct discoverer for GDS devices: %w", err)
6053
}
6154
discoverers = append(discoverers, d)
6255
}
6356

64-
if image.Getenv(nvidiaMOFEDEnvvar) == "enabled" {
57+
if cfg.NVIDIAContainerRuntimeConfig.Features.IsEnabled(config.FeatureMOFED, image) {
6558
d, err := discover.NewMOFEDDiscoverer(logger, devRoot)
6659
if err != nil {
6760
return nil, fmt.Errorf("failed to construct discoverer for MOFED devices: %w", err)
6861
}
6962
discoverers = append(discoverers, d)
7063
}
7164

72-
if image.Getenv(nvidiaNVSWITCHEnvvar) == "enabled" {
65+
if cfg.NVIDIAContainerRuntimeConfig.Features.IsEnabled(config.FeatureNVSWITCH, image) {
7366
d, err := discover.NewNvSwitchDiscoverer(logger, devRoot)
7467
if err != nil {
7568
return nil, fmt.Errorf("failed to construct discoverer for NVSWITCH devices: %w", err)
7669
}
7770
discoverers = append(discoverers, d)
7871
}
7972

80-
if image.Getenv(nvidiaGDRCOPYEnvvar) == "enabled" {
73+
if cfg.NVIDIAContainerRuntimeConfig.Features.IsEnabled(config.FeatureGDRCopy, image) {
8174
d, err := discover.NewGDRCopyDiscoverer(logger, devRoot)
8275
if err != nil {
8376
return nil, fmt.Errorf("failed to construct discoverer for GDRCopy devices: %w", err)

0 commit comments

Comments
 (0)