diff --git a/api/nvidia/v1/clusterpolicy_types.go b/api/nvidia/v1/clusterpolicy_types.go index 06607e708..f63c3ce3f 100644 --- a/api/nvidia/v1/clusterpolicy_types.go +++ b/api/nvidia/v1/clusterpolicy_types.go @@ -667,6 +667,10 @@ type DriverSpec struct { // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="Custom Repo Configuration For NVIDIA Driver Container" RepoConfig *DriverRepoConfigSpec `json:"repoConfig,omitempty"` + // HostSubscription controls host subscription mounts independently of custom repositories. + // +optional + HostSubscription DriverHostSubscriptionSpec `json:"hostSubscription,omitempty"` + // Optional: Custom certificates configuration for NVIDIA Driver container // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="Custom Certificates Configuration For NVIDIA Driver Container" @@ -1279,6 +1283,20 @@ type NodeStatusExporterSpec struct { HostNetwork *bool `json:"hostNetwork,omitempty"` } +// DriverHostSubscriptionSpec controls host subscription mounts for the NVIDIA driver container. +// OS and runtime eligibility checks still apply when mounts are enabled. +type DriverHostSubscriptionSpec struct { + // Enabled defaults to true when omitted. Set to false for air-gapped hosts + // without subscription files, even when custom repositories are configured. + // +optional + Enabled *bool `json:"enabled,omitempty"` +} + +// IsEnabled preserves automatic host subscription mounts unless explicitly disabled. +func (h *DriverHostSubscriptionSpec) IsEnabled() bool { + return h.Enabled == nil || *h.Enabled +} + // DriverRepoConfigSpec defines custom repo configuration for NVIDIA Driver container type DriverRepoConfigSpec struct { // +kubebuilder:validation:Optional diff --git a/api/nvidia/v1/clusterpolicy_types_test.go b/api/nvidia/v1/clusterpolicy_types_test.go index 93b15c255..01ff5fd49 100644 --- a/api/nvidia/v1/clusterpolicy_types_test.go +++ b/api/nvidia/v1/clusterpolicy_types_test.go @@ -17,6 +17,7 @@ package v1 import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" @@ -86,3 +87,30 @@ func TestImagePath(t *testing.T) { assert.ErrorContains(t, err, "invalid nil spec") }) } + +func TestDriverHostSubscriptionSpec(t *testing.T) { + for name, tc := range map[string]struct { + input string + enabled bool + omitted bool + }{ + "omitted": {input: `{}`, enabled: true, omitted: true}, + "enabled": {input: `{"enabled":true}`, enabled: true}, + "disabled": {input: `{"enabled":false}`, enabled: false}, + } { + t.Run(name, func(t *testing.T) { + var spec DriverHostSubscriptionSpec + require.NoError(t, json.Unmarshal([]byte(tc.input), &spec)) + require.Equal(t, tc.enabled, spec.IsEnabled()) + require.Equal(t, tc.omitted, spec.Enabled == nil) + encoded, err := json.Marshal(spec) + require.NoError(t, err) + require.JSONEq(t, tc.input, string(encoded)) + copied := spec.DeepCopy() + if spec.Enabled != nil { + *copied.Enabled = !*copied.Enabled + require.Equal(t, tc.enabled, spec.IsEnabled()) + } + }) + } +} diff --git a/api/nvidia/v1/zz_generated.deepcopy.go b/api/nvidia/v1/zz_generated.deepcopy.go index 21fa99252..68fe24d89 100644 --- a/api/nvidia/v1/zz_generated.deepcopy.go +++ b/api/nvidia/v1/zz_generated.deepcopy.go @@ -632,6 +632,26 @@ func (in *DriverCertConfigSpec) DeepCopy() *DriverCertConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DriverHostSubscriptionSpec) DeepCopyInto(out *DriverHostSubscriptionSpec) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DriverHostSubscriptionSpec. +func (in *DriverHostSubscriptionSpec) DeepCopy() *DriverHostSubscriptionSpec { + if in == nil { + return nil + } + out := new(DriverHostSubscriptionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DriverLicensingConfigSpec) DeepCopyInto(out *DriverLicensingConfigSpec) { *out = *in @@ -766,6 +786,7 @@ func (in *DriverSpec) DeepCopyInto(out *DriverSpec) { *out = new(DriverRepoConfigSpec) **out = **in } + in.HostSubscription.DeepCopyInto(&out.HostSubscription) if in.CertConfig != nil { in, out := &in.CertConfig, &out.CertConfig *out = new(DriverCertConfigSpec) diff --git a/api/nvidia/v1alpha1/nvidiadriver_types.go b/api/nvidia/v1alpha1/nvidiadriver_types.go index 5cd6a985e..72c411e39 100644 --- a/api/nvidia/v1alpha1/nvidiadriver_types.go +++ b/api/nvidia/v1alpha1/nvidiadriver_types.go @@ -153,6 +153,10 @@ type NVIDIADriverSpec struct { // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="Custom Repo Configuration For NVIDIA Driver Container" RepoConfig *DriverRepoConfigSpec `json:"repoConfig,omitempty"` + // HostSubscription controls host subscription mounts independently of custom repositories. + // +optional + HostSubscription DriverHostSubscriptionSpec `json:"hostSubscription,omitempty"` + // Optional: Custom certificates configuration for NVIDIA Driver container // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="Custom Certificates Configuration For NVIDIA Driver Container" @@ -448,6 +452,20 @@ type DriverCertConfigSpec struct { Name string `json:"name,omitempty"` } +// DriverHostSubscriptionSpec controls host subscription mounts for the NVIDIA driver container. +// OS and runtime eligibility checks still apply when mounts are enabled. +type DriverHostSubscriptionSpec struct { + // Enabled defaults to true when omitted. Set to false for air-gapped hosts + // without subscription files, even when custom repositories are configured. + // +optional + Enabled *bool `json:"enabled,omitempty"` +} + +// IsEnabled preserves automatic host subscription mounts unless explicitly disabled. +func (h *DriverHostSubscriptionSpec) IsEnabled() bool { + return h.Enabled == nil || *h.Enabled +} + // DriverRepoConfigSpec defines custom repo configuration for NVIDIA Driver container type DriverRepoConfigSpec struct { // +kubebuilder:validation:Optional diff --git a/api/nvidia/v1alpha1/nvidiadriver_types_test.go b/api/nvidia/v1alpha1/nvidiadriver_types_test.go index 9f2574b37..0ec2ab270 100644 --- a/api/nvidia/v1alpha1/nvidiadriver_types_test.go +++ b/api/nvidia/v1alpha1/nvidiadriver_types_test.go @@ -17,6 +17,7 @@ package v1alpha1 import ( + "encoding/json" "testing" "github.com/stretchr/testify/require" @@ -416,3 +417,30 @@ func TestGDRCopyGetImagePath(t *testing.T) { }) } } + +func TestDriverHostSubscriptionSpec(t *testing.T) { + for name, tc := range map[string]struct { + input string + enabled bool + omitted bool + }{ + "omitted": {input: `{}`, enabled: true, omitted: true}, + "enabled": {input: `{"enabled":true}`, enabled: true}, + "disabled": {input: `{"enabled":false}`, enabled: false}, + } { + t.Run(name, func(t *testing.T) { + var spec DriverHostSubscriptionSpec + require.NoError(t, json.Unmarshal([]byte(tc.input), &spec)) + require.Equal(t, tc.enabled, spec.IsEnabled()) + require.Equal(t, tc.omitted, spec.Enabled == nil) + encoded, err := json.Marshal(spec) + require.NoError(t, err) + require.JSONEq(t, tc.input, string(encoded)) + copied := spec.DeepCopy() + if spec.Enabled != nil { + *copied.Enabled = !*copied.Enabled + require.Equal(t, tc.enabled, spec.IsEnabled()) + } + }) + } +} diff --git a/api/nvidia/v1alpha1/zz_generated.deepcopy.go b/api/nvidia/v1alpha1/zz_generated.deepcopy.go index c75265398..18b9778fe 100644 --- a/api/nvidia/v1alpha1/zz_generated.deepcopy.go +++ b/api/nvidia/v1alpha1/zz_generated.deepcopy.go @@ -205,6 +205,26 @@ func (in *DriverCertConfigSpec) DeepCopy() *DriverCertConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DriverHostSubscriptionSpec) DeepCopyInto(out *DriverHostSubscriptionSpec) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DriverHostSubscriptionSpec. +func (in *DriverHostSubscriptionSpec) DeepCopy() *DriverHostSubscriptionSpec { + if in == nil { + return nil + } + out := new(DriverHostSubscriptionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DriverLicensingConfigSpec) DeepCopyInto(out *DriverLicensingConfigSpec) { *out = *in @@ -677,6 +697,7 @@ func (in *NVIDIADriverSpec) DeepCopyInto(out *NVIDIADriverSpec) { *out = new(DriverRepoConfigSpec) **out = **in } + in.HostSubscription.DeepCopyInto(&out.HostSubscription) if in.CertConfig != nil { in, out := &in.CertConfig, &out.CertConfig *out = new(DriverCertConfigSpec) diff --git a/bundle/manifests/nvidia.com_clusterpolicies.yaml b/bundle/manifests/nvidia.com_clusterpolicies.yaml index d2c9a9a8b..360b1ad8f 100644 --- a/bundle/manifests/nvidia.com_clusterpolicies.yaml +++ b/bundle/manifests/nvidia.com_clusterpolicies.yaml @@ -1041,6 +1041,16 @@ spec: description: HostNetwork indicates whether the Driver pod uses the host's network namespace. type: boolean + hostSubscription: + description: HostSubscription controls host subscription mounts + independently of custom repositories. + properties: + enabled: + description: |- + Enabled defaults to true when omitted. Set to false for air-gapped hosts + without subscription files, even when custom repositories are configured. + type: boolean + type: object image: description: NVIDIA Driver image name pattern: '[a-zA-Z0-9\-]+' diff --git a/bundle/manifests/nvidia.com_nvidiadrivers.yaml b/bundle/manifests/nvidia.com_nvidiadrivers.yaml index 86993d6db..a32ce410b 100644 --- a/bundle/manifests/nvidia.com_nvidiadrivers.yaml +++ b/bundle/manifests/nvidia.com_nvidiadrivers.yaml @@ -208,6 +208,16 @@ spec: description: HostNetwork indicates whether the Driver pod uses the host's network namespace. type: boolean + hostSubscription: + description: HostSubscription controls host subscription mounts independently + of custom repositories. + properties: + enabled: + description: |- + Enabled defaults to true when omitted. Set to false for air-gapped hosts + without subscription files, even when custom repositories are configured. + type: boolean + type: object image: default: nvcr.io/nvidia/driver description: NVIDIA Driver container image name diff --git a/config/crd/bases/nvidia.com_clusterpolicies.yaml b/config/crd/bases/nvidia.com_clusterpolicies.yaml index d2c9a9a8b..360b1ad8f 100644 --- a/config/crd/bases/nvidia.com_clusterpolicies.yaml +++ b/config/crd/bases/nvidia.com_clusterpolicies.yaml @@ -1041,6 +1041,16 @@ spec: description: HostNetwork indicates whether the Driver pod uses the host's network namespace. type: boolean + hostSubscription: + description: HostSubscription controls host subscription mounts + independently of custom repositories. + properties: + enabled: + description: |- + Enabled defaults to true when omitted. Set to false for air-gapped hosts + without subscription files, even when custom repositories are configured. + type: boolean + type: object image: description: NVIDIA Driver image name pattern: '[a-zA-Z0-9\-]+' diff --git a/config/crd/bases/nvidia.com_nvidiadrivers.yaml b/config/crd/bases/nvidia.com_nvidiadrivers.yaml index 86993d6db..a32ce410b 100644 --- a/config/crd/bases/nvidia.com_nvidiadrivers.yaml +++ b/config/crd/bases/nvidia.com_nvidiadrivers.yaml @@ -208,6 +208,16 @@ spec: description: HostNetwork indicates whether the Driver pod uses the host's network namespace. type: boolean + hostSubscription: + description: HostSubscription controls host subscription mounts independently + of custom repositories. + properties: + enabled: + description: |- + Enabled defaults to true when omitted. Set to false for air-gapped hosts + without subscription files, even when custom repositories are configured. + type: boolean + type: object image: default: nvcr.io/nvidia/driver description: NVIDIA Driver container image name diff --git a/controllers/object_controls.go b/controllers/object_controls.go index 247e13e62..7f0b2cdcf 100644 --- a/controllers/object_controls.go +++ b/controllers/object_controls.go @@ -3778,8 +3778,8 @@ func transformDriverContainer(obj *appsv1.DaemonSet, config *gpuv1.ClusterPolicy // set up subscription entitlements for RHEL(using K8s with a non-CRIO runtime) and SLES if (osID == "rhel" && n.openshift == "" && n.runtime != gpuv1.CRIO) || osID == "sles" || osID == "sl-micro" { pathToVolumeSource := MountPathToVolumeSource{} - if config.Driver.RepoConfig != nil && config.Driver.RepoConfig.ConfigMapName != "" && osID == "rhel" { - n.logger.Info("Skipping host subscription mounts because repoConfig is enabled", "OS", osID) + if !config.Driver.HostSubscription.IsEnabled() { + n.logger.Info("Skipping host subscription mounts because hostSubscription is disabled", "OS", osID) } else { n.logger.Info("Mounting subscriptions into the driver container", "OS", osID) pathToVolumeSource, err = n.getSubscriptionPathsToVolumeSources() diff --git a/controllers/transforms_test.go b/controllers/transforms_test.go index 11d5b9633..b67473cc4 100644 --- a/controllers/transforms_test.go +++ b/controllers/transforms_test.go @@ -17,6 +17,7 @@ package controllers import ( + "fmt" "os" "path" "path/filepath" @@ -4769,7 +4770,7 @@ func TestTransformDriverSubscriptionMounts(t *testing.T) { Namespace: "test-ns", }, Data: map[string]string{ - "redhat.repo": "[test-repo]", + "custom.repo": "[test-repo]", }, } mockClient := fake.NewFakeClient(repoConfigMap) @@ -4778,67 +4779,120 @@ func TestTransformDriverSubscriptionMounts(t *testing.T) { description string osRelease string osTag string - repoConfigEnabled bool + runtime string expectSubscriptionMounts bool expectedSubscriptionHostMap map[string]corev1.HostPathType }{ { - description: "rhel with repo config skips host subscription mounts", + description: "rhel/containerd/openshift=", osRelease: "rhel", + runtime: "containerd", osTag: "rhel8.10", - repoConfigEnabled: true, - expectSubscriptionMounts: false, + expectSubscriptionMounts: true, + expectedSubscriptionHostMap: map[string]corev1.HostPathType{ + "/etc/pki/entitlement": corev1.HostPathDirectory, + "/etc/yum.repos.d/redhat.repo": corev1.HostPathFile, + "/etc/rhsm": corev1.HostPathDirectory, + }, }, { - description: "rhel without repo config mounts host subscription paths", + description: "rhel/crio/openshift=", osRelease: "rhel", + runtime: "crio", osTag: "rhel8.10", + expectSubscriptionMounts: false, + }, + { + description: "ubuntu/containerd/openshift=", + osRelease: "ubuntu", + runtime: "containerd", + osTag: "ubuntu22.04", + expectSubscriptionMounts: false, + }, + { + description: "sles/containerd/openshift=", + osRelease: "sles", + runtime: "containerd", + osTag: "sles15.6", expectSubscriptionMounts: true, expectedSubscriptionHostMap: map[string]corev1.HostPathType{ - "/etc/pki/entitlement": corev1.HostPathDirectory, - "/etc/yum.repos.d/redhat.repo": corev1.HostPathFile, - "/etc/rhsm": corev1.HostPathDirectory, + "/etc/zypp/credentials.d": corev1.HostPathDirectory, + "/etc/SUSEConnect": corev1.HostPathFileOrCreate, + }, + }, + { + description: "sl-micro/containerd/openshift=", + osRelease: "sl-micro", + runtime: "containerd", + osTag: "sl-micro6.1", + expectSubscriptionMounts: true, + expectedSubscriptionHostMap: map[string]corev1.HostPathType{ + "/etc/zypp/credentials.d": corev1.HostPathDirectory, + "/etc/SUSEConnect": corev1.HostPathFileOrCreate, }, }, } for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - ds := NewDaemonset().WithContainer(corev1.Container{Name: "nvidia-driver-ctr"}). - WithInitContainer(corev1.Container{Name: "k8s-driver-manager"}) - cpSpec := &gpuv1.ClusterPolicySpec{ - Driver: gpuv1.DriverSpec{ - Repository: "nvcr.io/nvidia", - Image: "driver", - ImagePullPolicy: "IfNotPresent", - Version: "580.126.16", - Manager: gpuv1.DriverManagerSpec{ - Repository: "nvcr.io/nvidia/cloud-native", - Image: "k8s-driver-manager", - ImagePullPolicy: "IfNotPresent", - Version: "v0.8.0", - }, - }, - } - if tc.repoConfigEnabled { - cpSpec.Driver.RepoConfig = &gpuv1.DriverRepoConfigSpec{ConfigMapName: "test-repo-config"} + for mode, enabled := range map[string]*bool{"omitted": nil, "enabled": new(true), "disabled": new(false)} { + for _, repoEnabled := range []bool{false, true} { + t.Run(fmt.Sprintf("%s/%s/repo=%t", tc.description, mode, repoEnabled), func(t *testing.T) { + ds := NewDaemonset().WithContainer(corev1.Container{Name: "nvidia-driver-ctr"}). + WithInitContainer(corev1.Container{Name: "k8s-driver-manager"}) + cpSpec := &gpuv1.ClusterPolicySpec{ + Driver: gpuv1.DriverSpec{ + Repository: "nvcr.io/nvidia", + Image: "driver", + ImagePullPolicy: "IfNotPresent", + Version: "580.126.16", + Manager: gpuv1.DriverManagerSpec{ + Repository: "nvcr.io/nvidia/cloud-native", + Image: "k8s-driver-manager", + ImagePullPolicy: "IfNotPresent", + Version: "v0.8.0", + }, + }, + } + if repoEnabled { + cpSpec.Driver.RepoConfig = &gpuv1.DriverRepoConfigSpec{ConfigMapName: "test-repo-config"} + } + + cpSpec.Driver.HostSubscription.Enabled = enabled + + err := TransformDriver(ds.DaemonSet, cpSpec, ClusterPolicyController{ + client: mockClient, + runtime: gpuv1.Runtime(tc.runtime), + operatorNamespace: "test-ns", + logger: ctrl.Log.WithName("test"), + gpuNodeOSRelease: tc.osRelease, + gpuNodeOSTag: tc.osTag, + }) + require.NoError(t, err) + + driverContainer := findContainerByName(ds.Spec.Template.Spec.Containers, "nvidia-driver-ctr") + require.NotNil(t, driverContainer) + expectedPaths := tc.expectedSubscriptionHostMap + expectMounts := tc.expectSubscriptionMounts + if enabled != nil && !*enabled { + expectedPaths = nil + expectMounts = false + } + assertSubscriptionHostPathVolumesForTransform(t, ds.Spec.Template.Spec.Volumes, expectedPaths) + repoMounted := false + for _, mount := range driverContainer.VolumeMounts { + if strings.HasPrefix(mount.Name, "subscription-config-") { + require.True(t, mount.ReadOnly) + } + if mount.SubPath == "custom.repo" { + repoMounted = true + } + } + assert.Equal(t, repoEnabled, repoMounted) + + assert.Equal(t, expectMounts, hasSubscriptionVolumeMountForTransform(driverContainer.VolumeMounts)) + }) } - - err := TransformDriver(ds.DaemonSet, cpSpec, ClusterPolicyController{ - client: mockClient, - runtime: gpuv1.Containerd, - operatorNamespace: "test-ns", - logger: ctrl.Log.WithName("test"), - gpuNodeOSRelease: tc.osRelease, - gpuNodeOSTag: tc.osTag, - }) - require.NoError(t, err) - - driverContainer := findContainerByName(ds.Spec.Template.Spec.Containers, "nvidia-driver-ctr") - require.NotNil(t, driverContainer) - assertSubscriptionHostPathVolumesForTransform(t, ds.Spec.Template.Spec.Volumes, tc.expectedSubscriptionHostMap) - assert.Equal(t, tc.expectSubscriptionMounts, hasSubscriptionVolumeMountForTransform(driverContainer.VolumeMounts)) - }) + } } } diff --git a/deployments/gpu-operator/crds/nvidia.com_clusterpolicies.yaml b/deployments/gpu-operator/crds/nvidia.com_clusterpolicies.yaml index d2c9a9a8b..360b1ad8f 100644 --- a/deployments/gpu-operator/crds/nvidia.com_clusterpolicies.yaml +++ b/deployments/gpu-operator/crds/nvidia.com_clusterpolicies.yaml @@ -1041,6 +1041,16 @@ spec: description: HostNetwork indicates whether the Driver pod uses the host's network namespace. type: boolean + hostSubscription: + description: HostSubscription controls host subscription mounts + independently of custom repositories. + properties: + enabled: + description: |- + Enabled defaults to true when omitted. Set to false for air-gapped hosts + without subscription files, even when custom repositories are configured. + type: boolean + type: object image: description: NVIDIA Driver image name pattern: '[a-zA-Z0-9\-]+' diff --git a/deployments/gpu-operator/crds/nvidia.com_nvidiadrivers.yaml b/deployments/gpu-operator/crds/nvidia.com_nvidiadrivers.yaml index 86993d6db..a32ce410b 100644 --- a/deployments/gpu-operator/crds/nvidia.com_nvidiadrivers.yaml +++ b/deployments/gpu-operator/crds/nvidia.com_nvidiadrivers.yaml @@ -208,6 +208,16 @@ spec: description: HostNetwork indicates whether the Driver pod uses the host's network namespace. type: boolean + hostSubscription: + description: HostSubscription controls host subscription mounts independently + of custom repositories. + properties: + enabled: + description: |- + Enabled defaults to true when omitted. Set to false for air-gapped hosts + without subscription files, even when custom repositories are configured. + type: boolean + type: object image: default: nvcr.io/nvidia/driver description: NVIDIA Driver container image name diff --git a/deployments/gpu-operator/templates/clusterpolicy.yaml b/deployments/gpu-operator/templates/clusterpolicy.yaml index 8dcbdecab..9dc293fde 100644 --- a/deployments/gpu-operator/templates/clusterpolicy.yaml +++ b/deployments/gpu-operator/templates/clusterpolicy.yaml @@ -202,6 +202,9 @@ spec: {{- if .Values.driver.repoConfig }} repoConfig: {{ toYaml .Values.driver.repoConfig | nindent 6 }} {{- end }} + {{- if .Values.driver.hostSubscription }} + hostSubscription: {{ toYaml .Values.driver.hostSubscription | nindent 6 }} + {{- end }} {{- if .Values.driver.certConfig }} certConfig: {{ toYaml .Values.driver.certConfig | nindent 6 }} {{- end }} diff --git a/deployments/gpu-operator/values.yaml b/deployments/gpu-operator/values.yaml index 203b505d9..16fa791a6 100644 --- a/deployments/gpu-operator/values.yaml +++ b/deployments/gpu-operator/values.yaml @@ -239,6 +239,10 @@ driver: # Private mirror repository configuration repoConfig: configMapName: "" + # Mount host subscription files on eligible OS/runtime combinations, including with repoConfig. + # Set to false for air-gapped hosts without subscription files. + hostSubscription: + enabled: true # custom ssl key/certificate configuration certConfig: name: "" diff --git a/internal/state/driver_test.go b/internal/state/driver_test.go index 9a06c9718..fb73ee67c 100644 --- a/internal/state/driver_test.go +++ b/internal/state/driver_test.go @@ -19,6 +19,7 @@ package state import ( "bytes" "context" + "fmt" "os" "path/filepath" "strings" @@ -628,59 +629,122 @@ func TestDriverAdditionalConfigsSubscriptionMounts(t *testing.T) { Namespace: "test-ns", }, Data: map[string]string{ - "redhat.repo": "[test-repo]", + "custom.repo": "[test-repo]", }, } testCases := []struct { description string osRelease string - repoConfigEnabled bool + runtime string + openshift string expectSubscriptionMounts bool expectedSubscriptionHostMap map[string]corev1.HostPathType }{ { - description: "rhel with repo config skips host subscription mounts", + description: "rhel/containerd/openshift=", osRelease: "rhel", - repoConfigEnabled: true, + runtime: "containerd", + openshift: "", + expectSubscriptionMounts: true, + expectedSubscriptionHostMap: map[string]corev1.HostPathType{ + "/etc/pki/entitlement": corev1.HostPathDirectory, + "/etc/yum.repos.d/redhat.repo": corev1.HostPathFile, + "/etc/rhsm": corev1.HostPathDirectory, + }, + }, + { + description: "rhel/crio/openshift=", + osRelease: "rhel", + runtime: "crio", + openshift: "", expectSubscriptionMounts: false, }, { - description: "rhel without repo config mounts host subscription paths", + description: "rhel/containerd/openshift=4.18", osRelease: "rhel", + runtime: "containerd", + openshift: "4.18", + expectSubscriptionMounts: false, + }, + { + description: "ubuntu/containerd/openshift=", + osRelease: "ubuntu", + runtime: "containerd", + openshift: "", + expectSubscriptionMounts: false, + }, + { + description: "sles/containerd/openshift=", + osRelease: "sles", + runtime: "containerd", + openshift: "", expectSubscriptionMounts: true, expectedSubscriptionHostMap: map[string]corev1.HostPathType{ - "/etc/pki/entitlement": corev1.HostPathDirectory, - "/etc/yum.repos.d/redhat.repo": corev1.HostPathFile, - "/etc/rhsm": corev1.HostPathDirectory, + "/etc/zypp/credentials.d": corev1.HostPathDirectory, + "/etc/SUSEConnect": corev1.HostPathFileOrCreate, + }, + }, + { + description: "sl-micro/containerd/openshift=", + osRelease: "sl-micro", + runtime: "containerd", + openshift: "", + expectSubscriptionMounts: true, + expectedSubscriptionHostMap: map[string]corev1.HostPathType{ + "/etc/zypp/credentials.d": corev1.HostPathDirectory, + "/etc/SUSEConnect": corev1.HostPathFileOrCreate, }, }, } for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - stateDriver := &stateDriver{ - stateSkel: stateSkel{ - client: fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(repoConfigMap).Build(), - namespace: "test-ns", - }, - } - driver := &nvidiav1alpha1.NVIDIADriver{} - if tc.repoConfigEnabled { - driver.Spec.RepoConfig = &nvidiav1alpha1.DriverRepoConfigSpec{Name: "test-repo-config"} + for mode, enabled := range map[string]*bool{"omitted": nil, "enabled": new(true), "disabled": new(false)} { + for _, repoEnabled := range []bool{false, true} { + t.Run(fmt.Sprintf("%s/%s/repo=%t", tc.description, mode, repoEnabled), func(t *testing.T) { + stateDriver := &stateDriver{ + stateSkel: stateSkel{ + client: fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(repoConfigMap).Build(), + namespace: "test-ns", + }, + } + driver := &nvidiav1alpha1.NVIDIADriver{} + if repoEnabled { + driver.Spec.RepoConfig = &nvidiav1alpha1.DriverRepoConfigSpec{Name: "test-repo-config"} + } + + driver.Spec.HostSubscription.Enabled = enabled + + configs, err := stateDriver.getDriverAdditionalConfigs( + context.Background(), + driver, + testClusterInfo{runtime: tc.runtime, openshiftVersion: tc.openshift}, + nodePool{osRelease: tc.osRelease, osVersion: tc.osRelease}, + ) + require.NoError(t, err) + + expectedPaths := tc.expectedSubscriptionHostMap + expectMounts := tc.expectSubscriptionMounts + if enabled != nil && !*enabled { + expectedPaths = nil + expectMounts = false + } + assertSubscriptionHostPathVolumes(t, configs.Volumes, expectedPaths) + repoMounted := false + for _, mount := range configs.VolumeMounts { + if strings.HasPrefix(mount.Name, "subscription-config-") { + require.True(t, mount.ReadOnly) + } + if mount.SubPath == "custom.repo" { + repoMounted = true + } + } + assert.Equal(t, repoEnabled, repoMounted) + + assert.Equal(t, expectMounts, hasSubscriptionVolumeMount(configs.VolumeMounts)) + }) } - - configs, err := stateDriver.getDriverAdditionalConfigs( - context.Background(), - driver, - testClusterInfo{runtime: consts.Containerd}, - nodePool{osRelease: tc.osRelease, osVersion: tc.osRelease}, - ) - require.NoError(t, err) - - assertSubscriptionHostPathVolumes(t, configs.Volumes, tc.expectedSubscriptionHostMap) - assert.Equal(t, tc.expectSubscriptionMounts, hasSubscriptionVolumeMount(configs.VolumeMounts)) - }) + } } } diff --git a/internal/state/driver_volumes.go b/internal/state/driver_volumes.go index 34f60ea60..d4eefad08 100644 --- a/internal/state/driver_volumes.go +++ b/internal/state/driver_volumes.go @@ -185,12 +185,8 @@ func (s *stateDriver) getDriverAdditionalConfigs(ctx context.Context, cr *v1alph // set up subscription entitlements for RHEL(using K8s with a non-CRIO runtime) and SLES if (pool.osRelease == "rhel" && openshiftVersion == "" && runtime != consts.CRIO) || pool.osRelease == "sles" || pool.osRelease == "sl-micro" { pathToVolumeSource := MountPathToVolumeSource{} - // Custom repo ConfigMap supplies yum repos in offline/air-gapped installs. Skip - // mounting host RHSM paths (/etc/pki/entitlement, redhat.repo, /etc/rhsm): they may - // be missing or not directories on minimal nodes, and are not needed when packages - // come only from the mounted repo ConfigMap. - if cr.Spec.IsRepoConfigEnabled() && pool.osRelease == "rhel" { - logger.Info("Skipping host subscription mounts because repoConfig is enabled", "OS", pool.osVersion) + if !cr.Spec.HostSubscription.IsEnabled() { + logger.Info("Skipping host subscription mounts because hostSubscription is disabled", "OS", pool.osVersion) } else { logger.Info("Mounting subscriptions into the driver container", "OS", pool.osVersion) pathToVolumeSource, err = getSubscriptionPathsToVolumeSources(pool.osRelease)