@@ -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"
@@ -2444,6 +2448,104 @@ func TestReconcileMachinePhases(t *testing.T) {
24442448 }, 10 * time .Second ).Should (BeTrue ())
24452449 })
24462450
2451+ t .Run ("Should set `Updating` when Machine is in-place updating" , func (t * testing.T ) {
2452+ utilfeature .SetFeatureGateDuringTest (t , feature .Gates , feature .InPlaceUpdates , true )
2453+
2454+ g := NewWithT (t )
2455+
2456+ ns , err := env .CreateNamespace (ctx , "test-reconcile-machine-phases" )
2457+ g .Expect (err ).ToNot (HaveOccurred ())
2458+ defer func () {
2459+ g .Expect (env .Cleanup (ctx , ns )).To (Succeed ())
2460+ }()
2461+
2462+ nodeProviderID := fmt .Sprintf ("test://%s" , util .RandomString (6 ))
2463+
2464+ cluster := defaultCluster .DeepCopy ()
2465+ cluster .Namespace = ns .Name
2466+
2467+ bootstrapConfig := defaultBootstrap .DeepCopy ()
2468+ bootstrapConfig .SetNamespace (ns .Name )
2469+ infraMachine := defaultInfra .DeepCopy ()
2470+ infraMachine .SetNamespace (ns .Name )
2471+ g .Expect (unstructured .SetNestedField (infraMachine .Object , nodeProviderID , "spec" , "providerID" )).To (Succeed ())
2472+ machine := defaultMachine .DeepCopy ()
2473+ machine .Namespace = ns .Name
2474+
2475+ // Create Node.
2476+ node := & corev1.Node {
2477+ ObjectMeta : metav1.ObjectMeta {
2478+ GenerateName : "machine-test-node-" ,
2479+ },
2480+ Spec : corev1.NodeSpec {ProviderID : nodeProviderID },
2481+ }
2482+ g .Expect (env .Create (ctx , node )).To (Succeed ())
2483+ defer func () {
2484+ g .Expect (env .Cleanup (ctx , node )).To (Succeed ())
2485+ }()
2486+
2487+ g .Expect (env .Create (ctx , cluster )).To (Succeed ())
2488+ defaultKubeconfigSecret = kubeconfig .GenerateSecret (cluster , kubeconfig .FromEnvTestConfig (env .Config , cluster ))
2489+ g .Expect (env .Create (ctx , defaultKubeconfigSecret )).To (Succeed ())
2490+ // Set InfrastructureReady to true so ClusterCache creates the clusterAccessor.
2491+ patch := client .MergeFrom (cluster .DeepCopy ())
2492+ cluster .Status .Initialization .InfrastructureProvisioned = ptr .To (true )
2493+ g .Expect (env .Status ().Patch (ctx , cluster , patch )).To (Succeed ())
2494+
2495+ g .Expect (env .Create (ctx , bootstrapConfig )).To (Succeed ())
2496+ g .Expect (env .Create (ctx , infraMachine )).To (Succeed ())
2497+ // We have to subtract 2 seconds, because .status.lastUpdated does not contain milliseconds.
2498+ preUpdate := time .Now ().Add (- 2 * time .Second )
2499+ // Create and wait on machine to make sure caches sync and reconciliation triggers.
2500+ g .Expect (env .CreateAndWait (ctx , machine )).To (Succeed ())
2501+
2502+ modifiedMachine := machine .DeepCopy ()
2503+ // Set NodeRef.
2504+ machine .Status .NodeRef = clusterv1.MachineNodeReference {Name : node .Name }
2505+ g .Expect (env .Status ().Patch (ctx , modifiedMachine , client .MergeFrom (machine ))).To (Succeed ())
2506+
2507+ // Set bootstrap ready.
2508+ modifiedBootstrapConfig := bootstrapConfig .DeepCopy ()
2509+ g .Expect (unstructured .SetNestedField (modifiedBootstrapConfig .Object , true , "status" , "initialization" , "dataSecretCreated" )).To (Succeed ())
2510+ g .Expect (unstructured .SetNestedField (modifiedBootstrapConfig .Object , "secret-data" , "status" , "dataSecretName" )).To (Succeed ())
2511+ g .Expect (env .Status ().Patch (ctx , modifiedBootstrapConfig , client .MergeFrom (bootstrapConfig ))).To (Succeed ())
2512+
2513+ // Set infra ready.
2514+ modifiedInfraMachine := infraMachine .DeepCopy ()
2515+ g .Expect (unstructured .SetNestedField (modifiedInfraMachine .Object , true , "status" , "initialization" , "provisioned" )).To (Succeed ())
2516+ g .Expect (env .Status ().Patch (ctx , modifiedInfraMachine , client .MergeFrom (infraMachine ))).To (Succeed ())
2517+
2518+ // Set annotations on Machine, BootstrapConfig and InfraMachine to trigger an in-place update.
2519+ orig := modifiedBootstrapConfig .DeepCopy ()
2520+ annotations .AddAnnotations (modifiedBootstrapConfig , map [string ]string {
2521+ clusterv1 .UpdateInProgressAnnotation : "" ,
2522+ })
2523+ g .Expect (env .Patch (ctx , modifiedBootstrapConfig , client .MergeFrom (orig ))).To (Succeed ())
2524+ orig = modifiedInfraMachine .DeepCopy ()
2525+ annotations .AddAnnotations (modifiedInfraMachine , map [string ]string {
2526+ clusterv1 .UpdateInProgressAnnotation : "" ,
2527+ })
2528+ g .Expect (env .Patch (ctx , modifiedInfraMachine , client .MergeFrom (orig ))).To (Succeed ())
2529+ origMachine := modifiedMachine .DeepCopy ()
2530+ annotations .AddAnnotations (modifiedMachine , map [string ]string {
2531+ runtimev1 .PendingHooksAnnotation : "UpdateMachine" ,
2532+ clusterv1 .UpdateInProgressAnnotation : "" ,
2533+ })
2534+ g .Expect (env .Patch (ctx , modifiedMachine , client .MergeFrom (origMachine ))).To (Succeed ())
2535+
2536+ // Wait until Machine was reconciled.
2537+ g .Eventually (func (g Gomega ) bool {
2538+ if err := env .DirectAPIServerGet (ctx , client .ObjectKeyFromObject (machine ), machine ); err != nil {
2539+ return false
2540+ }
2541+ g .Expect (machine .Status .GetTypedPhase ()).To (Equal (clusterv1 .MachinePhaseUpdating ))
2542+ // Verify that the LastUpdated timestamp was updated
2543+ g .Expect (machine .Status .LastUpdated .IsZero ()).To (BeFalse ())
2544+ g .Expect (machine .Status .LastUpdated .After (preUpdate )).To (BeTrue ())
2545+ return true
2546+ }, 10 * time .Second ).Should (BeTrue ())
2547+ })
2548+
24472549 t .Run ("Should set `Provisioned` when there is a ProviderID and there is no Node" , func (t * testing.T ) {
24482550 g := NewWithT (t )
24492551
0 commit comments