Skip to content

Commit 7af907a

Browse files
Add gpuop-cfg list-images command for csv and clusterpolicy
Signed-off-by: Karthik Vetrivel <kvetrivel@nvidia.com>
1 parent f86da6d commit 7af907a

File tree

9 files changed

+775
-146
lines changed

9 files changed

+775
-146
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
# Copyright (c), NVIDIA CORPORATION. All rights reserved.
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 images
18+
19+
import (
20+
"fmt"
21+
22+
v1 "github.com/NVIDIA/gpu-operator/api/nvidia/v1"
23+
)
24+
25+
type OperandImage struct {
26+
Name string
27+
Image string
28+
}
29+
30+
func FromClusterPolicy(spec *v1.ClusterPolicySpec) ([]OperandImage, error) {
31+
type operand struct {
32+
name string
33+
spec interface{}
34+
}
35+
36+
operands := []operand{
37+
{"Driver", &spec.Driver},
38+
{"Toolkit", &spec.Toolkit},
39+
{"DevicePlugin", &spec.DevicePlugin},
40+
{"DCGMExporter", &spec.DCGMExporter},
41+
{"DCGM", &spec.DCGM},
42+
{"GPUFeatureDiscovery", &spec.GPUFeatureDiscovery},
43+
{"MIGManager", &spec.MIGManager},
44+
{"GPUDirectStorage", spec.GPUDirectStorage},
45+
{"VFIOManager", &spec.VFIOManager},
46+
{"SandboxDevicePlugin", &spec.SandboxDevicePlugin},
47+
{"VGPUDeviceManager", &spec.VGPUDeviceManager},
48+
}
49+
50+
var images []OperandImage
51+
for _, op := range operands {
52+
path, err := v1.ImagePath(op.spec)
53+
if err != nil {
54+
return nil, fmt.Errorf("failed to construct image path for %s: %v", op.name, err)
55+
}
56+
images = append(images, OperandImage{Name: op.name, Image: path})
57+
}
58+
59+
return images, nil
60+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
# Copyright (c), NVIDIA CORPORATION. All rights reserved.
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 images
18+
19+
import (
20+
"testing"
21+
22+
v1 "github.com/NVIDIA/gpu-operator/api/nvidia/v1"
23+
)
24+
25+
func newClusterPolicySpec() *v1.ClusterPolicySpec {
26+
return &v1.ClusterPolicySpec{
27+
Driver: v1.DriverSpec{
28+
Repository: "nvcr.io/nvidia",
29+
Image: "driver",
30+
Version: "550.127.05",
31+
},
32+
Toolkit: v1.ToolkitSpec{
33+
Repository: "nvcr.io/nvidia/k8s",
34+
Image: "container-toolkit",
35+
Version: "v1.16.1",
36+
},
37+
DevicePlugin: v1.DevicePluginSpec{
38+
Repository: "nvcr.io/nvidia",
39+
Image: "k8s-device-plugin",
40+
Version: "v0.16.1",
41+
},
42+
DCGMExporter: v1.DCGMExporterSpec{
43+
Repository: "nvcr.io/nvidia/k8s",
44+
Image: "dcgm-exporter",
45+
Version: "3.3.6",
46+
},
47+
DCGM: v1.DCGMSpec{
48+
Repository: "nvcr.io/nvidia/cloud-native",
49+
Image: "dcgm",
50+
Version: "3.3.6",
51+
},
52+
GPUFeatureDiscovery: v1.GPUFeatureDiscoverySpec{
53+
Repository: "nvcr.io/nvidia",
54+
Image: "gpu-feature-discovery",
55+
Version: "v0.16.1",
56+
},
57+
MIGManager: v1.MIGManagerSpec{
58+
Repository: "nvcr.io/nvidia/cloud-native",
59+
Image: "k8s-mig-manager",
60+
Version: "v0.8.0",
61+
},
62+
GPUDirectStorage: &v1.GPUDirectStorageSpec{
63+
Repository: "nvcr.io/nvidia/cloud-native",
64+
Image: "nvidia-fs",
65+
Version: "2.20.5",
66+
},
67+
VFIOManager: v1.VFIOManagerSpec{
68+
Repository: "nvcr.io/nvidia",
69+
Image: "vfio-manager",
70+
Version: "v0.4.0",
71+
},
72+
SandboxDevicePlugin: v1.SandboxDevicePluginSpec{
73+
Repository: "nvcr.io/nvidia",
74+
Image: "kubevirt-gpu-device-plugin",
75+
Version: "v1.2.7",
76+
},
77+
VGPUDeviceManager: v1.VGPUDeviceManagerSpec{
78+
Repository: "nvcr.io/nvidia/cloud-native",
79+
Image: "vgpu-device-manager",
80+
Version: "v0.2.7",
81+
},
82+
}
83+
}
84+
85+
func Test_FromClusterPolicy(t *testing.T) {
86+
tests := []struct {
87+
name string
88+
spec *v1.ClusterPolicySpec
89+
wantImages []OperandImage
90+
}{
91+
{
92+
name: "constructs image paths from repository, image, and version",
93+
spec: newClusterPolicySpec(),
94+
wantImages: []OperandImage{
95+
{Name: "Driver", Image: "nvcr.io/nvidia/driver:550.127.05"},
96+
{Name: "Toolkit", Image: "nvcr.io/nvidia/k8s/container-toolkit:v1.16.1"},
97+
{Name: "DevicePlugin", Image: "nvcr.io/nvidia/k8s-device-plugin:v0.16.1"},
98+
{Name: "DCGMExporter", Image: "nvcr.io/nvidia/k8s/dcgm-exporter:3.3.6"},
99+
{Name: "DCGM", Image: "nvcr.io/nvidia/cloud-native/dcgm:3.3.6"},
100+
{Name: "GPUFeatureDiscovery", Image: "nvcr.io/nvidia/gpu-feature-discovery:v0.16.1"},
101+
{Name: "MIGManager", Image: "nvcr.io/nvidia/cloud-native/k8s-mig-manager:v0.8.0"},
102+
{Name: "GPUDirectStorage", Image: "nvcr.io/nvidia/cloud-native/nvidia-fs:2.20.5"},
103+
{Name: "VFIOManager", Image: "nvcr.io/nvidia/vfio-manager:v0.4.0"},
104+
{Name: "SandboxDevicePlugin", Image: "nvcr.io/nvidia/kubevirt-gpu-device-plugin:v1.2.7"},
105+
{Name: "VGPUDeviceManager", Image: "nvcr.io/nvidia/cloud-native/vgpu-device-manager:v0.2.7"},
106+
},
107+
},
108+
{
109+
name: "uses image as full path when repository and version are empty",
110+
spec: func() *v1.ClusterPolicySpec {
111+
s := newClusterPolicySpec()
112+
s.Driver = v1.DriverSpec{
113+
Image: "nvcr.io/nvidia/driver:550.127.05",
114+
}
115+
return s
116+
}(),
117+
wantImages: []OperandImage{
118+
{Name: "Driver", Image: "nvcr.io/nvidia/driver:550.127.05"},
119+
{Name: "Toolkit", Image: "nvcr.io/nvidia/k8s/container-toolkit:v1.16.1"},
120+
{Name: "DevicePlugin", Image: "nvcr.io/nvidia/k8s-device-plugin:v0.16.1"},
121+
{Name: "DCGMExporter", Image: "nvcr.io/nvidia/k8s/dcgm-exporter:3.3.6"},
122+
{Name: "DCGM", Image: "nvcr.io/nvidia/cloud-native/dcgm:3.3.6"},
123+
{Name: "GPUFeatureDiscovery", Image: "nvcr.io/nvidia/gpu-feature-discovery:v0.16.1"},
124+
{Name: "MIGManager", Image: "nvcr.io/nvidia/cloud-native/k8s-mig-manager:v0.8.0"},
125+
{Name: "GPUDirectStorage", Image: "nvcr.io/nvidia/cloud-native/nvidia-fs:2.20.5"},
126+
{Name: "VFIOManager", Image: "nvcr.io/nvidia/vfio-manager:v0.4.0"},
127+
{Name: "SandboxDevicePlugin", Image: "nvcr.io/nvidia/kubevirt-gpu-device-plugin:v1.2.7"},
128+
{Name: "VGPUDeviceManager", Image: "nvcr.io/nvidia/cloud-native/vgpu-device-manager:v0.2.7"},
129+
},
130+
},
131+
}
132+
for _, tt := range tests {
133+
t.Run(tt.name, func(t *testing.T) {
134+
got, err := FromClusterPolicy(tt.spec)
135+
if err != nil {
136+
t.Fatalf("FromClusterPolicy() unexpected error: %v", err)
137+
}
138+
if len(got) != len(tt.wantImages) {
139+
t.Fatalf("FromClusterPolicy() returned %d images, want %d", len(got), len(tt.wantImages))
140+
}
141+
for i, op := range got {
142+
if op.Name != tt.wantImages[i].Name {
143+
t.Errorf("FromClusterPolicy()[%d].Name = %q, want %q", i, op.Name, tt.wantImages[i].Name)
144+
}
145+
if op.Image != tt.wantImages[i].Image {
146+
t.Errorf("FromClusterPolicy()[%d].Image = %q, want %q", i, op.Image, tt.wantImages[i].Image)
147+
}
148+
}
149+
})
150+
}
151+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
# Copyright (c), NVIDIA CORPORATION. All rights reserved.
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 images
18+
19+
import (
20+
"strings"
21+
22+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
23+
)
24+
25+
func FromCSV(csv *v1alpha1.ClusterServiceVersion) []string {
26+
var images []string
27+
28+
for _, image := range csv.Spec.RelatedImages {
29+
images = append(images, image.Image)
30+
}
31+
32+
deployment := csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0]
33+
ctr := deployment.Spec.Template.Spec.Containers[0]
34+
images = append(images, ctr.Image)
35+
36+
for _, env := range ctr.Env {
37+
if !strings.HasSuffix(env.Name, "_IMAGE") {
38+
continue
39+
}
40+
images = append(images, env.Value)
41+
}
42+
43+
return images
44+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
# Copyright (c), NVIDIA CORPORATION. All rights reserved.
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 images
18+
19+
import (
20+
"testing"
21+
22+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
23+
appsv1 "k8s.io/api/apps/v1"
24+
corev1 "k8s.io/api/core/v1"
25+
)
26+
27+
func newCSV(relatedImages []v1alpha1.RelatedImage, containerImage string, envVars []corev1.EnvVar) *v1alpha1.ClusterServiceVersion {
28+
return &v1alpha1.ClusterServiceVersion{
29+
Spec: v1alpha1.ClusterServiceVersionSpec{
30+
RelatedImages: relatedImages,
31+
InstallStrategy: v1alpha1.NamedInstallStrategy{
32+
StrategySpec: v1alpha1.StrategyDetailsDeployment{
33+
DeploymentSpecs: []v1alpha1.StrategyDeploymentSpec{
34+
{
35+
Spec: appsv1.DeploymentSpec{
36+
Template: corev1.PodTemplateSpec{
37+
Spec: corev1.PodSpec{
38+
Containers: []corev1.Container{
39+
{
40+
Image: containerImage,
41+
Env: envVars,
42+
},
43+
},
44+
},
45+
},
46+
},
47+
},
48+
},
49+
},
50+
},
51+
},
52+
}
53+
}
54+
55+
func Test_FromCSV(t *testing.T) {
56+
tests := []struct {
57+
name string
58+
csv *v1alpha1.ClusterServiceVersion
59+
want []string
60+
}{
61+
{
62+
name: "collects related images, container image, and IMAGE env vars",
63+
csv: newCSV(
64+
[]v1alpha1.RelatedImage{
65+
{Image: "nvcr.io/nvidia/gpu-operator:v24.9.0"},
66+
{Image: "nvcr.io/nvidia/driver:550.127.05"},
67+
},
68+
"nvcr.io/nvidia/gpu-operator:v24.9.0",
69+
[]corev1.EnvVar{
70+
{Name: "DRIVER_IMAGE", Value: "nvcr.io/nvidia/driver:550"},
71+
{Name: "TOOLKIT_IMAGE", Value: "nvcr.io/nvidia/toolkit:1.16"},
72+
{Name: "LOG_LEVEL", Value: "debug"},
73+
},
74+
),
75+
want: []string{
76+
"nvcr.io/nvidia/gpu-operator:v24.9.0",
77+
"nvcr.io/nvidia/driver:550.127.05",
78+
"nvcr.io/nvidia/gpu-operator:v24.9.0",
79+
"nvcr.io/nvidia/driver:550",
80+
"nvcr.io/nvidia/toolkit:1.16",
81+
},
82+
},
83+
{
84+
name: "no related images and no env vars",
85+
csv: newCSV(nil, "nvcr.io/nvidia/gpu-operator:v24.9.0", nil),
86+
want: []string{
87+
"nvcr.io/nvidia/gpu-operator:v24.9.0",
88+
},
89+
},
90+
}
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
got := FromCSV(tt.csv)
94+
if len(got) != len(tt.want) {
95+
t.Fatalf("FromCSV() returned %d images, want %d", len(got), len(tt.want))
96+
}
97+
for i, image := range got {
98+
if image != tt.want[i] {
99+
t.Errorf("FromCSV()[%d] = %q, want %q", i, image, tt.want[i])
100+
}
101+
}
102+
})
103+
}
104+
}

0 commit comments

Comments
 (0)