diff --git a/internal/kmmmodule/dockerfiles/DockerfileTemplate.sles b/internal/kmmmodule/dockerfiles/DockerfileTemplate.sles new file mode 100644 index 00000000..9fc6abd3 --- /dev/null +++ b/internal/kmmmodule/dockerfiles/DockerfileTemplate.sles @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: MIT +# +# Uses a SUSE built AMD GPU driver image as source, avoiding the need +# for a SUSEConnect registration key. modules.dep uses relative paths so it is +# valid for any kernel version in the same codestream (stable kABI). +# +# Build args: +# KERNEL_FULL_VERSION - target kernel version, e.g. "6.4.0-150700.53.25-default" +# SUSE_PREBUILT_DRIVER_IMG - SUSE-published driver image (built against GA kernel) + +ARG SUSE_PREBUILT_DRIVER_IMG + +FROM ${SUSE_PREBUILT_DRIVER_IMG} AS driver-source + +FROM $$BASEIMG_REGISTRY/bci/bci-micro:$$VERSION + +ARG KERNEL_FULL_VERSION + +# Relocate modules, kernel tree and depmod metadata from the GA kernel path to +# the target kernel version path; no depmod re-run needed (relative paths). +COPY --from=driver-source /opt/lib/modules/ /opt/lib/modules-prebuilt/ +RUN set -euo pipefail; \ + GA_KERNEL=$(ls /opt/lib/modules-prebuilt/ | head -1); \ + mkdir -p /opt/lib/modules/${KERNEL_FULL_VERSION}/updates/dkms; \ + cp /opt/lib/modules-prebuilt/${GA_KERNEL}/updates/dkms/amd* \ + /opt/lib/modules/${KERNEL_FULL_VERSION}/updates/dkms/; \ + cp /opt/lib/modules-prebuilt/${GA_KERNEL}/modules.* \ + /opt/lib/modules/${KERNEL_FULL_VERSION}/; \ + cp -r /opt/lib/modules-prebuilt/${GA_KERNEL}/kernel \ + /opt/lib/modules/${KERNEL_FULL_VERSION}/kernel; \ + rm -rf /opt/lib/modules-prebuilt + +RUN mkdir -p /firmwareDir/updates/amdgpu +COPY --from=driver-source /firmwareDir/updates/amdgpu /firmwareDir/updates/amdgpu diff --git a/internal/kmmmodule/kmmmodule.go b/internal/kmmmodule/kmmmodule.go index ea4f600a..838d9b15 100644 --- a/internal/kmmmodule/kmmmodule.go +++ b/internal/kmmmodule/kmmmodule.go @@ -85,6 +85,8 @@ var ( dockerfileTemplateCoreOSFromSrcImage string //go:embed dockerfiles/DockerfileTemplate.rpm.coreos dockerfileTemplateCoreOSFromRPM string + //go:embed dockerfiles/DockerfileTemplate.sles + dockerfileTemplateSLES string //go:embed devdockerfiles/devdockerfile.txt dockerfileDevTemplateUbuntu string //go:embed dockerfiles/DockerfileTemplate.ubuntu.gim @@ -93,6 +95,14 @@ var ( dockerfileTemplateGIMCoreOS string ) +// slesCSDPrebuiltDriverImages maps SLES codestream version -> driver version -> prebuilt image. +// Tag format: sles--- +var slesCSDPrebuiltDriverImages = map[string]map[string]string{ + "15.7": { + "7.0.3": "registry.suse.com/third-party/amd/amdgpu-driver:sles-15.7-7.0.3", + }, +} + //go:generate mockgen -source=kmmmodule.go -package=kmmmodule -destination=mock_kmmmodule.go KMMModuleAPI type KMMModuleAPI interface { SetNodeVersionLabelAsDesired(ctx context.Context, devConfig *amdv1alpha1.DeviceConfig, nodes *v1.NodeList) error @@ -290,6 +300,8 @@ func resolveDockerfile(cmName string, devConfig *amdv1alpha1.DeviceConfig) (stri dockerfileTemplate = dockerfileTemplateCoreOSFromSrcImage } } + case "sles": + dockerfileTemplate = dockerfileTemplateSLES // FIX ME // add the RHEL back when it is fully supported /*case "rhel": @@ -308,7 +320,11 @@ func resolveDockerfile(cmName string, devConfig *amdv1alpha1.DeviceConfig) (stri // render base image registry baseImageRegistry := defaultBaseImageRegistry if devConfig.Spec.Driver.ImageBuild.BaseImageRegistry != "" { + // user-specified registry takes precedence baseImageRegistry = devConfig.Spec.Driver.ImageBuild.BaseImageRegistry + } else if osDistro == "sles" { + // if OS == "sles", use default image registry as "registry.suse.com" + baseImageRegistry = "registry.suse.com" } dockerfileTemplate = strings.Replace(dockerfileTemplate, "$$BASEIMG_REGISTRY", baseImageRegistry, -1) // render driver version @@ -564,6 +580,25 @@ func getKM(devConfig *amdv1alpha1.DeviceConfig, node v1.Node, inTreeModuleToRemo ) } + // Inject SUSE_PREBUILT_DRIVER_IMG build arg for SLES nodes. + if strings.HasPrefix(osName, "sles-") { + csVersion := strings.TrimPrefix(osName, "sles-") // e.g. "15.7" + driverVersions, ok := slesCSDPrebuiltDriverImages[csVersion] + if !ok { + return kmmv1beta1.KernelMapping{}, "", fmt.Errorf("no prebuilt driver image registered for SLES codestream %q", csVersion) + } + prebuiltImg, ok := driverVersions[driversVersion] + if !ok { + return kmmv1beta1.KernelMapping{}, "", fmt.Errorf("no prebuilt driver image registered for SLES codestream %q with driver version %q", csVersion, driversVersion) + } + kmmBuild.BuildArgs = append(kmmBuild.BuildArgs, + kmmv1beta1.BuildArg{ + Name: "SUSE_PREBUILT_DRIVER_IMG", + Value: prebuiltImg, + }, + ) + } + // trim suffix "+" to handle the dirty build kernel version // e.g., "5.15.0-76-generic+" // on KMM side it is trimming the suffix "+" to read kernel mapping @@ -624,6 +659,8 @@ var cmNameMappers = map[string]func(fullImageStr string) string{ "rhel": rhelCMNameMapper, "red hat": rhelCMNameMapper, "redhat": rhelCMNameMapper, + "sles": slesCMNameMapper, + "suse": slesCMNameMapper, } func rhelCMNameMapper(osImageStr string) string { @@ -657,6 +694,19 @@ func ubuntuCMNameMapper(osImageStr string) string { return fmt.Sprintf("%s-%s", os, trimmedVersion) } +func slesCMNameMapper(osImageStr string) string { + // Example: "SUSE Linux Enterprise Server 15 SP7" -> "sles-15.7" + // Example: "suse linux enterprise server 15-sp7" -> "sles-15.7" + // Convert to lowercase for consistent matching + osImageLower := strings.ToLower(osImageStr) + re := regexp.MustCompile(`(\d+)\s*-?\s*sp(\d+)`) + matches := re.FindStringSubmatch(osImageLower) + if len(matches) >= 3 { + return fmt.Sprintf("sles-%s.%s", matches[1], matches[2]) + } + return "sles-" + osImageLower +} + func GetK8SNodes(ctx context.Context, cli client.Client, labelSelector labels.Selector) (*v1.NodeList, error) { options := &client.ListOptions{ LabelSelector: labelSelector, diff --git a/internal/kmmmodule/kmmmodule_test.go b/internal/kmmmodule/kmmmodule_test.go index 67f17f76..8c5bd1b1 100644 --- a/internal/kmmmodule/kmmmodule_test.go +++ b/internal/kmmmodule/kmmmodule_test.go @@ -732,3 +732,60 @@ var _ = Describe("getKernelMappings", func() { } }) }) + +var _ = Describe("resolveDockerfile", func() { + It("should use correct default registry when not specified by user", func() { + testCases := []struct { + cmName string + expectedImageUrl string + }{ + {"ubuntu-22.04", "docker.io/ubuntu:22.04"}, + {"sles-15.6", "registry.suse.com/bci/bci-micro:15.6"}, + } + for _, tc := range testCases { + input := &amdv1alpha1.DeviceConfig{ + Spec: amdv1alpha1.DeviceConfigSpec{ + Driver: amdv1alpha1.DriverSpec{}, + }, + } + dockerfile, err := resolveDockerfile(tc.cmName, input) + Expect(err).To(BeNil()) + Expect(dockerfile).To(ContainSubstring(tc.expectedImageUrl)) + } + }) + It("should respect user-specified BaseImageRegistry for all OS types", func() { + testCases := []struct { + cmName string + expectedImageUrl string + }{ + {"ubuntu-22.04", "example-image-registry.com/ubuntu:22.04"}, + {"sles-15.6", "example-image-registry.com/bci/bci-micro:15.6"}, + } + for _, tc := range testCases { + input := &amdv1alpha1.DeviceConfig{ + Spec: amdv1alpha1.DeviceConfigSpec{ + Driver: amdv1alpha1.DriverSpec{ + ImageBuild: amdv1alpha1.ImageBuildSpec{ + BaseImageRegistry: "example-image-registry.com", + }, + }, + }, + } + dockerfile, err := resolveDockerfile(tc.cmName, input) + Expect(err).To(BeNil()) + Expect(dockerfile).To(ContainSubstring(tc.expectedImageUrl)) + Expect(dockerfile).NotTo(ContainSubstring("docker.io")) + Expect(dockerfile).NotTo(ContainSubstring("registry.suse.com")) + } + }) + It("should return error for unsupported OS", func() { + input := &amdv1alpha1.DeviceConfig{ + Spec: amdv1alpha1.DeviceConfigSpec{ + Driver: amdv1alpha1.DriverSpec{}, + }, + } + _, err := resolveDockerfile("unsupported-os", input) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("not supported OS")) + }) +}) diff --git a/internal/utils.go b/internal/utils.go index 0ff2e4ed..0ed7eb76 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -198,6 +198,8 @@ var defaultDriverversionsMappers = map[string]func(fullImageStr string) (string, "red hat": func(f string) (string, error) { return defaultOcDriversVersion, nil }, + "sles": SLESDefaultDriverVersionsMapper, + "suse": SLESDefaultDriverVersionsMapper, } func UbuntuDefaultDriverVersionsMapper(fullImageStr string) (string, error) { @@ -232,6 +234,21 @@ func UbuntuDefaultDriverVersionsMapper(fullImageStr string) (string, error) { return "", fmt.Errorf("unsupported Ubuntu version: %s. Supported versions include 20.04, 22.04 and 24.04", fullImageStr) } +var slesSPRegexp = regexp.MustCompile(`15\s*-?\s*sp(\d+)`) + +func SLESDefaultDriverVersionsMapper(fullImageStr string) (string, error) { + if strings.Contains(fullImageStr, "15") { + match := slesSPRegexp.FindStringSubmatch(strings.ToLower(fullImageStr)) + if len(match) > 1 { + spVersion, err := strconv.Atoi(match[1]) + if err == nil && spVersion >= 7 { + return "7.0.3", nil // Latest stable version for SP7+ + } + } + } + return "", fmt.Errorf("unsupported SLES version: %s. Supported versions include SLES 15 SP7 and above", fullImageStr) +} + func HasNodeLabelKey(node v1.Node, labelKey string) bool { for k := range node.Labels { if k == labelKey { diff --git a/internal/utils_test.go b/internal/utils_test.go index b9c36085..6d1584a7 100644 --- a/internal/utils_test.go +++ b/internal/utils_test.go @@ -364,3 +364,50 @@ func TestUbuntuDefaultDriverVersionsMapper(t *testing.T) { }) } } + +func TestSLESDefaultDriverVersionsMapper(t *testing.T) { + tests := []struct { + name string + osImage string + expected string + wantErr bool + }{ + { + name: "SLES 15 SP7", + osImage: "SUSE Linux Enterprise Server 15 SP7", + expected: "7.0.3", + wantErr: false, + }, + { + name: "SLES 15 SP7 with dash format", + osImage: "sles 15-sp7", + expected: "7.0.3", + wantErr: false, + }, + { + name: "SLES 15 SP6 unsupported", + osImage: "SUSE Linux Enterprise Server 15 SP6", + wantErr: true, + }, + { + name: "SLES 15 base unsupported", + osImage: "SUSE Linux Enterprise Server 15", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := SLESDefaultDriverVersionsMapper(tt.osImage) + + if (err != nil) != tt.wantErr { + t.Errorf("SLESDefaultDriverVersionsMapper() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if result != tt.expected { + t.Errorf("SLESDefaultDriverVersionsMapper() = %q, want %q", result, tt.expected) + } + }) + } +}