diff --git a/.gitignore b/.gitignore index 4562ecc76..ba43b72af 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ TODO # GitGuardian .cache_ggshield .gitguardian.yaml + +# AI coding agents +# Claude +.claude/ +/skills-lock.json diff --git a/pkg/controller/worker/machines.go b/pkg/controller/worker/machines.go index 9c7b807f5..1f061639e 100644 --- a/pkg/controller/worker/machines.go +++ b/pkg/controller/worker/machines.go @@ -210,6 +210,20 @@ func (w *WorkerDelegate) generateMachineConfig(ctx context.Context) error { // specifying the volume type requires a custom volume size to be specified too. if pool.Volume != nil && pool.Volume.Type != nil { machineClassSpec["rootDiskType"] = *pool.Volume.Type + } else if machineTypeFromCloudProfile.Storage != nil && + machineTypeFromCloudProfile.Storage.Type != "" && + machineTypeFromCloudProfile.Storage.Type != "default" { + // Use the storage type from the cloud profile as the default if not explicitly set in the shoot spec. + // This is required e.g. for KVM machines that need a "premium" disk type for boot disks. + machineClassSpec["rootDiskType"] = machineTypeFromCloudProfile.Storage.Type + if machineTypeFromCloudProfile.Storage.StorageSize != nil { + cloudProfileVolumeSize, err := worker.DiskSize(machineTypeFromCloudProfile.Storage.StorageSize.String()) + if err == nil && cloudProfileVolumeSize > 0 { + if _, alreadySet := machineClassSpec["rootDiskSize"]; !alreadySet { + machineClassSpec["rootDiskSize"] = cloudProfileVolumeSize + } + } + } } if machineImage.ID != "" { diff --git a/pkg/controller/worker/machines_test.go b/pkg/controller/worker/machines_test.go index 76482c930..4310cc116 100644 --- a/pkg/controller/worker/machines_test.go +++ b/pkg/controller/worker/machines_test.go @@ -6,6 +6,7 @@ package worker_test import ( "context" + "embed" "encoding/json" "fmt" "path/filepath" @@ -1374,6 +1375,69 @@ var _ = Describe("Machines", func() { Expect(result[1].ClusterAutoscalerAnnotations[extensionsv1alpha1.ScaleDownUnreadyTimeAnnotation]).To(Equal("3m0s")) Expect(result[1].ClusterAutoscalerAnnotations[extensionsv1alpha1.ScaleDownUtilizationThresholdAnnotation]).To(Equal("0.5")) }) + + It("should use storage type and size from cloud profile machine type as default when no pool volume is specified", func() { + premiumStorageSize := resource.MustParse("64Gi") + clusterWithPremiumMachineType := &extensionscontroller.Cluster{ + CloudProfile: cluster.CloudProfile.DeepCopy(), + Shoot: cluster.Shoot, + Seed: cluster.Seed, + } + clusterWithPremiumMachineType.CloudProfile.Spec.MachineTypes = []gardencorev1beta1.MachineType{ + { + Name: machineType, + Capabilities: capabilitiesAmd, + Storage: &gardencorev1beta1.MachineTypeStorage{ + Class: "standard", + StorageSize: &premiumStorageSize, + Type: "premium", + }, + }, + { + Name: machineTypeArm, + Architecture: ptr.To(archARM), + Capabilities: capabilitiesArm, + }, + } + + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, w, clusterWithPremiumMachineType, nil) + + var capturedMachineClasses []map[string]interface{} + expectedUserDataSecretRefRead() + chartApplier. + EXPECT(). + ApplyFromEmbeddedFS( + ctx, + charts.InternalChart, + filepath.Join("internal", "machineclass"), + namespace, + "machineclass", + gomock.AssignableToTypeOf(kubernetes.Values(nil)), + ). + DoAndReturn(func(_ context.Context, _ embed.FS, _, _, _ string, opts ...kubernetes.ApplyOption) error { + applyOpts := &kubernetes.ApplyOptions{} + for _, o := range opts { + o.MutateApplyOptions(applyOpts) + } + if values, ok := applyOpts.Values.(map[string]interface{}); ok { + if classes, ok := values["machineClasses"].([]map[string]interface{}); ok { + capturedMachineClasses = classes + } + } + return nil + }) + + err := workerDelegate.DeployMachineClasses(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(capturedMachineClasses).NotTo(BeEmpty()) + for _, class := range capturedMachineClasses { + if class["machineType"] == machineType { + Expect(class).To(HaveKeyWithValue("rootDiskType", "premium"), "expected rootDiskType to be set from cloud profile storage type") + Expect(class).To(HaveKeyWithValue("rootDiskSize", 64), "expected rootDiskSize to be set from cloud profile storage size") + } + } + }) }, Entry("with capabilities and using imageIDs", true, false), Entry("with capabilities and using ImageNames", true, true),