From f3dc56ed4d5c120ad2225530b019ff4422d02482 Mon Sep 17 00:00:00 2001 From: Theo Barber-Bany Date: Tue, 23 Sep 2025 11:11:52 +0100 Subject: [PATCH] Updates InfraMachine watch_filters --- .../machinemigration/suite_test.go | 5 +- .../machinesetmigration/suite_test.go | 5 +- .../machineset_sync_controller.go | 44 + .../machineset_sync_controller_test.go | 1190 +++++++++-------- pkg/controllers/machinesetsync/suite_test.go | 5 +- .../machinesync/machine_sync_controller.go | 69 +- .../machine_sync_controller_test.go | 709 +++++----- pkg/controllers/machinesync/suite_test.go | 5 +- pkg/util/watch_filters.go | 46 +- 9 files changed, 1154 insertions(+), 924 deletions(-) diff --git a/pkg/controllers/machinemigration/suite_test.go b/pkg/controllers/machinemigration/suite_test.go index 94a268539..d5983a613 100644 --- a/pkg/controllers/machinemigration/suite_test.go +++ b/pkg/controllers/machinemigration/suite_test.go @@ -29,8 +29,8 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/rest" "k8s.io/klog/v2" - "k8s.io/klog/v2/textlogger" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -54,7 +54,8 @@ func TestMachineMigration(t *testing.T) { var _ = BeforeSuite(func() { klog.SetOutput(GinkgoWriter) - logf.SetLogger(textlogger.NewLogger(textlogger.NewConfig())) + logf.SetLogger(GinkgoLogr) + ctrl.SetLogger(GinkgoLogr) By("bootstrapping test environment") var err error diff --git a/pkg/controllers/machinesetmigration/suite_test.go b/pkg/controllers/machinesetmigration/suite_test.go index 62592ac57..c4b08b32f 100644 --- a/pkg/controllers/machinesetmigration/suite_test.go +++ b/pkg/controllers/machinesetmigration/suite_test.go @@ -29,8 +29,8 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/rest" "k8s.io/klog/v2" - "k8s.io/klog/v2/textlogger" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -54,7 +54,8 @@ func TestMachineSetMigration(t *testing.T) { var _ = BeforeSuite(func() { klog.SetOutput(GinkgoWriter) - logf.SetLogger(textlogger.NewLogger(textlogger.NewConfig())) + logf.SetLogger(GinkgoLogr) + ctrl.SetLogger(GinkgoLogr) By("bootstrapping test environment") var err error diff --git a/pkg/controllers/machinesetsync/machineset_sync_controller.go b/pkg/controllers/machinesetsync/machineset_sync_controller.go index d4654a75c..75eb0f6a6 100644 --- a/pkg/controllers/machinesetsync/machineset_sync_controller.go +++ b/pkg/controllers/machinesetsync/machineset_sync_controller.go @@ -86,6 +86,12 @@ var ( // errMachineAPIMachineSetOwnerReferenceConversionUnsupported. errMachineAPIMachineSetOwnerReferenceConversionUnsupported = errors.New("could not convert Machine API machine set owner references to Cluster API") + + // errInvalidInfraClusterReference is returned when the cluster name is empty in CAPI machineset spec. + errInvalidInfraClusterReference = errors.New("cluster name is empty in Cluster API machine set spec") + + // errInvalidInfraMachineTemplateReference is returned when the infrastructure machine template reference is invalid or incomplete. + errInvalidInfraMachineTemplateReference = errors.New("infrastructure machine template reference is invalid or incomplete") ) const ( @@ -104,6 +110,8 @@ const ( messageSuccessfullySynchronizedMAPItoCAPI = "Successfully synchronized MAPI MachineSet to CAPI" controllerName string = "MachineSetSyncController" + + terminalConfigurationErrorLog string = "Detected terminal configuration error - cluster name or infrastructure machine template reference is empty, not requeuing" ) // MachineSetSyncReconciler reconciles CAPI and MAPI MachineSets. @@ -234,6 +242,18 @@ func (r *MachineSetSyncReconciler) fetchCAPIInfraResources(ctx context.Context, Name: infraMachineTemplateRef.Name, } + // Validate that required references are not empty to avoid nil pointer issues later. + // These are terminal configuration errors that require user intervention. + if capiMachineSet.Spec.ClusterName == "" { + return nil, nil, fmt.Errorf("machine set %s/%s: %w", + capiMachineSet.Namespace, capiMachineSet.Name, errInvalidInfraClusterReference) + } + + if infraMachineTemplateRef.Name == "" || infraMachineTemplateRef.Namespace == "" { + return nil, nil, fmt.Errorf("machine %s/%s: %w", + capiMachineSet.Namespace, capiMachineSet.Name, errInvalidInfraMachineTemplateReference) + } + infraMachineTemplate, infraCluster, err := controllers.InitInfraMachineTemplateAndInfraClusterFromProvider(r.Platform) if err != nil { return nil, nil, fmt.Errorf("unable to devise CAPI infra resources: %w", err) @@ -340,6 +360,12 @@ func (r *MachineSetSyncReconciler) reconcileMAPIMachineSetToCAPIMachineSet(ctx c restoreCAPIFields(existingCAPIMachineSet, convertedCAPIMachineSet, r.CAPINamespace, authoritativeAPI, clusterOwnerRefence) if err := r.ensureCAPIInfraMachineTemplate(ctx, sourceMAPIMachineSet, convertedCAPIMachineSet, convertedCAPIInfraMachineTemplate, clusterOwnerRefence); err != nil { + // Don't requeue for terminal configuration errors + if isTerminalConfigurationError(err) { + logger.Error(err, terminalConfigurationErrorLog) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("unable to ensure CAPI infra machine template: %w", err) } @@ -571,6 +597,14 @@ func (r *MachineSetSyncReconciler) reconcileCAPIMachineSetToMAPIMachineSet(ctx c if err != nil { fetchErr := fmt.Errorf("failed to fetch CAPI infra resources: %w", err) + // Don't requeue for terminal configuration errors + if isTerminalConfigurationError(err) { + logger.Error(err, terminalConfigurationErrorLog) + r.Recorder.Event(sourceCAPIMachineSet, corev1.EventTypeWarning, "SynchronizationError", fetchErr.Error()) + + return ctrl.Result{}, nil + } + if condErr := r.applySynchronizedConditionWithPatch( ctx, existingMAPIMachineSet, corev1.ConditionFalse, reasonFailedToGetCAPIInfraResources, fetchErr.Error(), nil); condErr != nil { return ctrl.Result{}, utilerrors.NewAggregate([]error{fetchErr, condErr}) @@ -1531,3 +1565,13 @@ func hasSpecOrMetadataOrProviderSpecChanges(diff map[string]any) bool { return ok1 || ok2 || ok3 } + +// isTerminalConfigurationError returns true if the provided error is +// errInvalidClusterInfraReference or errInvalidInfraMachineTemplateReference. +func isTerminalConfigurationError(err error) bool { + if errors.Is(err, errInvalidInfraClusterReference) || errors.Is(err, errInvalidInfraMachineTemplateReference) { + return true + } + + return false +} diff --git a/pkg/controllers/machinesetsync/machineset_sync_controller_test.go b/pkg/controllers/machinesetsync/machineset_sync_controller_test.go index 5fd634f20..59bfc1217 100644 --- a/pkg/controllers/machinesetsync/machineset_sync_controller_test.go +++ b/pkg/controllers/machinesetsync/machineset_sync_controller_test.go @@ -235,70 +235,139 @@ var _ = Describe("With a running MachineSetSync controller", func() { ) }) - Context("when all the CAPI infra resources exist", func() { + Context("when the MAPI machine set has MachineAuthority set to Machine API", func() { BeforeEach(func() { - By("Creating the CAPI infra machine template") - Expect(k8sClient.Create(ctx, capaMachineTemplate)).To(Succeed(), "capa machine template should be able to be created") + By("Creating the MAPI machine set") + mapiMachineSet = mapiMachineSetBuilder.Build() + Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) + + By("Setting the MAPI machine set AuthoritativeAPI to MachineAPI") + Eventually(k.UpdateStatus(mapiMachineSet, func() { + mapiMachineSet.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI + })).Should(Succeed()) + + // We need to set the observed generation to the metadata generation + // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. + By("Setting the MAPI machine set observed generation to its metadata generation") + Eventually(k.UpdateStatus(mapiMachineSet, func() { + mapiMachineSet.Status.ObservedGeneration = mapiMachineSet.Generation + })).Should(Succeed()) }) - Context("when the MAPI machine set has MachineAuthority set to Machine API", func() { - BeforeEach(func() { - By("Creating the MAPI machine set") - mapiMachineSet = mapiMachineSetBuilder.Build() - Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) + Context("when the CAPI machine set does not exist", func() { + It("should create the CAPI machine set", func() { + Eventually(k.Get( + capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build(), + )).Should(Succeed()) + }) - By("Setting the MAPI machine set AuthoritativeAPI to MachineAPI") - Eventually(k.UpdateStatus(mapiMachineSet, func() { - mapiMachineSet.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI - })).Should(Succeed()) + It("should create MachineSet and InfraMachineTemplate with CAPI Cluster OwnerReference", func() { + capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() + Eventually(k.Get(capiMachineSet)).Should(Succeed()) + Expect(capiMachineSet.OwnerReferences).To(Equal(capiClusterOwnerReference)) - // We need to set the observed generation to the metadata generation - // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. - By("Setting the MAPI machine set observed generation to its metadata generation") - Eventually(k.UpdateStatus(mapiMachineSet, func() { - mapiMachineSet.Status.ObservedGeneration = mapiMachineSet.Generation - })).Should(Succeed()) - }) + By("Checking the CAPI infra machine template") + newCAPAMachineTemplate := capav1builder.AWSMachineTemplate().WithName(capiMachineSet.Spec.Template.Spec.InfrastructureRef.Name).WithNamespace(capiNamespace.Name).Build() + Eventually(k.Get(newCAPAMachineTemplate)).Should(Succeed()) - Context("when the CAPI machine set does not exist", func() { - It("should create the CAPI machine set", func() { - Eventually(k.Get( - capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build(), - )).Should(Succeed()) - }) + By("Checking the CAPI infra machine template has the expected name") + generateName, err := util.GenerateInfraMachineTemplateNameWithSpecHash(capiMachineSet.Name, newCAPAMachineTemplate.Spec.Template.Spec) + Expect(err).To(BeNil()) + Expect(newCAPAMachineTemplate.Name).To(Equal(generateName)) - It("should create MachineSet and InfraMachineTemplate with CAPI Cluster OwnerReference", func() { - capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() - Eventually(k.Get(capiMachineSet)).Should(Succeed()) - Expect(capiMachineSet.OwnerReferences).To(Equal(capiClusterOwnerReference)) + By("Checking the CAPI infra machine template has the expected owner reference") + Expect(newCAPAMachineTemplate.OwnerReferences).To(Equal(capiClusterOwnerReference)) + }) - By("Checking the CAPI infra machine template") - newCAPAMachineTemplate := capav1builder.AWSMachineTemplate().WithName(capiMachineSet.Spec.Template.Spec.InfrastructureRef.Name).WithNamespace(capiNamespace.Name).Build() - Eventually(k.Get(newCAPAMachineTemplate)).Should(Succeed()) + It("should update the synchronized condition on the MAPI machine set to True", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized MAPI MachineSet to CAPI")), + ))), + ) + }) - By("Checking the CAPI infra machine template has the expected name") - generateName, err := util.GenerateInfraMachineTemplateNameWithSpecHash(capiMachineSet.Name, newCAPAMachineTemplate.Spec.Template.Spec) - Expect(err).To(BeNil()) - Expect(newCAPAMachineTemplate.Name).To(Equal(generateName)) + It("should set the sync finalizer on both the mapi and capi machine sets", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) - By("Checking the CAPI infra machine template has the expected owner reference") - Expect(newCAPAMachineTemplate.OwnerReferences).To(Equal(capiClusterOwnerReference)) + capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() + Eventually(k.Get(capiMachineSet)).Should(Succeed()) + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + }) + Context("when the infra machine template exists", func() { + BeforeEach(func() { + By("Creating the CAPI infra machine template") + Expect(k8sClient.Create(ctx, capaMachineTemplate)).To(Succeed(), "capa machine template should be able to be created") }) - It("should not delete the old CAPI infra machine template without MAPI machine set label", func() { Consistently(k.Get(capaMachineTemplate)).Should(Succeed()) }) - It("should update the synchronized condition on the MAPI machine set to True", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized MAPI MachineSet to CAPI")), - ))), - ) + }) + + Context("when the MAPI machine set has a non-zero deletion timestamp", func() { + BeforeEach(func() { + Expect(k8sClient.Delete(ctx, mapiMachineSet)).To(Succeed()) + }) + It("should not create the CAPI machine set", func() { + Consistently(k.Get( + capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build(), + ), timeout).Should(Not(Succeed())) + }) + + It("should delete the MAPI machine set", func() { + Eventually(k.Get(mapiMachineSet)).ShouldNot(Succeed()) + }) + }) + }) + + Context("when the CAPI machine set does exist", func() { + BeforeEach(func() { + capiMachineSet = capiMachineSetBuilder.Build() + capiMachineSet.SetFinalizers([]string{clusterv1.MachineSetFinalizer}) + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + }) + + It("should update MachineSet and InfraMachineTemplate with CAPI Cluster OwnerReference", func() { + capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() + + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("OwnerReferences", Equal(capiClusterOwnerReference)), + ) + + Eventually(k.Get(capiMachineSet)).Should(Succeed()) + capaMachineTemplate := capav1builder.AWSMachineTemplate().WithName(capiMachineSet.Spec.Template.Spec.InfrastructureRef.Name).WithNamespace(capiNamespace.Name).Build() + Eventually(k.Object(capaMachineTemplate), timeout).Should( + HaveField("OwnerReferences", Equal(capiClusterOwnerReference)), + ) + }) + + It("should update the synchronized condition on the MAPI machine set to True", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized MAPI MachineSet to CAPI")), + ))), + ) + }) + + Context("when the CAPI machine set spec differs (replica count)", func() { + BeforeEach(func() { + By("Updating the CAPI machine set with a differing spec from the MAPI machine set") + Eventually(k.Update(capiMachineSet, func() { + capiMachineSet.Spec.Replicas = ptr.To(int32(4)) + })).Should(Succeed()) }) It("should set the sync finalizer on both the mapi and capi machine sets", func() { @@ -313,201 +382,193 @@ var _ = Describe("With a running MachineSetSync controller", func() { ) }) - Context("when the MAPI machine set has a non-zero deletion timestamp", func() { - BeforeEach(func() { - Expect(k8sClient.Delete(ctx, mapiMachineSet)).To(Succeed()) - }) - It("should not create the CAPI machine set", func() { - Consistently(k.Get( - capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build(), - ), timeout).Should(Not(Succeed())) - }) + It("should update the synchronized condition on the MAPI machine set to True", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + SatisfyAll( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + ))), + HaveField("Status.SynchronizedGeneration", Equal(mapiMachineSet.GetGeneration())), + )) + }) - It("should delete the MAPI machine set", func() { - Eventually(k.Get(mapiMachineSet)).ShouldNot(Succeed()) - }) + It("should sync the spec of the machine sets (updating the CAPI machine set replica count)", func() { + // The CAPI machine set should be updated to the original MAPI machine set spec. + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("Spec.Replicas", BeEquivalentTo(mapiMachineSet.Spec.Replicas)), + ) + }) + + It("should sync the status of the machine sets (updating the CAPI machine set status replicas)", func() { + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("Status.Replicas", BeEquivalentTo(mapiMachineSet.Status.Replicas)), + ) }) }) - Context("when the CAPI machine set does exist", func() { + Context("when the CAPI machine set status differs (ready replicas)", func() { BeforeEach(func() { - capiMachineSet = capiMachineSetBuilder.Build() - capiMachineSet.SetFinalizers([]string{clusterv1.MachineSetFinalizer}) - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + By("Updating the CAPI machine set with a differing status from the MAPI machine set") + Eventually(k.UpdateStatus(capiMachineSet, func() { + capiMachineSet.Status.ReadyReplicas = 2 + })).Should(Succeed()) }) - It("should update MachineSet and InfraMachineTemplate with CAPI Cluster OwnerReference", func() { - capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() - + It("should sync the status of the machine sets (updating the CAPI machine set status ready replicas)", func() { Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("OwnerReferences", Equal(capiClusterOwnerReference)), - ) - - Eventually(k.Get(capiMachineSet)).Should(Succeed()) - capaMachineTemplate := capav1builder.AWSMachineTemplate().WithName(capiMachineSet.Spec.Template.Spec.InfrastructureRef.Name).WithNamespace(capiNamespace.Name).Build() - Eventually(k.Object(capaMachineTemplate), timeout).Should( - HaveField("OwnerReferences", Equal(capiClusterOwnerReference)), + HaveField("Status.ReadyReplicas", BeEquivalentTo(mapiMachineSet.Status.ReadyReplicas)), ) }) + }) - It("should update the synchronized condition on the MAPI machine set to True", func() { + Context("when the MAPI machine set has a non-zero deletion timestamp", func() { + BeforeEach(func() { Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized MAPI MachineSet to CAPI")), - ))), + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), ) - }) - - Context("when the CAPI machine set spec differs (replica count)", func() { - BeforeEach(func() { - By("Updating the CAPI machine set with a differing spec from the MAPI machine set") - Eventually(k.Update(capiMachineSet, func() { - capiMachineSet.Spec.Replicas = ptr.To(int32(4)) - })).Should(Succeed()) - }) - - It("should set the sync finalizer on both the mapi and capi machine sets", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - - capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() - Eventually(k.Get(capiMachineSet)).Should(Succeed()) - Eventually(k.Object(capiMachineSet), timeout).Should( + Eventually(k.Object(capiMachineSet), timeout).Should( + SatisfyAll( HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - }) - - It("should update the synchronized condition on the MAPI machine set to True", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - SatisfyAll( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - ))), - HaveField("Status.SynchronizedGeneration", Equal(mapiMachineSet.GetGeneration())), - )) - }) - - It("should sync the spec of the machine sets (updating the CAPI machine set replica count)", func() { - // The CAPI machine set should be updated to the original MAPI machine set spec. - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("Spec.Replicas", BeEquivalentTo(mapiMachineSet.Spec.Replicas)), - ) - }) - - It("should sync the status of the machine sets (updating the CAPI machine set status replicas)", func() { - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("Status.Replicas", BeEquivalentTo(mapiMachineSet.Status.Replicas)), - ) - }) + HaveField("ObjectMeta.Finalizers", ContainElement(clusterv1.MachineSetFinalizer)), + ), + ) + By("waiting for CAPA template to be created", eventuallyCAPIMachineSetShouldHaveValidAWSMachineTemplateRefWithMachineSetLabel) + Expect(k8sClient.Delete(ctx, mapiMachineSet)).To(Succeed()) }) - - Context("when the CAPI machine set status differs (ready replicas)", func() { - BeforeEach(func() { - By("Updating the CAPI machine set with a differing status from the MAPI machine set") - Eventually(k.UpdateStatus(capiMachineSet, func() { - capiMachineSet.Status.ReadyReplicas = 2 - })).Should(Succeed()) - }) - - It("should sync the status of the machine sets (updating the CAPI machine set status ready replicas)", func() { - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("Status.ReadyReplicas", BeEquivalentTo(mapiMachineSet.Status.ReadyReplicas)), - ) - }) + // Expect to see the finalizers, so they're in place before + // we Expect logic that relies on them to work + It("should delete the CAPI machine set", func() { + Eventually(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually capiMachineSet should not be found") + // We don't want to re-create the machineset just deleted + Consistently(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the capiMachineSet should not be recreated") }) - Context("when the MAPI machine set has a non-zero deletion timestamp", func() { - BeforeEach(func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - Eventually(k.Object(capiMachineSet), timeout).Should( - SatisfyAll( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - HaveField("ObjectMeta.Finalizers", ContainElement(clusterv1.MachineSetFinalizer)), - ), - ) - By("waiting for CAPA template to be created", eventuallyCAPIMachineSetShouldHaveValidAWSMachineTemplateRefWithMachineSetLabel) - Expect(k8sClient.Delete(ctx, mapiMachineSet)).To(Succeed()) - }) - // Expect to see the finalizers, so they're in place before - // we Expect logic that relies on them to work - It("should delete the CAPI machine set", func() { - Eventually(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually capiMachineSet should not be found") - // We don't want to re-create the machineset just deleted - Consistently(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the capiMachineSet should not be recreated") - }) - - It("should delete the MAPI machine set", func() { - Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") - // We don't want to re-create the machineset just deleted - Consistently(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the mapiMachineSet should not be recreated") - }) + It("should delete the MAPI machine set", func() { + Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") + // We don't want to re-create the machineset just deleted + Consistently(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the mapiMachineSet should not be recreated") + }) - It("should delete all associated CAPI infrastructure machine templates", func() { - Eventually(func() []awsv1.AWSMachineTemplate { - templateList := &awsv1.AWSMachineTemplateList{} - listOptions := []client.ListOption{ - client.InNamespace(capiNamespace.Name), - client.MatchingLabels(map[string]string{consts.MachineSetOpenshiftLabelKey: mapiMachineSet.Name}), - } + It("should delete all associated CAPI infrastructure machine templates", func() { + Eventually(func() []awsv1.AWSMachineTemplate { + templateList := &awsv1.AWSMachineTemplateList{} + listOptions := []client.ListOption{ + client.InNamespace(capiNamespace.Name), + client.MatchingLabels(map[string]string{consts.MachineSetOpenshiftLabelKey: mapiMachineSet.Name}), + } - if err := k8sClient.List(ctx, templateList, listOptions...); err != nil { - return nil - } + if err := k8sClient.List(ctx, templateList, listOptions...); err != nil { + return nil + } - return templateList.Items - }, timeout).Should(BeEmpty(), "all associated AWS machine templates should be deleted") - }) + return templateList.Items + }, timeout).Should(BeEmpty(), "all associated AWS machine templates should be deleted") }) + }) - Context("when the CAPI machine set has a non-zero deletion timestamp", func() { - BeforeEach(func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - Expect(k8sClient.Delete(ctx, capiMachineSet)).To(Succeed()) - }) - It("should delete the MAPI machine set", func() { - Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") - }) + Context("when the CAPI machine set has a non-zero deletion timestamp", func() { + BeforeEach(func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + Expect(k8sClient.Delete(ctx, capiMachineSet)).To(Succeed()) + }) + It("should delete the MAPI machine set", func() { + Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") }) }) }) + }) - Context("when the MAPI machine set has MachineAuthority set to Cluster API", func() { + Context("when the MAPI machine set has MachineAuthority set to Cluster API", func() { + BeforeEach(func() { + By("Creating the MAPI machine set") + mapiMachineSet = mapiMachineSetBuilder.Build() + Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) + + By("Setting the MAPI machine set AuthoritativeAPI to ClusterAPI") + Eventually(k.UpdateStatus(mapiMachineSet, func() { + mapiMachineSet.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI + })).Should(Succeed()) + + // We need to set the observed generation to the metadata generation + // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. + By("Setting the MAPI machine set observed generation to its metadata generation") + Eventually(k.UpdateStatus(mapiMachineSet, func() { + mapiMachineSet.Status.ObservedGeneration = mapiMachineSet.Generation + })).Should(Succeed()) + }) + + Context("when the CAPI machine set exists and the spec differs (replica count)", func() { BeforeEach(func() { - By("Creating the MAPI machine set") - mapiMachineSet = mapiMachineSetBuilder.Build() - Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) + // We expect to run a sync, so require the template. + By("Creating the CAPI infra machine template") + Expect(k8sClient.Create(ctx, capaMachineTemplate)).To(Succeed(), "capa machine template should be able to be created") - By("Setting the MAPI machine set AuthoritativeAPI to ClusterAPI") - Eventually(k.UpdateStatus(mapiMachineSet, func() { - mapiMachineSet.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI - })).Should(Succeed()) + By("Creating the CAPI machine set with a differing spec") + capiMachineSet = capiMachineSetBuilder.WithReplicas(int32(4)).Build() + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) // We need to set the observed generation to the metadata generation // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. - By("Setting the MAPI machine set observed generation to its metadata generation") - Eventually(k.UpdateStatus(mapiMachineSet, func() { - mapiMachineSet.Status.ObservedGeneration = mapiMachineSet.Generation + By("Setting the CAPI machine set observed generation to its metadata generation") + Eventually(k.UpdateStatus(capiMachineSet, func() { + capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation })).Should(Succeed()) }) - Context("when the CAPI machine set exists and the spec differs (replica count)", func() { + It("should set the sync finalizer on both the mapi and capi machine sets", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + + capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() + Eventually(k.Get(capiMachineSet)).Should(Succeed()) + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + }) + + It("should update the synchronized condition on the MAPI machine set to True", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + SatisfyAll( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + ))), + HaveField("Status.SynchronizedGeneration", Equal(capiMachineSet.GetGeneration())), + )) + }) + + It("should sync the spec of the machine sets (updating the MAPI machine set spec replicas)", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Spec.Replicas", BeEquivalentTo(capiMachineSet.Spec.Replicas)), + ) + }) + + It("should sync the status of the machine sets (updating the MAPI machine set status replicas)", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Status.Replicas", BeEquivalentTo(capiMachineSet.Status.Replicas)), + ) + }) + }) + + Context("when the CAPI machine set exists and the object meta differs", func() { + BeforeEach(func() { + // We expect a sync, so we require the infra template + By("Creating the CAPI infra machine template") + Expect(k8sClient.Create(ctx, capaMachineTemplate)).To(Succeed(), "capa machine template should be able to be created") + }) + Context("where the field is meant to be copied", func() { BeforeEach(func() { - By("Creating the CAPI machine set with a differing spec") - capiMachineSet = capiMachineSetBuilder.WithReplicas(int32(4)).Build() + By("Creating the MAPI machine set with differing object meta in relevant field") + capiMachineSet = capiMachineSetBuilder.WithLabels(map[string]string{"foo": "bar"}).Build() Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) // We need to set the observed generation to the metadata generation @@ -518,18 +579,6 @@ var _ = Describe("With a running MachineSetSync controller", func() { })).Should(Succeed()) }) - It("should set the sync finalizer on both the mapi and capi machine sets", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - - capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() - Eventually(k.Get(capiMachineSet)).Should(Succeed()) - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - }) - It("should update the synchronized condition on the MAPI machine set to True", func() { Eventually(k.Object(mapiMachineSet), timeout).Should( SatisfyAll( @@ -542,452 +591,419 @@ var _ = Describe("With a running MachineSetSync controller", func() { )) }) - It("should sync the spec of the machine sets (updating the MAPI machine set spec replicas)", func() { + It("should update the labels", func() { Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Spec.Replicas", BeEquivalentTo(capiMachineSet.Spec.Replicas)), + HaveField("Labels", Equal(map[string]string{"foo": "bar"})), ) }) - It("should sync the status of the machine sets (updating the MAPI machine set status replicas)", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Status.Replicas", BeEquivalentTo(capiMachineSet.Status.Replicas)), - ) - }) }) - Context("when the CAPI machine set exists and the object meta differs", func() { - Context("where the field is meant to be copied", func() { - BeforeEach(func() { - By("Creating the MAPI machine set with differing object meta in relevant field") - capiMachineSet = capiMachineSetBuilder.WithLabels(map[string]string{"foo": "bar"}).Build() - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - - // We need to set the observed generation to the metadata generation - // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. - By("Setting the CAPI machine set observed generation to its metadata generation") - Eventually(k.UpdateStatus(capiMachineSet, func() { - capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation - })).Should(Succeed()) - }) - - It("should update the synchronized condition on the MAPI machine set to True", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - SatisfyAll( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - ))), - HaveField("Status.SynchronizedGeneration", Equal(capiMachineSet.GetGeneration())), - )) - }) + Context("where the field is not meant to be copied", func() { + BeforeEach(func() { + By("Creating the CAPI machine set with differing object meta in non relevant field") + capiMachineSet = capiMachineSetBuilder.Build() + capiMachineSet.Finalizers = []string{"foo", "bar"} + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - It("should update the labels", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Labels", Equal(map[string]string{"foo": "bar"})), - ) - }) - - }) - - Context("where the field is not meant to be copied", func() { - BeforeEach(func() { - By("Creating the CAPI machine set with differing object meta in non relevant field") - capiMachineSet = capiMachineSetBuilder.Build() - capiMachineSet.Finalizers = []string{"foo", "bar"} - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - - // We need to set the observed generation to the metadata generation - // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. - By("Setting the CAPI machine set observed generation to its metadata generation") - Eventually(k.UpdateStatus(capiMachineSet, func() { - capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation - })).Should(Succeed()) - }) - - It("should update the synchronized condition on the MAPI machine set to True", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - SatisfyAll( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - ))), - HaveField("Status.SynchronizedGeneration", Equal(capiMachineSet.GetGeneration())), - )) - }) - - It("should not populate the field", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Finalizers", Not(ContainElements("foo", "bar"))), - ) - }) - - }) - }) - - Context("when the CAPI machine set exists and the status differs (ready replicas)", func() { - BeforeEach(func() { - By("Creating the CAPI machine set") - capiMachineSet = capiMachineSetBuilder.WithReplicas(int32(3)).Build() - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - - By("Updating the CAPI machine set with a differing status") + // We need to set the observed generation to the metadata generation + // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. + By("Setting the CAPI machine set observed generation to its metadata generation") Eventually(k.UpdateStatus(capiMachineSet, func() { - capiMachineSet.Status.ReadyReplicas = 2 - capiMachineSet.Status.Replicas = 3 - capiMachineSet.Status.AvailableReplicas = 2 - capiMachineSet.Status.FailureMessage = ptr.To("test failure message") - capiMachineSet.Status.FailureReason = ptr.To(capierrors.MachineSetStatusError("test failure reason")) - // We need to set the observed generation to the metadata generation - // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. - By("Setting the CAPI machine set observed generation to its metadata generation") capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation - // capiMachineSet.Status.Conditions // Conditions are not a 1:1 matching and are computed separately, so don't check them here. We have a separate test for this. })).Should(Succeed()) }) - It("should sync the status of the machine sets (updating the MAPI machine set status ready replicas)", func() { + It("should update the synchronized condition on the MAPI machine set to True", func() { Eventually(k.Object(mapiMachineSet), timeout).Should( SatisfyAll( - HaveField("Status.Replicas", BeEquivalentTo(capiMachineSet.Status.Replicas)), - HaveField("Status.ReadyReplicas", BeEquivalentTo(capiMachineSet.Status.ReadyReplicas)), - HaveField("Status.AvailableReplicas", BeEquivalentTo(capiMachineSet.Status.AvailableReplicas)), - HaveField("Status.ErrorMessage", BeEquivalentTo(capiMachineSet.Status.FailureMessage)), - HaveField("Status.ErrorReason", BeEquivalentTo(capiMachineSet.Status.FailureReason)), - // Status.Conditions // Conditions are not a 1:1 matching and are computed separately, so don't check them here. We have a separate test for this. + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + ))), + HaveField("Status.SynchronizedGeneration", Equal(capiMachineSet.GetGeneration())), )) }) - }) - Context("when the CAPI machine set exists and the conversion fails", func() { - BeforeEach(func() { - By("Creating the CAPI machine set") - capiMachineSet = capiMachineSetBuilder.WithOwnerReferences( - []metav1.OwnerReference{ - {APIVersion: "FooAPIVersion", Kind: "FooKind", Name: "FooName", UID: "123"}, - }).Build() - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - - // We need to set the observed generation to the metadata generation - // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. - By("Setting the CAPI machine set observed generation to its metadata generation") - Eventually(k.UpdateStatus(capiMachineSet, func() { - capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation - })).Should(Succeed()) - }) - - It("should update the synchronized condition on the MAPI machine set to False", func() { + It("should not populate the field", func() { Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionFalse)), - HaveField("Severity", Equal(mapiv1beta1.ConditionSeverityError)), - HaveField("Reason", Equal("FailedToConvertCAPIMachineSetToMAPI")), - ))), + HaveField("Finalizers", Not(ContainElements("foo", "bar"))), ) - }) - }) - Context("when the CAPI machine set exists and the conversion has warnings", func() { - // The AWS conversion library currently does not throw any warnings. - // When we have a conversion that does, this test should be filled out. - // We could also mock the conversion interface. }) + }) - Context("when the CAPI machine set is updated to reference a new AWSMachineTemplate with different instanceType", func() { - var newCapaMachineTemplate *awsv1.AWSMachineTemplate - - BeforeEach(func() { - capiMachineSet = capiMachineSetBuilder.Build() - - By("Waiting for initial synchronization") - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized CAPI MachineSet to MAPI")), - ))), - ) - - By("Creating a new CAPA machine template with different instanceType") - newCapaMachineTemplate = capav1builder.AWSMachineTemplate(). - WithNamespace(capiNamespace.GetName()). - WithName("new-machine-template"). - WithInstanceType("m5.xlarge"). - Build() - Expect(k8sClient.Create(ctx, newCapaMachineTemplate)).Should(Succeed()) + Context("when the CAPI machine set exists and the status differs (ready replicas)", func() { + BeforeEach(func() { + // We expect a sync, so we require the infra template + By("Creating the CAPI infra machine template") + Expect(k8sClient.Create(ctx, capaMachineTemplate)).To(Succeed(), "capa machine template should be able to be created") - By("Updating the CAPI machine set to reference the new template") - Eventually(k.Update(capiMachineSet, func() { - capiMachineSet.Spec.Template.Spec.InfrastructureRef.Name = newCapaMachineTemplate.Name - })).Should(Succeed()) + By("Creating the CAPI machine set") + capiMachineSet = capiMachineSetBuilder.WithReplicas(int32(3)).Build() + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + By("Updating the CAPI machine set with a differing status") + Eventually(k.UpdateStatus(capiMachineSet, func() { + capiMachineSet.Status.ReadyReplicas = 2 + capiMachineSet.Status.Replicas = 3 + capiMachineSet.Status.AvailableReplicas = 2 + capiMachineSet.Status.FailureMessage = ptr.To("test failure message") + capiMachineSet.Status.FailureReason = ptr.To(capierrors.MachineSetStatusError("test failure reason")) // We need to set the observed generation to the metadata generation // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. By("Setting the CAPI machine set observed generation to its metadata generation") - Eventually(k.UpdateStatus(capiMachineSet, func() { - capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation - })).Should(Succeed()) - }) + capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation + // capiMachineSet.Status.Conditions // Conditions are not a 1:1 matching and are computed separately, so don't check them here. We have a separate test for this. + })).Should(Succeed()) + }) - It("should update the MAPI machine set instanceType to match the new template", func() { - Eventually(func() (string, error) { - if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(mapiMachineSet), mapiMachineSet); err != nil { - return "", err - } - providerSpec, err := mapi2capi.AWSProviderSpecFromRawExtension(mapiMachineSet.Spec.Template.Spec.ProviderSpec.Value) - if err != nil { - return "", err - } + It("should sync the status of the machine sets (updating the MAPI machine set status ready replicas)", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + SatisfyAll( + HaveField("Status.Replicas", BeEquivalentTo(capiMachineSet.Status.Replicas)), + HaveField("Status.ReadyReplicas", BeEquivalentTo(capiMachineSet.Status.ReadyReplicas)), + HaveField("Status.AvailableReplicas", BeEquivalentTo(capiMachineSet.Status.AvailableReplicas)), + HaveField("Status.ErrorMessage", BeEquivalentTo(capiMachineSet.Status.FailureMessage)), + HaveField("Status.ErrorReason", BeEquivalentTo(capiMachineSet.Status.FailureReason)), + // Status.Conditions // Conditions are not a 1:1 matching and are computed separately, so don't check them here. We have a separate test for this. + )) + }) + }) - return providerSpec.InstanceType, nil - }, timeout).Should(Equal("m5.xlarge")) - }) + Context("when the CAPI machine set exists and the conversion fails", func() { + BeforeEach(func() { + By("Creating the CAPI machine set") + capiMachineSet = capiMachineSetBuilder.WithOwnerReferences( + []metav1.OwnerReference{ + {APIVersion: "FooAPIVersion", Kind: "FooKind", Name: "FooName", UID: "123"}, + }).Build() + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - It("should update the synchronized condition on the MAPI machine set to True", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized CAPI MachineSet to MAPI")), - ))), - ) - }) + // We need to set the observed generation to the metadata generation + // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. + By("Setting the CAPI machine set observed generation to its metadata generation") + Eventually(k.UpdateStatus(capiMachineSet, func() { + capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation + })).Should(Succeed()) }) - Context("when the CAPI machine set does not exist", func() { - It("should create the CAPI machine set", func() { - Eventually(k.Get( - capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build()), - ).Should(Succeed()) - }) + It("should update the synchronized condition on the MAPI machine set to False", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionFalse)), + HaveField("Severity", Equal(mapiv1beta1.ConditionSeverityError)), + HaveField("Reason", Equal("FailedToConvertCAPIMachineSetToMAPI")), + ))), + ) - It("should set the sync finalizer on both the mapi and capi machine sets", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) + }) + }) - capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() - Eventually(k.Get(capiMachineSet)).Should(Succeed()) - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - }) + Context("when the CAPI machine set exists and the conversion has warnings", func() { + // The AWS conversion library currently does not throw any warnings. + // When we have a conversion that does, this test should be filled out. + // We could also mock the conversion interface. + }) - It("should update the synchronized condition on the MAPI machine set to True", func() { - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized MAPI MachineSet to CAPI")), - ))), - ) - }) - }) + Context("when the CAPI machine set is updated to reference a new AWSMachineTemplate with different instanceType", func() { + var newCapaMachineTemplate *awsv1.AWSMachineTemplate - Context("when the CAPI machine set has a non-zero deletion timestamp", func() { - BeforeEach(func() { - capiMachineSet = capiMachineSetBuilder.Build() - capiMachineSet.SetFinalizers([]string{clusterv1.MachineSetFinalizer}) - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + BeforeEach(func() { + capiMachineSet = capiMachineSetBuilder.Build() - // Expect to see the finalizers, so they're in place before - // we Expect logic that relies on them to work - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - Eventually(k.Object(capiMachineSet), timeout).Should(SatisfyAll( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - HaveField("ObjectMeta.Finalizers", ContainElement(clusterv1.MachineSetFinalizer)), - ), - ) - Expect(k8sClient.Delete(ctx, capiMachineSet)).To(Succeed()) - }) + By("Waiting for initial synchronization") + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized CAPI MachineSet to MAPI")), + ))), + ) - Context("when the CAPI finalizer is removed", func() { - // Mock the CAPI machine set controller removing the - // finalizer that goes once all machines have been deleted. - BeforeEach(func() { - Eventually(k.Update(capiMachineSet, func() { - capiMachineSet.SetFinalizers([]string{machinesync.SyncFinalizer}) - })).Should(Succeed()) - }) + By("Creating a new CAPA machine template with different instanceType") + newCapaMachineTemplate = capav1builder.AWSMachineTemplate(). + WithNamespace(capiNamespace.GetName()). + WithName("new-machine-template"). + WithInstanceType("m5.xlarge"). + Build() + Expect(k8sClient.Create(ctx, newCapaMachineTemplate)).Should(Succeed()) + + By("Updating the CAPI machine set to reference the new template") + Eventually(k.Update(capiMachineSet, func() { + capiMachineSet.Spec.Template.Spec.InfrastructureRef.Name = newCapaMachineTemplate.Name + })).Should(Succeed()) - It("should delete the MAPI machine set", func() { - Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") + // We need to set the observed generation to the metadata generation + // to ensure the status is updated as that's a prerequisite for the status to be updated by the machinesetsync controller. + By("Setting the CAPI machine set observed generation to its metadata generation") + Eventually(k.UpdateStatus(capiMachineSet, func() { + capiMachineSet.Status.ObservedGeneration = capiMachineSet.Generation + })).Should(Succeed()) + }) - // We don't want to re-create the machineset just deleted - Consistently(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the mapiMachineSet should not be recreated") - }) + It("should update the MAPI machine set instanceType to match the new template", func() { + Eventually(func() (string, error) { + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(mapiMachineSet), mapiMachineSet); err != nil { + return "", err + } + providerSpec, err := mapi2capi.AWSProviderSpecFromRawExtension(mapiMachineSet.Spec.Template.Spec.ProviderSpec.Value) + if err != nil { + return "", err + } + + return providerSpec.InstanceType, nil + }, timeout).Should(Equal("m5.xlarge")) + }) - It("should delete the CAPI machine set", func() { - Eventually(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually capiMachineSet should not be found") - // We don't want to re-create the machineset just deleted - Consistently(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the capiMachineSet should not be recreated") - }) + It("should update the synchronized condition on the MAPI machine set to True", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized CAPI MachineSet to MAPI")), + ))), + ) + }) + }) - It("should not delete the CAPA machine template because it does not have MachineSet label", func() { - uid := capaMachineTemplate.GetUID() - // Both the MAPI and CAPI machine sets should be deleted - Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") - Eventually(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually capiMachineSet should not be found") + Context("when the CAPI machine set does not exist", func() { + It("should create the CAPI machine set", func() { + Eventually(k.Get( + capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build()), + ).Should(Succeed()) + }) - // The CAPA machine template should still exist after the MAPI and CAPI machine sets are deleted - Expect(k.Object(capaMachineTemplate)()).To(SatisfyAll( - HaveField("ObjectMeta.UID", Equal(uid)), - HaveField("ObjectMeta.DeletionTimestamp", BeNil()), - )) - }) + It("should set the sync finalizer on both the mapi and capi machine sets", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) - Context("when the CAPA machine template is updated to contain the machine set label", func() { - BeforeEach(func() { - Eventually(k.Update(capaMachineTemplate, func() { - if capaMachineTemplate.Labels == nil { - capaMachineTemplate.Labels = make(map[string]string) - } - capaMachineTemplate.Labels[consts.MachineSetOpenshiftLabelKey] = mapiMachineSet.Name - })).Should(Succeed()) - }) - - It("should delete the CAPA machine template", func() { - By("checking that there are no templates with the machine set label") - Eventually(func() []awsv1.AWSMachineTemplate { - templateList := &awsv1.AWSMachineTemplateList{} - listOptions := []client.ListOption{ - client.InNamespace(capiNamespace.Name), - client.MatchingLabels(map[string]string{consts.MachineSetOpenshiftLabelKey: mapiMachineSet.Name}), - } - - if err := k8sClient.List(ctx, templateList, listOptions...); err != nil { - return nil - } - - return templateList.Items - }, timeout).Should(BeEmpty(), "all associated AWS machine templates should be deleted") - - By("checking that the CAPA machine template is deleted") - Eventually(k.Get(capaMachineTemplate), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually CAPA machine template should not be found") - }) + capiMachineSet := capiv1resourcebuilder.MachineSet().WithName(mapiMachineSet.Name).WithNamespace(capiNamespace.Name).Build() + Eventually(k.Get(capiMachineSet)).Should(Succeed()) + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + }) - }) - }) + It("should update the synchronized condition on the MAPI machine set to True", func() { + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized MAPI MachineSet to CAPI")), + ))), + ) }) + }) - Context("when the non-authoritative MAPI machine set has a non-zero deletion timestamp", func() { - BeforeEach(func() { - capiMachineSet = capiMachineSetBuilder.Build() - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - Eventually(k.Object(mapiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), - ) - Expect(k8sClient.Delete(ctx, mapiMachineSet)).To(Succeed()) - }) + Context("when the CAPI machine set has a non-zero deletion timestamp", func() { + BeforeEach(func() { + // We expect deletion logic to intract with the template, so create it here + By("Creating the CAPI infra machine template") + Expect(k8sClient.Create(ctx, capaMachineTemplate)).To(Succeed(), "capa machine template should be able to be created") - It("should remove the machinesync finalizer from the CAPI machine set", func() { - Eventually(k.Object(capiMachineSet), timeout).Should( - HaveField("ObjectMeta.Finalizers", Not(ContainElement(machinesync.SyncFinalizer))), - ) + capiMachineSet = capiMachineSetBuilder.Build() + capiMachineSet.SetFinalizers([]string{clusterv1.MachineSetFinalizer}) + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + + // Expect to see the finalizers, so they're in place before + // we Expect logic that relies on them to work + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + Eventually(k.Object(capiMachineSet), timeout).Should(SatisfyAll( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + HaveField("ObjectMeta.Finalizers", ContainElement(clusterv1.MachineSetFinalizer)), + ), + ) + Expect(k8sClient.Delete(ctx, capiMachineSet)).To(Succeed()) + }) + + Context("when the CAPI finalizer is removed", func() { + // Mock the CAPI machine set controller removing the + // finalizer that goes once all machines have been deleted. + BeforeEach(func() { + Eventually(k.Update(capiMachineSet, func() { + capiMachineSet.SetFinalizers([]string{machinesync.SyncFinalizer}) + })).Should(Succeed()) }) It("should delete the MAPI machine set", func() { Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") + // We don't want to re-create the machineset just deleted + Consistently(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the mapiMachineSet should not be recreated") + }) + + It("should delete the CAPI machine set", func() { + Eventually(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually capiMachineSet should not be found") + // We don't want to re-create the machineset just deleted + Consistently(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "the capiMachineSet should not be recreated") }) - It("should not delete the CAPI machine set", func() { - Consistently(k.Object(capiMachineSet), timeout).Should( + It("should not delete the CAPA machine template because it does not have MachineSet label", func() { + uid := capaMachineTemplate.GetUID() + // Both the MAPI and CAPI machine sets should be deleted + Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") + Eventually(k.Get(capiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually capiMachineSet should not be found") + + // The CAPA machine template should still exist after the MAPI and CAPI machine sets are deleted + Expect(k.Object(capaMachineTemplate)()).To(SatisfyAll( + HaveField("ObjectMeta.UID", Equal(uid)), HaveField("ObjectMeta.DeletionTimestamp", BeNil()), - ) + )) }) - }) - }) - Context("when the MAPI machine set has MachineAuthority set to Migrating", func() { - BeforeEach(func() { - By("Creating the CAPI and MAPI machine sets") - // We want a difference, so if we try to reconcile either way we - // will get a new resourceversion - mapiMachineSet = mapiMachineSetBuilder.WithReplicas(6).Build() - capiMachineSet = capiMachineSetBuilder.WithReplicas(9).Build() + Context("when the CAPA machine template is updated to contain the machine set label", func() { + BeforeEach(func() { + Eventually(k.Update(capaMachineTemplate, func() { + if capaMachineTemplate.Labels == nil { + capaMachineTemplate.Labels = make(map[string]string) + } + capaMachineTemplate.Labels[consts.MachineSetOpenshiftLabelKey] = mapiMachineSet.Name + })).Should(Succeed()) + }) - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) + It("should delete the CAPA machine template", func() { + By("checking that there are no templates with the machine set label") + Eventually(func() []awsv1.AWSMachineTemplate { + templateList := &awsv1.AWSMachineTemplateList{} + listOptions := []client.ListOption{ + client.InNamespace(capiNamespace.Name), + client.MatchingLabels(map[string]string{consts.MachineSetOpenshiftLabelKey: mapiMachineSet.Name}), + } - By("Setting the AuthoritativeAPI to Migrating") - Eventually(k.UpdateStatus(mapiMachineSet, func() { - mapiMachineSet.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMigrating - })).Should(Succeed()) - }) + if err := k8sClient.List(ctx, templateList, listOptions...); err != nil { + return nil + } - It("should not make any changes to either machineset", func() { - // We want to make sure that this is the original ResourceVersion - // since we haven't fetched the resource since it was created. - mapiResourceVersion := mapiMachineSet.GetResourceVersion() - capiResourceVersion := capiMachineSet.GetResourceVersion() - Consistently(k.Object(mapiMachineSet), timeout).Should( - HaveField("ResourceVersion", Equal(mapiResourceVersion)), - ) - Consistently(k.Object(capiMachineSet), timeout).Should( - HaveField("ResourceVersion", Equal(capiResourceVersion)), - ) + return templateList.Items + }, timeout).Should(BeEmpty(), "all associated AWS machine templates should be deleted") + + By("checking that the CAPA machine template is deleted") + Eventually(k.Get(capaMachineTemplate), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually CAPA machine template should not be found") + }) + + }) }) }) - Context("when the MAPI machine set has MachineAuthority not set", func() { + Context("when the non-authoritative MAPI machine set has a non-zero deletion timestamp", func() { BeforeEach(func() { - By("Creating the CAPI and MAPI MachineSets") - mapiMachineSet = mapiMachineSetBuilder.Build() capiMachineSet = capiMachineSetBuilder.Build() - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) - Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) - - By("Setting the AuthoritativeAPI to Migrating") - Eventually(k.UpdateStatus(mapiMachineSet, func() { - mapiMachineSet.Status.AuthoritativeAPI = "" - })).Should(Succeed()) + Eventually(k.Object(mapiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", ContainElement(machinesync.SyncFinalizer)), + ) + Expect(k8sClient.Delete(ctx, mapiMachineSet)).To(Succeed()) }) - It("should not make any changes", func() { - resourceVersion := mapiMachineSet.GetResourceVersion() - Consistently(k.Object(mapiMachineSet), timeout).Should( - HaveField("ResourceVersion", Equal(resourceVersion)), + It("should remove the machinesync finalizer from the CAPI machine set", func() { + Eventually(k.Object(capiMachineSet), timeout).Should( + HaveField("ObjectMeta.Finalizers", Not(ContainElement(machinesync.SyncFinalizer))), ) }) - }) - Context("when the MAPI machine set does not exist and the CAPI machine set does", func() { - BeforeEach(func() { - By("Creating the CAPI machine set") - capiMachineSet = capiMachineSetBuilder.Build() + It("should delete the MAPI machine set", func() { + Eventually(k.Get(mapiMachineSet), timeout).Should(WithTransform(apierrors.IsNotFound, BeTrue()), "eventually mapiMachineSet should not be found") - Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) }) - It("should not make any changes to the CAPI machine set", func() { - resourceVersion := capiMachineSet.GetResourceVersion() + It("should not delete the CAPI machine set", func() { Consistently(k.Object(capiMachineSet), timeout).Should( - HaveField("ResourceVersion", Equal(resourceVersion)), + HaveField("ObjectMeta.DeletionTimestamp", BeNil()), ) }) + }) + }) - It("should not create a MAPI machine set", func() { - Consistently(k.ObjectList(&mapiv1beta1.MachineSetList{}), timeout).ShouldNot(HaveField("Items", - ContainElement(HaveField("ObjectMeta.Name", Equal(capiMachineSet.GetName()))), - )) - }) + Context("when the MAPI machine set has MachineAuthority set to Migrating", func() { + BeforeEach(func() { + By("Creating the CAPI and MAPI machine sets") + // We want a difference, so if we try to reconcile either way we + // will get a new resourceversion + mapiMachineSet = mapiMachineSetBuilder.WithReplicas(6).Build() + capiMachineSet = capiMachineSetBuilder.WithReplicas(9).Build() + + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) + + By("Setting the AuthoritativeAPI to Migrating") + Eventually(k.UpdateStatus(mapiMachineSet, func() { + mapiMachineSet.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMigrating + })).Should(Succeed()) + }) + + It("should not make any changes to either machineset", func() { + // We want to make sure that this is the original ResourceVersion + // since we haven't fetched the resource since it was created. + mapiResourceVersion := mapiMachineSet.GetResourceVersion() + capiResourceVersion := capiMachineSet.GetResourceVersion() + Consistently(k.Object(mapiMachineSet), timeout).Should( + HaveField("ResourceVersion", Equal(mapiResourceVersion)), + ) + Consistently(k.Object(capiMachineSet), timeout).Should( + HaveField("ResourceVersion", Equal(capiResourceVersion)), + ) + }) + }) + + Context("when the MAPI machine set has MachineAuthority not set", func() { + BeforeEach(func() { + By("Creating the CAPI and MAPI MachineSets") + mapiMachineSet = mapiMachineSetBuilder.Build() + capiMachineSet = capiMachineSetBuilder.Build() + + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + Expect(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed()) + + By("Setting the AuthoritativeAPI to Migrating") + Eventually(k.UpdateStatus(mapiMachineSet, func() { + mapiMachineSet.Status.AuthoritativeAPI = "" + })).Should(Succeed()) + }) + + It("should not make any changes", func() { + resourceVersion := mapiMachineSet.GetResourceVersion() + Consistently(k.Object(mapiMachineSet), timeout).Should( + HaveField("ResourceVersion", Equal(resourceVersion)), + ) + }) + }) + + Context("when the MAPI machine set does not exist and the CAPI machine set does", func() { + BeforeEach(func() { + By("Creating the CAPI machine set") + capiMachineSet = capiMachineSetBuilder.Build() + + Expect(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed()) + }) + + It("should not make any changes to the CAPI machine set", func() { + resourceVersion := capiMachineSet.GetResourceVersion() + Consistently(k.Object(capiMachineSet), timeout).Should( + HaveField("ResourceVersion", Equal(resourceVersion)), + ) + }) + + It("should not create a MAPI machine set", func() { + Consistently(k.ObjectList(&mapiv1beta1.MachineSetList{}), timeout).ShouldNot(HaveField("Items", + ContainElement(HaveField("ObjectMeta.Name", Equal(capiMachineSet.GetName()))), + )) }) }) diff --git a/pkg/controllers/machinesetsync/suite_test.go b/pkg/controllers/machinesetsync/suite_test.go index 703803d98..38a3f5e5e 100644 --- a/pkg/controllers/machinesetsync/suite_test.go +++ b/pkg/controllers/machinesetsync/suite_test.go @@ -31,8 +31,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/klog/v2" - "k8s.io/klog/v2/textlogger" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -61,7 +61,8 @@ func TestAPIs(t *testing.T) { var _ = BeforeSuite(func() { klog.SetOutput(GinkgoWriter) - logf.SetLogger(textlogger.NewLogger(textlogger.NewConfig())) + logf.SetLogger(GinkgoLogr) + ctrl.SetLogger(GinkgoLogr) By("bootstrapping test environment") var err error diff --git a/pkg/controllers/machinesync/machine_sync_controller.go b/pkg/controllers/machinesync/machine_sync_controller.go index 39ae65243..1f93b4c14 100644 --- a/pkg/controllers/machinesync/machine_sync_controller.go +++ b/pkg/controllers/machinesync/machine_sync_controller.go @@ -122,6 +122,16 @@ var ( // errUnsupportedCPMSOwnedMachineConversion is returned when attempting to convert ControlPlaneMachineSet owned machines. errUnsupportedCPMSOwnedMachineConversion = errors.New("conversion of control plane machines owned by control plane machine set is currently not supported") + + // errInvalidInfraClusterReference is returned when the cluster name is empty in CAPI machine spec. + errInvalidInfraClusterReference = errors.New("cluster name is empty in Cluster API machine spec") + + // errInvalidInfraMachineReference is returned when the infrastructure machine reference is invalid or incomplete. + errInvalidInfraMachineReference = errors.New("infrastructure machine reference is invalid or incomplete") +) + +const ( + terminalConfigurationErrorLog string = "Detected terminal configuration error - cluster name or infrastructure machine reference is empty, not requeuing" ) // MachineSyncReconciler reconciles CAPI and MAPI machines. @@ -136,7 +146,7 @@ type MachineSyncReconciler struct { MAPINamespace string } -// SetupWithManager sets the CoreClusterReconciler controller up with the given manager. +// SetupWithManager sets the MachineSyncReconciler controller up with the given manager. func (r *MachineSyncReconciler) SetupWithManager(mgr ctrl.Manager) error { infraMachine, _, err := controllers.InitInfraMachineAndInfraClusterFromProvider(r.Platform) if err != nil { @@ -163,7 +173,7 @@ func (r *MachineSyncReconciler) SetupWithManager(mgr ctrl.Manager) error { ). Watches( infraMachine, - handler.EnqueueRequestsFromMapFunc(util.RewriteNamespace(r.MAPINamespace)), + handler.EnqueueRequestsFromMapFunc(util.ResolveCAPIMachineFromInfraMachine(r.MAPINamespace)), builder.WithPredicates(util.FilterNamespace(r.CAPINamespace)), ). Complete(r); err != nil { @@ -282,11 +292,20 @@ func (r *MachineSyncReconciler) reconcileCAPIMachinetoMAPIMachine(ctx context.Co } infraCluster, infraMachine, err := r.fetchCAPIInfraResources(ctx, sourceCAPIMachine) - if err != nil { + if err != nil { //nolint:nestif fetchErr := fmt.Errorf("failed to fetch Cluster API infra resources: %w", err) if existingMAPIMachine == nil { + // Don't requeue for terminal configuration errors + if isTerminalConfigurationError(err) { + logger.Error(err, terminalConfigurationErrorLog) + r.Recorder.Event(sourceCAPIMachine, corev1.EventTypeWarning, "SynchronizationError", fetchErr.Error()) + + return ctrl.Result{}, nil + } + r.Recorder.Event(sourceCAPIMachine, corev1.EventTypeWarning, "SynchronizationWarning", fetchErr.Error()) + return ctrl.Result{}, fetchErr } @@ -295,6 +314,12 @@ func (r *MachineSyncReconciler) reconcileCAPIMachinetoMAPIMachine(ctx context.Co return ctrl.Result{}, utilerrors.NewAggregate([]error{fetchErr, condErr}) } + // Don't requeue for terminal configuration errors + if isTerminalConfigurationError(err) { + logger.Error(err, terminalConfigurationErrorLog) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fetchErr } @@ -410,6 +435,12 @@ func (r *MachineSyncReconciler) reconcileMAPIMachinetoCAPIMachine(ctx context.Co return ctrl.Result{}, utilerrors.NewAggregate([]error{fetchErr, condErr}) } + // Don't requeue for terminal configuration errors + if isTerminalConfigurationError(err) { + logger.Error(err, terminalConfigurationErrorLog) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fetchErr } @@ -949,6 +980,18 @@ func (r *MachineSyncReconciler) fetchCAPIInfraResources(ctx context.Context, cap Name: infraMachineRef.Name, } + // Validate that required references are not empty to avoid nil pointer issues later. + // These are terminal configuration errors that require user intervention. + if capiMachine.Spec.ClusterName == "" { + return nil, nil, fmt.Errorf("machine %s/%s: %w", + capiMachine.Namespace, capiMachine.Name, errInvalidInfraClusterReference) + } + + if infraMachineRef.Name == "" || infraMachineRef.Namespace == "" { + return nil, nil, fmt.Errorf("machine %s/%s: %w", + capiMachine.Namespace, capiMachine.Name, errInvalidInfraMachineReference) + } + infraMachine, infraCluster, err := controllers.InitInfraMachineAndInfraClusterFromProvider(r.Platform) if err != nil { return nil, nil, fmt.Errorf("unable to devise Cluster API infra resources: %w", err) @@ -968,7 +1011,9 @@ func (r *MachineSyncReconciler) fetchCAPIInfraResources(ctx context.Context, cap } //nolint:funlen,gocognit,cyclop -func (r *MachineSyncReconciler) reconcileMAPItoCAPIMachineDeletion(ctx context.Context, mapiMachine *mapiv1beta1.Machine, capiMachine *clusterv1.Machine, infraMachine client.Object) (bool, error) { +func (r *MachineSyncReconciler) reconcileMAPItoCAPIMachineDeletion(ctx context.Context, mapiMachine *mapiv1beta1.Machine, capiMachine *clusterv1.Machine, infraMachine client.Object) (shouldRequeue bool, err error) { + logger := logf.FromContext(ctx) + if mapiMachine.DeletionTimestamp.IsZero() { if capiMachine == nil || capiMachine.DeletionTimestamp.IsZero() { // Neither MAPI authoritative machine nor its CAPI non-authoritative machine mirror @@ -986,8 +1031,6 @@ func (r *MachineSyncReconciler) reconcileMAPItoCAPIMachineDeletion(ctx context.C return true, nil } - logger := logf.FromContext(ctx) - if capiMachine == nil && util.IsNilObject(infraMachine) { logger.Info("Cluster API machine and infra machine do not exist, removing corresponding Machine API machine sync finalizer") // We don't have a capi machine or infra resouorces to clean up we can @@ -1185,9 +1228,7 @@ func (r *MachineSyncReconciler) reconcileCAPItoMAPIMachineDeletion(ctx context.C // ensureSyncFinalizer ensures the sync finalizer is present across the mapi // machine, capi machine and capi infra machine. -func (r *MachineSyncReconciler) ensureSyncFinalizer(ctx context.Context, mapiMachine *mapiv1beta1.Machine, capiMachine *clusterv1.Machine, infraMachine client.Object) (bool, error) { - var shouldRequeue bool - +func (r *MachineSyncReconciler) ensureSyncFinalizer(ctx context.Context, mapiMachine *mapiv1beta1.Machine, capiMachine *clusterv1.Machine, infraMachine client.Object) (shouldRequeue bool, err error) { var errors []error if mapiMachine != nil { @@ -1523,3 +1564,13 @@ func hasMetadataChanges(diff map[string]any) bool { _, hasSpec := diff[".metadata"] return hasSpec } + +// isTerminalConfigurationError returns true if the provided error is +// errInvalidClusterInfraReference or errInvalidInfraMachineReference. +func isTerminalConfigurationError(err error) bool { + if errors.Is(err, errInvalidInfraClusterReference) || errors.Is(err, errInvalidInfraMachineReference) { + return true + } + + return false +} diff --git a/pkg/controllers/machinesync/machine_sync_controller_test.go b/pkg/controllers/machinesync/machine_sync_controller_test.go index 939e73303..9dee669ab 100644 --- a/pkg/controllers/machinesync/machine_sync_controller_test.go +++ b/pkg/controllers/machinesync/machine_sync_controller_test.go @@ -191,6 +191,7 @@ var _ = Describe("With a running MachineSync Reconciler", func() { mgr, err = ctrl.NewManager(controllerCfg, ctrl.Options{ Scheme: testScheme, + Logger: GinkgoLogr, Controller: config.Controller{ SkipNameValidation: ptr.To(true), }, @@ -234,62 +235,72 @@ var _ = Describe("With a running MachineSync Reconciler", func() { ) }) - Context("when all the CAPI infra resources exist", func() { + Context("when the MAPI machine has MachineAuthority set to Machine API", func() { BeforeEach(func() { - By("Creating the CAPI infra machine") - Eventually(k8sClient.Create(ctx, capaMachine)).Should(Succeed(), "capa machine should be able to be created") + By("Creating the MAPI machine") + mapiMachine = mapiMachineBuilder.Build() + Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + + By("Setting the MAPI machine AuthoritativeAPI to MachineAPI") + Eventually(k.UpdateStatus(mapiMachine, func() { + mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI + })).Should(Succeed()) }) - Context("when the MAPI machine has MachineAuthority set to Machine API", func() { - BeforeEach(func() { - By("Creating the MAPI machine") - mapiMachine = mapiMachineBuilder.Build() - Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + Context("when the CAPI machine does not exist", func() { + It("should create a paused CAPI machine", func() { + Eventually(k.Get( + clusterv1resourcebuilder.Machine().WithName(mapiMachine.Name).WithNamespace(capiNamespace.Name).Build(), + )).Should(Succeed()) + }) - By("Setting the MAPI machine AuthoritativeAPI to MachineAPI") - Eventually(k.UpdateStatus(mapiMachine, func() { - mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI - })).Should(Succeed()) + It("should update the synchronized condition on the MAPI machine to True", func() { + Eventually(k.Object(mapiMachine), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")), + ))), + ) }) + }) - Context("when the CAPI machine does not exist", func() { - It("should create a paused CAPI machine", func() { - Eventually(k.Get( - clusterv1resourcebuilder.Machine().WithName(mapiMachine.Name).WithNamespace(capiNamespace.Name).Build(), - )).Should(Succeed()) - }) + Context("when the CAPI machine and infra machine do exist", func() { + BeforeEach(func() { + By("Creating the CAPI machine") + capiMachine = capiMachineBuilder.Build() + Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") - It("should update the synchronized condition on the MAPI machine to True", func() { - Eventually(k.Object(mapiMachine), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")), - ))), - ) - }) - }) + By("Creating the CAPI infra machine") + // we must set the capi machine as an owner of the capa machine + // in order to ensure we reconcile capa changes in our sync controller. + capaMachine = capaMachineBuilder.WithOwnerReferences([]metav1.OwnerReference{ + { + Kind: machineKind, + APIVersion: clusterv1.GroupVersion.String(), + Name: capiMachine.Name, + UID: capiMachine.UID, + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(false), + }, + }).Build() - Context("when the CAPI machine does exist", func() { - BeforeEach(func() { - By("Creating the CAPI machine") - capiMachine = capiMachineBuilder.Build() - Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") - }) + Eventually(k8sClient.Create(ctx, capaMachine)).Should(Succeed(), "capa machine should be able to be created") - It("should update the synchronized condition on the MAPI machine to True", func() { - Eventually(k.Object(mapiMachine), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")), - ))), - ) - }) + }) + + It("should update the synchronized condition on the MAPI machine to True", func() { + Eventually(k.Object(mapiMachine), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")), + ))), + ) }) Context("when the MAPI machine providerSpec gets updated", func() { @@ -328,209 +339,329 @@ var _ = Describe("With a running MachineSync Reconciler", func() { }) }) }) + }) - Context("when the MAPI machine has MachineAuthority set to Cluster API", func() { - BeforeEach(func() { - - By("Creating the MAPI machine") - mapiMachine = mapiMachineBuilder.Build() - Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + Context("when the MAPI machine has MachineAuthority set to Cluster API", func() { + BeforeEach(func() { - By("Creating the CAPI Machine") - capiMachine = capiMachineBuilder.Build() - Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") + By("Creating the MAPI machine") + mapiMachine = mapiMachineBuilder.Build() + Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") - By("Setting the MAPI machine AuthoritativeAPI to Cluster API") - Eventually(k.UpdateStatus(mapiMachine, func() { - mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI - })).Should(Succeed()) + By("Creating the CAPI Machine") + capiMachine = capiMachineBuilder.Build() + Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") + By("Creating the CAPI infra machine") + // we must set the capi machine as an owner of the capa machine + // in order to ensure we reconcile capa changes in our sync controller. + + // Updates the capaMachineBuilder with the correct owner ref, + // so when we use it later on, we don't need to repeat ourselves. + capaMachineBuilder = capaMachineBuilder.WithOwnerReferences([]metav1.OwnerReference{ + { + Kind: machineKind, + APIVersion: clusterv1.GroupVersion.String(), + Name: capiMachine.Name, + UID: capiMachine.UID, + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(false), + }, }) - Context("when a MAPI counterpart exists", func() { - Context("when the CAPI Infra Machine gets updated", func() { - BeforeEach(func() { - By("Updating the CAPI Infra Machine (CAPA Machine)") - modifiedCapaMachine := capaMachineBuilder.WithInstanceType("m7i.4xlarge").Build() - modifiedCapaMachine.ResourceVersion = capaMachine.GetResourceVersion() - Eventually(k8sClient.Update(ctx, modifiedCapaMachine)).Should(Succeed(), "capa machine should be able to be updated") - }) + capaMachine = capaMachineBuilder.Build() + Eventually(k8sClient.Create(ctx, capaMachine)).Should(Succeed(), "capa machine should be able to be created") - It("should update the MAPI provider spec", func() { - Eventually(k.Object(mapiMachine), timeout).Should( - WithTransform(awsProviderSpecFromMachine, - HaveField("InstanceType", Equal("m7i.4xlarge")), - )) - }) + By("Setting the MAPI machine AuthoritativeAPI to Cluster API") + Eventually(k.UpdateStatus(mapiMachine, func() { + mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI + })).Should(Succeed()) - It("should update the synchronized condition on the MAPI machine to True", func() { - Eventually(k.Object(mapiMachine), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized CAPI Machine to MAPI")), - ))), - ) - }) + }) + + Context("when a MAPI counterpart exists", func() { + Context("when the CAPI Infra Machine gets updated", func() { + BeforeEach(func() { + By("Updating the CAPI Infra Machine (CAPA Machine)") + modifiedCapaMachine := capaMachineBuilder.WithInstanceType("m7i.4xlarge").Build() + modifiedCapaMachine.ResourceVersion = capaMachine.GetResourceVersion() + Eventually(k8sClient.Update(ctx, modifiedCapaMachine)).Should(Succeed(), "capa machine should be able to be updated") + }) + + It("should update the MAPI provider spec", func() { + Eventually(k.Object(mapiMachine), timeout).Should( + WithTransform(awsProviderSpecFromMachine, + HaveField("InstanceType", Equal("m7i.4xlarge")), + )) + }) + + It("should update the synchronized condition on the MAPI machine to True", func() { + Eventually(k.Object(mapiMachine), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized CAPI Machine to MAPI")), + ))), + ) }) }) }) + }) - Context("when the MAPI machine has status.authoritativeAPI set to MachineAPI and has CPMS owner reference", func() { - BeforeEach(func() { - fakeCPMSOwnerReference := metav1.OwnerReference{ - APIVersion: mapiv1beta1.GroupVersion.String(), - Kind: "ControlPlaneMachineSet", - Name: "cluster", - UID: "cpms-uid", - Controller: ptr.To(true), - BlockOwnerDeletion: ptr.To(true), - } + Context("when the MAPI machine has status.authoritativeAPI set to MachineAPI and has CPMS owner reference", func() { + BeforeEach(func() { + fakeCPMSOwnerReference := metav1.OwnerReference{ + APIVersion: mapiv1beta1.GroupVersion.String(), + Kind: "ControlPlaneMachineSet", + Name: "cluster", + UID: "cpms-uid", + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + } - By("Creating the MAPI machine") - mapiMachine = mapiMachineBuilder.WithOwnerReferences([]metav1.OwnerReference{fakeCPMSOwnerReference}).Build() - Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + By("Creating the MAPI machine") + mapiMachine = mapiMachineBuilder.WithOwnerReferences([]metav1.OwnerReference{fakeCPMSOwnerReference}).Build() + Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") - By("Setting the MAPI machine status.authoritativeAPI to MachineAPI") - Eventually(k.UpdateStatus(mapiMachine, func() { - mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI - })).Should(Succeed()) - }) + By("Setting the MAPI machine status.authoritativeAPI to MachineAPI") + Eventually(k.UpdateStatus(mapiMachine, func() { + mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI + })).Should(Succeed()) + }) - It("should not create the CAPI machine", func() { - Consistently(k.Get( - clusterv1resourcebuilder.Machine().WithName(mapiMachine.Name).WithNamespace(capiNamespace.Name).Build(), - )).Should(Not(Succeed())) - }) + It("should not create the CAPI machine", func() { + Consistently(k.Get( + clusterv1resourcebuilder.Machine().WithName(mapiMachine.Name).WithNamespace(capiNamespace.Name).Build(), + )).Should(Not(Succeed())) + }) - It("should update the synchronized condition on the MAPI machine to False", func() { - Eventually(k.Object(mapiMachine), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionFalse)), - HaveField("Reason", Equal("FailedToConvertMAPIMachineToCAPI")), - HaveField("Message", Equal("conversion of control plane machines owned by control plane machine set is currently not supported")), - ))), - ) - }) + It("should update the synchronized condition on the MAPI machine to False", func() { + Eventually(k.Object(mapiMachine), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionFalse)), + HaveField("Reason", Equal("FailedToConvertMAPIMachineToCAPI")), + HaveField("Message", Equal("conversion of control plane machines owned by control plane machine set is currently not supported")), + ))), + ) + }) + + }) + Context("when the MAPI machine has status.authoritativeAPI set to MachineAPI and has no owner references", func() { + BeforeEach(func() { + By("Creating the MAPI machine") + mapiMachine = mapiMachineBuilder.Build() + Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + + By("Setting the MAPI machine status.authoritativeAPI to MachineAPI") + Eventually(k.UpdateStatus(mapiMachine, func() { + mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI + })).Should(Succeed()) + + capiMachine = capiMachineBuilder.WithName(mapiMachine.Name).Build() }) - Context("when the MAPI machine has status.authoritativeAPI set to MachineAPI and has no owner references", func() { - BeforeEach(func() { - By("Creating the MAPI machine") - mapiMachine = mapiMachineBuilder.Build() - Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + It("should successfully create the CAPI machine", func() { + Eventually(k.Get(capiMachine)).Should(Succeed()) + }) - By("Setting the MAPI machine status.authoritativeAPI to MachineAPI") - Eventually(k.UpdateStatus(mapiMachine, func() { - mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI - })).Should(Succeed()) - }) + It("should successfully create the CAPA machine with correct owner references", func() { + // Get the CAPI machine, so we know the UID in the api server + Eventually(k.Get(capiMachine)).Should(Succeed()) - It("should successfully create the CAPI machine", func() { - Eventually(k.Get( - clusterv1resourcebuilder.Machine().WithName(mapiMachine.Name).WithNamespace(capiNamespace.Name).Build(), - )).Should(Succeed()) - }) + // the MAPI Machine builder uses generateName, the controller makes an infra machine + // with the same name, so in order to avoid a 404 we rebuild the capa machine. + capaMachine = capaMachineBuilder.WithName(mapiMachine.Name).Build() - It("should successfully create the CAPA machine", func() { - Eventually(k.Get( - awsv1resourcebuilder.AWSMachine().WithName(mapiMachine.Name).WithNamespace(capiNamespace.Name).Build(), - )).Should(Succeed()) - }) + Eventually(k.Object(capaMachine), timeout).Should( + HaveField("ObjectMeta.OwnerReferences", ContainElement( + SatisfyAll( + HaveField("Kind", Equal(machineKind)), + HaveField("APIVersion", Equal(clusterv1.GroupVersion.String())), + HaveField("Name", Equal(capiMachine.Name)), + HaveField("UID", Equal(capiMachine.UID)), + ))), + ) - It("should update the synchronized condition on the MAPI machine to True", func() { - Eventually(k.Object(mapiMachine), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")), - ))), - ) - }) + }) + It("should update the synchronized condition on the MAPI machine to True", func() { + Eventually(k.Object(mapiMachine), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")), + ))), + ) }) - Context("when the MAPI machine has status.authoritativeAPI set to Migrating", func() { - BeforeEach(func() { - By("Creating the CAPI and MAPI machines") - // We want a difference, so if we try to reconcile either way we - // will get a new resourceversion - mapiMachine = mapiMachineBuilder.Build() - capiMachine = capiMachineBuilder.Build() + }) - Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") - Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + Context("when the MAPI machine has status.authoritativeAPI set to Migrating", func() { + BeforeEach(func() { + By("Creating the CAPI and MAPI machines") + // We want a difference, so if we try to reconcile either way we + // will get a new resourceversion + mapiMachine = mapiMachineBuilder.Build() + capiMachine = capiMachineBuilder.Build() + + Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") + Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + + By("Setting the status.authoritativeAPI to Migrating") + Eventually(k.UpdateStatus(mapiMachine, func() { + mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMigrating + })).Should(Succeed()) + }) - By("Setting the status.authoritativeAPI to Migrating") - Eventually(k.UpdateStatus(mapiMachine, func() { - mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMigrating - })).Should(Succeed()) - }) + It("should not make any changes to either machine", func() { + // We want to make sure that this is the original ResourceVersion + // since we haven't fetched the resource since it was created. + mapiResourceVersion := mapiMachine.GetResourceVersion() + capiResourceVersion := capiMachine.GetResourceVersion() + Consistently(k.Object(mapiMachine), timeout).Should( + HaveField("ResourceVersion", Equal(mapiResourceVersion)), + ) + Consistently(k.Object(capiMachine), timeout).Should( + HaveField("ResourceVersion", Equal(capiResourceVersion)), + ) + }) + }) - It("should not make any changes to either machine", func() { - // We want to make sure that this is the original ResourceVersion - // since we haven't fetched the resource since it was created. - mapiResourceVersion := mapiMachine.GetResourceVersion() - capiResourceVersion := capiMachine.GetResourceVersion() - Consistently(k.Object(mapiMachine), timeout).Should( - HaveField("ResourceVersion", Equal(mapiResourceVersion)), - ) - Consistently(k.Object(capiMachine), timeout).Should( - HaveField("ResourceVersion", Equal(capiResourceVersion)), - ) - }) + Context("when the MAPI machine has status.authoritativeAPI not set", func() { + BeforeEach(func() { + By("Creating the CAPI and MAPI Machines") + mapiMachine = mapiMachineBuilder.Build() + capiMachine = capiMachineBuilder.Build() + + Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") + Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + + By("Setting the status.authoritativeAPI to the empty string") + Eventually(k.UpdateStatus(mapiMachine, func() { + mapiMachine.Status.AuthoritativeAPI = "" + })).Should(Succeed()) }) - Context("when the MAPI machine has status.authoritativeAPI not set", func() { - BeforeEach(func() { - By("Creating the CAPI and MAPI Machines") - mapiMachine = mapiMachineBuilder.Build() - capiMachine = capiMachineBuilder.Build() + It("should not make any changes", func() { + resourceVersion := mapiMachine.GetResourceVersion() + Consistently(k.Object(mapiMachine), timeout).Should( + HaveField("ResourceVersion", Equal(resourceVersion)), + ) + }) + }) - Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") - Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") + Context("when the MAPI machine does not exist and the CAPI machine does", func() { + Context("and there is no CAPI machineSet owning the machine", func() { + BeforeEach(func() { - By("Setting the status.authoritativeAPI to Migrating") - Eventually(k.UpdateStatus(mapiMachine, func() { - mapiMachine.Status.AuthoritativeAPI = "" - })).Should(Succeed()) + // The CAPI Machine must reference the CAPA machine, + // so build the CAPA machine (without the owner reference) fist. + // Note we don't create it on the API server yet. + noMachineSetCapaMachineBuilder := capaMachineBuilder.WithName("test-machine-no-machineset") + capaMachine = noMachineSetCapaMachineBuilder.Build() + + capiMachine = capiMachineBuilder.WithName("test-machine-no-machineset").WithInfrastructureRef(corev1.ObjectReference{ + Kind: capaMachine.Kind, + Name: capaMachine.GetName(), + Namespace: capaMachine.GetNamespace(), + }).Build() + Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed()) + + // Once we have created the CAPI Machine, and have a UID, add the owner reference to the CAPA machine + // then we can Create() on the API server + noMachineSetCapaMachineBuilder = noMachineSetCapaMachineBuilder.WithOwnerReferences([]metav1.OwnerReference{{ + Kind: machineKind, + APIVersion: clusterv1.GroupVersion.String(), + Name: capiMachine.Name, + UID: capiMachine.UID, + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(false), + }}) + capaMachine = noMachineSetCapaMachineBuilder.Build() + Eventually(k8sClient.Create(ctx, capaMachine)).Should(Succeed()) }) - It("should not make any changes", func() { - resourceVersion := mapiMachine.GetResourceVersion() - Consistently(k.Object(mapiMachine), timeout).Should( + It("should not make any changes to the CAPI machine", func() { + resourceVersion := capiMachine.GetResourceVersion() + Consistently(k.Object(capiMachine), timeout).Should( HaveField("ResourceVersion", Equal(resourceVersion)), ) }) - }) - Context("when the MAPI machine does not exist and the CAPI machine does", func() { - Context("and there is no CAPI machineSet owning the machine", func() { + It("should not create a MAPI machine", func() { + Consistently(k.ObjectList(&mapiv1beta1.MachineList{}), timeout).ShouldNot(HaveField("Items", + ContainElement(HaveField("ObjectMeta.Name", Equal(capiMachine.GetName()))), + )) + }) + + Context("when MAPI machine with the same name and status.authoritativeAPI set to ClusterAPI is created", func() { BeforeEach(func() { - By("Creating the CAPI machine") - capiMachine = capiMachineBuilder.Build() - Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") + mapiMachine = mapiMachineBuilder.WithGenerateName("").WithName(capiMachine.Name).WithAuthoritativeAPI(mapiv1beta1.MachineAuthorityClusterAPI).Build() + Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") - By("Updating the CAPA machine adding the CAPI machine as an owner") - Eventually(k.Update(capaMachine, func() { - capaMachine.OwnerReferences = append(capaMachine.OwnerReferences, metav1.OwnerReference{ - Kind: machineKind, - APIVersion: clusterv1.GroupVersion.String(), - Name: capiMachine.Name, - UID: capiMachine.UID, - BlockOwnerDeletion: ptr.To(true), - Controller: ptr.To(false), - }) - })).Should(Succeed(), "capa machine should be able to be updated") + By("Setting the status.authoritativeAPI to Cluster API") + Eventually(k.UpdateStatus(mapiMachine, func() { + mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI + })).Should(Succeed()) }) + It("should update the synchronized condition on the MAPI machine to True", func() { + Eventually(k.Object(mapiMachine), timeout).Should( + HaveField("Status.Conditions", ContainElement( + SatisfyAll( + HaveField("Type", Equal(consts.SynchronizedCondition)), + HaveField("Status", Equal(corev1.ConditionTrue)), + HaveField("Reason", Equal("ResourceSynchronized")), + HaveField("Message", Equal("Successfully synchronized CAPI Machine to MAPI")), + ))), + ) + }) + + }) + }) + + Context("And there is a CAPI Machineset owning the machine", func() { + var ownerReferencesToCapiMachineSet []metav1.OwnerReference + BeforeEach(func() { + By("Creating the CAPI machineset") + capiMachineSet = capiMachineSetBuilder.Build() + Eventually(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed(), "capi machine set should be able to be created") + + ownerReferencesToCapiMachineSet = []metav1.OwnerReference{{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: machineSetKind, + Name: capiMachineSet.Name, + UID: capiMachineSet.UID, + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + }} + + By("Creating the CAPI machine") + capiMachine = capiMachineBuilder.WithOwnerReferences(ownerReferencesToCapiMachineSet).Build() + Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") + + // Because we expect our controller to sync from CAPI -> MAPI, + // we must ensure the CAPA machine exists, with the correct owner ref. + capaMachine = capaMachineBuilder.WithOwnerReferences([]metav1.OwnerReference{{ + Kind: machineKind, + APIVersion: clusterv1.GroupVersion.String(), + Name: capiMachine.Name, + UID: capiMachine.UID, + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(false), + }}).Build() + Eventually(k8sClient.Create(ctx, capaMachine)).Should(Succeed()) + + }) + + Context("with no MAPI counterpart", func() { It("should not make any changes to the CAPI machine", func() { resourceVersion := capiMachine.GetResourceVersion() Consistently(k.Object(capiMachine), timeout).Should( @@ -543,108 +674,46 @@ var _ = Describe("With a running MachineSync Reconciler", func() { ContainElement(HaveField("ObjectMeta.Name", Equal(capiMachine.GetName()))), )) }) - - Context("when MAPI machine with the same name and status.authoritativeAPI set to ClusterAPI is created", func() { - BeforeEach(func() { - mapiMachine = mapiMachineBuilder.WithName(capiMachine.Name).WithAuthoritativeAPI(mapiv1beta1.MachineAuthorityClusterAPI).Build() - Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created") - - By("Setting the status.authoritativeAPI to Cluster API") - Eventually(k.UpdateStatus(mapiMachine, func() { - mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI - })).Should(Succeed()) - }) - - It("should update the synchronized condition on the MAPI machine to True", func() { - Eventually(k.Object(mapiMachine), timeout).Should( - HaveField("Status.Conditions", ContainElement( - SatisfyAll( - HaveField("Type", Equal(consts.SynchronizedCondition)), - HaveField("Status", Equal(corev1.ConditionTrue)), - HaveField("Reason", Equal("ResourceSynchronized")), - HaveField("Message", Equal("Successfully synchronized CAPI Machine to MAPI")), - ))), - ) - }) - - }) }) - Context("And there is a CAPI Machineset owning the machine", func() { - var ownerReferencesToCapiMachineSet []metav1.OwnerReference + Context("with a MAPI counterpart", func() { BeforeEach(func() { - By("Creating the CAPI machineset") - capiMachineSet = capiMachineSetBuilder.Build() - Eventually(k8sClient.Create(ctx, capiMachineSet)).Should(Succeed(), "capi machine set should be able to be created") - - ownerReferencesToCapiMachineSet = []metav1.OwnerReference{{ - APIVersion: clusterv1.GroupVersion.String(), - Kind: machineSetKind, - Name: capiMachineSet.Name, - UID: capiMachineSet.UID, - Controller: ptr.To(true), - BlockOwnerDeletion: ptr.To(true), - }} + mapiMachineSet := mapiMachineSetBuilder.Build() - By("Creating the CAPI machine") - capiMachine = capiMachineBuilder.WithOwnerReferences(ownerReferencesToCapiMachineSet).Build() - Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created") + Eventually(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed(), "mapi machine set should be able to be created") }) - Context("with no MAPI counterpart", func() { - It("should not make any changes to the CAPI machine", func() { - resourceVersion := capiMachine.GetResourceVersion() - Consistently(k.Object(capiMachine), timeout).Should( - HaveField("ResourceVersion", Equal(resourceVersion)), - ) - }) - - It("should not create a MAPI machine", func() { - Consistently(k.ObjectList(&mapiv1beta1.MachineList{}), timeout).ShouldNot(HaveField("Items", - ContainElement(HaveField("ObjectMeta.Name", Equal(capiMachine.GetName()))), - )) - }) - }) - - Context("with a MAPI counterpart", func() { - BeforeEach(func() { - mapiMachineSet := mapiMachineSetBuilder.Build() - - Eventually(k8sClient.Create(ctx, mapiMachineSet)).Should(Succeed(), "mapi machine set should be able to be created") - }) + // We now set finalizers regardless, so this does not work any more. - // We now set finalizers regardless, so this does not work any more. + // It("should not make any changes to the CAPI machine", func() { + // resourceVersion := capiMachine.GetResourceVersion() + // Consistently(k.Object(capiMachine), timeout).Should( + // HaveField("ResourceVersion", Equal(resourceVersion)), + // ) + // }) - // It("should not make any changes to the CAPI machine", func() { - // resourceVersion := capiMachine.GetResourceVersion() - // Consistently(k.Object(capiMachine), timeout).Should( - // HaveField("ResourceVersion", Equal(resourceVersion)), - // ) - // }) - - It("should create a MAPI machine", func() { - Eventually(k.ObjectList(&mapiv1beta1.MachineList{}), timeout).Should(HaveField("Items", - ContainElement(HaveField("ObjectMeta.Name", Equal(capiMachine.GetName()))), - )) - - mapiMachine = machinev1resourcebuilder.Machine().WithName(capiMachine.Name).WithNamespace(mapiNamespace.Name).Build() - Eventually(k.Object(mapiMachine), timeout).Should(HaveField("ObjectMeta.OwnerReferences", ContainElement( - SatisfyAll( - HaveField("APIVersion", Equal(mapiv1beta1.GroupVersion.String())), - HaveField("Kind", Equal(machineSetKind)), - HaveField("Name", Equal(capiMachineSet.Name)), - HaveField("Controller", Equal(ptr.To(true))), - HaveField("BlockOwnerDeletion", Equal(ptr.To(true))), - ), - ))) + It("should create a MAPI machine", func() { + Eventually(k.ObjectList(&mapiv1beta1.MachineList{}), timeout).Should(HaveField("Items", + ContainElement(HaveField("ObjectMeta.Name", Equal(capiMachine.GetName()))), + )) - }) + mapiMachine = machinev1resourcebuilder.Machine().WithName(capiMachine.Name).WithNamespace(mapiNamespace.Name).Build() + Eventually(k.Object(mapiMachine), timeout).Should(HaveField("ObjectMeta.OwnerReferences", ContainElement( + SatisfyAll( + HaveField("APIVersion", Equal(mapiv1beta1.GroupVersion.String())), + HaveField("Kind", Equal(machineSetKind)), + HaveField("Name", Equal(capiMachineSet.Name)), + HaveField("Controller", Equal(ptr.To(true))), + HaveField("BlockOwnerDeletion", Equal(ptr.To(true))), + ), + ))) }) }) }) + }) Context("when the CAPI infra machine resource does not exist", func() { @@ -859,9 +928,6 @@ var _ = Describe("With a running MachineSync Reconciler", func() { }, timeout).Should(Succeed()) } - By("Creating the CAPI infra machine") - Eventually(k8sClient.Create(ctx, capaMachine), timeout).Should(Succeed(), "capa machine should be able to be created") - By("Creating the MAPI machine") mapiMachine = mapiMachineBuilder.WithName("test-machine").WithLabels(map[string]string{ "machine.openshift.io/cluster-api-cluster": "ci-op-gs2k97d6-c9e33-2smph", @@ -892,6 +958,20 @@ var _ = Describe("With a running MachineSync Reconciler", func() { Eventually(k.Get(capiMachine)).Should(Succeed()) Eventually(k.Get(mapiMachine)).Should(Succeed()) + By("Creating the CAPI infra machine") + capaMachine = capaMachineBuilder.WithOwnerReferences([]metav1.OwnerReference{ + { + Kind: machineKind, + APIVersion: clusterv1.GroupVersion.String(), + Name: capiMachine.Name, + UID: capiMachine.UID, + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(false), + }, + }).Build() + + Eventually(k8sClient.Create(ctx, capaMachine), timeout).Should(Succeed(), "capa machine should be able to be created") + }) AfterEach(func() { @@ -947,21 +1027,22 @@ var _ = Describe("With a running MachineSync Reconciler", func() { ) By("Creating a throwaway MAPI machine") - testMachine := mapiMachineBuilder.WithGenerateName("test-machine").Build() - Eventually(k8sClient.Create(ctx, testMachine), timeout).Should(Succeed()) + sentinelMachine := mapiMachineBuilder.WithGenerateName("sentinel-machine").Build() + Eventually(k8sClient.Create(ctx, sentinelMachine), timeout).Should(Succeed()) By("Setting the throwaway MAPI machine AuthoritativeAPI to Cluster API") - Eventually(k.UpdateStatus(testMachine, func() { - testMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI + Eventually(k.UpdateStatus(sentinelMachine, func() { + sentinelMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI })).Should(Succeed()) - Eventually(k.Object(testMachine), timeout).Should( + Eventually(k.Object(sentinelMachine), timeout).Should( HaveField("Status.AuthoritativeAPI", Equal(mapiv1beta1.MachineAuthorityClusterAPI))) - Eventually(k.Update(testMachine, func() { - testMachine.ObjectMeta.Labels = map[string]string{"test-sentinel": "fubar"} + Eventually(k.Update(sentinelMachine, func() { + sentinelMachine.ObjectMeta.Labels = map[string]string{"test-sentinel": "fubar"} }), timeout).Should(MatchError(ContainSubstring("policy in place"))) }) + Context("with status.AuthoritativeAPI: Machine API", func() { BeforeEach(func() { By("Setting the MAPI machine AuthoritativeAPI to Machine API") @@ -1146,12 +1227,12 @@ var _ = Describe("With a running MachineSync Reconciler", func() { ), ) - checkVAPMachine := clusterv1resourcebuilder.Machine().WithName("vap-checking-machine").WithNamespace(capiNamespace.Name).Build() - Eventually(k8sClient.Create(ctx, checkVAPMachine)).Should(Succeed(), "check vap machine should be able to be created") + sentinelMachine := clusterv1resourcebuilder.Machine().WithName("sentinel-machine").WithNamespace(capiNamespace.Name).Build() + Eventually(k8sClient.Create(ctx, sentinelMachine)).Should(Succeed()) // Continually try to update the capiMachine to a forbidden field until the VAP blocks it - Eventually(k.Update(checkVAPMachine, func() { - checkVAPMachine.ObjectMeta.Labels = map[string]string{"test-sentinel": "fubar"} + Eventually(k.Update(sentinelMachine, func() { + sentinelMachine.ObjectMeta.Labels = map[string]string{"test-sentinel": "fubar"} }), timeout).Should(MatchError(ContainSubstring("policy in place"))) }) @@ -1221,7 +1302,7 @@ var _ = Describe("With a running MachineSync Reconciler", func() { Eventually(k8sClient.Create(ctx, sentinelMachine), timeout).Should(Succeed()) capiSentinelMachine := clusterv1resourcebuilder.Machine().WithName("sentinel-machine").WithNamespace(capiNamespace.Name).Build() - Expect(k8sClient.Create(ctx, capiSentinelMachine)).To(Succeed()) + Eventually(k8sClient.Create(ctx, capiSentinelMachine)).Should(Succeed()) Eventually(k.Get(capiSentinelMachine)).Should(Succeed()) @@ -1234,7 +1315,7 @@ var _ = Describe("With a running MachineSync Reconciler", func() { It("Doesn't allow creation of a MAPI machine with authoritative API MachineAPI and the same name", func() { By("Create the Capi Machine") newCapiMachine := clusterv1resourcebuilder.Machine().WithName("validation-machine").WithNamespace(capiNamespace.Name).Build() - Expect(k8sClient.Create(ctx, newCapiMachine)).To(Succeed()) + Eventually(k8sClient.Create(ctx, newCapiMachine)).Should(Succeed()) By("Create the Mapi Machine") newMapiMachine := mapiMachineBuilder.WithName("validation-machine").WithAuthoritativeAPI(mapiv1beta1.MachineAuthorityMachineAPI).Build() @@ -1244,7 +1325,7 @@ var _ = Describe("With a running MachineSync Reconciler", func() { It("Does allow creation of a MAPI machine with authoritative API Cluster and the same name", func() { By("Create the Capi Machine") newCapiMachine := clusterv1resourcebuilder.Machine().WithName("validation-machine").WithNamespace(capiNamespace.Name).Build() - Expect(k8sClient.Create(ctx, newCapiMachine)).To(Succeed()) + Eventually(k8sClient.Create(ctx, newCapiMachine)).Should(Succeed()) By("Create the Mapi Machine") newMapiMachine := mapiMachineBuilder.WithName("validation-machine").WithAuthoritativeAPI(mapiv1beta1.MachineAuthorityClusterAPI).Build() @@ -1435,7 +1516,7 @@ var _ = Describe("Unsupported AWS fields validating admission policy", Ordered, } } - Expect(k8sClient.Create(ctx, newObj)).To(Succeed()) + Eventually(k8sClient.Create(ctx, newObj)).Should(Succeed()) } checkVAPMachine := awsv1resourcebuilder.AWSMachine().WithName("check-vap-machine").WithNamespace(namespace.Name).Build() @@ -1566,7 +1647,7 @@ var _ = Describe("Unsupported AWS fields validating admission policy", Ordered, It("should block updates that add multiple forbidden fields to AWSMachine", func() { awsMachine := awsv1resourcebuilder.AWSMachine().WithGenerateName("test-aws-machine").WithNamespace(namespace.Name).Build() - Expect(k8sClient.Create(ctx, awsMachine)).To(Succeed()) + Eventually(k8sClient.Create(ctx, awsMachine)).Should(Succeed()) // Add multiple forbidden fields in one update awsMachine.Spec.ImageLookupFormat = testImageLookupFormat @@ -1581,7 +1662,7 @@ var _ = Describe("Unsupported AWS fields validating admission policy", Ordered, It("should not enforce the VAP on other namespaces", func() { otherNamespace := corev1resourcebuilder.Namespace().WithGenerateName("other-namespace").Build() - Expect(k8sClient.Create(ctx, otherNamespace)).To(Succeed()) + Eventually(k8sClient.Create(ctx, otherNamespace)).Should(Succeed()) awsMachine := awsv1resourcebuilder.AWSMachine().WithGenerateName("test-aws-machine").WithNamespace(otherNamespace.Name).Build() awsMachine.Spec.ImageLookupFormat = testImageLookupFormat diff --git a/pkg/controllers/machinesync/suite_test.go b/pkg/controllers/machinesync/suite_test.go index 187d6f07a..d6cead13f 100644 --- a/pkg/controllers/machinesync/suite_test.go +++ b/pkg/controllers/machinesync/suite_test.go @@ -33,7 +33,7 @@ import ( "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/klog/v2" - "k8s.io/klog/v2/textlogger" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" @@ -64,7 +64,8 @@ func TestAPIs(t *testing.T) { var _ = BeforeSuite(func() { klog.SetOutput(GinkgoWriter) - logf.SetLogger(textlogger.NewLogger(textlogger.NewConfig())) + logf.SetLogger(GinkgoLogr) + ctrl.SetLogger(GinkgoLogr) By("bootstrapping test environment") var err error diff --git a/pkg/util/watch_filters.go b/pkg/util/watch_filters.go index 32c9525bd..6e23b6716 100644 --- a/pkg/util/watch_filters.go +++ b/pkg/util/watch_filters.go @@ -21,8 +21,10 @@ import ( "fmt" "github.com/openshift/cluster-capi-operator/pkg/controllers" - "k8s.io/klog/v2" + "k8s.io/apimachinery/pkg/runtime/schema" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -36,12 +38,12 @@ const machineSetKind = "MachineSet" // there to be a mirror object in the MAPI namespace. func RewriteNamespace(namespace string) func(context.Context, client.Object) []reconcile.Request { return func(ctx context.Context, obj client.Object) []reconcile.Request { - klog.V(4).Info( - "reconcile triggered by object", + logger := logf.FromContext(ctx).WithValues( "objectType", fmt.Sprintf("%T", obj), "namespace", obj.GetNamespace(), "name", obj.GetName(), ) + logger.V(4).Info("reconcile triggered") return []reconcile.Request{{ NamespacedName: client.ObjectKey{Namespace: namespace, Name: obj.GetName()}, @@ -55,19 +57,19 @@ func RewriteNamespace(namespace string) func(context.Context, client.Object) []r // for the corresponding MachineSet in the MAPI namespace to trigger reconciliation of the mirror MAPI MachineSet. func ResolveCAPIMachineSetFromInfraMachineTemplate(namespace string) func(context.Context, client.Object) []reconcile.Request { return func(ctx context.Context, obj client.Object) []reconcile.Request { - klog.V(4).Info( - "reconcile triggered by object", + logger := logf.FromContext(ctx).WithValues( "objectType", fmt.Sprintf("%T", obj), "namespace", obj.GetNamespace(), "name", obj.GetName(), ) + logger.V(4).Info("reconcile triggered") objLabels := obj.GetLabels() requests := []reconcile.Request{} machineSetName, ok := objLabels[controllers.MachineSetOpenshiftLabelKey] if ok { - klog.V(4).Info("Object has machine.openshift.io/cluster-api-machineset label, enqueueing request", + logger.V(4).Info("Object has machine.openshift.io/cluster-api-machineset label, enqueueing request", "InfraMachineTemplate", obj.GetName(), machineSetKind, machineSetName) requests = append(requests, reconcile.Request{ @@ -79,6 +81,38 @@ func ResolveCAPIMachineSetFromInfraMachineTemplate(namespace string) func(contex } } +// ResolveCAPIMachineFromInfraMachine resolves a CAPI Machine from an InfraMachine. It takes client.Object, +// and uses owner references to determine the owning CAPI machine. If one is found, it returns a reconcile.Request +// for the corresponding MAPI Machine in the MAPI namespace to trigger reconciliation of the mirror MAPI Machine. +func ResolveCAPIMachineFromInfraMachine(namespace string) func(context.Context, client.Object) []reconcile.Request { + return func(ctx context.Context, obj client.Object) []reconcile.Request { + logger := logf.FromContext(ctx).WithValues( + "objectType", fmt.Sprintf("%T", obj), + "namespace", obj.GetNamespace(), + "name", obj.GetName(), + ) + logger.V(4).Info("reconcile triggered") + + requests := []reconcile.Request{} + + for _, ref := range obj.GetOwnerReferences() { + gv, err := schema.ParseGroupVersion(ref.APIVersion) + if err != nil { + logger.Error(err, "Failed to parse GroupVersion", "APIVersion", ref.APIVersion) + continue + } + + if ref.Kind == "Machine" && gv.Group == clusterv1.GroupVersion.Group { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{Namespace: namespace, Name: ref.Name}, + }) + } + } + + return requests + } +} + // FilterNamespace filters a client.Object request, ensuring they are in the // namespace provided. func FilterNamespace(namespace string) predicate.Predicate {