@@ -26,12 +26,16 @@ import (
2626 corev1 "k8s.io/api/core/v1"
2727 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29+ utilfeature "k8s.io/component-base/featuregate/testing"
2930 "k8s.io/utils/ptr"
3031 "sigs.k8s.io/controller-runtime/pkg/client"
3132
3233 clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
34+ runtimev1 "sigs.k8s.io/cluster-api/api/runtime/v1beta2"
3335 "sigs.k8s.io/cluster-api/controllers/clustercache"
36+ "sigs.k8s.io/cluster-api/feature"
3437 "sigs.k8s.io/cluster-api/util"
38+ "sigs.k8s.io/cluster-api/util/annotations"
3539 "sigs.k8s.io/cluster-api/util/conditions"
3640 v1beta1conditions "sigs.k8s.io/cluster-api/util/conditions/deprecated/v1beta1"
3741 "sigs.k8s.io/cluster-api/util/kubeconfig"
@@ -2519,6 +2523,104 @@ func TestReconcileMachinePhases(t *testing.T) {
25192523 }, 10 * time .Second ).Should (BeTrue ())
25202524 })
25212525
2526+ t .Run ("Should set `Updating` when Machine is in-place updating" , func (t * testing.T ) {
2527+ utilfeature .SetFeatureGateDuringTest (t , feature .Gates , feature .InPlaceUpdates , true )
2528+
2529+ g := NewWithT (t )
2530+
2531+ ns , err := env .CreateNamespace (ctx , "test-reconcile-machine-phases" )
2532+ g .Expect (err ).ToNot (HaveOccurred ())
2533+ defer func () {
2534+ g .Expect (env .Cleanup (ctx , ns )).To (Succeed ())
2535+ }()
2536+
2537+ nodeProviderID := fmt .Sprintf ("test://%s" , util .RandomString (6 ))
2538+
2539+ cluster := defaultCluster .DeepCopy ()
2540+ cluster .Namespace = ns .Name
2541+
2542+ bootstrapConfig := defaultBootstrap .DeepCopy ()
2543+ bootstrapConfig .SetNamespace (ns .Name )
2544+ infraMachine := defaultInfra .DeepCopy ()
2545+ infraMachine .SetNamespace (ns .Name )
2546+ g .Expect (unstructured .SetNestedField (infraMachine .Object , nodeProviderID , "spec" , "providerID" )).To (Succeed ())
2547+ machine := defaultMachine .DeepCopy ()
2548+ machine .Namespace = ns .Name
2549+
2550+ // Create Node.
2551+ node := & corev1.Node {
2552+ ObjectMeta : metav1.ObjectMeta {
2553+ GenerateName : "machine-test-node-" ,
2554+ },
2555+ Spec : corev1.NodeSpec {ProviderID : nodeProviderID },
2556+ }
2557+ g .Expect (env .Create (ctx , node )).To (Succeed ())
2558+ defer func () {
2559+ g .Expect (env .Cleanup (ctx , node )).To (Succeed ())
2560+ }()
2561+
2562+ g .Expect (env .Create (ctx , cluster )).To (Succeed ())
2563+ defaultKubeconfigSecret = kubeconfig .GenerateSecret (cluster , kubeconfig .FromEnvTestConfig (env .Config , cluster ))
2564+ g .Expect (env .Create (ctx , defaultKubeconfigSecret )).To (Succeed ())
2565+ // Set InfrastructureReady to true so ClusterCache creates the clusterAccessor.
2566+ patch := client .MergeFrom (cluster .DeepCopy ())
2567+ cluster .Status .Initialization .InfrastructureProvisioned = ptr .To (true )
2568+ g .Expect (env .Status ().Patch (ctx , cluster , patch )).To (Succeed ())
2569+
2570+ g .Expect (env .Create (ctx , bootstrapConfig )).To (Succeed ())
2571+ g .Expect (env .Create (ctx , infraMachine )).To (Succeed ())
2572+ // We have to subtract 2 seconds, because .status.lastUpdated does not contain milliseconds.
2573+ preUpdate := time .Now ().Add (- 2 * time .Second )
2574+ // Create and wait on machine to make sure caches sync and reconciliation triggers.
2575+ g .Expect (env .CreateAndWait (ctx , machine )).To (Succeed ())
2576+
2577+ modifiedMachine := machine .DeepCopy ()
2578+ // Set NodeRef.
2579+ machine .Status .NodeRef = clusterv1.MachineNodeReference {Name : node .Name }
2580+ g .Expect (env .Status ().Patch (ctx , modifiedMachine , client .MergeFrom (machine ))).To (Succeed ())
2581+
2582+ // Set bootstrap ready.
2583+ modifiedBootstrapConfig := bootstrapConfig .DeepCopy ()
2584+ g .Expect (unstructured .SetNestedField (modifiedBootstrapConfig .Object , true , "status" , "initialization" , "dataSecretCreated" )).To (Succeed ())
2585+ g .Expect (unstructured .SetNestedField (modifiedBootstrapConfig .Object , "secret-data" , "status" , "dataSecretName" )).To (Succeed ())
2586+ g .Expect (env .Status ().Patch (ctx , modifiedBootstrapConfig , client .MergeFrom (bootstrapConfig ))).To (Succeed ())
2587+
2588+ // Set infra ready.
2589+ modifiedInfraMachine := infraMachine .DeepCopy ()
2590+ g .Expect (unstructured .SetNestedField (modifiedInfraMachine .Object , true , "status" , "initialization" , "provisioned" )).To (Succeed ())
2591+ g .Expect (env .Status ().Patch (ctx , modifiedInfraMachine , client .MergeFrom (infraMachine ))).To (Succeed ())
2592+
2593+ // Set annotations on Machine, BootstrapConfig and InfraMachine to trigger an in-place update.
2594+ orig := modifiedBootstrapConfig .DeepCopy ()
2595+ annotations .AddAnnotations (modifiedBootstrapConfig , map [string ]string {
2596+ clusterv1 .UpdateInProgressAnnotation : "" ,
2597+ })
2598+ g .Expect (env .Patch (ctx , modifiedBootstrapConfig , client .MergeFrom (orig ))).To (Succeed ())
2599+ orig = modifiedInfraMachine .DeepCopy ()
2600+ annotations .AddAnnotations (modifiedInfraMachine , map [string ]string {
2601+ clusterv1 .UpdateInProgressAnnotation : "" ,
2602+ })
2603+ g .Expect (env .Patch (ctx , modifiedInfraMachine , client .MergeFrom (orig ))).To (Succeed ())
2604+ origMachine := modifiedMachine .DeepCopy ()
2605+ annotations .AddAnnotations (modifiedMachine , map [string ]string {
2606+ runtimev1 .PendingHooksAnnotation : "UpdateMachine" ,
2607+ clusterv1 .UpdateInProgressAnnotation : "" ,
2608+ })
2609+ g .Expect (env .Patch (ctx , modifiedMachine , client .MergeFrom (origMachine ))).To (Succeed ())
2610+
2611+ // Wait until Machine was reconciled.
2612+ g .Eventually (func (g Gomega ) bool {
2613+ if err := env .DirectAPIServerGet (ctx , client .ObjectKeyFromObject (machine ), machine ); err != nil {
2614+ return false
2615+ }
2616+ g .Expect (machine .Status .GetTypedPhase ()).To (Equal (clusterv1 .MachinePhaseUpdating ))
2617+ // Verify that the LastUpdated timestamp was updated
2618+ g .Expect (machine .Status .LastUpdated .IsZero ()).To (BeFalse ())
2619+ g .Expect (machine .Status .LastUpdated .After (preUpdate )).To (BeTrue ())
2620+ return true
2621+ }, 10 * time .Second ).Should (BeTrue ())
2622+ })
2623+
25222624 t .Run ("Should set `Provisioned` when there is a ProviderID and there is no Node" , func (t * testing.T ) {
25232625 g := NewWithT (t )
25242626
0 commit comments