diff --git a/pkg/controller/worker/actuator.go b/pkg/controller/worker/actuator.go index be22a2f5..7076da2a 100644 --- a/pkg/controller/worker/actuator.go +++ b/pkg/controller/worker/actuator.go @@ -25,6 +25,8 @@ import ( "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/helper" stackitv1alpha1 "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/v1alpha1" openstackclient "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/openstack/client" + "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit" + stackitclient "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit/client" ) type delegateFactory struct { @@ -71,6 +73,8 @@ func (d *delegateFactory) WorkerDelegate(ctx context.Context, worker *extensions return nil, err } + stackitClient := stackitclient.New(stackit.DetermineRegion(cluster), cluster) + return NewWorkerDelegate( d.seedClient, d.scheme, @@ -81,6 +85,7 @@ func (d *delegateFactory) WorkerDelegate(ctx context.Context, worker *extensions worker, cluster, d.customLabelDomain, + stackitClient, ) } @@ -102,6 +107,7 @@ type workerDelegate struct { machineImages []stackitv1alpha1.MachineImage openstackClient openstackclient.Factory + stackitClient stackitclient.Factory } // NewWorkerDelegate creates a new context for a worker reconciliation. @@ -115,6 +121,7 @@ func NewWorkerDelegate( worker *extensionsv1alpha1.Worker, cluster *extensionscontroller.Cluster, customLabelDomain string, + stackitClient stackitclient.Factory, ) (genericactuator.WorkerDelegate, error) { config, err := helper.CloudProfileConfigFromCluster(cluster) if err != nil { @@ -133,5 +140,6 @@ func NewWorkerDelegate( cluster: cluster, worker: worker, customLabelDomain: customLabelDomain, + stackitClient: stackitClient, }, nil } diff --git a/pkg/controller/worker/machines.go b/pkg/controller/worker/machines.go index c81ba5fb..bacc71e0 100644 --- a/pkg/controller/worker/machines.go +++ b/pkg/controller/worker/machines.go @@ -11,6 +11,7 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" @@ -24,6 +25,7 @@ import ( "github.com/gardener/gardener/pkg/client/kubernetes" gardenutils "github.com/gardener/gardener/pkg/utils" machinev1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" + iaas2 "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -36,6 +38,12 @@ import ( stackitutils "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/utils" ) +const ( + shouldMigrateMachineAnnotation = "stackit.cloud/machine-should-be-migrated" + migratedMachineAnnotation = "stackit.cloud/migrated-machine" + workerMigratedAnnotation = "stackit.cloud/machine-controller-manager-migrated" +) + // MachineClassKind yields the name of the machine class kind used by OpenStack provider. func (w *workerDelegate) MachineClassKind() string { return "MachineClass" @@ -63,7 +71,19 @@ func (w *workerDelegate) DeployMachineClasses(ctx context.Context) error { if feature.UseStackitMachineControllerManager(w.cluster) { chartPath = "machineclass-stackit" } - return w.seedChartApplier.ApplyFromEmbeddedFS(ctx, charts.InternalChart, filepath.Join(charts.InternalChartsPath, chartPath), w.worker.Namespace, "machineclass", kubernetes.Values(map[string]any{"machineClasses": w.machineClasses})) + err := w.seedChartApplier.ApplyFromEmbeddedFS(ctx, charts.InternalChart, filepath.Join(charts.InternalChartsPath, chartPath), w.worker.Namespace, "machineclass", kubernetes.Values(map[string]any{"machineClasses": w.machineClasses})) + if err != nil { + return err + } + + if feature.MigrateStackitMachineControllerManager(w.cluster) && w.worker.Annotations[workerMigratedAnnotation] != "true" { + err = w.migrateMachines(ctx) + if err != nil { + return err + } + } + + return nil } // GenerateMachineDeployments generates the configuration for the desired machine deployments. @@ -395,3 +415,92 @@ func EnsureUniformMachineImages(images []stackitv1alpha1.MachineImage, definitio } return uniformMachineImages } + +func (w *workerDelegate) migrateMachines(ctx context.Context) error { + var allMachines machinev1alpha1.MachineList + var migrateMachines []machinev1alpha1.Machine + + err := w.seedClient.List(ctx, &allMachines, &client.ListOptions{Namespace: w.worker.Namespace}) + if err != nil { + return err + } + + for i := range allMachines.Items { + // ignore error as default is false + migrateAnnotation, _ := strconv.ParseBool(allMachines.Items[i].Annotations[shouldMigrateMachineAnnotation]) + if !strings.HasPrefix(allMachines.Items[i].Spec.ProviderID, "stackit://") || migrateAnnotation { + migrateMachines = append(migrateMachines, allMachines.Items[i]) + } + } + + if len(migrateMachines) == 0 { + // no old openstack machine + return w.markWorkerAsMigrated(ctx) + } + + iaas, err := w.stackitClient.IaaS(ctx, w.seedClient, w.worker.Spec.SecretRef) + if err != nil { + return err + } + + for _, m := range migrateMachines { + patchAnnotations := client.MergeFrom(m.DeepCopy()) + if m.Annotations == nil { + m.Annotations = make(map[string]string) + } + m.Annotations[shouldMigrateMachineAnnotation] = "true" + m.Annotations[migratedMachineAnnotation] = "true" + err = w.seedClient.Patch(ctx, &m, patchAnnotations) + if err != nil { + return err + } + + if m.Spec.ProviderID != "" { + providerIDParts := strings.Split(m.Spec.ProviderID, "/") + if len(providerIDParts) == 0 { + return fmt.Errorf("migrateMachines: malformed machine provider ID: %s", m.Spec.ProviderID) + } + serverID := providerIDParts[len(providerIDParts)-1] + + patch := client.MergeFrom(m.DeepCopy()) + m.Spec.ProviderID = fmt.Sprintf("stackit://%s/%s", iaas.ProjectID(), serverID) + err = w.seedClient.Patch(ctx, &m, patch) + if err != nil { + return err + } + + _, err = iaas.UpdateServer(ctx, serverID, iaas2.UpdateServerPayload{ + Labels: map[string]any{ + // // TODO refine labels + "mcm.gardener.cloud/machine": m.Name, + "mcm.gardener.cloud/machineclass": m.Spec.Class.Name, + "mcm.gardener.cloud/role": "node", + }, + }) + if err != nil { + return err + } + } + + patchRemoveMigrationAnnotation := client.MergeFrom(m.DeepCopy()) + delete(m.Annotations, shouldMigrateMachineAnnotation) + err = w.seedClient.Patch(ctx, &m, patchRemoveMigrationAnnotation) + if err != nil { + return err + } + } + + return w.markWorkerAsMigrated(ctx) +} + +func (w *workerDelegate) markWorkerAsMigrated(ctx context.Context) error { + patchWorker := client.MergeFrom(w.worker.DeepCopy()) + + if w.worker.Annotations == nil { + w.worker.Annotations = make(map[string]string) + } + + w.worker.Annotations[workerMigratedAnnotation] = "true" + + return w.seedClient.Patch(ctx, w.worker, patchWorker) +} diff --git a/pkg/controller/worker/machines_test.go b/pkg/controller/worker/machines_test.go index c8101e98..fda9ebc7 100644 --- a/pkg/controller/worker/machines_test.go +++ b/pkg/controller/worker/machines_test.go @@ -26,6 +26,7 @@ import ( machinev1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + iaas2 "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" "go.uber.org/mock/gomock" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -40,6 +41,7 @@ import ( . "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/controller/worker" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/feature" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/openstack" + mockstackitclient "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit/client/mock" ) var _ = Describe("Machines", func() { @@ -50,6 +52,8 @@ var _ = Describe("Machines", func() { c client.Client chartApplier *mockkubernetes.MockChartApplier + mockStackitClient *mockstackitclient.MockFactory + workerDelegate genericworkeractuator.WorkerDelegate scheme *runtime.Scheme ) @@ -70,7 +74,7 @@ var _ = Describe("Machines", func() { Context("workerDelegate", func() { BeforeEach(func() { - workerDelegate, _ = NewWorkerDelegate(nil, scheme, nil, "", nil, nil, "") + workerDelegate, _ = NewWorkerDelegate(nil, scheme, nil, "", nil, nil, "", nil) }) Describe("#TestLabelNormalization", func() { @@ -556,6 +560,7 @@ var _ = Describe("Machines", func() { fakeScheme := runtime.NewScheme() Expect(corev1.AddToScheme(fakeScheme)).To(Succeed()) Expect(extensionsv1alpha1.AddToScheme(fakeScheme)).To(Succeed()) + Expect(machinev1alpha1.AddToScheme(fakeScheme)).To(Succeed()) c = fakeclient.NewClientBuilder(). WithScheme(fakeScheme). WithObjects( @@ -571,7 +576,7 @@ var _ = Describe("Machines", func() { WithStatusSubresource(&extensionsv1alpha1.Worker{}). Build() - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, clusterWithoutImages, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, clusterWithoutImages, customLabelDomain, nil) }) Describe("machine images", func() { @@ -841,7 +846,7 @@ var _ = Describe("Machines", func() { }) It("should return the expected machine deployments for profile image types", func() { - workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) // Test workerDelegate.DeployMachineClasses() @@ -880,7 +885,7 @@ var _ = Describe("Machines", func() { It("should return the expected machine deployments for profile image types with id", func() { // setup(region, "", machineImageID, archARM) - workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", workerWithRegion, clusterWithRegion, customLabelDomain) + workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", workerWithRegion, clusterWithRegion, customLabelDomain, nil) clusterWithRegion.Shoot.Spec.Hibernation = &gardencorev1beta1.Hibernation{Enabled: new(true)} // Test workerDelegate.DeployMachineClasses() @@ -923,7 +928,7 @@ var _ = Describe("Machines", func() { w.Spec.Pools[0].ProviderConfig = &runtime.RawExtension{ Raw: encode(workerConfig), } - workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).NotTo(HaveOccurred()) @@ -970,10 +975,185 @@ var _ = Describe("Machines", func() { }) }) + Context("MCM migration", func() { + var machine *machinev1alpha1.Machine + + BeforeEach(func() { + DeferCleanup(testutils.WithFeatureGate(feature.MutableGate, feature.UseSTACKITMachineControllerManager, true)) + + mockStackitClient = mockstackitclient.NewMockFactory(ctrl) + if cluster.Shoot.Annotations == nil { + cluster.Shoot.Annotations = map[string]string{} + } + + cluster.Shoot.Annotations[feature.ShootMigrateSTACKITMachineControllerManager] = "true" + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, mockStackitClient) + + machine = &machinev1alpha1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine-1", + Namespace: w.Namespace, + }, + Spec: machinev1alpha1.MachineSpec{ + Class: machinev1alpha1.ClassSpec{ + Name: "machineclass", + }, + ProviderID: "openstack:///RegionOne/server-123", + }, + } + + Expect(c.Create(ctx, machine)).To(Succeed()) + + chartApplier. + EXPECT(). + ApplyFromEmbeddedFS( + ctx, + charts.InternalChart, + filepath.Join("internal", "machineclass-stackit"), + w.Namespace, + "machineclass", + gomock.Any(), + ). + Return(nil) + }) + + It("should migrate the machine controller manager", func() { + mockIaaSClient := mockstackitclient.NewMockIaaSClient(ctrl) + + mockStackitClient.EXPECT(). + IaaS(ctx, c, w.Spec.SecretRef). + Return(mockIaaSClient, nil) + + mockIaaSClient.EXPECT(). + ProjectID(). + Return("project-id-123") + + mockIaaSClient.EXPECT(). + UpdateServer(ctx, "server-123", iaas2.UpdateServerPayload{ + Labels: map[string]any{ + "mcm.gardener.cloud/machine": machine.Name, + "mcm.gardener.cloud/machineclass": machine.Spec.Class.Name, + "mcm.gardener.cloud/role": "node", + }, + }). + Return(nil, nil) + + Expect(workerDelegate.DeployMachineClasses(ctx)).To(Succeed()) + + By("checking for the migrated machine") + migratedMachine := &machinev1alpha1.Machine{} + + Expect(c.Get(ctx, client.ObjectKey{ + Name: "machine-1", + Namespace: w.Namespace, + }, migratedMachine)).To(Succeed()) + + Expect(migratedMachine.Spec.ProviderID). + To(Equal("stackit://project-id-123/server-123")) + + Expect(migratedMachine.Annotations). + To(HaveKeyWithValue("stackit.cloud/migrated-machine", "true")) + + Expect(migratedMachine.Annotations). + NotTo(HaveKey("stackit.cloud/machine-should-be-migrated")) + + By("checking for the worker") + migratedWorker := &extensionsv1alpha1.Worker{} + + Expect(c.Get(ctx, client.ObjectKey{ + Name: w.Name, + Namespace: w.Namespace, + }, migratedWorker)).To(Succeed()) + + Expect(migratedWorker.Annotations). + To(HaveKeyWithValue( + "stackit.cloud/machine-controller-manager-migrated", + "true", + )) + }) + + It("should retry a machine after an IaaS API failure without touching migrated machines", func() { + migratedMachine := &machinev1alpha1.Machine{} + Expect(c.Get(ctx, client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}, migratedMachine)).To(Succeed()) + migratedMachine.Spec.ProviderID = "stackit://project-id-123/server-123" + migratedMachine.Annotations = map[string]string{"stackit.cloud/migrated-machine": "true"} + Expect(c.Update(ctx, migratedMachine)).To(Succeed()) + expectedMigratedMachine := migratedMachine.DeepCopy() + + pendingMachine := &machinev1alpha1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine-2", + Namespace: w.Namespace, + Annotations: map[string]string{"stackit.cloud/machine-should-be-migrated": "true", "stackit.cloud/migrated-machine": "true"}, + }, + Spec: machinev1alpha1.MachineSpec{ + Class: machinev1alpha1.ClassSpec{Name: "machineclass"}, + ProviderID: "stackit://project-id-123/server-456", + }, + } + Expect(c.Create(ctx, pendingMachine)).To(Succeed()) + + failingIaaSClient := mockstackitclient.NewMockIaaSClient(ctrl) + mockStackitClient.EXPECT(). + IaaS(ctx, c, w.Spec.SecretRef). + Return(failingIaaSClient, nil) + failingIaaSClient.EXPECT().ProjectID().Return("project-id-123") + failingIaaSClient.EXPECT(). + UpdateServer(ctx, "server-456", iaas2.UpdateServerPayload{ + Labels: map[string]any{ + "mcm.gardener.cloud/machine": pendingMachine.Name, + "mcm.gardener.cloud/machineclass": pendingMachine.Spec.Class.Name, + "mcm.gardener.cloud/role": "node", + }, + }). + Return(nil, fmt.Errorf("temporary IaaS API failure")) + + Expect(workerDelegate.DeployMachineClasses(ctx)).To(MatchError(ContainSubstring("temporary IaaS API failure"))) + + failedMachine := &machinev1alpha1.Machine{} + Expect(c.Get(ctx, client.ObjectKey{Name: pendingMachine.Name, Namespace: pendingMachine.Namespace}, failedMachine)).To(Succeed()) + Expect(failedMachine.Annotations).To(HaveKeyWithValue("stackit.cloud/machine-should-be-migrated", "true")) + + unchangedMigratedMachine := &machinev1alpha1.Machine{} + Expect(c.Get(ctx, client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}, unchangedMigratedMachine)).To(Succeed()) + Expect(unchangedMigratedMachine).To(Equal(expectedMigratedMachine)) + + retryIaaSClient := mockstackitclient.NewMockIaaSClient(ctrl) + mockStackitClient.EXPECT(). + IaaS(ctx, c, w.Spec.SecretRef). + Return(retryIaaSClient, nil) + retryIaaSClient.EXPECT().ProjectID().Return("project-id-123") + retryIaaSClient.EXPECT(). + UpdateServer(ctx, "server-456", iaas2.UpdateServerPayload{ + Labels: map[string]any{ + "mcm.gardener.cloud/machine": pendingMachine.Name, + "mcm.gardener.cloud/machineclass": pendingMachine.Spec.Class.Name, + "mcm.gardener.cloud/role": "node", + }, + }). + Return(nil, nil) + chartApplier.EXPECT(). + ApplyFromEmbeddedFS(ctx, charts.InternalChart, filepath.Join("internal", "machineclass-stackit"), w.Namespace, "machineclass", gomock.Any()). + Return(nil) + + Expect(workerDelegate.DeployMachineClasses(ctx)).To(Succeed()) + + retriedMachine := &machinev1alpha1.Machine{} + Expect(c.Get(ctx, client.ObjectKey{Name: pendingMachine.Name, Namespace: pendingMachine.Namespace}, retriedMachine)).To(Succeed()) + Expect(retriedMachine.Annotations).NotTo(HaveKey("stackit.cloud/machine-should-be-migrated")) + Expect(c.Get(ctx, client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}, unchangedMigratedMachine)).To(Succeed()) + Expect(unchangedMigratedMachine).To(Equal(expectedMigratedMachine)) + + migratedWorker := &extensionsv1alpha1.Worker{} + Expect(c.Get(ctx, client.ObjectKey{Name: w.Name, Namespace: w.Namespace}, migratedWorker)).To(Succeed()) + Expect(migratedWorker.Annotations).To(HaveKeyWithValue("stackit.cloud/machine-controller-manager-migrated", "true")) + }) + }) + It("should fail because the infrastructure status cannot be decoded", func() { w.Spec.InfrastructureProviderStatus = &runtime.RawExtension{} - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).To(HaveOccurred()) @@ -985,7 +1165,7 @@ var _ = Describe("Machines", func() { Raw: encode(&stackitv1alpha1.InfrastructureStatus{}), } - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).To(HaveOccurred()) @@ -995,7 +1175,7 @@ var _ = Describe("Machines", func() { It("should fail because the machine image for this cloud profile cannot be found", func() { clusterWithoutImages.CloudProfile.Name = "another-cloud-profile" - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, clusterWithoutImages, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, clusterWithoutImages, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).To(HaveOccurred()) @@ -1016,7 +1196,7 @@ var _ = Describe("Machines", func() { NodeConditions: testNodeConditions, } - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) resultSettings := result[0].MachineConfiguration @@ -1039,7 +1219,7 @@ var _ = Describe("Machines", func() { ScaleDownUtilizationThreshold: new("0.5"), } w.Spec.Pools[1].ClusterAutoscaler = nil - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).NotTo(HaveOccurred()) @@ -1068,7 +1248,7 @@ var _ = Describe("Machines", func() { }) DescribeTable("customLabelDomain in machineclass helm chart", func(customDomain string) { - workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customDomain) + workerDelegate, _ := NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customDomain, nil) machineClassPath := filepath.Join("internal", "machineclass") if useStackitMCM { @@ -1107,7 +1287,7 @@ var _ = Describe("Machines", func() { w.Spec.Pools[0].MachineControllerManagerSettings = &gardencorev1beta1.MachineControllerManagerSettings{ AutoPreserveFailedMachineMax: new(int32(4)), } - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).NotTo(HaveOccurred()) @@ -1118,7 +1298,7 @@ var _ = Describe("Machines", func() { It("should set autoPreserveFailedMachineMax to 0 per zone when machineControllerManagerSettings is nil", func() { w.Spec.Pools[0].MachineControllerManagerSettings = nil - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).NotTo(HaveOccurred()) @@ -1131,7 +1311,7 @@ var _ = Describe("Machines", func() { w.Spec.Pools[0].MachineControllerManagerSettings = &gardencorev1beta1.MachineControllerManagerSettings{ AutoPreserveFailedMachineMax: nil, } - workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain) + workerDelegate, _ = NewWorkerDelegate(c, scheme, chartApplier, "", w, cluster, customLabelDomain, nil) result, err := workerDelegate.GenerateMachineDeployments(ctx) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/feature/feature.go b/pkg/feature/feature.go index e5c4a0a7..681b94f3 100644 --- a/pkg/feature/feature.go +++ b/pkg/feature/feature.go @@ -22,8 +22,12 @@ const ( UseSTACKITAPIInfrastructureController featuregate.Feature = "UseSTACKITAPIInfrastructureController" // UseSTACKITMachineControllerManager Uses the STACKIT machine controller Manager to manage nodes. UseSTACKITMachineControllerManager featuregate.Feature = "UseSTACKITMachineControllerManager" + // MigrateSTACKITMachineControllerManager Migrates the existing openstack machines to stackit. Only works if feature gate UseSTACKITMachineControllerManager is enabled. + MigrateSTACKITMachineControllerManager featuregate.Feature = "MigrateSTACKITMachineControllerManager" // ShootUseSTACKITMachineControllerManager Uses the STACKIT machine controller Manager to manage nodes for a specific Shoot. ShootUseSTACKITMachineControllerManager = "shoot.gardener.cloud/use-stackit-machine-controller-manager" + // ShootMigrateSTACKITMachineControllerManager Migrates the existing openstack machines to stackit. Only works if feature gate UseSTACKITMachineControllerManager is enabled. + ShootMigrateSTACKITMachineControllerManager = "shoot.gardener.cloud/migrate-stackit-machine-controller-manager" // ShootUseSTACKITAPIInfrastructureController Uses the STACKIT API to create the shoot resources instead of OpenStack for a specific Shoot. ShootUseSTACKITAPIInfrastructureController = "shoot.gardener.cloud/use-stackit-api-infrastructure-controller" // EnableSTACKITWorkloadIdentity activates the deployment of the stackit-pod-identity-webhook to enable workload identity injection into pods. @@ -44,11 +48,12 @@ var ( Gate featuregate.FeatureGate = MutableGate allGates = map[featuregate.Feature]featuregate.FeatureSpec{ - EnsureSTACKITLBDeletion: {Default: true, PreRelease: featuregate.Alpha}, - EnsureSTACKITALBDeletion: {Default: false, PreRelease: featuregate.Alpha}, - UseSTACKITAPIInfrastructureController: {Default: true, PreRelease: featuregate.Alpha}, - UseSTACKITMachineControllerManager: {Default: true, PreRelease: featuregate.Alpha}, - EnableSTACKITWorkloadIdentity: {Default: false, PreRelease: featuregate.Alpha}, + EnsureSTACKITLBDeletion: {Default: true, PreRelease: featuregate.Alpha}, + EnsureSTACKITALBDeletion: {Default: false, PreRelease: featuregate.Alpha}, + UseSTACKITAPIInfrastructureController: {Default: true, PreRelease: featuregate.Alpha}, + UseSTACKITMachineControllerManager: {Default: true, PreRelease: featuregate.Alpha}, + EnableSTACKITWorkloadIdentity: {Default: false, PreRelease: featuregate.Alpha}, + MigrateSTACKITMachineControllerManager: {Default: false, PreRelease: featuregate.Alpha}, } ) @@ -81,3 +86,20 @@ func UseStackitAPIInfrastructureController(cluster *extensionscontroller.Cluster } return Gate.Enabled(UseSTACKITAPIInfrastructureController) } + +func MigrateStackitMachineControllerManager(cluster *extensionscontroller.Cluster) bool { + if !UseStackitMachineControllerManager(cluster) { + return false + } + + if cluster != nil && cluster.Shoot != nil { + annotation, ok := cluster.Shoot.Annotations[ShootMigrateSTACKITMachineControllerManager] + if ok { + enabledByAnnotation, err := strconv.ParseBool(annotation) + if err == nil { + return enabledByAnnotation + } + } + } + return Gate.Enabled(MigrateSTACKITMachineControllerManager) +} diff --git a/pkg/stackit/client/iaas.go b/pkg/stackit/client/iaas.go index 628f1a96..f7605012 100644 --- a/pkg/stackit/client/iaas.go +++ b/pkg/stackit/client/iaas.go @@ -36,6 +36,7 @@ type IaaSClient interface { CreateServer(ctx context.Context, payload iaas.CreateServerPayload) (*iaas.Server, error) DeleteServer(ctx context.Context, serverId string) error + UpdateServer(ctx context.Context, serverId string, payload iaas.UpdateServerPayload) (*iaas.Server, error) GetServerByName(ctx context.Context, name string) ([]iaas.Server, error) CreatePublicIp(ctx context.Context, payload iaas.CreatePublicIPPayload) (*iaas.PublicIp, error) @@ -288,6 +289,10 @@ func (c iaasClient) DeleteServer(ctx context.Context, serverId string) error { return c.Client.DeleteServer(ctx, c.projectID, c.region, serverId).Execute() } +func (c iaasClient) UpdateServer(ctx context.Context, serverId string, payload iaas.UpdateServerPayload) (*iaas.Server, error) { + return c.Client.UpdateServer(ctx, c.projectID, c.region, serverId).UpdateServerPayload(payload).Execute() +} + // GetServerByName finds the first server with the given name. func (c iaasClient) GetServerByName(ctx context.Context, name string) ([]iaas.Server, error) { servers, err := c.Client.ListServers(ctx, c.projectID, c.region).Execute() diff --git a/pkg/stackit/client/mock/iaas_mock.go b/pkg/stackit/client/mock/iaas_mock.go index 3527b584..c62431d7 100644 --- a/pkg/stackit/client/mock/iaas_mock.go +++ b/pkg/stackit/client/mock/iaas_mock.go @@ -379,3 +379,18 @@ func (mr *MockIaaSClientMockRecorder) UpdateSecurityGroupRules(ctx, group, desir mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSecurityGroupRules", reflect.TypeOf((*MockIaaSClient)(nil).UpdateSecurityGroupRules), ctx, group, desiredRules, allowDelete) } + +// UpdateServer mocks base method. +func (m *MockIaaSClient) UpdateServer(ctx context.Context, serverId string, payload v2api.UpdateServerPayload) (*v2api.Server, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateServer", ctx, serverId, payload) + ret0, _ := ret[0].(*v2api.Server) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateServer indicates an expected call of UpdateServer. +func (mr *MockIaaSClientMockRecorder) UpdateServer(ctx, serverId, payload any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateServer", reflect.TypeOf((*MockIaaSClient)(nil).UpdateServer), ctx, serverId, payload) +}