Skip to content

Commit 55c3a76

Browse files
committed
Refactor BootstrapConfig/InfraMachine managedFields for in-place
1 parent f0d3d3e commit 55c3a76

File tree

14 files changed

+1282
-478
lines changed

14 files changed

+1282
-478
lines changed

controlplane/kubeadm/controllers/alias.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
// KubeadmControlPlaneReconciler reconciles a KubeadmControlPlane object.
3434
type KubeadmControlPlaneReconciler struct {
3535
Client client.Client
36+
APIReader client.Reader
3637
SecretCachingClient client.Client
3738
RuntimeClient runtimeclient.Client
3839
ClusterCache clustercache.ClusterCache
@@ -51,6 +52,7 @@ type KubeadmControlPlaneReconciler struct {
5152
func (r *KubeadmControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
5253
return (&kubeadmcontrolplanecontrollers.KubeadmControlPlaneReconciler{
5354
Client: r.Client,
55+
APIReader: r.APIReader,
5456
SecretCachingClient: r.SecretCachingClient,
5557
RuntimeClient: r.RuntimeClient,
5658
ClusterCache: r.ClusterCache,

controlplane/kubeadm/internal/controllers/controller.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import (
4848
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
4949
runtimeclient "sigs.k8s.io/cluster-api/exp/runtime/client"
5050
"sigs.k8s.io/cluster-api/feature"
51-
"sigs.k8s.io/cluster-api/internal/contract"
5251
"sigs.k8s.io/cluster-api/internal/util/ssa"
5352
"sigs.k8s.io/cluster-api/util"
5453
"sigs.k8s.io/cluster-api/util/cache"
@@ -66,6 +65,7 @@ import (
6665

6766
const (
6867
kcpManagerName = "capi-kubeadmcontrolplane"
68+
kcpMetadataManagerName = "capi-kubeadmcontrolplane-metadata"
6969
kubeadmControlPlaneKind = "KubeadmControlPlane"
7070
)
7171

@@ -80,6 +80,7 @@ const (
8080
// KubeadmControlPlaneReconciler reconciles a KubeadmControlPlane object.
8181
type KubeadmControlPlaneReconciler struct {
8282
Client client.Client
83+
APIReader client.Reader
8384
SecretCachingClient client.Client
8485
RuntimeClient runtimeclient.Client
8586
controller controller.Controller
@@ -106,6 +107,9 @@ type KubeadmControlPlaneReconciler struct {
106107
overridePreflightChecksFunc func(ctx context.Context, controlPlane *internal.ControlPlane, excludeFor ...*clusterv1.Machine) ctrl.Result
107108
overrideCanUpdateMachineFunc func(ctx context.Context, machine *clusterv1.Machine, machineUpToDateResult internal.UpToDateResult) (bool, error)
108109
overrideCanExtensionsUpdateMachine func(ctx context.Context, machine *clusterv1.Machine, machineUpToDateResult internal.UpToDateResult, extensionHandlers []string) (bool, []string, error)
110+
// Note: This field is only used for unit tests that use fake client because the fake client does not properly set resourceVersion
111+
// on BootstrapConfig/InfraMachine after ssa.Patch and then ssa.RemoveManagedFieldsForLabelsAndAnnotations would fail.
112+
disableRemoveManagedFieldsForLabelsAndAnnotations bool
109113
}
110114

111115
func (r *KubeadmControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
@@ -857,23 +861,15 @@ func (r *KubeadmControlPlaneReconciler) syncMachines(ctx context.Context, contro
857861
}
858862
patchHelpers[machineName] = patchHelper
859863

860-
labelsAndAnnotationsManagedFieldPaths := []contract.Path{
861-
{"f:metadata", "f:annotations"},
862-
{"f:metadata", "f:labels"},
863-
}
864864
infraMachine, infraMachineFound := controlPlane.InfraResources[machineName]
865865
// Only update the InfraMachine if it is already found, otherwise just skip it.
866866
// This could happen e.g. if the cache is not up-to-date yet.
867867
if infraMachineFound {
868-
// Cleanup managed fields of all InfrastructureMachines to drop ownership of labels and annotations
869-
// from "manager". We do this so that InfrastructureMachines that are created using the Create method
870-
// can also work with SSA. Otherwise, labels and annotations would be co-owned by our "old" "manager"
871-
// and "capi-kubeadmcontrolplane" and then we would not be able to e.g. drop labels and annotations.
872-
if err := ssa.DropManagedFields(ctx, r.Client, infraMachine, kcpManagerName, labelsAndAnnotationsManagedFieldPaths); err != nil {
868+
if err := ssa.MigrateManagedFields(ctx, r.Client, infraMachine, kcpManagerName, kcpMetadataManagerName); err != nil {
873869
return errors.Wrapf(err, "failed to clean up managedFields of InfrastructureMachine %s", klog.KObj(infraMachine))
874870
}
875871
// Update in-place mutating fields on InfrastructureMachine.
876-
if err := r.updateExternalObject(ctx, infraMachine, infraMachine.GroupVersionKind(), controlPlane.KCP, controlPlane.Cluster); err != nil {
872+
if err := r.updateLabelsAndAnnotations(ctx, infraMachine, infraMachine.GroupVersionKind(), controlPlane.KCP, controlPlane.Cluster); err != nil {
877873
return errors.Wrapf(err, "failed to update InfrastructureMachine %s", klog.KObj(infraMachine))
878874
}
879875
}
@@ -882,15 +878,11 @@ func (r *KubeadmControlPlaneReconciler) syncMachines(ctx context.Context, contro
882878
// Only update the KubeadmConfig if it is already found, otherwise just skip it.
883879
// This could happen e.g. if the cache is not up-to-date yet.
884880
if kubeadmConfigFound {
885-
// Cleanup managed fields of all KubeadmConfigs to drop ownership of labels and annotations
886-
// from "manager". We do this so that KubeadmConfigs that are created using the Create method
887-
// can also work with SSA. Otherwise, labels and annotations would be co-owned by our "old" "manager"
888-
// and "capi-kubeadmcontrolplane" and then we would not be able to e.g. drop labels and annotations.
889-
if err := ssa.DropManagedFields(ctx, r.Client, kubeadmConfig, kcpManagerName, labelsAndAnnotationsManagedFieldPaths); err != nil {
881+
if err := ssa.MigrateManagedFields(ctx, r.Client, kubeadmConfig, kcpManagerName, kcpMetadataManagerName); err != nil {
890882
return errors.Wrapf(err, "failed to clean up managedFields of KubeadmConfig %s", klog.KObj(kubeadmConfig))
891883
}
892884
// Update in-place mutating fields on BootstrapConfig.
893-
if err := r.updateExternalObject(ctx, kubeadmConfig, bootstrapv1.GroupVersion.WithKind("KubeadmConfig"), controlPlane.KCP, controlPlane.Cluster); err != nil {
885+
if err := r.updateLabelsAndAnnotations(ctx, kubeadmConfig, bootstrapv1.GroupVersion.WithKind("KubeadmConfig"), controlPlane.KCP, controlPlane.Cluster); err != nil {
894886
return errors.Wrapf(err, "failed to update KubeadmConfig %s", klog.KObj(kubeadmConfig))
895887
}
896888
}

controlplane/kubeadm/internal/controllers/controller_test.go

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ import (
3333
"github.com/pkg/errors"
3434
appsv1 "k8s.io/api/apps/v1"
3535
corev1 "k8s.io/api/core/v1"
36+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3637
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3738
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
39+
"k8s.io/apimachinery/pkg/runtime"
3840
"k8s.io/apimachinery/pkg/types"
3941
"k8s.io/apimachinery/pkg/util/intstr"
4042
"k8s.io/client-go/tools/clientcmd"
@@ -1705,6 +1707,14 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
17051707
namespace, testCluster := setup(t, g)
17061708
defer teardown(t, g, namespace, testCluster)
17071709

1710+
reconciler := &KubeadmControlPlaneReconciler{
1711+
// Note: Ensure the fieldManager defaults to manager like in prod.
1712+
// Otherwise it defaults to the binary name which is not manager in tests.
1713+
Client: client.WithFieldOwner(env.Client, "manager"),
1714+
SecretCachingClient: secretCachingClient,
1715+
ssaCache: ssa.NewCache("test-controller"),
1716+
}
1717+
17081718
classicManager := "manager"
17091719
duration5s := ptr.To(int32(5))
17101720
duration10s := ptr.To(int32(10))
@@ -1720,12 +1730,12 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
17201730
"metadata": map[string]interface{}{
17211731
"name": "existing-inframachine",
17221732
"namespace": testCluster.Namespace,
1723-
"labels": map[string]string{
1733+
"labels": map[string]interface{}{
17241734
"preserved-label": "preserved-value",
17251735
"dropped-label": "dropped-value",
17261736
"modified-label": "modified-value",
17271737
},
1728-
"annotations": map[string]string{
1738+
"annotations": map[string]interface{}{
17291739
"preserved-annotation": "preserved-value",
17301740
"dropped-annotation": "dropped-value",
17311741
"modified-annotation": "modified-value",
@@ -1739,8 +1749,9 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
17391749
Name: "existing-inframachine",
17401750
APIGroup: clusterv1.GroupVersionInfrastructure.Group,
17411751
}
1742-
// Note: use "manager" as the field owner to mimic the manager used before ClusterAPI v1.4.0.
1743-
g.Expect(env.Create(ctx, existingInfraMachine, client.FieldOwner("manager"))).To(Succeed())
1752+
// Note: Matches up with createInfraMachine.
1753+
g.Expect(ssa.Patch(ctx, env.Client, kcpManagerName, existingInfraMachine)).To(Succeed())
1754+
g.Expect(ssa.RemoveManagedFieldsForLabelsAndAnnotations(ctx, env.Client, env.GetAPIReader(), existingInfraMachine, kcpManagerName)).To(Succeed())
17441755

17451756
// Existing KubeadmConfig
17461757
bootstrapSpec := &bootstrapv1.KubeadmConfigSpec{
@@ -1772,8 +1783,9 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
17721783
Name: "existing-kubeadmconfig",
17731784
APIGroup: bootstrapv1.GroupVersion.Group,
17741785
}
1775-
// Note: use "manager" as the field owner to mimic the manager used before ClusterAPI v1.4.0.
1776-
g.Expect(env.Create(ctx, existingKubeadmConfig, client.FieldOwner("manager"))).To(Succeed())
1786+
// Note: Matches up with createKubeadmConfig.
1787+
g.Expect(ssa.Patch(ctx, env.Client, kcpManagerName, existingKubeadmConfig)).To(Succeed())
1788+
g.Expect(ssa.RemoveManagedFieldsForLabelsAndAnnotations(ctx, env.Client, env.GetAPIReader(), existingKubeadmConfig, kcpManagerName)).To(Succeed())
17771789

17781790
// Existing Machine to validate in-place mutation
17791791
fd := "fd1"
@@ -1812,7 +1824,7 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
18121824
},
18131825
},
18141826
}
1815-
g.Expect(env.PatchAndWait(ctx, inPlaceMutatingMachine, client.FieldOwner(kcpManagerName))).To(Succeed())
1827+
g.Expect(reconciler.createMachine(ctx, &controlplanev1.KubeadmControlPlane{}, inPlaceMutatingMachine)).To(Succeed())
18161828

18171829
// Existing machine that is in deleting state
18181830
deletingMachine := &clusterv1.Machine{
@@ -1835,7 +1847,11 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
18351847
Name: "inframachine",
18361848
},
18371849
Bootstrap: clusterv1.Bootstrap{
1838-
DataSecretName: ptr.To("machine-bootstrap-secret"),
1850+
ConfigRef: clusterv1.ContractVersionedObjectReference{
1851+
Kind: "KubeadmConfig",
1852+
Name: "non-existing-kubeadmconfig",
1853+
APIGroup: bootstrapv1.GroupVersion.Group,
1854+
},
18391855
},
18401856
Deletion: clusterv1.MachineDeletionSpec{
18411857
NodeDrainTimeoutSeconds: duration5s,
@@ -1845,7 +1861,7 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
18451861
ReadinessGates: desiredstate.MandatoryMachineReadinessGates,
18461862
},
18471863
}
1848-
g.Expect(env.PatchAndWait(ctx, deletingMachine, client.FieldOwner(kcpManagerName))).To(Succeed())
1864+
g.Expect(reconciler.createMachine(ctx, &controlplanev1.KubeadmControlPlane{}, deletingMachine)).To(Succeed())
18491865
// Delete the machine to put it in the deleting state
18501866
g.Expect(env.Delete(ctx, deletingMachine)).To(Succeed())
18511867
// Wait till the machine is marked for deletion
@@ -1877,12 +1893,15 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
18771893
Name: "inframachine",
18781894
},
18791895
Bootstrap: clusterv1.Bootstrap{
1880-
DataSecretName: ptr.To("machine-bootstrap-secret"),
1896+
ConfigRef: clusterv1.ContractVersionedObjectReference{
1897+
Kind: "KubeadmConfig",
1898+
Name: "non-existing-kubeadmconfig",
1899+
APIGroup: bootstrapv1.GroupVersion.Group,
1900+
},
18811901
},
18821902
},
18831903
}
1884-
g.Expect(env.Create(ctx, nilInfraMachineMachine, client.FieldOwner(classicManager))).To(Succeed())
1885-
// Delete the machine to put it in the deleting state
1904+
g.Expect(reconciler.createMachine(ctx, &controlplanev1.KubeadmControlPlane{}, nilInfraMachineMachine)).To(Succeed())
18861905

18871906
kcp := &controlplanev1.KubeadmControlPlane{
18881907
TypeMeta: metav1.TypeMeta{
@@ -1950,11 +1969,6 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
19501969

19511970
// Run syncMachines to clean up managed fields and have proper field ownership
19521971
// for Machines, InfrastructureMachines and KubeadmConfigs.
1953-
reconciler := &KubeadmControlPlaneReconciler{
1954-
Client: env,
1955-
SecretCachingClient: secretCachingClient,
1956-
ssaCache: ssa.NewCache("test-controller"),
1957-
}
19581972
g.Expect(reconciler.syncMachines(ctx, controlPlane)).To(Succeed())
19591973

19601974
// The inPlaceMutatingMachine should have cleaned up managed fields.
@@ -1970,41 +1984,39 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
19701984
"in-place mutable machine should not contain an entry for old manager",
19711985
)
19721986

1973-
// The InfrastructureMachine should have ownership of "labels" and "annotations" transferred to
1974-
// "capi-kubeadmcontrolplane" manager.
19751987
updatedInfraMachine := existingInfraMachine.DeepCopy()
19761988
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedInfraMachine), updatedInfraMachine)).To(Succeed())
19771989

19781990
// Verify ManagedFields
1991+
// capi-kubeadmcontrolplane-metadata owns labels and annotations
19791992
g.Expect(updatedInfraMachine.GetManagedFields()).Should(
1980-
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
1993+
ssa.MatchFieldOwnership(kcpMetadataManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
19811994
g.Expect(updatedInfraMachine.GetManagedFields()).Should(
1982-
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
1995+
ssa.MatchFieldOwnership(kcpMetadataManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
19831996
g.Expect(updatedInfraMachine.GetManagedFields()).ShouldNot(
1984-
ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:labels"}))
1997+
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
19851998
g.Expect(updatedInfraMachine.GetManagedFields()).ShouldNot(
1986-
ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:annotations"}))
1987-
// Verify ownership of other fields is not changed.
1999+
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
2000+
// capi-kubeadmcontrolplane owns spec
19882001
g.Expect(updatedInfraMachine.GetManagedFields()).Should(
1989-
ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:spec"}))
2002+
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:spec"}))
19902003

1991-
// The KubeadmConfig should have ownership of "labels" and "annotations" transferred to
1992-
// "capi-kubeadmcontrolplane" manager.
19932004
updatedKubeadmConfig := existingKubeadmConfig.DeepCopy()
19942005
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedKubeadmConfig), updatedKubeadmConfig)).To(Succeed())
19952006

19962007
// Verify ManagedFields
2008+
// capi-kubeadmcontrolplane-metadata owns labels and annotations
19972009
g.Expect(updatedKubeadmConfig.GetManagedFields()).Should(
1998-
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
2010+
ssa.MatchFieldOwnership(kcpMetadataManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
19992011
g.Expect(updatedKubeadmConfig.GetManagedFields()).Should(
2000-
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
2012+
ssa.MatchFieldOwnership(kcpMetadataManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
20012013
g.Expect(updatedKubeadmConfig.GetManagedFields()).ShouldNot(
2002-
ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:labels"}))
2014+
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
20032015
g.Expect(updatedKubeadmConfig.GetManagedFields()).ShouldNot(
2004-
ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:annotations"}))
2005-
// Verify ownership of other fields is not changed.
2016+
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
2017+
// capi-kubeadmcontrolplane owns spec
20062018
g.Expect(updatedKubeadmConfig.GetManagedFields()).Should(
2007-
ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:spec"}))
2019+
ssa.MatchFieldOwnership(kcpManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:spec"}))
20082020

20092021
//
20102022
// Verify In-place mutating fields
@@ -2098,9 +2110,10 @@ func TestKubeadmControlPlaneReconciler_syncMachines(t *testing.T) {
20982110
ContainElement(ssa.MatchManagedFieldsEntry(kcpManagerName, metav1.ManagedFieldsOperationApply)),
20992111
"deleting machine should contain an entry for SSA manager",
21002112
)
2101-
g.Expect(updatedDeletingMachine.ManagedFields).ShouldNot(
2113+
// "manager" will own fields propagated to the deleting Machine with the pachHelper.
2114+
g.Expect(updatedDeletingMachine.ManagedFields).Should(
21022115
ContainElement(ssa.MatchManagedFieldsEntry(classicManager, metav1.ManagedFieldsOperationUpdate)),
2103-
"deleting machine should not contain an entry for old manager",
2116+
"deleting machine should contain an entry for old manager",
21042117
)
21052118

21062119
// Verify the machine labels and annotations are unchanged.
@@ -3923,9 +3936,17 @@ func TestObjectsPendingDelete(t *testing.T) {
39233936
// test utils.
39243937

39253938
func newFakeClient(initObjs ...client.Object) client.WithWatch {
3939+
// Use a new scheme to avoid side effects if multiple tests are sharing the same global scheme.
3940+
scheme := runtime.NewScheme()
3941+
_ = appsv1.AddToScheme(scheme)
3942+
_ = corev1.AddToScheme(scheme)
3943+
_ = apiextensionsv1.AddToScheme(scheme)
3944+
_ = clusterv1.AddToScheme(scheme)
3945+
_ = bootstrapv1.AddToScheme(scheme)
3946+
_ = controlplanev1.AddToScheme(scheme)
39263947
return &fakeClient{
39273948
startTime: time.Now(),
3928-
WithWatch: fake.NewClientBuilder().WithObjects(initObjs...).WithStatusSubresource(&controlplanev1.KubeadmControlPlane{}).Build(),
3949+
WithWatch: fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithStatusSubresource(&controlplanev1.KubeadmControlPlane{}).Build(),
39293950
}
39303951
}
39313952

0 commit comments

Comments
 (0)