From 0e29b7ac67f1d9d69b917cd0c07cbbc07dd9f505 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 14 Apr 2025 23:21:41 +0200 Subject: [PATCH 1/9] Align control plane and infra cluster contract utils to v1beta2 --- controllers/external/util.go | 10 -- internal/contract/bootstrap.go | 7 -- internal/contract/bootstrap_test.go | 19 ++- internal/contract/controlplane.go | 20 ++-- internal/contract/controlplane_test.go | 34 ++++-- internal/contract/infrastructure_cluster.go | 108 +++++++++++++----- .../contract/infrastructure_cluster_test.go | 39 ++++++- internal/contract/infrastructure_machine.go | 7 -- .../contract/infrastructure_machine_test.go | 19 ++- 9 files changed, 184 insertions(+), 79 deletions(-) diff --git a/controllers/external/util.go b/controllers/external/util.go index b5c149168808..dff6aa0e5c38 100644 --- a/controllers/external/util.go +++ b/controllers/external/util.go @@ -247,13 +247,3 @@ func IsReady(obj *unstructured.Unstructured) (bool, error) { } return ready && found, nil } - -// IsInitialized returns true if the Status.Initialized field on an external object is true. -func IsInitialized(obj *unstructured.Unstructured) (bool, error) { - initialized, found, err := unstructured.NestedBool(obj.Object, "status", "initialized") - if err != nil { - return false, errors.Wrapf(err, "failed to determine %v %q initialized", - obj.GroupVersionKind(), obj.GetName()) - } - return initialized && found, nil -} diff --git a/internal/contract/bootstrap.go b/internal/contract/bootstrap.go index 6a80c2fdd165..30c20b75fe09 100644 --- a/internal/contract/bootstrap.go +++ b/internal/contract/bootstrap.go @@ -32,13 +32,6 @@ func Bootstrap() *BootstrapContract { return bootstrap } -// Ready provide access to status.ready field in a bootstrap object. -func (b *BootstrapContract) Ready() *Bool { - return &Bool{ - path: []string{"status", "ready"}, - } -} - // DataSecretCreated returns if the data secret has been created. func (b *BootstrapContract) DataSecretCreated(contractVersion string) *Bool { if contractVersion == "v1beta1" { diff --git a/internal/contract/bootstrap_test.go b/internal/contract/bootstrap_test.go index 93f58ec1275e..0fa620fdd068 100644 --- a/internal/contract/bootstrap_test.go +++ b/internal/contract/bootstrap_test.go @@ -26,15 +26,26 @@ import ( func TestBootstrap(t *testing.T) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} - t.Run("Manages status.ready", func(t *testing.T) { + t.Run("Manages status.initialization.dataSecretCreated", func(t *testing.T) { g := NewWithT(t) - g.Expect(Bootstrap().Ready().Path()).To(Equal(Path{"status", "ready"})) + g.Expect(Bootstrap().DataSecretCreated("v1beta2").Path()).To(Equal(Path{"status", "initialization", "dataSecretCreated"})) - err := Bootstrap().Ready().Set(obj, true) + err := Bootstrap().DataSecretCreated("v1beta2").Set(obj, true) g.Expect(err).ToNot(HaveOccurred()) - got, err := Bootstrap().Ready().Get(obj) + got, err := Bootstrap().DataSecretCreated("v1beta2").Get(obj) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(*got).To(BeTrue()) + + g.Expect(Bootstrap().DataSecretCreated("v1beta1").Path()).To(Equal(Path{"status", "ready"})) + + objV1beta1 := &unstructured.Unstructured{Object: map[string]interface{}{}} + err = Bootstrap().DataSecretCreated("v1beta1").Set(objV1beta1, true) + g.Expect(err).ToNot(HaveOccurred()) + + got, err = Bootstrap().DataSecretCreated("v1beta1").Get(objV1beta1) g.Expect(err).ToNot(HaveOccurred()) g.Expect(got).ToNot(BeNil()) g.Expect(*got).To(BeTrue()) diff --git a/internal/contract/controlplane.go b/internal/contract/controlplane.go index 4ea0a10e69e7..1d3424b02231 100644 --- a/internal/contract/controlplane.go +++ b/internal/contract/controlplane.go @@ -69,17 +69,23 @@ func (c *ControlPlaneContract) StatusVersion() *String { } } -// Ready provide access to the status.ready field in a ControlPlane object. -func (c *ControlPlaneContract) Ready() *Bool { +// Initialized returns if the control plane has been initialized. +func (c *ControlPlaneContract) Initialized(contractVersion string) *Bool { + if contractVersion == "v1beta1" { + return &Bool{ + path: []string{"status", "ready"}, + } + } + return &Bool{ - path: []string{"status", "ready"}, + path: []string{"status", "initialization", "controlPlaneInitialized"}, } } -// Initialized provide access to status.initialized field in a ControlPlane object. -func (c *ControlPlaneContract) Initialized() *Bool { - return &Bool{ - path: []string{"status", "initialized"}, +// ControlPlaneEndpoint provides access to ControlPlaneEndpoint in an ControlPlane object. +func (c *ControlPlaneContract) ControlPlaneEndpoint() *ControlPlaneEndpoint { + return &ControlPlaneEndpoint{ + path: []string{"spec", "controlPlaneEndpoint"}, } } diff --git a/internal/contract/controlplane_test.go b/internal/contract/controlplane_test.go index e669ffecab48..12d1e3ee853a 100644 --- a/internal/contract/controlplane_test.go +++ b/internal/contract/controlplane_test.go @@ -56,31 +56,47 @@ func TestControlPlane(t *testing.T) { g.Expect(got).ToNot(BeNil()) g.Expect(*got).To(Equal("1.2.3")) }) - t.Run("Manages status.ready", func(t *testing.T) { + t.Run("Manages status.initialization.controlPlaneInitialized", func(t *testing.T) { g := NewWithT(t) - g.Expect(ControlPlane().Ready().Path()).To(Equal(Path{"status", "ready"})) + g.Expect(ControlPlane().Initialized("v1beta2").Path()).To(Equal(Path{"status", "initialization", "controlPlaneInitialized"})) - err := ControlPlane().Ready().Set(obj, true) + err := ControlPlane().Initialized("v1beta2").Set(obj, true) g.Expect(err).ToNot(HaveOccurred()) - got, err := ControlPlane().Ready().Get(obj) + got, err := ControlPlane().Initialized("v1beta2").Get(obj) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(*got).To(BeTrue()) + + g.Expect(ControlPlane().Initialized("v1beta1").Path()).To(Equal(Path{"status", "ready"})) + + objV1beta1 := &unstructured.Unstructured{Object: map[string]interface{}{}} + err = ControlPlane().Initialized("v1beta1").Set(objV1beta1, true) + g.Expect(err).ToNot(HaveOccurred()) + + got, err = ControlPlane().Initialized("v1beta1").Get(objV1beta1) g.Expect(err).ToNot(HaveOccurred()) g.Expect(got).ToNot(BeNil()) g.Expect(*got).To(BeTrue()) }) - t.Run("Manages status.initialized", func(t *testing.T) { + t.Run("Manages spec.ControlPlaneEndpoint", func(t *testing.T) { g := NewWithT(t) - g.Expect(ControlPlane().Initialized().Path()).To(Equal(Path{"status", "initialized"})) + g.Expect(ControlPlane().ControlPlaneEndpoint().Path()).To(Equal(Path{"spec", "controlPlaneEndpoint"})) - err := ControlPlane().Initialized().Set(obj, true) + endpoint := clusterv1.APIEndpoint{ + Host: "example.com", + Port: 1234, + } + + err := ControlPlane().ControlPlaneEndpoint().Set(obj, endpoint) g.Expect(err).ToNot(HaveOccurred()) - got, err := ControlPlane().Initialized().Get(obj) + got, err := ControlPlane().ControlPlaneEndpoint().Get(obj) g.Expect(err).ToNot(HaveOccurred()) g.Expect(got).ToNot(BeNil()) - g.Expect(*got).To(BeTrue()) + g.Expect(*got).To(Equal(endpoint)) }) t.Run("Manages spec.replicas", func(t *testing.T) { g := NewWithT(t) diff --git a/internal/contract/infrastructure_cluster.go b/internal/contract/infrastructure_cluster.go index 3e3084f9b424..7fba2ee52553 100644 --- a/internal/contract/infrastructure_cluster.go +++ b/internal/contract/infrastructure_cluster.go @@ -43,32 +43,22 @@ func InfrastructureCluster() *InfrastructureClusterContract { } // ControlPlaneEndpoint provides access to ControlPlaneEndpoint in an InfrastructureCluster object. -func (c *InfrastructureClusterContract) ControlPlaneEndpoint() *InfrastructureClusterControlPlaneEndpoint { - return &InfrastructureClusterControlPlaneEndpoint{} -} - -// InfrastructureClusterControlPlaneEndpoint provides a helper struct for working with ControlPlaneEndpoint -// in an InfrastructureCluster object. -type InfrastructureClusterControlPlaneEndpoint struct{} - -// Host provides access to the host field in the ControlPlaneEndpoint in an InfrastructureCluster object. -func (c *InfrastructureClusterControlPlaneEndpoint) Host() *String { - return &String{ - path: []string{"spec", "controlPlaneEndpoint", "host"}, +func (c *InfrastructureClusterContract) ControlPlaneEndpoint() *ControlPlaneEndpoint { + return &ControlPlaneEndpoint{ + path: []string{"spec", "controlPlaneEndpoint"}, } } -// Port provides access to the port field in the ControlPlaneEndpoint in an InfrastructureCluster object. -func (c *InfrastructureClusterControlPlaneEndpoint) Port() *Int64 { - return &Int64{ - path: []string{"spec", "controlPlaneEndpoint", "port"}, +// Provisioned returns if the infrastructure cluster has been provisioned. +func (c *InfrastructureClusterContract) Provisioned(contractVersion string) *Bool { + if contractVersion == "v1beta1" { + return &Bool{ + path: []string{"status", "ready"}, + } } -} -// Ready provides access to the status.ready field in an InfrastructureCluster object. -func (c *InfrastructureClusterContract) Ready() *Bool { return &Bool{ - path: []string{"status", "ready"}, + path: []string{"status", "initialization", "provisioned"}, } } @@ -110,20 +100,20 @@ func (c *InfrastructureClusterContract) FailureDomains() *FailureDomains { func (c *InfrastructureClusterContract) IgnorePaths(infrastructureCluster *unstructured.Unstructured) ([]Path, error) { var ignorePaths []Path - host, ok, err := unstructured.NestedString(infrastructureCluster.UnstructuredContent(), InfrastructureCluster().ControlPlaneEndpoint().Host().Path()...) + host, ok, err := unstructured.NestedString(infrastructureCluster.UnstructuredContent(), InfrastructureCluster().ControlPlaneEndpoint().host().Path()...) if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve %s", InfrastructureCluster().ControlPlaneEndpoint().Host().Path().String()) + return nil, errors.Wrapf(err, "failed to retrieve %s", InfrastructureCluster().ControlPlaneEndpoint().host().Path().String()) } if ok && host == "" { - ignorePaths = append(ignorePaths, InfrastructureCluster().ControlPlaneEndpoint().Host().Path()) + ignorePaths = append(ignorePaths, InfrastructureCluster().ControlPlaneEndpoint().host().Path()) } - port, ok, err := unstructured.NestedInt64(infrastructureCluster.UnstructuredContent(), InfrastructureCluster().ControlPlaneEndpoint().Port().Path()...) + port, ok, err := unstructured.NestedInt64(infrastructureCluster.UnstructuredContent(), InfrastructureCluster().ControlPlaneEndpoint().port().Path()...) if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve %s", InfrastructureCluster().ControlPlaneEndpoint().Port().Path().String()) + return nil, errors.Wrapf(err, "failed to retrieve %s", InfrastructureCluster().ControlPlaneEndpoint().port().Path().String()) } if ok && port == 0 { - ignorePaths = append(ignorePaths, InfrastructureCluster().ControlPlaneEndpoint().Port().Path()) + ignorePaths = append(ignorePaths, InfrastructureCluster().ControlPlaneEndpoint().port().Path()) } return ignorePaths, nil @@ -179,3 +169,69 @@ func (d *FailureDomains) Set(obj *unstructured.Unstructured, values clusterv1.Fa } return nil } + +// ControlPlaneEndpoint provides a helper struct for working with ControlPlaneEndpoint +// in an InfrastructureCluster object. +type ControlPlaneEndpoint struct { + path Path +} + +// Path returns the path to the ControlPlaneEndpoint in an InfrastructureCluster object. +func (c *ControlPlaneEndpoint) Path() Path { + return c.path +} + +// Get gets the ControlPlaneEndpoint value. +func (c *ControlPlaneEndpoint) Get(obj *unstructured.Unstructured) (*clusterv1.APIEndpoint, error) { + controlPlaneEndpointMap, ok, err := unstructured.NestedMap(obj.UnstructuredContent(), c.path...) + if err != nil { + return nil, errors.Wrapf(err, "failed to get %s from object", "."+strings.Join(c.path, ".")) + } + if !ok { + return nil, errors.Wrapf(ErrFieldNotFound, "path %s", "."+strings.Join(c.path, ".")) + } + + endpoint := &clusterv1.APIEndpoint{} + s, err := json.Marshal(controlPlaneEndpointMap) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshall field at %s to json", "."+strings.Join(c.path, ".")) + } + err = json.Unmarshal(s, &endpoint) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshall field at %s to json", "."+strings.Join(c.path, ".")) + } + + return endpoint, nil +} + +// Set sets the ControlPlaneEndpoint value. +func (c *ControlPlaneEndpoint) Set(obj *unstructured.Unstructured, value clusterv1.APIEndpoint) error { + controlPlaneEndpointMap := make(map[string]interface{}) + s, err := json.Marshal(value) + if err != nil { + return errors.Wrapf(err, "failed to marshall supplied values to json for path %s", "."+strings.Join(c.path, ".")) + } + err = json.Unmarshal(s, &controlPlaneEndpointMap) + if err != nil { + return errors.Wrapf(err, "failed to unmarshall supplied values to json for path %s", "."+strings.Join(c.path, ".")) + } + + if err := unstructured.SetNestedField(obj.UnstructuredContent(), controlPlaneEndpointMap, c.path...); err != nil { + return errors.Wrapf(err, "failed to set path %s of object %v", "."+strings.Join(c.path, "."), obj.GroupVersionKind()) + } + return nil +} + +// host provides access to the host field in the ControlPlaneEndpoint in an InfrastructureCluster object. +func (c *ControlPlaneEndpoint) host() *String { + return &String{ + path: c.path.Append("host"), + } +} + +// port provides access to the port field in the ControlPlaneEndpoint in an InfrastructureCluster object. +func (c *ControlPlaneEndpoint) port() *Int64 { + return &Int64{ + path: c.path.Append("port"), + } +} diff --git a/internal/contract/infrastructure_cluster_test.go b/internal/contract/infrastructure_cluster_test.go index b936ee6cec0e..1aafd1d1a01c 100644 --- a/internal/contract/infrastructure_cluster_test.go +++ b/internal/contract/infrastructure_cluster_test.go @@ -28,15 +28,44 @@ import ( func TestInfrastructureCluster(t *testing.T) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} - t.Run("Manages status.ready", func(t *testing.T) { + t.Run("Manages spec.ControlPlaneEndpoint", func(t *testing.T) { g := NewWithT(t) - g.Expect(InfrastructureCluster().Ready().Path()).To(Equal(Path{"status", "ready"})) + g.Expect(InfrastructureCluster().ControlPlaneEndpoint().Path()).To(Equal(Path{"spec", "controlPlaneEndpoint"})) - err := InfrastructureCluster().Ready().Set(obj, true) + endpoint := clusterv1.APIEndpoint{ + Host: "example.com", + Port: 1234, + } + + err := InfrastructureCluster().ControlPlaneEndpoint().Set(obj, endpoint) + g.Expect(err).ToNot(HaveOccurred()) + + got, err := InfrastructureCluster().ControlPlaneEndpoint().Get(obj) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(*got).To(Equal(endpoint)) + }) + t.Run("Manages status.initialization.provisioned", func(t *testing.T) { + g := NewWithT(t) + + g.Expect(InfrastructureCluster().Provisioned("v1beta2").Path()).To(Equal(Path{"status", "initialization", "provisioned"})) + + err := InfrastructureCluster().Provisioned("v1beta2").Set(obj, true) + g.Expect(err).ToNot(HaveOccurred()) + + got, err := InfrastructureCluster().Provisioned("v1beta2").Get(obj) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(*got).To(BeTrue()) + + g.Expect(InfrastructureCluster().Provisioned("v1beta1").Path()).To(Equal(Path{"status", "ready"})) + + objV1beta1 := &unstructured.Unstructured{Object: map[string]interface{}{}} + err = InfrastructureCluster().Provisioned("v1beta1").Set(objV1beta1, true) g.Expect(err).ToNot(HaveOccurred()) - got, err := InfrastructureCluster().Ready().Get(obj) + got, err = InfrastructureCluster().Provisioned("v1beta1").Get(objV1beta1) g.Expect(err).ToNot(HaveOccurred()) g.Expect(got).ToNot(BeNil()) g.Expect(*got).To(BeTrue()) @@ -98,7 +127,7 @@ func TestInfrastructureCluster(t *testing.T) { }) } -func TestInfrastructureClusterControlPlaneEndpoint(t *testing.T) { +func TestInfrastructureClusterIgnorePaths(t *testing.T) { tests := []struct { name string infrastructureCluster *unstructured.Unstructured diff --git a/internal/contract/infrastructure_machine.go b/internal/contract/infrastructure_machine.go index 8431dafc83af..9f2992c1ce59 100644 --- a/internal/contract/infrastructure_machine.go +++ b/internal/contract/infrastructure_machine.go @@ -42,13 +42,6 @@ func InfrastructureMachine() *InfrastructureMachineContract { return infrastructureMachine } -// Ready provides access to status.ready field in an InfrastructureMachine object. -func (m *InfrastructureMachineContract) Ready() *Bool { - return &Bool{ - path: []string{"status", "ready"}, - } -} - // Provisioned returns if the InfrastructureMachine is provisioned. func (m *InfrastructureMachineContract) Provisioned(contractVersion string) *Bool { if contractVersion == "v1beta1" { diff --git a/internal/contract/infrastructure_machine_test.go b/internal/contract/infrastructure_machine_test.go index 67fd22b1e84e..c3b8f47f3ac8 100644 --- a/internal/contract/infrastructure_machine_test.go +++ b/internal/contract/infrastructure_machine_test.go @@ -41,15 +41,26 @@ func TestInfrastructureMachine(t *testing.T) { g.Expect(got).ToNot(BeNil()) g.Expect(*got).To(Equal("fake-provider-id")) }) - t.Run("Manages status.ready", func(t *testing.T) { + t.Run("Manages status.initialization.provisioned", func(t *testing.T) { g := NewWithT(t) - g.Expect(InfrastructureMachine().Ready().Path()).To(Equal(Path{"status", "ready"})) + g.Expect(InfrastructureMachine().Provisioned("v1beta2").Path()).To(Equal(Path{"status", "initialization", "provisioned"})) - err := InfrastructureMachine().Ready().Set(obj, true) + err := InfrastructureMachine().Provisioned("v1beta2").Set(obj, true) g.Expect(err).ToNot(HaveOccurred()) - got, err := InfrastructureMachine().Ready().Get(obj) + got, err := InfrastructureMachine().Provisioned("v1beta2").Get(obj) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(*got).To(BeTrue()) + + g.Expect(InfrastructureMachine().Provisioned("v1beta1").Path()).To(Equal(Path{"status", "ready"})) + + objV1beta1 := &unstructured.Unstructured{Object: map[string]interface{}{}} + err = InfrastructureMachine().Provisioned("v1beta1").Set(objV1beta1, true) + g.Expect(err).ToNot(HaveOccurred()) + + got, err = InfrastructureMachine().Provisioned("v1beta1").Get(objV1beta1) g.Expect(err).ToNot(HaveOccurred()) g.Expect(got).ToNot(BeNil()) g.Expect(*got).To(BeTrue()) From b50b7cec4cd8a0b8be508e47a0200012b77b0baa Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 14 Apr 2025 23:23:09 +0200 Subject: [PATCH 2/9] Align cluster controller to v1beta2 --- .../cluster/cluster_controller_phases.go | 90 ++++++++++++------- .../cluster/cluster_controller_phases_test.go | 9 +- .../cluster/cluster_controller_status.go | 30 ++++--- .../cluster/cluster_controller_status_test.go | 22 ++--- 4 files changed, 91 insertions(+), 60 deletions(-) diff --git a/internal/controllers/cluster/cluster_controller_phases.go b/internal/controllers/cluster/cluster_controller_phases.go index 7620af851739..5577d3b4726e 100644 --- a/internal/controllers/cluster/cluster_controller_phases.go +++ b/internal/controllers/cluster/cluster_controller_phases.go @@ -19,6 +19,7 @@ package cluster import ( "context" "fmt" + "strings" "time" "github.com/pkg/errors" @@ -36,6 +37,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta2" "sigs.k8s.io/cluster-api/controllers/external" capierrors "sigs.k8s.io/cluster-api/errors" + "sigs.k8s.io/cluster-api/internal/contract" "sigs.k8s.io/cluster-api/util" v1beta1conditions "sigs.k8s.io/cluster-api/util/conditions/deprecated/v1beta1" utilconversion "sigs.k8s.io/cluster-api/util/conversion" @@ -210,17 +212,26 @@ func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctr } s.infraCluster = obj - // Determine if the infrastructure provider is ready. - ready, err := external.IsReady(s.infraCluster) + // Determine contract version used by the InfrastructureCluster. + contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, obj.GroupVersionKind()) if err != nil { return ctrl.Result{}, err } - if ready && !cluster.Status.InfrastructureReady { - log.Info("Infrastructure provider has completed cluster infrastructure provisioning and reports status.ready", cluster.Spec.InfrastructureRef.Kind, klog.KObj(s.infraCluster)) + + // Determine if the InfrastructureCluster is provisioned. + provisioned, err := contract.InfrastructureCluster().Provisioned(contractVersion).Get(obj) + if err != nil { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, err + } + provisioned = ptr.To(false) + } + if *provisioned && !cluster.Status.InfrastructureReady { + log.Info("Infrastructure provider has completed provisioning", cluster.Spec.InfrastructureRef.Kind, klog.KObj(s.infraCluster)) } // Report a summary of current status of the infrastructure object defined for this cluster. - fallBack := v1beta1conditions.WithFallbackValue(ready, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "") + fallBack := v1beta1conditions.WithFallbackValue(*provisioned, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "") if !s.cluster.DeletionTimestamp.IsZero() { fallBack = v1beta1conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") } @@ -234,27 +245,35 @@ func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctr return ctrl.Result{}, nil } - // If the infrastructure provider is not ready (and it wasn't ready before), return early. - if !ready && !cluster.Status.InfrastructureReady { + // If the InfrastructureCluster is not provisioned (and it wasn't already provisioned before), return. + if !*provisioned && !cluster.Status.InfrastructureReady { log.V(3).Info("Infrastructure provider is not ready yet") return ctrl.Result{}, nil } // Get and parse Spec.ControlPlaneEndpoint field from the infrastructure provider. if !cluster.Spec.ControlPlaneEndpoint.IsValid() { - if err := util.UnstructuredUnmarshalField(s.infraCluster, &cluster.Spec.ControlPlaneEndpoint, "spec", "controlPlaneEndpoint"); err != nil && err != util.ErrUnstructuredFieldNotFound { - return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Spec.ControlPlaneEndpoint from infrastructure provider for Cluster %q in namespace %q", - cluster.Name, cluster.Namespace) + if endpoint, err := contract.InfrastructureCluster().ControlPlaneEndpoint().Get(obj); err == nil { + cluster.Spec.ControlPlaneEndpoint = *endpoint + } else { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve %s from infrastructure provider for Cluster %q in namespace %q", + strings.Join(contract.InfrastructureCluster().ControlPlaneEndpoint().Path(), "."), cluster.Name, cluster.Namespace) + } + cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{} } } // Get and parse Status.FailureDomains from the infrastructure provider. - failureDomains := clusterv1.FailureDomains{} - if err := util.UnstructuredUnmarshalField(s.infraCluster, &failureDomains, "status", "failureDomains"); err != nil && err != util.ErrUnstructuredFieldNotFound { - return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Status.FailureDomains from infrastructure provider for Cluster %q in namespace %q", - cluster.Name, cluster.Namespace) + if failureDomains, err := contract.InfrastructureCluster().FailureDomains().Get(obj); err == nil { + cluster.Status.FailureDomains = *failureDomains + } else { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve %s from infrastructure provider for Cluster %q in namespace %q", + strings.Join(contract.InfrastructureCluster().FailureDomains().Path(), "."), cluster.Name, cluster.Namespace) + } + cluster.Status.FailureDomains = clusterv1.FailureDomains{} } - cluster.Status.FailureDomains = failureDomains // Only record the event if the status has changed if !cluster.Status.InfrastructureReady { @@ -296,17 +315,26 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, s *scope) (ctrl. } s.controlPlane = obj - // Determine if the control plane provider is ready. - ready, err := external.IsReady(s.controlPlane) + // Determine contract version used by the ControlPlane. + contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, obj.GroupVersionKind()) if err != nil { return ctrl.Result{}, err } - if ready && !cluster.Status.ControlPlaneReady { - log.Info("ControlPlane provider has completed provisioning and reports status.ready", cluster.Spec.ControlPlaneRef.Kind, klog.KObj(s.controlPlane)) + + // Determine if the ControlPlane is provisioned. + initialized, err := contract.ControlPlane().Initialized(contractVersion).Get(obj) + if err != nil { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, err + } + initialized = ptr.To(false) + } + if *initialized && !cluster.Status.ControlPlaneReady { + log.Info("Infrastructure provider has completed provisioning", cluster.Spec.ControlPlaneRef.Kind, klog.KObj(s.controlPlane)) } // Report a summary of current status of the control plane object defined for this cluster. - fallBack := v1beta1conditions.WithFallbackValue(ready, clusterv1.WaitingForControlPlaneFallbackReason, clusterv1.ConditionSeverityInfo, "") + fallBack := v1beta1conditions.WithFallbackValue(*initialized, clusterv1.WaitingForControlPlaneFallbackReason, clusterv1.ConditionSeverityInfo, "") if !s.cluster.DeletionTimestamp.IsZero() { fallBack = v1beta1conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") } @@ -320,14 +348,9 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, s *scope) (ctrl. return ctrl.Result{}, nil } - // Update cluster.Status.ControlPlaneInitialized if it hasn't already been set - // Determine if the control plane provider is initialized. + // Update cluster.Status.ControlPlaneInitialized if it hasn't already been set. if !v1beta1conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) { - initialized, err := external.IsInitialized(s.controlPlane) - if err != nil { - return ctrl.Result{}, err - } - if initialized { + if *initialized { v1beta1conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition) } else { v1beta1conditions.MarkFalse(cluster, clusterv1.ControlPlaneInitializedCondition, clusterv1.WaitingForControlPlaneProviderInitializedReason, clusterv1.ConditionSeverityInfo, "Waiting for control plane provider to indicate the control plane has been initialized") @@ -335,16 +358,21 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, s *scope) (ctrl. } // If the control plane is not ready (and it wasn't ready before), return early. - if !ready && !cluster.Status.ControlPlaneReady { + if !*initialized && !cluster.Status.ControlPlaneReady { log.V(3).Info("Control Plane provider is not ready yet") return ctrl.Result{}, nil } // Get and parse Spec.ControlPlaneEndpoint field from the control plane provider. if !cluster.Spec.ControlPlaneEndpoint.IsValid() { - if err := util.UnstructuredUnmarshalField(s.controlPlane, &cluster.Spec.ControlPlaneEndpoint, "spec", "controlPlaneEndpoint"); err != nil && err != util.ErrUnstructuredFieldNotFound { - return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Spec.ControlPlaneEndpoint from control plane provider for Cluster %q in namespace %q", - cluster.Name, cluster.Namespace) + if endpoint, err := contract.ControlPlane().ControlPlaneEndpoint().Get(obj); err == nil { + cluster.Spec.ControlPlaneEndpoint = *endpoint + } else { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve %s from control plane provider for Cluster %q in namespace %q", + strings.Join(contract.ControlPlane().ControlPlaneEndpoint().Path(), "."), cluster.Name, cluster.Namespace) + } + cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{} } } diff --git a/internal/controllers/cluster/cluster_controller_phases_test.go b/internal/controllers/cluster/cluster_controller_phases_test.go index 1dfb9b886a69..0e84ba969cc9 100644 --- a/internal/controllers/cluster/cluster_controller_phases_test.go +++ b/internal/controllers/cluster/cluster_controller_phases_test.go @@ -433,7 +433,9 @@ func TestClusterReconcileControlPlane(t *testing.T) { }, }, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "controlPlaneInitialized": true, + }, }, }, expectErr: false, @@ -455,8 +457,9 @@ func TestClusterReconcileControlPlane(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, - "initialized": true, + "initialization": map[string]interface{}{ + "controlPlaneInitialized": true, + }, }, }, expectErr: false, diff --git a/internal/controllers/cluster/cluster_controller_status.go b/internal/controllers/cluster/cluster_controller_status.go index 1fd98f4be32e..b922388918d7 100644 --- a/internal/controllers/cluster/cluster_controller_status.go +++ b/internal/controllers/cluster/cluster_controller_status.go @@ -22,6 +22,7 @@ import ( "sort" "time" + "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -30,7 +31,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta2" - "sigs.k8s.io/cluster-api/controllers/external" expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta2" "sigs.k8s.io/cluster-api/internal/contract" "sigs.k8s.io/cluster-api/util" @@ -73,7 +73,7 @@ func (r *Reconciler) updateStatus(ctx context.Context, s *scope) error { // conditions setInfrastructureReadyCondition(ctx, s.cluster, s.infraCluster, s.infraClusterIsNotFound) setControlPlaneAvailableCondition(ctx, s.cluster, s.controlPlane, s.controlPlaneIsNotFound) - setControlPlaneInitializedCondition(ctx, s.cluster, s.controlPlane, s.descendants.controlPlaneMachines, s.infraClusterIsNotFound, s.getDescendantsSucceeded) + setControlPlaneInitializedCondition(ctx, s.cluster, s.controlPlane, controlPlaneContractVersion, s.descendants.controlPlaneMachines, s.infraClusterIsNotFound, s.getDescendantsSucceeded) setWorkersAvailableCondition(ctx, s.cluster, expv1.MachinePoolList{}, s.descendants.machineDeployments, s.getDescendantsSucceeded) setControlPlaneMachinesReadyCondition(ctx, s.cluster, controlPlaneMachines, s.getDescendantsSucceeded) setWorkerMachinesReadyCondition(ctx, s.cluster, workerMachines, s.getDescendantsSucceeded) @@ -445,7 +445,7 @@ func setControlPlaneAvailableCondition(_ context.Context, cluster *clusterv1.Clu }) } -func setControlPlaneInitializedCondition(ctx context.Context, cluster *clusterv1.Cluster, controlPlane *unstructured.Unstructured, controlPlaneMachines collections.Machines, controlPlaneIsNotFound bool, getDescendantsSucceeded bool) { +func setControlPlaneInitializedCondition(ctx context.Context, cluster *clusterv1.Cluster, controlPlane *unstructured.Unstructured, contractVersion string, controlPlaneMachines collections.Machines, controlPlaneIsNotFound bool, getDescendantsSucceeded bool) { log := ctrl.LoggerFrom(ctx) // No-op if control plane is already initialized. @@ -490,19 +490,23 @@ func setControlPlaneInitializedCondition(ctx context.Context, cluster *clusterv1 return } - initialized, err := external.IsInitialized(controlPlane) + // Determine if the ControlPlane is provisioned. + initialized, err := contract.ControlPlane().Initialized(contractVersion).Get(controlPlane) if err != nil { - log.Error(err, fmt.Sprintf("Failed to get status.initialized from %s", cluster.Spec.ControlPlaneRef.Kind)) - conditions.Set(cluster, metav1.Condition{ - Type: clusterv1.ClusterControlPlaneInitializedV1Beta2Condition, - Status: metav1.ConditionUnknown, - Reason: clusterv1.ClusterControlPlaneInitializedInternalErrorV1Beta2Reason, - Message: "Please check controller logs for errors", - }) - return + if !errors.Is(err, contract.ErrFieldNotFound) { + log.Error(err, fmt.Sprintf("Failed to get status.initialized from %s", cluster.Spec.ControlPlaneRef.Kind)) + conditions.Set(cluster, metav1.Condition{ + Type: clusterv1.ClusterControlPlaneInitializedV1Beta2Condition, + Status: metav1.ConditionUnknown, + Reason: clusterv1.ClusterControlPlaneInitializedInternalErrorV1Beta2Reason, + Message: "Please check controller logs for errors", + }) + return + } + initialized = ptr.To(false) } - if initialized { + if *initialized { conditions.Set(cluster, metav1.Condition{ Type: clusterv1.ClusterControlPlaneInitializedV1Beta2Condition, Status: metav1.ConditionTrue, diff --git a/internal/controllers/cluster/cluster_controller_status_test.go b/internal/controllers/cluster/cluster_controller_status_test.go index 9e232dccaa3b..d77c0abb2ea3 100644 --- a/internal/controllers/cluster/cluster_controller_status_test.go +++ b/internal/controllers/cluster/cluster_controller_status_test.go @@ -264,7 +264,7 @@ func TestSetInfrastructureReadyCondition(t *testing.T) { { name: "Use status.InfrastructureReady flag as a fallback Ready condition from infra cluster is missing", cluster: fakeCluster("c", infrastructureRef{Kind: "FakeInfraCluster"}), - infraCluster: fakeInfraCluster("i1", ready(false)), + infraCluster: fakeInfraCluster("i1", provisioned(false)), infraClusterIsNotFound: false, expectCondition: metav1.Condition{ Type: clusterv1.ClusterInfrastructureReadyV1Beta2Condition, @@ -276,7 +276,7 @@ func TestSetInfrastructureReadyCondition(t *testing.T) { { name: "Use status.InfrastructureReady flag as a fallback Ready condition from infra cluster is missing (ready true)", cluster: fakeCluster("c", infrastructureRef{Kind: "FakeInfraCluster"}, infrastructureReady(true)), - infraCluster: fakeInfraCluster("i1", ready(true)), + infraCluster: fakeInfraCluster("i1", provisioned(true)), infraClusterIsNotFound: false, expectCondition: metav1.Condition{ Type: clusterv1.ClusterInfrastructureReadyV1Beta2Condition, @@ -429,7 +429,7 @@ func TestSetControlPlaneAvailableCondition(t *testing.T) { { name: "Use status.controlPlaneReady flag as a fallback Available condition from control plane is missing", cluster: fakeCluster("c", controlPlaneRef{Kind: "FakeControlPlane"}), - controlPlane: fakeControlPlane("cp1", ready(false)), + controlPlane: fakeControlPlane("cp1", initialized(false)), controlPlaneIsNotFound: false, expectCondition: metav1.Condition{ Type: clusterv1.ClusterControlPlaneAvailableV1Beta2Condition, @@ -441,7 +441,7 @@ func TestSetControlPlaneAvailableCondition(t *testing.T) { { name: "Use status.controlPlaneReady flag as a fallback Available condition from control plane is missing (ready true)", cluster: fakeCluster("c", controlPlaneRef{Kind: "FakeControlPlane"}, controlPlaneReady(true)), - controlPlane: fakeControlPlane("cp1", ready(true)), + controlPlane: fakeControlPlane("cp1", initialized(true)), controlPlaneIsNotFound: false, expectCondition: metav1.Condition{ Type: clusterv1.ClusterControlPlaneAvailableV1Beta2Condition, @@ -664,7 +664,7 @@ func TestSetControlPlaneInitialized(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - setControlPlaneInitializedCondition(ctx, tt.cluster, tt.controlPlane, tt.machines, tt.controlPlaneIsNotFound, tt.getDescendantsSucceeded) + setControlPlaneInitializedCondition(ctx, tt.cluster, tt.controlPlane, "v1beta2", tt.machines, tt.controlPlaneIsNotFound, tt.getDescendantsSucceeded) condition := conditions.Get(tt.cluster, clusterv1.ClusterControlPlaneInitializedV1Beta2Condition) g.Expect(condition).ToNot(BeNil()) @@ -3106,20 +3106,16 @@ func (c OwnedByCluster) ApplyToMachine(m *clusterv1.Machine) { }) } -type ready bool +type provisioned bool -func (r ready) ApplyToControlPlane(cp *unstructured.Unstructured) { - _ = contract.ControlPlane().Ready().Set(cp, bool(r)) -} - -func (r ready) ApplyToInfraCluster(i *unstructured.Unstructured) { - _ = contract.InfrastructureCluster().Ready().Set(i, bool(r)) +func (r provisioned) ApplyToInfraCluster(i *unstructured.Unstructured) { + _ = contract.InfrastructureCluster().Provisioned("v1beta2").Set(i, bool(r)) } type initialized bool func (r initialized) ApplyToControlPlane(cp *unstructured.Unstructured) { - _ = contract.ControlPlane().Initialized().Set(cp, bool(r)) + _ = contract.ControlPlane().Initialized("v1beta2").Set(cp, bool(r)) } type creationTimestamp metav1.Time From 653b80b23f36b622ea1c8c5cbf907f978aa6b134 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 14 Apr 2025 23:23:56 +0200 Subject: [PATCH 3/9] Move test builders to v1beta2 --- util/test/builder/bootstrap.go | 7 ++++++- util/test/builder/controlplane.go | 7 ++++++- util/test/builder/crds.go | 4 ++-- util/test/builder/infrastructure.go | 15 ++++++++++++--- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/util/test/builder/bootstrap.go b/util/test/builder/bootstrap.go index 69ea453bc029..1ec1936115b8 100644 --- a/util/test/builder/bootstrap.go +++ b/util/test/builder/bootstrap.go @@ -84,7 +84,12 @@ func testBootstrapConfigCRD(gvk schema.GroupVersionKind) *apiextensionsv1.Custom Type: "object", Properties: map[string]apiextensionsv1.JSONSchemaProps{ // mandatory field from the Cluster API contract - "ready": {Type: "boolean"}, + "initialization": { + Type: "object", + Properties: map[string]apiextensionsv1.JSONSchemaProps{ + "dataSecretCreated": {Type: "boolean"}, + }, + }, "dataSecretName": {Type: "string"}, // General purpose fields to be used in different test scenario. "foo": {Type: "string"}, diff --git a/util/test/builder/controlplane.go b/util/test/builder/controlplane.go index 2653d217bbeb..c58f695c1902 100644 --- a/util/test/builder/controlplane.go +++ b/util/test/builder/controlplane.go @@ -84,7 +84,12 @@ func testControlPlaneCRD(gvk schema.GroupVersionKind) *apiextensionsv1.CustomRes Type: "object", Properties: map[string]apiextensionsv1.JSONSchemaProps{ // mandatory field from the Cluster API contract - "ready": {Type: "boolean"}, + "initialization": { + Type: "object", + Properties: map[string]apiextensionsv1.JSONSchemaProps{ + "controlPlaneInitialized": {Type: "boolean"}, + }, + }, // mandatory field from the Cluster API contract - replicas support "replicas": {Type: "integer", Format: "int32"}, "selector": {Type: "string"}, diff --git a/util/test/builder/crds.go b/util/test/builder/crds.go index 374f2b97646a..78c707165772 100644 --- a/util/test/builder/crds.go +++ b/util/test/builder/crds.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/utils/ptr" - clusterv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta2" "sigs.k8s.io/cluster-api/util/contract" ) @@ -51,7 +51,7 @@ func generateCRD(gvk schema.GroupVersionKind, properties map[string]apiextension ObjectMeta: metav1.ObjectMeta{ Name: contract.CalculateCRDName(gvk.Group, gvk.Kind), Labels: map[string]string{ - clusterv1beta1.GroupVersion.String(): gvk.Version, // TODO(v1beta2) bump to v1beta2 eventually + clusterv1.GroupVersion.String(): gvk.Version, }, }, Spec: apiextensionsv1.CustomResourceDefinitionSpec{ diff --git a/util/test/builder/infrastructure.go b/util/test/builder/infrastructure.go index bb3651d6d2d0..7ae8f8c35f22 100644 --- a/util/test/builder/infrastructure.go +++ b/util/test/builder/infrastructure.go @@ -124,12 +124,16 @@ func testInfrastructureClusterCRD(gvk schema.GroupVersionKind) *apiextensionsv1. Type: "object", Properties: map[string]apiextensionsv1.JSONSchemaProps{ // mandatory field from the Cluster API contract - "ready": {Type: "boolean"}, + "initialization": { + Type: "object", + Properties: map[string]apiextensionsv1.JSONSchemaProps{ + "provisioned": {Type: "boolean"}, + }, + }, // General purpose fields to be used in different test scenario. "foo": {Type: "string"}, "bar": {Type: "string"}, }, - Required: []string{"ready"}, }, }) } @@ -192,7 +196,12 @@ func testInfrastructureMachineCRD(gvk schema.GroupVersionKind) *apiextensionsv1. Type: "object", Properties: map[string]apiextensionsv1.JSONSchemaProps{ // mandatory field from the Cluster API contract - "ready": {Type: "boolean"}, + "initialization": { + Type: "object", + Properties: map[string]apiextensionsv1.JSONSchemaProps{ + "provisioned": {Type: "boolean"}, + }, + }, // General purpose fields to be used in different test scenario. "foo": {Type: "string"}, "bar": {Type: "string"}, From b81bde23d5a1a2a1710d2737c6b0d758d9721d23 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 14 Apr 2025 23:24:12 +0200 Subject: [PATCH 4/9] Fix unit tests --- .../machine/machine_controller_status_test.go | 22 ++-- .../machine/machine_controller_test.go | 110 +++++++++--------- .../machinedeployment_controller_test.go | 4 +- .../machinedeployment/suite_test.go | 4 +- .../machinehealthcheck_controller_test.go | 2 +- .../machineset/machineset_controller_test.go | 8 +- internal/controllers/machineset/suite_test.go | 8 +- .../serversidepathhelper_test.go | 12 +- 8 files changed, 87 insertions(+), 83 deletions(-) diff --git a/internal/controllers/machine/machine_controller_status_test.go b/internal/controllers/machine/machine_controller_status_test.go index f4dcb25b0c24..fd4762382554 100644 --- a/internal/controllers/machine/machine_controller_status_test.go +++ b/internal/controllers/machine/machine_controller_status_test.go @@ -2114,7 +2114,7 @@ func TestReconcileMachinePhases(t *testing.T) { // Set bootstrap ready. modifiedBootstrapConfig := bootstrapConfig.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) @@ -2189,13 +2189,13 @@ func TestReconcileMachinePhases(t *testing.T) { // Set bootstrap ready. modifiedBootstrapConfig := bootstrapConfig.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) // Set infra ready. modifiedInfraMachine := infraMachine.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, []interface{}{ map[string]interface{}{ "type": "InternalIP", @@ -2280,13 +2280,13 @@ func TestReconcileMachinePhases(t *testing.T) { // Set bootstrap ready. modifiedBootstrapConfig := bootstrapConfig.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) // Set infra ready. modifiedInfraMachine := infraMachine.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) // Wait until Machine was reconciled. @@ -2360,13 +2360,13 @@ func TestReconcileMachinePhases(t *testing.T) { // Set bootstrap ready. modifiedBootstrapConfig := bootstrapConfig.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) // Set infra ready. modifiedInfraMachine := infraMachine.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) // Wait until Machine was reconciled. @@ -2424,13 +2424,13 @@ func TestReconcileMachinePhases(t *testing.T) { // Set bootstrap ready. modifiedBootstrapConfig := bootstrapConfig.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) // Set infra ready. modifiedInfraMachine := infraMachine.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) // Wait until Machine was reconciled. @@ -2498,13 +2498,13 @@ func TestReconcileMachinePhases(t *testing.T) { // Set bootstrap ready. modifiedBootstrapConfig := bootstrapConfig.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) // Set infra ready. modifiedInfraMachine := infraMachine.DeepCopy() - g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) // Wait until the Machine has the Machine finalizer diff --git a/internal/controllers/machine/machine_controller_test.go b/internal/controllers/machine/machine_controller_test.go index 118e64f5bd1e..563792ca1b2c 100644 --- a/internal/controllers/machine/machine_controller_test.go +++ b/internal/controllers/machine/machine_controller_test.go @@ -131,13 +131,13 @@ func TestWatches(t *testing.T) { // Patch infra machine ready patchHelper, err := patch.NewHelper(infraMachine, env) g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(patchHelper.Patch(ctx, infraMachine, patch.WithStatusObservedGeneration{})).To(Succeed()) // Patch bootstrap ready patchHelper, err = patch.NewHelper(defaultBootstrap, env) g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, "secretData", "status", "dataSecretName")).To(Succeed()) g.Expect(patchHelper.Patch(ctx, defaultBootstrap, patch.WithStatusObservedGeneration{})).To(Succeed()) @@ -272,13 +272,13 @@ func TestWatchesDelete(t *testing.T) { // Patch infra machine ready patchHelper, err := patch.NewHelper(infraMachine, env) g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(patchHelper.Patch(ctx, infraMachine, patch.WithStatusObservedGeneration{})).To(Succeed()) // Patch bootstrap ready patchHelper, err = patch.NewHelper(defaultBootstrap, env) g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, "secretData", "status", "dataSecretName")).To(Succeed()) g.Expect(patchHelper.Patch(ctx, defaultBootstrap, patch.WithStatusObservedGeneration{})).To(Succeed()) @@ -479,12 +479,12 @@ func TestMachine_Reconcile(t *testing.T) { // Set bootstrap ready. bootstrapPatch := client.MergeFrom(defaultBootstrap.DeepCopy()) - g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "ready")).ToNot(HaveOccurred()) + g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "initialization", "dataSecretCreated")).ToNot(HaveOccurred()) g.Expect(env.Status().Patch(ctx, defaultBootstrap, bootstrapPatch)).To(Succeed()) // Set infrastructure ready. infraMachinePatch := client.MergeFrom(infraMachine.DeepCopy()) - g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, infraMachine, infraMachinePatch)).To(Succeed()) // Wait for Machine Ready Condition to become True. @@ -981,7 +981,7 @@ func TestReconcileRequest(t *testing.T) { } func TestMachineConditions(t *testing.T) { - infraConfig := func(ready bool) *unstructured.Unstructured { + infraConfig := func(provisioned bool) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "GenericInfrastructureMachine", @@ -994,7 +994,9 @@ func TestMachineConditions(t *testing.T) { "providerID": "test://id-1", }, "status": map[string]interface{}{ - "ready": ready, + "initialization": map[string]interface{}{ + "provisioned": provisioned, + }, "addresses": []interface{}{ map[string]interface{}{ "type": "InternalIP", @@ -1006,11 +1008,13 @@ func TestMachineConditions(t *testing.T) { } } - boostrapConfig := func(ready bool) *unstructured.Unstructured { + boostrapConfig := func(dataSecretCreated bool) *unstructured.Unstructured { status := map[string]interface{}{ - "ready": ready, + "initialization": map[string]interface{}{ + "dataSecretCreated": dataSecretCreated, + }, } - if ready { + if dataSecretCreated { status["dataSecretName"] = "data" } return &unstructured.Unstructured{ @@ -1081,18 +1085,18 @@ func TestMachineConditions(t *testing.T) { } testcases := []struct { - name string - infraReady bool - bootstrapReady bool - beforeFunc func(bootstrap, infra *unstructured.Unstructured, m *clusterv1.Machine) - additionalObjects []client.Object - conditionsToAssert []*clusterv1.Condition - wantErr bool + name string + infraProvisioned bool + bootstrapDataSecretCreated bool + beforeFunc func(bootstrap, infra *unstructured.Unstructured, m *clusterv1.Machine) + additionalObjects []client.Object + conditionsToAssert []*clusterv1.Condition + wantErr bool }{ { - name: "all conditions true", - infraReady: true, - bootstrapReady: true, + name: "all conditions true", + infraProvisioned: true, + bootstrapDataSecretCreated: true, beforeFunc: func(_, _ *unstructured.Unstructured, m *clusterv1.Machine) { // since these conditions are set by an external controller v1beta1conditions.MarkTrue(m, clusterv1.MachineHealthCheckSucceededCondition) @@ -1107,9 +1111,9 @@ func TestMachineConditions(t *testing.T) { }, }, { - name: "infra condition consumes reason from the infra config", - infraReady: false, - bootstrapReady: true, + name: "infra condition consumes reason from the infra config", + infraProvisioned: false, + bootstrapDataSecretCreated: true, beforeFunc: func(_, infra *unstructured.Unstructured, _ *clusterv1.Machine) { addConditionsToExternal(infra, clusterv1.Conditions{ { @@ -1125,18 +1129,18 @@ func TestMachineConditions(t *testing.T) { }, }, { - name: "infra condition consumes the fallback reason", - infraReady: false, - bootstrapReady: true, + name: "infra condition consumes the fallback reason", + infraProvisioned: false, + bootstrapDataSecretCreated: true, conditionsToAssert: []*clusterv1.Condition{ v1beta1conditions.FalseCondition(clusterv1.InfrastructureReadyCondition, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, ""), v1beta1conditions.FalseCondition(clusterv1.ReadyCondition, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, ""), }, }, { - name: "bootstrap condition consumes reason from the bootstrap config", - infraReady: true, - bootstrapReady: false, + name: "bootstrap condition consumes reason from the bootstrap config", + infraProvisioned: true, + bootstrapDataSecretCreated: false, beforeFunc: func(bootstrap, _ *unstructured.Unstructured, _ *clusterv1.Machine) { addConditionsToExternal(bootstrap, clusterv1.Conditions{ { @@ -1152,9 +1156,9 @@ func TestMachineConditions(t *testing.T) { }, }, { - name: "bootstrap condition consumes the fallback reason", - infraReady: true, - bootstrapReady: false, + name: "bootstrap condition consumes the fallback reason", + infraProvisioned: true, + bootstrapDataSecretCreated: false, conditionsToAssert: []*clusterv1.Condition{ v1beta1conditions.FalseCondition(clusterv1.BootstrapReadyCondition, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, ""), v1beta1conditions.FalseCondition(clusterv1.ReadyCondition, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, ""), @@ -1163,17 +1167,17 @@ func TestMachineConditions(t *testing.T) { // Assert summary conditions // infra condition takes precedence over bootstrap condition in generating summary { - name: "ready condition summary consumes reason from the infra condition", - infraReady: false, - bootstrapReady: false, + name: "ready condition summary consumes reason from the infra condition", + infraProvisioned: false, + bootstrapDataSecretCreated: false, conditionsToAssert: []*clusterv1.Condition{ v1beta1conditions.FalseCondition(clusterv1.ReadyCondition, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, ""), }, }, { - name: "ready condition summary consumes reason from the machine owner remediated condition", - infraReady: true, - bootstrapReady: true, + name: "ready condition summary consumes reason from the machine owner remediated condition", + infraProvisioned: true, + bootstrapDataSecretCreated: true, beforeFunc: func(_, _ *unstructured.Unstructured, m *clusterv1.Machine) { v1beta1conditions.MarkFalse(m, clusterv1.MachineOwnerRemediatedCondition, clusterv1.WaitingForRemediationReason, clusterv1.ConditionSeverityWarning, "MHC failed") }, @@ -1182,9 +1186,9 @@ func TestMachineConditions(t *testing.T) { }, }, { - name: "ready condition summary consumes reason from the MHC succeeded condition", - infraReady: true, - bootstrapReady: true, + name: "ready condition summary consumes reason from the MHC succeeded condition", + infraProvisioned: true, + bootstrapDataSecretCreated: true, beforeFunc: func(_, _ *unstructured.Unstructured, m *clusterv1.Machine) { v1beta1conditions.MarkFalse(m, clusterv1.MachineHealthCheckSucceededCondition, clusterv1.NodeNotFoundReason, clusterv1.ConditionSeverityWarning, "") }, @@ -1193,9 +1197,9 @@ func TestMachineConditions(t *testing.T) { }, }, { - name: "machine ready and MachineNodeHealthy unknown", - infraReady: true, - bootstrapReady: true, + name: "machine ready and MachineNodeHealthy unknown", + infraProvisioned: true, + bootstrapDataSecretCreated: true, additionalObjects: []client.Object{&corev1.Node{ // This is a duplicate node with the same providerID // This should lead to an error when trying to get the Node for a Machine. @@ -1213,9 +1217,9 @@ func TestMachineConditions(t *testing.T) { }, }, { - name: "ready condition summary consumes reason from the draining succeeded condition", - infraReady: true, - bootstrapReady: true, + name: "ready condition summary consumes reason from the draining succeeded condition", + infraProvisioned: true, + bootstrapDataSecretCreated: true, beforeFunc: func(_, _ *unstructured.Unstructured, m *clusterv1.Machine) { v1beta1conditions.MarkFalse(m, clusterv1.DrainingSucceededCondition, clusterv1.DrainingFailedReason, clusterv1.ConditionSeverityWarning, "") }, @@ -1230,8 +1234,8 @@ func TestMachineConditions(t *testing.T) { g := NewWithT(t) // setup objects - bootstrap := boostrapConfig(tt.bootstrapReady) - infra := infraConfig(tt.infraReady) + bootstrap := boostrapConfig(tt.bootstrapDataSecretCreated) + infra := infraConfig(tt.infraProvisioned) m := machine.DeepCopy() if tt.beforeFunc != nil { tt.beforeFunc(bootstrap, infra, m) @@ -2999,19 +3003,19 @@ func TestNodeToMachine(t *testing.T) { // Patch infra expectedMachine ready patchHelper, err := patch.NewHelper(infraMachine, env) g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(patchHelper.Patch(ctx, infraMachine, patch.WithStatusObservedGeneration{})).To(Succeed()) // Patch infra randomMachine ready patchHelper, err = patch.NewHelper(infraMachine2, env) g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(infraMachine2.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(infraMachine2.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(patchHelper.Patch(ctx, infraMachine2, patch.WithStatusObservedGeneration{})).To(Succeed()) // Patch bootstrap ready patchHelper, err = patch.NewHelper(defaultBootstrap, env) g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, "secretData", "status", "dataSecretName")).To(Succeed()) g.Expect(patchHelper.Patch(ctx, defaultBootstrap, patch.WithStatusObservedGeneration{})).To(Succeed()) diff --git a/internal/controllers/machinedeployment/machinedeployment_controller_test.go b/internal/controllers/machinedeployment/machinedeployment_controller_test.go index d20020fe6a9d..6bc00c0a49d1 100644 --- a/internal/controllers/machinedeployment/machinedeployment_controller_test.go +++ b/internal/controllers/machinedeployment/machinedeployment_controller_test.go @@ -432,7 +432,7 @@ func TestMachineDeploymentReconciler(t *testing.T) { if !metav1.IsControlledBy(&m, newestMachineSet) { continue } - providerID := fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g) + providerID := fakeInfrastructureRefProvisioned(m.Spec.InfrastructureRef, infraResource, g) fakeMachineNodeRef(&m, providerID, g) } @@ -459,7 +459,7 @@ func TestMachineDeploymentReconciler(t *testing.T) { if !metav1.IsControlledBy(&m, &newms) { continue } - providerID := fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g) + providerID := fakeInfrastructureRefProvisioned(m.Spec.InfrastructureRef, infraResource, g) fakeMachineNodeRef(&m, providerID, g) } diff --git a/internal/controllers/machinedeployment/suite_test.go b/internal/controllers/machinedeployment/suite_test.go index 53900e6c977d..da7ca5442ec3 100644 --- a/internal/controllers/machinedeployment/suite_test.go +++ b/internal/controllers/machinedeployment/suite_test.go @@ -153,7 +153,7 @@ func intOrStrPtr(i int32) *intstr.IntOrString { return &res } -func fakeInfrastructureRefReady(ref corev1.ObjectReference, base map[string]interface{}, g *WithT) string { +func fakeInfrastructureRefProvisioned(ref corev1.ObjectReference, base map[string]interface{}, g *WithT) string { iref := (&unstructured.Unstructured{Object: base}).DeepCopy() g.Eventually(func() error { return env.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}, iref) @@ -165,7 +165,7 @@ func fakeInfrastructureRefReady(ref corev1.ObjectReference, base map[string]inte g.Expect(env.Patch(ctx, iref, irefPatch)).To(Succeed()) irefPatch = client.MergeFrom(iref.DeepCopy()) - g.Expect(unstructured.SetNestedField(iref.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(iref.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, iref, irefPatch)).To(Succeed()) return providerID } diff --git a/internal/controllers/machinehealthcheck/machinehealthcheck_controller_test.go b/internal/controllers/machinehealthcheck/machinehealthcheck_controller_test.go index ce410b12a5ce..3cf164b2cb04 100644 --- a/internal/controllers/machinehealthcheck/machinehealthcheck_controller_test.go +++ b/internal/controllers/machinehealthcheck/machinehealthcheck_controller_test.go @@ -2605,7 +2605,7 @@ func createMachinesWithNodes( // NB. Status cannot be set during object creation so we need to patch // it separately. infraMachinePatch := client.MergeFrom(infraMachine.DeepCopy()) - g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, infraMachine, infraMachinePatch)).To(Succeed()) machine.Spec.InfrastructureRef = corev1.ObjectReference{ diff --git a/internal/controllers/machineset/machineset_controller_test.go b/internal/controllers/machineset/machineset_controller_test.go index f8adb08bc4a1..70dd65e215ce 100644 --- a/internal/controllers/machineset/machineset_controller_test.go +++ b/internal/controllers/machineset/machineset_controller_test.go @@ -340,8 +340,8 @@ func TestMachineSetReconciler(t *testing.T) { // Set the infrastructure reference as ready. for _, m := range machines.Items { - fakeBootstrapRefReady(*m.Spec.Bootstrap.ConfigRef, bootstrapResource, g) - fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g) + fakeBootstrapRefDataSecretCreated(*m.Spec.Bootstrap.ConfigRef, bootstrapResource, g) + fakeInfrastructureRefProvisioned(m.Spec.InfrastructureRef, infraResource, g) } // Verify that in-place mutable fields propagate from MachineSet to Machines. @@ -406,8 +406,8 @@ func TestMachineSetReconciler(t *testing.T) { g.Expect(m.Spec.Version).ToNot(BeNil()) g.Expect(*m.Spec.Version).To(BeEquivalentTo("v1.14.2")) - fakeBootstrapRefReady(*m.Spec.Bootstrap.ConfigRef, bootstrapResource, g) - providerID := fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g) + fakeBootstrapRefDataSecretCreated(*m.Spec.Bootstrap.ConfigRef, bootstrapResource, g) + providerID := fakeInfrastructureRefProvisioned(m.Spec.InfrastructureRef, infraResource, g) fakeMachineNodeRef(&m, providerID, g) } diff --git a/internal/controllers/machineset/suite_test.go b/internal/controllers/machineset/suite_test.go index e67a59252b99..cd09d2a1c671 100644 --- a/internal/controllers/machineset/suite_test.go +++ b/internal/controllers/machineset/suite_test.go @@ -140,7 +140,7 @@ func TestMain(m *testing.M) { })) } -func fakeBootstrapRefReady(ref corev1.ObjectReference, base map[string]interface{}, g *WithT) { +func fakeBootstrapRefDataSecretCreated(ref corev1.ObjectReference, base map[string]interface{}, g *WithT) { bref := (&unstructured.Unstructured{Object: base}).DeepCopy() g.Eventually(func() error { return env.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}, bref) @@ -158,12 +158,12 @@ func fakeBootstrapRefReady(ref corev1.ObjectReference, base map[string]interface g.Expect(env.Create(ctx, bdataSecret)).To(Succeed()) brefPatch := client.MergeFrom(bref.DeepCopy()) - g.Expect(unstructured.SetNestedField(bref.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(bref.Object, true, "status", "initialization", "dataSecretCreated")).To(Succeed()) g.Expect(unstructured.SetNestedField(bref.Object, bdataSecret.Name, "status", "dataSecretName")).To(Succeed()) g.Expect(env.Status().Patch(ctx, bref, brefPatch)).To(Succeed()) } -func fakeInfrastructureRefReady(ref corev1.ObjectReference, base map[string]interface{}, g *WithT) string { +func fakeInfrastructureRefProvisioned(ref corev1.ObjectReference, base map[string]interface{}, g *WithT) string { iref := (&unstructured.Unstructured{Object: base}).DeepCopy() g.Eventually(func() error { return env.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}, iref) @@ -175,7 +175,7 @@ func fakeInfrastructureRefReady(ref corev1.ObjectReference, base map[string]inte g.Expect(env.Patch(ctx, iref, irefPatch)).To(Succeed()) irefPatch = client.MergeFrom(iref.DeepCopy()) - g.Expect(unstructured.SetNestedField(iref.Object, true, "status", "ready")).To(Succeed()) + g.Expect(unstructured.SetNestedField(iref.Object, true, "status", "initialization", "provisioned")).To(Succeed()) g.Expect(env.Status().Patch(ctx, iref, irefPatch)).To(Succeed()) return providerID } diff --git a/internal/controllers/topology/cluster/structuredmerge/serversidepathhelper_test.go b/internal/controllers/topology/cluster/structuredmerge/serversidepathhelper_test.go index 31480a975a84..69aa5c4050f7 100644 --- a/internal/controllers/topology/cluster/structuredmerge/serversidepathhelper_test.go +++ b/internal/controllers/topology/cluster/structuredmerge/serversidepathhelper_test.go @@ -269,10 +269,10 @@ func TestServerSideApply(t *testing.T) { p, err := patch.NewHelper(obj, env.Client) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "foo")).To(Succeed()) // Controller sets a well known field ignored in the topology controller - g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "bar")).To(Succeed()) // Controller sets an infra specific field the topology controller is not aware of - g.Expect(unstructured.SetNestedField(obj.Object, "changed", "status", "foo")).To(Succeed()) // Controller sets something in status - g.Expect(unstructured.SetNestedField(obj.Object, true, "status", "ready")).To(Succeed()) // Required field + g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "foo")).To(Succeed()) // Controller sets a well known field ignored in the topology controller + g.Expect(unstructured.SetNestedField(obj.Object, "changed", "spec", "bar")).To(Succeed()) // Controller sets an infra specific field the topology controller is not aware of + g.Expect(unstructured.SetNestedField(obj.Object, "changed", "status", "foo")).To(Succeed()) // Controller sets something in status + g.Expect(unstructured.SetNestedField(obj.Object, true, "status", "initialization", "provisioned")).To(Succeed()) // Required field g.Expect(p.Patch(ctx, obj)).To(Succeed()) @@ -321,7 +321,7 @@ func TestServerSideApply(t *testing.T) { g.Expect(v2).To(Equal("changed")) v3, _, _ := unstructured.NestedString(got.Object, "status", "foo") g.Expect(v3).To(Equal("changed")) - v4, _, _ := unstructured.NestedBool(got.Object, "status", "ready") + v4, _, _ := unstructured.NestedBool(got.Object, "status", "initialization", "provisioned") g.Expect(v4).To(BeTrue()) fieldV1 := getTopologyManagedFields(got) @@ -371,7 +371,7 @@ func TestServerSideApply(t *testing.T) { g.Expect(v2).To(Equal("changed")) v3, _, _ := unstructured.NestedString(got.Object, "status", "foo") g.Expect(v3).To(Equal("changed")) - v4, _, _ := unstructured.NestedBool(got.Object, "status", "ready") + v4, _, _ := unstructured.NestedBool(got.Object, "status", "initialization", "provisioned") g.Expect(v4).To(BeTrue()) }) t.Run("Topology controller reconcile again with an opinion on a field managed by another controller (co-ownership)", func(t *testing.T) { From 156460bd98c0976e2fd80a5df22e33ea36a8beea Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Tue, 15 Apr 2025 08:35:23 +0200 Subject: [PATCH 5/9] Implement v1beta2 contract in CABPK --- bootstrap/kubeadm/api/v1beta1/conversion.go | 13 +++++++ .../kubeadm/api/v1beta1/conversion_test.go | 7 ++++ .../api/v1beta1/zz_generated.conversion.go | 4 +-- .../api/v1beta2/kubeadmconfig_types.go | 13 +++++-- .../api/v1beta2/zz_generated.deepcopy.go | 20 +++++++++++ ...strap.cluster.x-k8s.io_kubeadmconfigs.yaml | 15 +++++--- .../controllers/kubeadmconfig_controller.go | 14 +++++--- .../kubeadmconfig_controller_test.go | 36 ++++++++++++------- .../bootstrap/kubeadm/v1alpha3/conversion.go | 13 +++++++ .../kubeadm/v1alpha3/conversion_test.go | 8 +++++ .../v1alpha3/zz_generated.conversion.go | 4 +-- .../bootstrap/kubeadm/v1alpha4/conversion.go | 13 +++++++ .../kubeadm/v1alpha4/conversion_test.go | 8 +++++ .../v1alpha4/zz_generated.conversion.go | 4 +-- 14 files changed, 143 insertions(+), 29 deletions(-) diff --git a/bootstrap/kubeadm/api/v1beta1/conversion.go b/bootstrap/kubeadm/api/v1beta1/conversion.go index 74e67d94ae15..6e8833cb15e2 100644 --- a/bootstrap/kubeadm/api/v1beta1/conversion.go +++ b/bootstrap/kubeadm/api/v1beta1/conversion.go @@ -68,6 +68,11 @@ func Convert_v1beta2_KubeadmConfigStatus_To_v1beta1_KubeadmConfigStatus(in *boot out.FailureMessage = in.Deprecated.V1Beta1.FailureMessage } + // Move initialization to old fields + if in.Initialization != nil { + out.Ready = in.Initialization.DataSecretCreated + } + // Move new conditions (v1beta2) to the v1beta2 field. if in.Conditions == nil { return nil @@ -103,6 +108,14 @@ func Convert_v1beta1_KubeadmConfigStatus_To_v1beta2_KubeadmConfigStatus(in *Kube } out.Deprecated.V1Beta1.FailureReason = in.FailureReason out.Deprecated.V1Beta1.FailureMessage = in.FailureMessage + + // Move ready to Initialization + if in.Ready { + if out.Initialization == nil { + out.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{} + } + out.Initialization.DataSecretCreated = in.Ready + } return nil } diff --git a/bootstrap/kubeadm/api/v1beta1/conversion_test.go b/bootstrap/kubeadm/api/v1beta1/conversion_test.go index 2c3ef7b4b591..dac8067ddda3 100644 --- a/bootstrap/kubeadm/api/v1beta1/conversion_test.go +++ b/bootstrap/kubeadm/api/v1beta1/conversion_test.go @@ -60,6 +60,13 @@ func hubKubeadmConfigStatus(in *bootstrapv1.KubeadmConfigStatus, c fuzz.Continue if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &bootstrapv1.KubeadmConfigV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &bootstrapv1.KubeadmConfigInitializationStatus{}) { + in.Initialization = nil + } + } } func spokeKubeadmConfigStatus(in *KubeadmConfigStatus, c fuzz.Continue) { diff --git a/bootstrap/kubeadm/api/v1beta1/zz_generated.conversion.go b/bootstrap/kubeadm/api/v1beta1/zz_generated.conversion.go index ee7375d86284..8cfc53559f65 100644 --- a/bootstrap/kubeadm/api/v1beta1/zz_generated.conversion.go +++ b/bootstrap/kubeadm/api/v1beta1/zz_generated.conversion.go @@ -1502,7 +1502,7 @@ func Convert_v1beta2_KubeadmConfigSpec_To_v1beta1_KubeadmConfigSpec(in *v1beta2. } func autoConvert_v1beta1_KubeadmConfigStatus_To_v1beta2_KubeadmConfigStatus(in *KubeadmConfigStatus, out *v1beta2.KubeadmConfigStatus, s conversion.Scope) error { - out.Ready = in.Ready + // WARNING: in.Ready requires manual conversion: does not exist in peer-type out.DataSecretName = (*string)(unsafe.Pointer(in.DataSecretName)) // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type @@ -1534,7 +1534,7 @@ func autoConvert_v1beta2_KubeadmConfigStatus_To_v1beta1_KubeadmConfigStatus(in * } else { out.Conditions = nil } - out.Ready = in.Ready + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.DataSecretName = (*string)(unsafe.Pointer(in.DataSecretName)) out.ObservedGeneration = in.ObservedGeneration // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type diff --git a/bootstrap/kubeadm/api/v1beta2/kubeadmconfig_types.go b/bootstrap/kubeadm/api/v1beta2/kubeadmconfig_types.go index 80b7625c705e..1ba4cb6e87cb 100644 --- a/bootstrap/kubeadm/api/v1beta2/kubeadmconfig_types.go +++ b/bootstrap/kubeadm/api/v1beta2/kubeadmconfig_types.go @@ -460,9 +460,10 @@ type KubeadmConfigStatus struct { // +kubebuilder:validation:MaxItems=32 Conditions []metav1.Condition `json:"conditions,omitempty"` - // ready indicates the BootstrapData field is ready to be consumed + // initialization provides observations of the KubeadmConfig initialization process. + // NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial Machine provisioning. // +optional - Ready bool `json:"ready"` + Initialization *KubeadmConfigInitializationStatus `json:"initialization,omitempty"` // dataSecretName is the name of the secret that stores the bootstrap data script. // +optional @@ -479,6 +480,14 @@ type KubeadmConfigStatus struct { Deprecated *KubeadmConfigDeprecatedStatus `json:"deprecated,omitempty"` } +// KubeadmConfigInitializationStatus provides observations of the KubeadmConfig initialization process. +type KubeadmConfigInitializationStatus struct { + // dataSecretCreated is true when the Machine's boostrap secret is created. + // NOTE: this field is part of the Cluster API contract, and it is used to orchestrate initial Machine provisioning. + // +optional + DataSecretCreated bool `json:"dataSecretCreated,omitempty"` +} + // KubeadmConfigDeprecatedStatus groups all the status fields that are deprecated and will be removed in a future version. // See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context. type KubeadmConfigDeprecatedStatus struct { diff --git a/bootstrap/kubeadm/api/v1beta2/zz_generated.deepcopy.go b/bootstrap/kubeadm/api/v1beta2/zz_generated.deepcopy.go index 046f58206e01..fccd4ada1e03 100644 --- a/bootstrap/kubeadm/api/v1beta2/zz_generated.deepcopy.go +++ b/bootstrap/kubeadm/api/v1beta2/zz_generated.deepcopy.go @@ -811,6 +811,21 @@ func (in *KubeadmConfigDeprecatedStatus) DeepCopy() *KubeadmConfigDeprecatedStat return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeadmConfigInitializationStatus) DeepCopyInto(out *KubeadmConfigInitializationStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmConfigInitializationStatus. +func (in *KubeadmConfigInitializationStatus) DeepCopy() *KubeadmConfigInitializationStatus { + if in == nil { + return nil + } + out := new(KubeadmConfigInitializationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeadmConfigList) DeepCopyInto(out *KubeadmConfigList) { *out = *in @@ -943,6 +958,11 @@ func (in *KubeadmConfigStatus) DeepCopyInto(out *KubeadmConfigStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Initialization != nil { + in, out := &in.Initialization, &out.Initialization + *out = new(KubeadmConfigInitializationStatus) + **out = **in + } if in.DataSecretName != nil { in, out := &in.DataSecretName, &out.DataSecretName *out = new(string) diff --git a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml index 6833f6e01b16..38c187de2668 100644 --- a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml +++ b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml @@ -6159,15 +6159,22 @@ spec: type: string type: object type: object + initialization: + description: |- + initialization provides observations of the KubeadmConfig initialization process. + NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial Machine provisioning. + properties: + dataSecretCreated: + description: |- + dataSecretCreated is true when the Machine's boostrap secret is created. + NOTE: this field is part of the Cluster API contract, and it is used to orchestrate initial Machine provisioning. + type: boolean + type: object observedGeneration: description: observedGeneration is the latest generation observed by the controller. format: int64 type: integer - ready: - description: ready indicates the BootstrapData field is ready to be - consumed - type: boolean type: object type: object served: true diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go index 681ff2c0e580..3651a55e13ef 100644 --- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go +++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go @@ -304,8 +304,11 @@ func (r *KubeadmConfigReconciler) reconcile(ctx context.Context, scope *Scope, c return ctrl.Result{}, nil // Reconcile status for machines that already have a secret reference, but our status isn't up to date. // This case solves the pivoting scenario (or a backup restore) which doesn't preserve the status subresource on objects. - case configOwner.DataSecretName() != nil && (!config.Status.Ready || config.Status.DataSecretName == nil): - config.Status.Ready = true + case configOwner.DataSecretName() != nil && (!(config.Status.Initialization != nil && config.Status.Initialization.DataSecretCreated) || config.Status.DataSecretName == nil): + if config.Status.Initialization == nil { + config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{} + } + config.Status.Initialization.DataSecretCreated = true config.Status.DataSecretName = configOwner.DataSecretName() v1beta1conditions.MarkTrue(config, bootstrapv1.DataSecretAvailableCondition) conditions.Set(scope.Config, metav1.Condition{ @@ -321,7 +324,7 @@ func (r *KubeadmConfigReconciler) reconcile(ctx context.Context, scope *Scope, c return ctrl.Result{}, nil // Status is ready means a config has been generated. // This also solves the upgrade scenario to a version which includes v1beta2 to ensure v1beta2 conditions are properly set. - case config.Status.Ready: + case config.Status.Initialization != nil && config.Status.Initialization.DataSecretCreated: // Based on existing code paths status.Ready is only true if status.dataSecretName is set // So we can assume that the DataSecret is available. v1beta1conditions.MarkTrue(config, bootstrapv1.DataSecretAvailableCondition) @@ -1416,7 +1419,10 @@ func (r *KubeadmConfigReconciler) storeBootstrapData(ctx context.Context, scope } } scope.Config.Status.DataSecretName = ptr.To(secret.Name) - scope.Config.Status.Ready = true + if scope.Config.Status.Initialization == nil { + scope.Config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{} + } + scope.Config.Status.Initialization.DataSecretCreated = true v1beta1conditions.MarkTrue(scope.Config, bootstrapv1.DataSecretAvailableCondition) conditions.Set(scope.Config, metav1.Condition{ Type: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Condition, diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go index c65235ef0906..02fde8c96341 100644 --- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go +++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go @@ -102,7 +102,7 @@ func TestKubeadmConfigReconciler_Reconcile_ReturnEarlyIfKubeadmConfigIsReady(t * config := newKubeadmConfig(metav1.NamespaceDefault, "cfg") addKubeadmConfigToMachine(config, machine) - config.Status.Ready = true + config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{DataSecretCreated: true} objects := []client.Object{ cluster, @@ -157,7 +157,7 @@ func TestKubeadmConfigReconciler_TestSecretOwnerReferenceReconciliation(t *testi }, Type: corev1.SecretTypeBootstrapToken, } - config.Status.Ready = true + config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{DataSecretCreated: true} objects := []client.Object{ config, @@ -532,7 +532,8 @@ func TestKubeadmConfigReconciler_Reconcile_GenerateCloudConfigData(t *testing.T) cfg, err := getKubeadmConfig(myclient, "control-plane-init-cfg", metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) assertHasTrueCondition(g, myclient, request, bootstrapv1.CertificatesAvailableCondition) @@ -714,7 +715,8 @@ func TestReconcileIfJoinCertificatesAvailableConditioninNodesAndControlPlaneIsRe cfg, err := getKubeadmConfig(myclient, rt.configName, metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) assertHasTrueCondition(g, myclient, request, bootstrapv1.DataSecretAvailableCondition) @@ -791,7 +793,8 @@ func TestReconcileIfJoinNodePoolsAndControlPlaneIsReady(t *testing.T) { cfg, err := getKubeadmConfig(myclient, rt.configName, metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) @@ -892,7 +895,8 @@ func TestBootstrapDataFormat(t *testing.T) { // Verify the KubeadmConfig resource state is correct. cfg, err := getKubeadmConfig(myclient, configName, metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) // Read the secret containing the bootstrap data which was generated by the @@ -997,7 +1001,8 @@ func TestKubeadmConfigSecretCreatedStatusNotPatched(t *testing.T) { cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) } @@ -1051,7 +1056,8 @@ func TestBootstrapTokenTTLExtension(t *testing.T) { cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) @@ -1067,7 +1073,8 @@ func TestBootstrapTokenTTLExtension(t *testing.T) { cfg, err = getKubeadmConfig(myclient, "control-plane-join-cfg", metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) @@ -1297,7 +1304,8 @@ func TestBootstrapTokenRotationMachinePool(t *testing.T) { cfg, err := getKubeadmConfig(myclient, "workerpool-join-cfg", metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) @@ -1489,7 +1497,8 @@ func TestBootstrapTokenRefreshIfTokenSecretCleaned(t *testing.T) { cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) g.Expect(cfg.Spec.JoinConfiguration.Discovery.BootstrapToken.Token).ToNot(BeEmpty()) @@ -1562,7 +1571,8 @@ func TestBootstrapTokenRefreshIfTokenSecretCleaned(t *testing.T) { cfg, err := getKubeadmConfig(myclient, "workerpool-join-cfg", metav1.NamespaceDefault) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(cfg.Status.Ready).To(BeTrue()) + g.Expect(cfg.Status.Initialization).ToNot(BeNil()) + g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue()) g.Expect(cfg.Status.DataSecretName).NotTo(BeNil()) g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil()) g.Expect(cfg.Spec.JoinConfiguration.Discovery.BootstrapToken.Token).ToNot(BeEmpty()) @@ -2791,7 +2801,7 @@ func TestKubeadmConfigReconciler_Reconcile_v1beta2_conditions(t *testing.T) { name: "conditions should be true after upgrading to v1beta2", config: func() *bootstrapv1.KubeadmConfig { c := kubeadmConfig.DeepCopy() - c.Status.Ready = true + c.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{DataSecretCreated: true} return c }(), machine: machine.DeepCopy(), diff --git a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go index ead9f7fdcce3..d7e6d5dbe956 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go @@ -47,6 +47,14 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.FailureReason = src.Status.FailureReason dst.Status.Deprecated.V1Beta1.FailureMessage = src.Status.FailureMessage + // Move ready to Initialization + if src.Status.Ready { + if dst.Status.Initialization == nil { + dst.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{} + } + dst.Status.Initialization.DataSecretCreated = src.Status.Ready + } + // Manually restore data. restored := &bootstrapv1.KubeadmConfig{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { @@ -149,6 +157,11 @@ func (dst *KubeadmConfig) ConvertFrom(srcRaw conversion.Hub) error { } } + // Move initialization to old fields + if src.Status.Initialization != nil { + dst.Status.Ready = src.Status.Initialization.DataSecretCreated + } + // Preserve Hub data on down-conversion except for metadata if err := utilconversion.MarshalData(src, dst); err != nil { return err diff --git a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go index 2e644d0d33fc..fcf550548702 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go @@ -19,6 +19,7 @@ limitations under the License. package v1alpha3 import ( + "reflect" "testing" fuzz "github.com/google/gofuzz" @@ -75,6 +76,13 @@ func hubKubeadmConfigStatus(in *bootstrapv1.KubeadmConfigStatus, c fuzz.Continue if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &bootstrapv1.KubeadmConfigV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &bootstrapv1.KubeadmConfigInitializationStatus{}) { + in.Initialization = nil + } + } } func KubeadmConfigTemplateFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { diff --git a/internal/apis/bootstrap/kubeadm/v1alpha3/zz_generated.conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha3/zz_generated.conversion.go index 2213d5e92de9..5d56177d30b2 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha3/zz_generated.conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha3/zz_generated.conversion.go @@ -550,7 +550,7 @@ func autoConvert_v1beta2_KubeadmConfigSpec_To_v1alpha3_KubeadmConfigSpec(in *v1b } func autoConvert_v1alpha3_KubeadmConfigStatus_To_v1beta2_KubeadmConfigStatus(in *KubeadmConfigStatus, out *v1beta2.KubeadmConfigStatus, s conversion.Scope) error { - out.Ready = in.Ready + // WARNING: in.Ready requires manual conversion: does not exist in peer-type out.DataSecretName = (*string)(unsafe.Pointer(in.DataSecretName)) // WARNING: in.BootstrapData requires manual conversion: does not exist in peer-type // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type @@ -582,7 +582,7 @@ func autoConvert_v1beta2_KubeadmConfigStatus_To_v1alpha3_KubeadmConfigStatus(in } else { out.Conditions = nil } - out.Ready = in.Ready + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.DataSecretName = (*string)(unsafe.Pointer(in.DataSecretName)) out.ObservedGeneration = in.ObservedGeneration // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go index a024a1ac55e1..86511b4c9f91 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go @@ -46,6 +46,14 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.FailureReason = src.Status.FailureReason dst.Status.Deprecated.V1Beta1.FailureMessage = src.Status.FailureMessage + // Move ready to Initialization + if src.Status.Ready { + if dst.Status.Initialization == nil { + dst.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{} + } + dst.Status.Initialization.DataSecretCreated = src.Status.Ready + } + // Manually restore data. restored := &bootstrapv1.KubeadmConfig{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { @@ -146,6 +154,11 @@ func (dst *KubeadmConfig) ConvertFrom(srcRaw conversion.Hub) error { } } + // Move initialization to old fields + if src.Status.Initialization != nil { + dst.Status.Ready = src.Status.Initialization.DataSecretCreated + } + // Preserve Hub data on down-conversion except for metadata. return utilconversion.MarshalData(src, dst) } diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go index fb836e14937b..c5f81a176627 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go @@ -19,6 +19,7 @@ limitations under the License. package v1alpha4 import ( + "reflect" "testing" fuzz "github.com/google/gofuzz" @@ -64,6 +65,13 @@ func hubKubeadmConfigStatus(in *bootstrapv1.KubeadmConfigStatus, c fuzz.Continue if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &bootstrapv1.KubeadmConfigV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &bootstrapv1.KubeadmConfigInitializationStatus{}) { + in.Initialization = nil + } + } } func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go index c84b6bdc1396..76a793714fad 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go @@ -1242,7 +1242,7 @@ func autoConvert_v1beta2_KubeadmConfigSpec_To_v1alpha4_KubeadmConfigSpec(in *v1b } func autoConvert_v1alpha4_KubeadmConfigStatus_To_v1beta2_KubeadmConfigStatus(in *KubeadmConfigStatus, out *v1beta2.KubeadmConfigStatus, s conversion.Scope) error { - out.Ready = in.Ready + // WARNING: in.Ready requires manual conversion: does not exist in peer-type out.DataSecretName = (*string)(unsafe.Pointer(in.DataSecretName)) // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type @@ -1273,7 +1273,7 @@ func autoConvert_v1beta2_KubeadmConfigStatus_To_v1alpha4_KubeadmConfigStatus(in } else { out.Conditions = nil } - out.Ready = in.Ready + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.DataSecretName = (*string)(unsafe.Pointer(in.DataSecretName)) out.ObservedGeneration = in.ObservedGeneration // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type From 9e7544dc26c0533df82b393de8384655ea70ab08 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Tue, 15 Apr 2025 08:35:39 +0200 Subject: [PATCH 6/9] Implement v1beta2 contract in KCP --- .../kubeadm/api/v1beta1/conversion.go | 14 ++ .../kubeadm/api/v1beta1/conversion_test.go | 9 ++ .../api/v1beta1/zz_generated.conversion.go | 7 +- .../v1beta2/kubeadm_control_plane_types.go | 31 ++-- .../api/v1beta2/zz_generated.deepcopy.go | 20 +++ ...cluster.x-k8s.io_kubeadmcontrolplanes.yaml | 27 ++-- .../internal/controllers/controller.go | 6 +- .../internal/controllers/controller_test.go | 12 +- .../internal/controllers/remediation.go | 8 +- .../internal/controllers/remediation_test.go | 76 +++++++--- .../kubeadm/internal/controllers/status.go | 13 +- .../internal/controllers/status_test.go | 143 ++++++++++++------ .../kubeadm/v1alpha3/conversion.go | 14 ++ .../kubeadm/v1alpha3/conversion_test.go | 15 ++ .../v1alpha3/zz_generated.conversion.go | 7 +- .../kubeadm/v1alpha4/conversion.go | 14 ++ .../kubeadm/v1alpha4/conversion_test.go | 15 ++ .../v1alpha4/zz_generated.conversion.go | 7 +- 18 files changed, 312 insertions(+), 126 deletions(-) diff --git a/controlplane/kubeadm/api/v1beta1/conversion.go b/controlplane/kubeadm/api/v1beta1/conversion.go index b5543cf5686b..e2cf8f52e80d 100644 --- a/controlplane/kubeadm/api/v1beta1/conversion.go +++ b/controlplane/kubeadm/api/v1beta1/conversion.go @@ -77,6 +77,12 @@ func Convert_v1beta2_KubeadmControlPlaneStatus_To_v1beta1_KubeadmControlPlaneSta out.UnavailableReplicas = in.Deprecated.V1Beta1.UnavailableReplicas } + // Move initialization to old fields + if in.Initialization != nil { + out.Initialized = in.Initialization.ControlPlaneInitialized + out.Ready = in.Initialization.ControlPlaneInitialized + } + // Move new conditions (v1beta2) and replica counter to the v1beta2 field. if in.Conditions == nil && in.ReadyReplicas == nil && in.AvailableReplicas == nil && in.UpToDateReplicas == nil { return nil @@ -125,6 +131,14 @@ func Convert_v1beta1_KubeadmControlPlaneStatus_To_v1beta2_KubeadmControlPlaneSta out.Deprecated.V1Beta1.UpdatedReplicas = in.UpdatedReplicas out.Deprecated.V1Beta1.ReadyReplicas = in.ReadyReplicas out.Deprecated.V1Beta1.UnavailableReplicas = in.UnavailableReplicas + + // Move ready to Initialization + if in.Ready { + if out.Initialization == nil { + out.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{} + } + out.Initialization.ControlPlaneInitialized = in.Ready + } return nil } diff --git a/controlplane/kubeadm/api/v1beta1/conversion_test.go b/controlplane/kubeadm/api/v1beta1/conversion_test.go index 4ed68ce5ea43..ea4e4af840ef 100644 --- a/controlplane/kubeadm/api/v1beta1/conversion_test.go +++ b/controlplane/kubeadm/api/v1beta1/conversion_test.go @@ -60,6 +60,13 @@ func hubKubeadmControlPlaneStatus(in *controlplanev1.KubeadmControlPlaneStatus, if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &controlplanev1.KubeadmControlPlaneV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &controlplanev1.KubeadmControlPlaneInitializationStatus{}) { + in.Initialization = nil + } + } } func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Continue) { @@ -70,4 +77,6 @@ func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Contin in.V1Beta2 = nil } } + + in.Ready = in.Initialized } diff --git a/controlplane/kubeadm/api/v1beta1/zz_generated.conversion.go b/controlplane/kubeadm/api/v1beta1/zz_generated.conversion.go index eba3864b37b1..2bf565883cce 100644 --- a/controlplane/kubeadm/api/v1beta1/zz_generated.conversion.go +++ b/controlplane/kubeadm/api/v1beta1/zz_generated.conversion.go @@ -406,8 +406,8 @@ func autoConvert_v1beta1_KubeadmControlPlaneStatus_To_v1beta2_KubeadmControlPlan return err } // WARNING: in.UnavailableReplicas requires manual conversion: does not exist in peer-type - out.Initialized = in.Initialized - out.Ready = in.Ready + // WARNING: in.Initialized requires manual conversion: does not exist in peer-type + // WARNING: in.Ready requires manual conversion: does not exist in peer-type // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type out.ObservedGeneration = in.ObservedGeneration @@ -439,6 +439,7 @@ func autoConvert_v1beta2_KubeadmControlPlaneStatus_To_v1beta1_KubeadmControlPlan } else { out.Conditions = nil } + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.Selector = in.Selector out.Replicas = in.Replicas if err := v1.Convert_Pointer_int32_To_int32(&in.ReadyReplicas, &out.ReadyReplicas, s); err != nil { @@ -447,8 +448,6 @@ func autoConvert_v1beta2_KubeadmControlPlaneStatus_To_v1beta1_KubeadmControlPlan // WARNING: in.AvailableReplicas requires manual conversion: does not exist in peer-type // WARNING: in.UpToDateReplicas requires manual conversion: does not exist in peer-type out.Version = (*string)(unsafe.Pointer(in.Version)) - out.Initialized = in.Initialized - out.Ready = in.Ready out.ObservedGeneration = in.ObservedGeneration out.LastRemediation = (*LastRemediationStatus)(unsafe.Pointer(in.LastRemediation)) // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type diff --git a/controlplane/kubeadm/api/v1beta2/kubeadm_control_plane_types.go b/controlplane/kubeadm/api/v1beta2/kubeadm_control_plane_types.go index 7d7a7275f7ef..5af349093790 100644 --- a/controlplane/kubeadm/api/v1beta2/kubeadm_control_plane_types.go +++ b/controlplane/kubeadm/api/v1beta2/kubeadm_control_plane_types.go @@ -291,6 +291,11 @@ type KubeadmControlPlaneStatus struct { // +kubebuilder:validation:MaxItems=32 Conditions []metav1.Condition `json:"conditions,omitempty"` + // initialization provides observations of the KubeadmControlPlane initialization process. + // NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial Machine provisioning. + // +optional + Initialization *KubeadmControlPlaneInitializationStatus `json:"initialization,omitempty"` + // selector is the label selector in string format to avoid introspection // by clients, and is used to provide the CRD-based integration for the // scale subresource and additional integrations for things like kubectl @@ -325,22 +330,6 @@ type KubeadmControlPlaneStatus struct { // +kubebuilder:validation:MaxLength=256 Version *string `json:"version,omitempty"` - // initialized denotes that the KubeadmControlPlane API Server is initialized and thus - // it can accept requests. - // NOTE: this field is part of the Cluster API contract and it is used to orchestrate provisioning. - // The value of this field is never updated after provisioning is completed. Please use conditions - // to check the operational state of the control plane. - // +optional - Initialized bool `json:"initialized"` - - // ready denotes that the KubeadmControlPlane API Server became ready during initial provisioning - // to receive requests. - // NOTE: this field is part of the Cluster API contract and it is used to orchestrate provisioning. - // The value of this field is never updated after provisioning is completed. Please use conditions - // to check the operational state of the control plane. - // +optional - Ready bool `json:"ready"` - // observedGeneration is the latest generation observed by the controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` @@ -354,6 +343,16 @@ type KubeadmControlPlaneStatus struct { Deprecated *KubeadmControlPlaneDeprecatedStatus `json:"deprecated,omitempty"` } +// KubeadmControlPlaneInitializationStatus provides observations of the KubeadmControlPlane initialization process. +type KubeadmControlPlaneInitializationStatus struct { + // controlPlaneInitialized is true when the KubeadmControlPlane provider reports that the Kubernetes control plane is initialized; + // A control plane is considered initialized when it can accept requests, no matter if this happens before + // the control plane is fully provisioned or not. + // NOTE: this field is part of the Cluster API contract, and it is used to orchestrate initial Machine provisioning. + // +optional + ControlPlaneInitialized bool `json:"controlPlaneInitialized,omitempty"` +} + // KubeadmControlPlaneDeprecatedStatus groups all the status fields that are deprecated and will be removed in a future version. // See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context. type KubeadmControlPlaneDeprecatedStatus struct { diff --git a/controlplane/kubeadm/api/v1beta2/zz_generated.deepcopy.go b/controlplane/kubeadm/api/v1beta2/zz_generated.deepcopy.go index d9836887a409..dbd61573b6c4 100644 --- a/controlplane/kubeadm/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/kubeadm/api/v1beta2/zz_generated.deepcopy.go @@ -74,6 +74,21 @@ func (in *KubeadmControlPlaneDeprecatedStatus) DeepCopy() *KubeadmControlPlaneDe return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeadmControlPlaneInitializationStatus) DeepCopyInto(out *KubeadmControlPlaneInitializationStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmControlPlaneInitializationStatus. +func (in *KubeadmControlPlaneInitializationStatus) DeepCopy() *KubeadmControlPlaneInitializationStatus { + if in == nil { + return nil + } + out := new(KubeadmControlPlaneInitializationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeadmControlPlaneList) DeepCopyInto(out *KubeadmControlPlaneList) { *out = *in @@ -199,6 +214,11 @@ func (in *KubeadmControlPlaneStatus) DeepCopyInto(out *KubeadmControlPlaneStatus (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Initialization != nil { + in, out := &in.Initialization, &out.Initialization + *out = new(KubeadmControlPlaneInitializationStatus) + **out = **in + } if in.ReadyReplicas != nil { in, out := &in.ReadyReplicas, &out.ReadyReplicas *out = new(int32) diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml index dab1a6c6acec..52a4d3fc2aba 100644 --- a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml +++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml @@ -7418,14 +7418,19 @@ spec: type: integer type: object type: object - initialized: + initialization: description: |- - initialized denotes that the KubeadmControlPlane API Server is initialized and thus - it can accept requests. - NOTE: this field is part of the Cluster API contract and it is used to orchestrate provisioning. - The value of this field is never updated after provisioning is completed. Please use conditions - to check the operational state of the control plane. - type: boolean + initialization provides observations of the KubeadmControlPlane initialization process. + NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial Machine provisioning. + properties: + controlPlaneInitialized: + description: |- + controlPlaneInitialized is true when the KubeadmControlPlane provider reports that the Kubernetes control plane is initialized; + A control plane is considered initialized when it can accept requests, no matter if this happens before + the control plane is fully provisioned or not. + NOTE: this field is part of the Cluster API contract, and it is used to orchestrate initial Machine provisioning. + type: boolean + type: object lastRemediation: description: lastRemediation stores info about last remediation performed. properties: @@ -7456,14 +7461,6 @@ spec: by the controller. format: int64 type: integer - ready: - description: |- - ready denotes that the KubeadmControlPlane API Server became ready during initial provisioning - to receive requests. - NOTE: this field is part of the Cluster API contract and it is used to orchestrate provisioning. - The value of this field is never updated after provisioning is completed. Please use conditions - to check the operational state of the control plane. - type: boolean readyReplicas: description: readyReplicas is the number of ready replicas for this KubeadmControlPlane. A machine is considered ready when Machine's diff --git a/controlplane/kubeadm/internal/controllers/controller.go b/controlplane/kubeadm/internal/controllers/controller.go index 612143f8291e..4a37dc8ed15e 100644 --- a/controlplane/kubeadm/internal/controllers/controller.go +++ b/controlplane/kubeadm/internal/controllers/controller.go @@ -242,7 +242,7 @@ func (r *KubeadmControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl. // resync (by default 10 minutes). // The alternative solution would be to watch the control plane nodes in the Cluster - similar to how the // MachineSet and MachineHealthCheck controllers watch the nodes under their control. - if !kcp.Status.Ready { + if kcp.Status.Initialization == nil || !kcp.Status.Initialization.ControlPlaneInitialized { res = ctrl.Result{RequeueAfter: 20 * time.Second} } @@ -895,7 +895,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileControlPlaneAndMachinesConditio // Reconcile does (currently) not get triggered from condition changes to the Cluster object. // TODO (v1beta2): Once we moved to v1beta2 conditions we should use the `Initialized` condition instead. controlPlaneInitialized := v1beta1conditions.Get(controlPlane.KCP, controlplanev1.AvailableCondition) - if !controlPlane.KCP.Status.Initialized || + if controlPlane.KCP.Status.Initialization == nil || !controlPlane.KCP.Status.Initialization.ControlPlaneInitialized || controlPlaneInitialized == nil || controlPlaneInitialized.Status != corev1.ConditionTrue { // Overwrite conditions to InspectionFailed. setConditionsToUnknown(setConditionsToUnknownInput{ @@ -1236,7 +1236,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileCertificateExpiries(ctx context } // Return if KCP is not yet initialized (no API server to contact for checking certificate expiration). - if !controlPlane.KCP.Status.Initialized { + if controlPlane.KCP.Status.Initialization == nil || !controlPlane.KCP.Status.Initialization.ControlPlaneInitialized { return nil } diff --git a/controlplane/kubeadm/internal/controllers/controller_test.go b/controlplane/kubeadm/internal/controllers/controller_test.go index 432df3a4bdac..e0d5f190f1d6 100644 --- a/controlplane/kubeadm/internal/controllers/controller_test.go +++ b/controlplane/kubeadm/internal/controllers/controller_test.go @@ -982,7 +982,11 @@ func TestReconcileCertificateExpiries(t *testing.T) { cluster := newCluster(&types.NamespacedName{Name: "foo", Namespace: metav1.NamespaceDefault}) kcp := &controlplanev1.KubeadmControlPlane{ - Status: controlplanev1.KubeadmControlPlaneStatus{Initialized: true}, + Status: controlplanev1.KubeadmControlPlaneStatus{ + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + }, } machineWithoutExpiryAnnotation := &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ @@ -2125,7 +2129,9 @@ func TestKubeadmControlPlaneReconciler_reconcileControlPlaneAndMachinesCondition Version: "v1.31.0", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, Deprecated: &controlplanev1.KubeadmControlPlaneDeprecatedStatus{ V1Beta1: &controlplanev1.KubeadmControlPlaneV1Beta1DeprecatedStatus{ Conditions: clusterv1.Conditions{ @@ -2159,7 +2165,7 @@ func TestKubeadmControlPlaneReconciler_reconcileControlPlaneAndMachinesCondition controlPlane: &internal.ControlPlane{ KCP: func() *controlplanev1.KubeadmControlPlane { kcp := defaultKCP.DeepCopy() - kcp.Status.Initialized = false + kcp.Status.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{ControlPlaneInitialized: false} v1beta1conditions.MarkFalse(kcp, controlplanev1.AvailableCondition, "", clusterv1.ConditionSeverityError, "") conditions.Set(kcp, metav1.Condition{ Type: controlplanev1.KubeadmControlPlaneInitializedV1Beta2Condition, diff --git a/controlplane/kubeadm/internal/controllers/remediation.go b/controlplane/kubeadm/internal/controllers/remediation.go index d2fc432414ec..d8310f66a0e8 100644 --- a/controlplane/kubeadm/internal/controllers/remediation.go +++ b/controlplane/kubeadm/internal/controllers/remediation.go @@ -114,7 +114,11 @@ func (r *KubeadmControlPlaneReconciler) reconcileUnhealthyMachines(ctx context.C return ctrl.Result{}, nil } - log = log.WithValues("Machine", klog.KObj(machineToBeRemediated), "initialized", controlPlane.KCP.Status.Initialized) + initialized := false + if controlPlane.KCP.Status.Initialization != nil && controlPlane.KCP.Status.Initialization.ControlPlaneInitialized { + initialized = true + } + log = log.WithValues("Machine", klog.KObj(machineToBeRemediated), "initialized", initialized) // Returns if another remediation is in progress but the new Machine is not yet created. // Note: This condition is checked after we check for machines to be remediated and if machineToBeRemediated @@ -203,7 +207,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileUnhealthyMachines(ctx context.C return ctrl.Result{}, nil } - if controlPlane.KCP.Status.Initialized { + if controlPlane.KCP.Status.Initialization != nil && controlPlane.KCP.Status.Initialization.ControlPlaneInitialized { // Executes checks that apply only if the control plane is already initialized; in this case KCP can // remediate only if it can safely assume that the operation preserves the operation state of the // existing cluster (or at least it doesn't make it worse). diff --git a/controlplane/kubeadm/internal/controllers/remediation_test.go b/controlplane/kubeadm/internal/controllers/remediation_test.go index a9755fc4f7a6..f9313f9366f6 100644 --- a/controlplane/kubeadm/internal/controllers/remediation_test.go +++ b/controlplane/kubeadm/internal/controllers/remediation_test.go @@ -292,7 +292,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{ @@ -572,7 +574,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { }, }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -602,7 +606,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Replicas: utilptr.To[int32](3), }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -632,7 +638,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Replicas: utilptr.To(int32(3)), }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -663,7 +671,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Replicas: utilptr.To(int32(3)), }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -694,7 +704,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Replicas: utilptr.To[int32](3), }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -739,7 +751,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Replicas: utilptr.To[int32](5), }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -784,7 +798,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: false, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: false, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -834,7 +850,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: false, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: false, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -926,7 +944,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -979,7 +999,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1032,7 +1054,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1086,7 +1110,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1140,7 +1166,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1194,7 +1222,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1239,7 +1269,9 @@ func TestReconcileUnhealthyMachines(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: false, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: false, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1347,7 +1379,9 @@ func TestReconcileUnhealthyMachinesSequences(t *testing.T) { Version: "v1.19.1", }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: false, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: false, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1458,7 +1492,9 @@ func TestReconcileUnhealthyMachinesSequences(t *testing.T) { }, }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, @@ -1572,7 +1608,9 @@ func TestReconcileUnhealthyMachinesSequences(t *testing.T) { }, }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, }, }, Cluster: &clusterv1.Cluster{}, diff --git a/controlplane/kubeadm/internal/controllers/status.go b/controlplane/kubeadm/internal/controllers/status.go index ac5fc1064f47..42cfc5a83ba5 100644 --- a/controlplane/kubeadm/internal/controllers/status.go +++ b/controlplane/kubeadm/internal/controllers/status.go @@ -111,14 +111,13 @@ func (r *KubeadmControlPlaneReconciler) updateStatus(ctx context.Context, contro // This only gets initialized once and does not change if the kubeadm config map goes away. if status.HasKubeadmConfig { - controlPlane.KCP.Status.Initialized = true + if controlPlane.KCP.Status.Initialization == nil { + controlPlane.KCP.Status.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{} + } + controlPlane.KCP.Status.Initialization.ControlPlaneInitialized = true v1beta1conditions.MarkTrue(controlPlane.KCP, controlplanev1.AvailableCondition) } - if controlPlane.KCP.Status.Deprecated.V1Beta1.ReadyReplicas > 0 { - controlPlane.KCP.Status.Ready = true - } - // Surface lastRemediation data in status. // LastRemediation is the remediation currently in progress, in any, or the // most recent of the remediation we are keeping track on machines. @@ -197,7 +196,7 @@ func setReplicas(_ context.Context, kcp *controlplanev1.KubeadmControlPlane, mac } func setInitializedCondition(_ context.Context, kcp *controlplanev1.KubeadmControlPlane) { - if kcp.Status.Initialized { + if kcp.Status.Initialization != nil && kcp.Status.Initialization.ControlPlaneInitialized { conditions.Set(kcp, metav1.Condition{ Type: controlplanev1.KubeadmControlPlaneInitializedV1Beta2Condition, Status: metav1.ConditionTrue, @@ -516,7 +515,7 @@ func setDeletingCondition(_ context.Context, kcp *controlplanev1.KubeadmControlP } func setAvailableCondition(_ context.Context, kcp *controlplanev1.KubeadmControlPlane, etcdIsManaged bool, etcdMembers []*etcd.Member, etcdMembersAndMachinesAreMatching bool, machines collections.Machines) { - if !kcp.Status.Initialized { + if kcp.Status.Initialization == nil || !kcp.Status.Initialization.ControlPlaneInitialized { conditions.Set(kcp, metav1.Condition{ Type: controlplanev1.KubeadmControlPlaneAvailableV1Beta2Condition, Status: metav1.ConditionFalse, diff --git a/controlplane/kubeadm/internal/controllers/status_test.go b/controlplane/kubeadm/internal/controllers/status_test.go index ec0cac18ab34..f31b83d6971b 100644 --- a/controlplane/kubeadm/internal/controllers/status_test.go +++ b/controlplane/kubeadm/internal/controllers/status_test.go @@ -98,7 +98,11 @@ func Test_setInitializedCondition(t *testing.T) { name: "KCP initialized", controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ - Status: controlplanev1.KubeadmControlPlaneStatus{Initialized: true}, + Status: controlplanev1.KubeadmControlPlaneStatus{ + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + }, }, }, expectCondition: metav1.Condition{ @@ -904,8 +908,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -934,8 +940,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -992,7 +1000,9 @@ func Test_setAvailableCondition(t *testing.T) { }, }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, Conditions: []metav1.Condition{ {Type: controlplanev1.KubeadmControlPlaneInitializedV1Beta2Condition, Status: metav1.ConditionTrue, Reason: controlplanev1.KubeadmControlPlaneInitializedV1Beta2Reason, LastTransitionTime: metav1.Time{Time: reconcileTime.Add(-5 * time.Second)}}, }, @@ -1019,7 +1029,9 @@ func Test_setAvailableCondition(t *testing.T) { }, }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, Conditions: []metav1.Condition{ {Type: controlplanev1.KubeadmControlPlaneInitializedV1Beta2Condition, Status: metav1.ConditionTrue, Reason: controlplanev1.KubeadmControlPlaneInitializedV1Beta2Reason, LastTransitionTime: metav1.Time{Time: reconcileTime.Add(-5 * time.Minute)}}, }, @@ -1045,7 +1057,11 @@ func Test_setAvailableCondition(t *testing.T) { }, }, }, - Status: controlplanev1.KubeadmControlPlaneStatus{Initialized: true}, + Status: controlplanev1.KubeadmControlPlaneStatus{ + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + }, }, EtcdMembers: []*etcd.Member{}, EtcdMembersAndMachinesAreMatching: false, @@ -1063,8 +1079,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1111,8 +1129,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1161,8 +1181,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1210,8 +1232,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1248,8 +1272,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1307,8 +1333,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1368,8 +1396,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1427,8 +1457,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1476,8 +1508,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1538,8 +1572,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1576,8 +1612,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1625,8 +1663,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1671,8 +1711,10 @@ func Test_setAvailableCondition(t *testing.T) { }, }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1714,8 +1756,10 @@ func Test_setAvailableCondition(t *testing.T) { }, }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1753,8 +1797,10 @@ func Test_setAvailableCondition(t *testing.T) { controlPlane: &internal.ControlPlane{ KCP: &controlplanev1.KubeadmControlPlane{ Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesNotReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesNotReady}, }, }, Machines: collections.FromMachines( @@ -1790,8 +1836,10 @@ func Test_setAvailableCondition(t *testing.T) { DeletionTimestamp: ptr.To(metav1.Now()), }, Status: controlplanev1.KubeadmControlPlaneStatus{ - Initialized: true, - Conditions: []metav1.Condition{certificatesReady}, + Initialization: &controlplanev1.KubeadmControlPlaneInitializationStatus{ + ControlPlaneInitialized: true, + }, + Conditions: []metav1.Condition{certificatesReady}, }, }, Machines: collections.FromMachines( @@ -1884,8 +1932,7 @@ func TestKubeadmControlPlaneReconciler_updateStatusNoMachines(t *testing.T) { g.Expect(kcp.Status.Replicas).To(BeEquivalentTo(0)) g.Expect(kcp.Status.Deprecated.V1Beta1.ReadyReplicas).To(BeEquivalentTo(0)) g.Expect(kcp.Status.Deprecated.V1Beta1.UnavailableReplicas).To(BeEquivalentTo(0)) - g.Expect(kcp.Status.Initialized).To(BeFalse()) - g.Expect(kcp.Status.Ready).To(BeFalse()) + g.Expect(kcp.Status.Initialization).To(BeNil()) g.Expect(kcp.Status.Selector).NotTo(BeEmpty()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureMessage).To(BeNil()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureReason).To(BeEquivalentTo("")) @@ -1960,8 +2007,7 @@ func TestKubeadmControlPlaneReconciler_updateStatusAllMachinesNotReady(t *testin g.Expect(kcp.Status.Selector).NotTo(BeEmpty()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureMessage).To(BeNil()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureReason).To(BeEquivalentTo("")) - g.Expect(kcp.Status.Initialized).To(BeFalse()) - g.Expect(kcp.Status.Ready).To(BeFalse()) + g.Expect(kcp.Status.Initialization).To(BeNil()) } func TestKubeadmControlPlaneReconciler_updateStatusAllMachinesReady(t *testing.T) { @@ -2039,10 +2085,10 @@ func TestKubeadmControlPlaneReconciler_updateStatusAllMachinesReady(t *testing.T g.Expect(kcp.Status.Selector).NotTo(BeEmpty()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureMessage).To(BeNil()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureReason).To(BeEquivalentTo("")) - g.Expect(kcp.Status.Initialized).To(BeTrue()) + g.Expect(kcp.Status.Initialization).ToNot(BeNil()) + g.Expect(kcp.Status.Initialization.ControlPlaneInitialized).To(BeTrue()) g.Expect(v1beta1conditions.IsTrue(kcp, controlplanev1.AvailableCondition)).To(BeTrue()) g.Expect(v1beta1conditions.IsTrue(kcp, controlplanev1.MachinesCreatedCondition)).To(BeTrue()) - g.Expect(kcp.Status.Ready).To(BeTrue()) } func TestKubeadmControlPlaneReconciler_updateStatusMachinesReadyMixed(t *testing.T) { @@ -2121,8 +2167,8 @@ func TestKubeadmControlPlaneReconciler_updateStatusMachinesReadyMixed(t *testing g.Expect(kcp.Status.Selector).NotTo(BeEmpty()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureMessage).To(BeNil()) g.Expect(kcp.Status.Deprecated.V1Beta1.FailureReason).To(BeEquivalentTo("")) - g.Expect(kcp.Status.Initialized).To(BeTrue()) - g.Expect(kcp.Status.Ready).To(BeTrue()) + g.Expect(kcp.Status.Initialization).ToNot(BeNil()) + g.Expect(kcp.Status.Initialization.ControlPlaneInitialized).To(BeTrue()) } func TestKubeadmControlPlaneReconciler_machinesCreatedIsIsTrueEvenWhenTheNodesAreNotReady(t *testing.T) { @@ -2199,7 +2245,6 @@ func TestKubeadmControlPlaneReconciler_machinesCreatedIsIsTrueEvenWhenTheNodesAr g.Expect(kcp.Status.Replicas).To(BeEquivalentTo(3)) g.Expect(kcp.Status.Deprecated.V1Beta1.ReadyReplicas).To(BeEquivalentTo(0)) g.Expect(kcp.Status.Deprecated.V1Beta1.UnavailableReplicas).To(BeEquivalentTo(3)) - g.Expect(kcp.Status.Ready).To(BeFalse()) g.Expect(v1beta1conditions.IsTrue(kcp, controlplanev1.MachinesCreatedCondition)).To(BeTrue()) } diff --git a/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go b/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go index 948f3466baf9..05f92758b575 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go @@ -51,6 +51,14 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.UpdatedReplicas = src.Status.UpdatedReplicas dst.Status.Deprecated.V1Beta1.UnavailableReplicas = src.Status.UnavailableReplicas + // Move ready to Initialization + if src.Status.Ready { + if dst.Status.Initialization == nil { + dst.Status.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{} + } + dst.Status.Initialization.ControlPlaneInitialized = src.Status.Ready + } + // Manually restore data. restored := &controlplanev1.KubeadmControlPlane{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { @@ -114,6 +122,12 @@ func (dst *KubeadmControlPlane) ConvertFrom(srcRaw conversion.Hub) error { } } + // Move initialization to old fields + if src.Status.Initialization != nil { + dst.Status.Initialized = src.Status.Initialization.ControlPlaneInitialized + dst.Status.Ready = src.Status.Initialization.ControlPlaneInitialized + } + // Preserve Hub data on down-conversion except for metadata if err := utilconversion.MarshalData(src, dst); err != nil { return err diff --git a/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go b/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go index 06bd3ad39db4..edbd6ef37f07 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go +++ b/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go @@ -19,6 +19,7 @@ limitations under the License. package v1alpha3 import ( + "reflect" "testing" fuzz "github.com/google/gofuzz" @@ -44,6 +45,7 @@ func TestFuzzyConversion(t *testing.T) { func KubeadmControlPlaneFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { return []interface{}{ hubKubeadmControlPlaneStatus, + spokeKubeadmControlPlaneStatus, spokeDNS, spokeKubeadmClusterConfiguration, // This custom function is needed when ConvertTo/ConvertFrom functions @@ -70,6 +72,19 @@ func hubKubeadmControlPlaneStatus(in *controlplanev1.KubeadmControlPlaneStatus, if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &controlplanev1.KubeadmControlPlaneV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &controlplanev1.KubeadmControlPlaneInitializationStatus{}) { + in.Initialization = nil + } + } +} + +func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Continue) { + c.FuzzNoCustom(in) + + in.Ready = in.Initialized } func hubBootstrapTokenString(in *bootstrapv1.BootstrapTokenString, _ fuzz.Continue) { diff --git a/internal/apis/controlplane/kubeadm/v1alpha3/zz_generated.conversion.go b/internal/apis/controlplane/kubeadm/v1alpha3/zz_generated.conversion.go index 6e8768df0833..65340cc75e47 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha3/zz_generated.conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha3/zz_generated.conversion.go @@ -234,8 +234,8 @@ func autoConvert_v1alpha3_KubeadmControlPlaneStatus_To_v1beta2_KubeadmControlPla return err } // WARNING: in.UnavailableReplicas requires manual conversion: does not exist in peer-type - out.Initialized = in.Initialized - out.Ready = in.Ready + // WARNING: in.Initialized requires manual conversion: does not exist in peer-type + // WARNING: in.Ready requires manual conversion: does not exist in peer-type // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type out.ObservedGeneration = in.ObservedGeneration @@ -265,6 +265,7 @@ func autoConvert_v1beta2_KubeadmControlPlaneStatus_To_v1alpha3_KubeadmControlPla } else { out.Conditions = nil } + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.Selector = in.Selector out.Replicas = in.Replicas if err := v1.Convert_Pointer_int32_To_int32(&in.ReadyReplicas, &out.ReadyReplicas, s); err != nil { @@ -273,8 +274,6 @@ func autoConvert_v1beta2_KubeadmControlPlaneStatus_To_v1alpha3_KubeadmControlPla // WARNING: in.AvailableReplicas requires manual conversion: does not exist in peer-type // WARNING: in.UpToDateReplicas requires manual conversion: does not exist in peer-type // WARNING: in.Version requires manual conversion: does not exist in peer-type - out.Initialized = in.Initialized - out.Ready = in.Ready out.ObservedGeneration = in.ObservedGeneration // WARNING: in.LastRemediation requires manual conversion: does not exist in peer-type // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type diff --git a/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go b/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go index 2ec1a093350a..a00c8ad525ab 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go @@ -52,6 +52,14 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.UpdatedReplicas = src.Status.UpdatedReplicas dst.Status.Deprecated.V1Beta1.UnavailableReplicas = src.Status.UnavailableReplicas + // Move ready to Initialization + if src.Status.Ready { + if dst.Status.Initialization == nil { + dst.Status.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{} + } + dst.Status.Initialization.ControlPlaneInitialized = src.Status.Ready + } + // Manually restore data. restored := &controlplanev1.KubeadmControlPlane{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { @@ -112,6 +120,12 @@ func (dst *KubeadmControlPlane) ConvertFrom(srcRaw conversion.Hub) error { } } + // Move initialization to old fields + if src.Status.Initialization != nil { + dst.Status.Initialized = src.Status.Initialization.ControlPlaneInitialized + dst.Status.Ready = src.Status.Initialization.ControlPlaneInitialized + } + // Preserve Hub data on down-conversion except for metadata return utilconversion.MarshalData(src, dst) } diff --git a/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go b/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go index 2abe02b19a1e..7aaa5d780624 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go +++ b/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go @@ -19,6 +19,7 @@ limitations under the License. package v1alpha4 import ( + "reflect" "testing" fuzz "github.com/google/gofuzz" @@ -57,6 +58,7 @@ func TestFuzzyConversion(t *testing.T) { func KubeadmControlPlaneFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { return []interface{}{ hubKubeadmControlPlaneStatus, + spokeKubeadmControlPlaneStatus, spokeKubeadmControlPlaneTemplateResource, // This custom function is needed when ConvertTo/ConvertFrom functions // uses the json package to unmarshal the bootstrap token string. @@ -82,6 +84,19 @@ func hubKubeadmControlPlaneStatus(in *controlplanev1.KubeadmControlPlaneStatus, if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &controlplanev1.KubeadmControlPlaneV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &controlplanev1.KubeadmControlPlaneInitializationStatus{}) { + in.Initialization = nil + } + } +} + +func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Continue) { + c.FuzzNoCustom(in) + + in.Ready = in.Initialized } func KubeadmControlPlaneTemplateFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { diff --git a/internal/apis/controlplane/kubeadm/v1alpha4/zz_generated.conversion.go b/internal/apis/controlplane/kubeadm/v1alpha4/zz_generated.conversion.go index 0e1cce482999..4c70412e0b40 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha4/zz_generated.conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha4/zz_generated.conversion.go @@ -340,8 +340,8 @@ func autoConvert_v1alpha4_KubeadmControlPlaneStatus_To_v1beta2_KubeadmControlPla return err } // WARNING: in.UnavailableReplicas requires manual conversion: does not exist in peer-type - out.Initialized = in.Initialized - out.Ready = in.Ready + // WARNING: in.Initialized requires manual conversion: does not exist in peer-type + // WARNING: in.Ready requires manual conversion: does not exist in peer-type // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type out.ObservedGeneration = in.ObservedGeneration @@ -371,6 +371,7 @@ func autoConvert_v1beta2_KubeadmControlPlaneStatus_To_v1alpha4_KubeadmControlPla } else { out.Conditions = nil } + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.Selector = in.Selector out.Replicas = in.Replicas if err := v1.Convert_Pointer_int32_To_int32(&in.ReadyReplicas, &out.ReadyReplicas, s); err != nil { @@ -379,8 +380,6 @@ func autoConvert_v1beta2_KubeadmControlPlaneStatus_To_v1alpha4_KubeadmControlPla // WARNING: in.AvailableReplicas requires manual conversion: does not exist in peer-type // WARNING: in.UpToDateReplicas requires manual conversion: does not exist in peer-type out.Version = (*string)(unsafe.Pointer(in.Version)) - out.Initialized = in.Initialized - out.Ready = in.Ready out.ObservedGeneration = in.ObservedGeneration // WARNING: in.LastRemediation requires manual conversion: does not exist in peer-type // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type From efd02694128ceabda300eb5da4bdee9f25240e62 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Tue, 15 Apr 2025 08:43:27 +0200 Subject: [PATCH 7/9] Move CABPK to v1beta2 --- bootstrap/kubeadm/config/crd/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/kubeadm/config/crd/kustomization.yaml b/bootstrap/kubeadm/config/crd/kustomization.yaml index e364fb5c7d98..bbd49f8dd950 100644 --- a/bootstrap/kubeadm/config/crd/kustomization.yaml +++ b/bootstrap/kubeadm/config/crd/kustomization.yaml @@ -1,6 +1,6 @@ labels: - pairs: - cluster.x-k8s.io/v1beta1: v1beta2 + cluster.x-k8s.io/v1beta2: v1beta2 # This kustomization.yaml is not intended to be run by itself, # since it depends on service name and namespace that are out of this kustomize package. From 3feff74ea227b8ab4cd3404905ac268dbb5fb49d Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Tue, 15 Apr 2025 12:09:31 +0200 Subject: [PATCH 8/9] Address comments --- .../kubeadm/api/v1beta1/conversion.go | 10 +++--- .../kubeadm/api/v1beta1/conversion_test.go | 3 +- .../kubeadm/v1alpha3/conversion.go | 10 +++--- .../kubeadm/v1alpha3/conversion_test.go | 3 +- .../kubeadm/v1alpha4/conversion.go | 10 +++--- .../kubeadm/v1alpha4/conversion_test.go | 3 +- .../cluster/cluster_controller_phases.go | 32 +++++++++-------- .../machine/machine_controller_phases.go | 34 ++++++++++++------- 8 files changed, 59 insertions(+), 46 deletions(-) diff --git a/controlplane/kubeadm/api/v1beta1/conversion.go b/controlplane/kubeadm/api/v1beta1/conversion.go index e2cf8f52e80d..2df904caf792 100644 --- a/controlplane/kubeadm/api/v1beta1/conversion.go +++ b/controlplane/kubeadm/api/v1beta1/conversion.go @@ -77,11 +77,11 @@ func Convert_v1beta2_KubeadmControlPlaneStatus_To_v1beta1_KubeadmControlPlaneSta out.UnavailableReplicas = in.Deprecated.V1Beta1.UnavailableReplicas } - // Move initialization to old fields + // Move initialized to ControlPlaneInitialized, rebuild ready if in.Initialization != nil { out.Initialized = in.Initialization.ControlPlaneInitialized - out.Ready = in.Initialization.ControlPlaneInitialized } + out.Ready = in.Deprecated.V1Beta1.ReadyReplicas > 0 // Move new conditions (v1beta2) and replica counter to the v1beta2 field. if in.Conditions == nil && in.ReadyReplicas == nil && in.AvailableReplicas == nil && in.UpToDateReplicas == nil { @@ -132,12 +132,12 @@ func Convert_v1beta1_KubeadmControlPlaneStatus_To_v1beta2_KubeadmControlPlaneSta out.Deprecated.V1Beta1.ReadyReplicas = in.ReadyReplicas out.Deprecated.V1Beta1.UnavailableReplicas = in.UnavailableReplicas - // Move ready to Initialization - if in.Ready { + // Move initialized to ControlPlaneInitialized + if in.Initialized { if out.Initialization == nil { out.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{} } - out.Initialization.ControlPlaneInitialized = in.Ready + out.Initialization.ControlPlaneInitialized = in.Initialized } return nil } diff --git a/controlplane/kubeadm/api/v1beta1/conversion_test.go b/controlplane/kubeadm/api/v1beta1/conversion_test.go index ea4e4af840ef..658489540372 100644 --- a/controlplane/kubeadm/api/v1beta1/conversion_test.go +++ b/controlplane/kubeadm/api/v1beta1/conversion_test.go @@ -78,5 +78,6 @@ func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Contin } } - in.Ready = in.Initialized + // Make sure ready is consistent with ready replicas, so we can rebuild the info after the round trip. + in.Ready = in.ReadyReplicas > 0 } diff --git a/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go b/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go index 05f92758b575..aae0a47bad70 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go @@ -51,12 +51,12 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.UpdatedReplicas = src.Status.UpdatedReplicas dst.Status.Deprecated.V1Beta1.UnavailableReplicas = src.Status.UnavailableReplicas - // Move ready to Initialization - if src.Status.Ready { + // Move Initialized to ControlPlaneInitialized + if src.Status.Initialized { if dst.Status.Initialization == nil { dst.Status.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{} } - dst.Status.Initialization.ControlPlaneInitialized = src.Status.Ready + dst.Status.Initialization.ControlPlaneInitialized = src.Status.Initialized } // Manually restore data. @@ -122,11 +122,11 @@ func (dst *KubeadmControlPlane) ConvertFrom(srcRaw conversion.Hub) error { } } - // Move initialization to old fields + // Move ControlPlaneInitialized to old fields, rebuild ready if src.Status.Initialization != nil { dst.Status.Initialized = src.Status.Initialization.ControlPlaneInitialized - dst.Status.Ready = src.Status.Initialization.ControlPlaneInitialized } + dst.Status.Ready = src.Status.Deprecated.V1Beta1.ReadyReplicas > 0 // Preserve Hub data on down-conversion except for metadata if err := utilconversion.MarshalData(src, dst); err != nil { diff --git a/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go b/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go index edbd6ef37f07..3689f4d892ee 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go +++ b/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go @@ -84,7 +84,8 @@ func hubKubeadmControlPlaneStatus(in *controlplanev1.KubeadmControlPlaneStatus, func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Continue) { c.FuzzNoCustom(in) - in.Ready = in.Initialized + // Make sure ready is consistent with ready replicas, so we can rebuild the info after the round trip. + in.Ready = in.ReadyReplicas > 0 } func hubBootstrapTokenString(in *bootstrapv1.BootstrapTokenString, _ fuzz.Continue) { diff --git a/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go b/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go index a00c8ad525ab..d28b9077db92 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go @@ -52,12 +52,12 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.UpdatedReplicas = src.Status.UpdatedReplicas dst.Status.Deprecated.V1Beta1.UnavailableReplicas = src.Status.UnavailableReplicas - // Move ready to Initialization - if src.Status.Ready { + // Move Initialized to ControlPlaneInitialized + if src.Status.Initialized { if dst.Status.Initialization == nil { dst.Status.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{} } - dst.Status.Initialization.ControlPlaneInitialized = src.Status.Ready + dst.Status.Initialization.ControlPlaneInitialized = src.Status.Initialized } // Manually restore data. @@ -120,11 +120,11 @@ func (dst *KubeadmControlPlane) ConvertFrom(srcRaw conversion.Hub) error { } } - // Move initialization to old fields + // Move ControlPlaneInitialized to old fields, rebuild ready if src.Status.Initialization != nil { dst.Status.Initialized = src.Status.Initialization.ControlPlaneInitialized - dst.Status.Ready = src.Status.Initialization.ControlPlaneInitialized } + dst.Status.Ready = src.Status.Deprecated.V1Beta1.ReadyReplicas > 0 // Preserve Hub data on down-conversion except for metadata return utilconversion.MarshalData(src, dst) diff --git a/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go b/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go index 7aaa5d780624..bb6285e7be3e 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go +++ b/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go @@ -96,7 +96,8 @@ func hubKubeadmControlPlaneStatus(in *controlplanev1.KubeadmControlPlaneStatus, func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Continue) { c.FuzzNoCustom(in) - in.Ready = in.Initialized + // Make sure ready is consistent with ready replicas, so we can rebuild the info after the round trip. + in.Ready = in.ReadyReplicas > 0 } func KubeadmControlPlaneTemplateFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { diff --git a/internal/controllers/cluster/cluster_controller_phases.go b/internal/controllers/cluster/cluster_controller_phases.go index 5577d3b4726e..66e3fa69807f 100644 --- a/internal/controllers/cluster/cluster_controller_phases.go +++ b/internal/controllers/cluster/cluster_controller_phases.go @@ -213,25 +213,26 @@ func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctr s.infraCluster = obj // Determine contract version used by the InfrastructureCluster. - contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, obj.GroupVersionKind()) + contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, s.infraCluster.GroupVersionKind()) if err != nil { return ctrl.Result{}, err } // Determine if the InfrastructureCluster is provisioned. - provisioned, err := contract.InfrastructureCluster().Provisioned(contractVersion).Get(obj) - if err != nil { + var provisioned bool + if provisionedPtr, err := contract.InfrastructureCluster().Provisioned(contractVersion).Get(s.infraCluster); err != nil { if !errors.Is(err, contract.ErrFieldNotFound) { return ctrl.Result{}, err } - provisioned = ptr.To(false) + } else { + provisioned = *provisionedPtr } - if *provisioned && !cluster.Status.InfrastructureReady { + if provisioned && !cluster.Status.InfrastructureReady { log.Info("Infrastructure provider has completed provisioning", cluster.Spec.InfrastructureRef.Kind, klog.KObj(s.infraCluster)) } // Report a summary of current status of the infrastructure object defined for this cluster. - fallBack := v1beta1conditions.WithFallbackValue(*provisioned, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "") + fallBack := v1beta1conditions.WithFallbackValue(provisioned, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "") if !s.cluster.DeletionTimestamp.IsZero() { fallBack = v1beta1conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") } @@ -246,7 +247,7 @@ func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctr } // If the InfrastructureCluster is not provisioned (and it wasn't already provisioned before), return. - if !*provisioned && !cluster.Status.InfrastructureReady { + if !provisioned && !cluster.Status.InfrastructureReady { log.V(3).Info("Infrastructure provider is not ready yet") return ctrl.Result{}, nil } @@ -316,25 +317,26 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, s *scope) (ctrl. s.controlPlane = obj // Determine contract version used by the ControlPlane. - contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, obj.GroupVersionKind()) + contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, s.controlPlane.GroupVersionKind()) if err != nil { return ctrl.Result{}, err } // Determine if the ControlPlane is provisioned. - initialized, err := contract.ControlPlane().Initialized(contractVersion).Get(obj) - if err != nil { + var initialized bool + if initializedPtr, err := contract.ControlPlane().Initialized(contractVersion).Get(s.controlPlane); err != nil { if !errors.Is(err, contract.ErrFieldNotFound) { return ctrl.Result{}, err } - initialized = ptr.To(false) + } else { + initialized = *initializedPtr } - if *initialized && !cluster.Status.ControlPlaneReady { + if initialized && !cluster.Status.ControlPlaneReady { log.Info("Infrastructure provider has completed provisioning", cluster.Spec.ControlPlaneRef.Kind, klog.KObj(s.controlPlane)) } // Report a summary of current status of the control plane object defined for this cluster. - fallBack := v1beta1conditions.WithFallbackValue(*initialized, clusterv1.WaitingForControlPlaneFallbackReason, clusterv1.ConditionSeverityInfo, "") + fallBack := v1beta1conditions.WithFallbackValue(initialized, clusterv1.WaitingForControlPlaneFallbackReason, clusterv1.ConditionSeverityInfo, "") if !s.cluster.DeletionTimestamp.IsZero() { fallBack = v1beta1conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") } @@ -350,7 +352,7 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, s *scope) (ctrl. // Update cluster.Status.ControlPlaneInitialized if it hasn't already been set. if !v1beta1conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) { - if *initialized { + if initialized { v1beta1conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition) } else { v1beta1conditions.MarkFalse(cluster, clusterv1.ControlPlaneInitializedCondition, clusterv1.WaitingForControlPlaneProviderInitializedReason, clusterv1.ConditionSeverityInfo, "Waiting for control plane provider to indicate the control plane has been initialized") @@ -358,7 +360,7 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, s *scope) (ctrl. } // If the control plane is not ready (and it wasn't ready before), return early. - if !*initialized && !cluster.Status.ControlPlaneReady { + if !initialized && !cluster.Status.ControlPlaneReady { log.V(3).Info("Control Plane provider is not ready yet") return ctrl.Result{}, nil } diff --git a/internal/controllers/machine/machine_controller_phases.go b/internal/controllers/machine/machine_controller_phases.go index 676cca58a2bd..4e030df730bb 100644 --- a/internal/controllers/machine/machine_controller_phases.go +++ b/internal/controllers/machine/machine_controller_phases.go @@ -200,15 +200,19 @@ func (r *Reconciler) reconcileBootstrap(ctx context.Context, s *scope) (ctrl.Res } // Determine if the data secret was created. - dataSecretCreated, err := contract.Bootstrap().DataSecretCreated(contractVersion).Get(s.bootstrapConfig) - if err != nil { - return ctrl.Result{}, err + var dataSecretCreated bool + if dataSecretCreatedPtr, err := contract.Bootstrap().DataSecretCreated(contractVersion).Get(s.bootstrapConfig); err != nil { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, err + } + } else { + dataSecretCreated = *dataSecretCreatedPtr } // Report a summary of current status of the bootstrap object defined for this machine. - fallBack := v1beta1conditions.WithFallbackValue(*dataSecretCreated, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, "") + fallBack := v1beta1conditions.WithFallbackValue(dataSecretCreated, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, "") if !s.machine.DeletionTimestamp.IsZero() { - fallBack = v1beta1conditions.WithFallbackValue(*dataSecretCreated, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") + fallBack = v1beta1conditions.WithFallbackValue(dataSecretCreated, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") } v1beta1conditions.SetMirror(m, clusterv1.BootstrapReadyCondition, v1beta1conditions.UnstructuredGetter(s.bootstrapConfig), fallBack) @@ -217,7 +221,7 @@ func (r *Reconciler) reconcileBootstrap(ctx context.Context, s *scope) (ctrl.Res } // If the data secret was not created yet, return. - if !*dataSecretCreated { + if !dataSecretCreated { log.Info(fmt.Sprintf("Waiting for bootstrap provider to generate data secret and set %s", contract.Bootstrap().DataSecretCreated(contractVersion).Path().String()), s.bootstrapConfig.GetKind(), klog.KObj(s.bootstrapConfig)) @@ -290,18 +294,22 @@ func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctr } // Determine if the InfrastructureMachine is provisioned. - provisioned, err := contract.InfrastructureMachine().Provisioned(contractVersion).Get(s.infraMachine) - if err != nil { - return ctrl.Result{}, err + var provisioned bool + if provisionedPtr, err := contract.InfrastructureMachine().Provisioned(contractVersion).Get(s.infraMachine); err != nil { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, err + } + } else { + provisioned = *provisionedPtr } - if *provisioned && !m.Status.InfrastructureReady { + if provisioned && !m.Status.InfrastructureReady { log.Info("Infrastructure provider has completed provisioning", s.infraMachine.GetKind(), klog.KObj(s.infraMachine)) } // Report a summary of current status of the InfrastructureMachine for this Machine. - fallBack := v1beta1conditions.WithFallbackValue(*provisioned, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "") + fallBack := v1beta1conditions.WithFallbackValue(provisioned, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "") if !s.machine.DeletionTimestamp.IsZero() { - fallBack = v1beta1conditions.WithFallbackValue(*provisioned, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") + fallBack = v1beta1conditions.WithFallbackValue(provisioned, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "") } v1beta1conditions.SetMirror(m, clusterv1.InfrastructureReadyCondition, v1beta1conditions.UnstructuredGetter(s.infraMachine), fallBack) @@ -310,7 +318,7 @@ func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctr } // If the InfrastructureMachine is not provisioned (and it wasn't already provisioned before), return. - if !*provisioned && !m.Status.InfrastructureReady { + if !provisioned && !m.Status.InfrastructureReady { log.Info(fmt.Sprintf("Waiting for infrastructure provider to create machine infrastructure and set %s", contract.InfrastructureMachine().Provisioned(contractVersion).Path().String()), s.infraMachine.GetKind(), klog.KObj(s.infraMachine)) From 82d3b4e97eff30e5850f69def445ea98623a9fe1 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Tue, 15 Apr 2025 16:44:01 +0200 Subject: [PATCH 9/9] Use v1beta2 boostrap contract in machine pools --- .../machinepool_controller_phases.go | 27 ++++++--- .../machinepool_controller_phases_test.go | 56 ++++++++++++------- .../machinepool_controller_test.go | 26 +++++---- 3 files changed, 69 insertions(+), 40 deletions(-) diff --git a/exp/internal/controllers/machinepool_controller_phases.go b/exp/internal/controllers/machinepool_controller_phases.go index a0b80a931549..acba0921d138 100644 --- a/exp/internal/controllers/machinepool_controller_phases.go +++ b/exp/internal/controllers/machinepool_controller_phases.go @@ -41,6 +41,7 @@ import ( capierrors "sigs.k8s.io/cluster-api/errors" expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta2" utilexp "sigs.k8s.io/cluster-api/exp/util" + "sigs.k8s.io/cluster-api/internal/contract" "sigs.k8s.io/cluster-api/internal/util/ssa" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/annotations" @@ -206,33 +207,43 @@ func (r *MachinePoolReconciler) reconcileBootstrap(ctx context.Context, s *scope return ctrl.Result{}, nil } - // Determine if the bootstrap provider is ready. - ready, err := external.IsReady(bootstrapConfig) + // Determine contract version used by the BootstrapConfig. + contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, bootstrapConfig.GroupVersionKind()) if err != nil { return ctrl.Result{}, err } + // Determine if the data secret was created. + var dataSecretCreated bool + if dataSecretCreatedPtr, err := contract.Bootstrap().DataSecretCreated(contractVersion).Get(bootstrapConfig); err != nil { + if !errors.Is(err, contract.ErrFieldNotFound) { + return ctrl.Result{}, err + } + } else { + dataSecretCreated = *dataSecretCreatedPtr + } + // Report a summary of current status of the bootstrap object defined for this machine pool. v1beta1conditions.SetMirror(m, clusterv1.BootstrapReadyCondition, v1beta1conditions.UnstructuredGetter(bootstrapConfig), - v1beta1conditions.WithFallbackValue(ready, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, ""), + v1beta1conditions.WithFallbackValue(dataSecretCreated, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, ""), ) - if !ready { + if !dataSecretCreated { log.Info("Waiting for bootstrap provider to generate data secret and report status.ready", bootstrapConfig.GetKind(), klog.KObj(bootstrapConfig)) - m.Status.BootstrapReady = ready + m.Status.BootstrapReady = dataSecretCreated return ctrl.Result{}, nil } // Get and set the name of the secret containing the bootstrap data. - secretName, _, err := unstructured.NestedString(bootstrapConfig.Object, "status", "dataSecretName") + secretName, err := contract.Bootstrap().DataSecretName().Get(bootstrapConfig) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve dataSecretName from bootstrap provider for MachinePool %q in namespace %q", m.Name, m.Namespace) - } else if secretName == "" { + } else if secretName == nil { return ctrl.Result{}, errors.Errorf("retrieved empty dataSecretName from bootstrap provider for MachinePool %q in namespace %q", m.Name, m.Namespace) } - m.Spec.Template.Spec.Bootstrap.DataSecretName = ptr.To(secretName) + m.Spec.Template.Spec.Bootstrap.DataSecretName = secretName m.Status.BootstrapReady = true return ctrl.Result{}, nil } diff --git a/exp/internal/controllers/machinepool_controller_phases_test.go b/exp/internal/controllers/machinepool_controller_phases_test.go index 89444aeffe19..778d227af235 100644 --- a/exp/internal/controllers/machinepool_controller_phases_test.go +++ b/exp/internal/controllers/machinepool_controller_phases_test.go @@ -208,7 +208,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -248,7 +248,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -311,7 +311,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -383,7 +383,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -426,7 +426,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -489,7 +489,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -559,7 +559,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -625,7 +625,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready. - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -693,7 +693,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { // Change bootstrap reference. newBootstrapConfig := defaultBootstrap.DeepCopy() newBootstrapConfig.SetName("bootstrap-config2") - err = unstructured.SetNestedField(newBootstrapConfig.Object, true, "status", "ready") + err = unstructured.SetNestedField(newBootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(newBootstrapConfig.Object, "secret-data-new", "status", "dataSecretName") g.Expect(err).ToNot(HaveOccurred()) @@ -721,7 +721,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { infraConfig := defaultInfra.DeepCopy() // Set bootstrap ready - err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") + err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") @@ -789,7 +789,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { // Change bootstrap reference newBootstrapConfig := defaultBootstrap.DeepCopy() newBootstrapConfig.SetName("bootstrap-config2") - err = unstructured.SetNestedField(newBootstrapConfig.Object, false, "status", "ready") + err = unstructured.SetNestedField(newBootstrapConfig.Object, false, "status", "initialization", "dataSecretCreated") g.Expect(err).ToNot(HaveOccurred()) // Fill the `dataSecretName` so we can check if the machine pool uses the non-ready secret immediately or, // as it should, not yet @@ -869,7 +869,9 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "dataSecretCreated": true, + }, "dataSecretName": "secret-data", }, }, @@ -891,7 +893,9 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "dataSecretCreated": true, + }, }, }, expectError: true, @@ -960,7 +964,9 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "dataSecretCreated": true, + }, "dataSecretName": "secret-data", }, }, @@ -1005,7 +1011,9 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "dataSecretCreated": true, + }, "dataSecretName": "secret-data", }, }, @@ -1044,8 +1052,10 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": false, - "data": "#!/bin/bash ... data", + "initialization": map[string]interface{}{ + "dataSecretCreated": false, + }, + "data": "#!/bin/bash ... data", }, }, machinepool: &expv1.MachinePool{ @@ -1245,7 +1255,9 @@ func TestReconcileMachinePoolInfrastructure(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "dataSecretCreated": true, + }, "dataSecretName": "secret-data", }, }, @@ -1306,7 +1318,9 @@ func TestReconcileMachinePoolInfrastructure(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "dataSecretCreated": true, + }, "dataSecretName": "secret-data", }, }, @@ -1817,7 +1831,9 @@ func TestReconcileMachinePoolScaleToFromZero(t *testing.T) { }, "spec": map[string]interface{}{}, "status": map[string]interface{}{ - "ready": true, + "initialization": map[string]interface{}{ + "dataSecretCreated": true, + }, "dataSecretName": "secret-data", }, }, diff --git a/exp/internal/controllers/machinepool_controller_test.go b/exp/internal/controllers/machinepool_controller_test.go index a286f9670428..217706c04725 100644 --- a/exp/internal/controllers/machinepool_controller_test.go +++ b/exp/internal/controllers/machinepool_controller_test.go @@ -917,7 +917,7 @@ func TestMachinePoolConditions(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "test-cluster"}, } - bootstrapConfig := func(ready bool) *unstructured.Unstructured { + bootstrapConfig := func(dataSecretCreated bool) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ "kind": builder.TestBootstrapConfigKind, @@ -927,7 +927,9 @@ func TestMachinePoolConditions(t *testing.T) { "namespace": metav1.NamespaceDefault, }, "status": map[string]interface{}{ - "ready": ready, + "initialization": map[string]interface{}{ + "dataSecretCreated": dataSecretCreated, + }, "dataSecretName": "data", }, }, @@ -1018,7 +1020,7 @@ func TestMachinePoolConditions(t *testing.T) { testcases := []struct { name string - bootstrapReady bool + dataSecretCreated bool infrastructureReady bool expectError bool beforeFunc func(bootstrap, infra *unstructured.Unstructured, mp *expv1.MachinePool, nodeList *corev1.NodeList) @@ -1026,7 +1028,7 @@ func TestMachinePoolConditions(t *testing.T) { }{ { name: "all conditions true", - bootstrapReady: true, + dataSecretCreated: true, infrastructureReady: true, beforeFunc: func(_, _ *unstructured.Unstructured, mp *expv1.MachinePool, _ *corev1.NodeList) { mp.Spec.ProviderIDList = []string{"azure://westus2/id-node-4", "aws://us-east-1/id-node-1"} @@ -1055,7 +1057,7 @@ func TestMachinePoolConditions(t *testing.T) { }, { name: "boostrap not ready", - bootstrapReady: false, + dataSecretCreated: false, infrastructureReady: true, beforeFunc: func(bootstrap, _ *unstructured.Unstructured, _ *expv1.MachinePool, _ *corev1.NodeList) { addConditionsToExternal(bootstrap, clusterv1.Conditions{ @@ -1079,7 +1081,7 @@ func TestMachinePoolConditions(t *testing.T) { }, { name: "bootstrap not ready with fallback condition", - bootstrapReady: false, + dataSecretCreated: false, infrastructureReady: true, conditionAssertFunc: func(t *testing.T, getter v1beta1conditions.Getter) { t.Helper() @@ -1096,7 +1098,7 @@ func TestMachinePoolConditions(t *testing.T) { }, { name: "infrastructure not ready", - bootstrapReady: true, + dataSecretCreated: true, infrastructureReady: false, beforeFunc: func(_, infra *unstructured.Unstructured, _ *expv1.MachinePool, _ *corev1.NodeList) { addConditionsToExternal(infra, clusterv1.Conditions{ @@ -1121,7 +1123,7 @@ func TestMachinePoolConditions(t *testing.T) { }, { name: "infrastructure not ready with fallback condition", - bootstrapReady: true, + dataSecretCreated: true, infrastructureReady: false, conditionAssertFunc: func(t *testing.T, getter v1beta1conditions.Getter) { t.Helper() @@ -1137,9 +1139,9 @@ func TestMachinePoolConditions(t *testing.T) { }, }, { - name: "incorrect infrastructure reference", - bootstrapReady: true, - expectError: true, + name: "incorrect infrastructure reference", + dataSecretCreated: true, + expectError: true, beforeFunc: func(_, _ *unstructured.Unstructured, mp *expv1.MachinePool, _ *corev1.NodeList) { mp.Spec.Template.Spec.InfrastructureRef = corev1.ObjectReference{ APIVersion: builder.InfrastructureGroupVersion.String(), @@ -1163,7 +1165,7 @@ func TestMachinePoolConditions(t *testing.T) { g := NewWithT(t) // setup objects - bootstrap := bootstrapConfig(tt.bootstrapReady) + bootstrap := bootstrapConfig(tt.dataSecretCreated) infra := infraConfig(tt.infrastructureReady) mp := machinePool.DeepCopy() nodes := nodeList.DeepCopy()