Skip to content

Commit d6b1414

Browse files
committed
chore: address review comments
Signed-off-by: Tarun Gupta Akirala <[email protected]>
1 parent 97fbdc8 commit d6b1414

File tree

2 files changed

+171
-60
lines changed

2 files changed

+171
-60
lines changed

cmd/clusterctl/client/cluster/mover.go

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
apierrors "k8s.io/apimachinery/pkg/api/errors"
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+
"k8s.io/apimachinery/pkg/runtime"
3031
"k8s.io/apimachinery/pkg/types"
3132
kerrors "k8s.io/apimachinery/pkg/util/errors"
3233
"k8s.io/apimachinery/pkg/util/sets"
@@ -332,11 +333,9 @@ func (o *objectMover) move(graph *objectGraph, toProxy Proxy, mutators ...Resour
332333
return errors.Wrap(err, "error pausing ClusterClasses")
333334
}
334335

335-
// Ensure all the expected target namespaces are in place before creating objects.
336-
log.V(1).Info("Creating target namespaces, if missing")
337-
if err := o.ensureNamespaces(graph, toProxy); err != nil {
338-
return err
339-
}
336+
// Nb. DO NOT call ensureNamespaces at this point because:
337+
// - namespace will be ensured to exist before creating the resource.
338+
// - If it's done here, we might create a namespace that can end up unused on target cluster (due to mutators).
340339

341340
// Define the move sequence by processing the ownerReference chain, so we ensure that a Kubernetes object is moved only after its owners.
342341
// The sequence is bases on object graph nodes, each one representing a Kubernetes object; nodes are grouped, so bulk of nodes can be moved in parallel. e.g.
@@ -353,6 +352,10 @@ func (o *objectMover) move(graph *objectGraph, toProxy Proxy, mutators ...Resour
353352
}
354353
}
355354

355+
// Nb. mutators used after this point (after creating the resources on target clusters) are mainly intended for
356+
// using the right namespace to fetch the resource from the target cluster.
357+
// mutators affecting non metadata fields are no-op after this point.
358+
356359
// Delete all objects group by group in reverse order.
357360
log.Info("Deleting objects from the source cluster")
358361
for groupIndex := len(moveSequence.groups) - 1; groupIndex >= 0; groupIndex-- {
@@ -599,17 +602,20 @@ func patchCluster(proxy Proxy, n *node, patch client.Patch, mutators ...Resource
599602
return err
600603
}
601604

602-
// Get the ClusterClass from the server
603-
clusterObj := &unstructured.Unstructured{}
604-
clusterObj.SetAPIVersion(clusterv1.GroupVersion.String())
605-
clusterObj.SetKind(clusterv1.ClusterKind)
606-
clusterObj.SetName(n.identity.Name)
607-
clusterObj.SetNamespace(n.identity.Namespace)
608-
for _, mutator := range mutators {
609-
if err = mutator(clusterObj); err != nil {
610-
return errors.Wrapf(err, "error applying resource mutator to %q %s/%s",
611-
clusterObj.GroupVersionKind(), clusterObj.GetNamespace(), clusterObj.GetName())
612-
}
605+
// Since the patch has been generated already in caller of this function, the ONLY affect that mutators can have
606+
// here is on namespace of the resource.
607+
clusterObj, err := applyMutators(&clusterv1.Cluster{
608+
TypeMeta: metav1.TypeMeta{
609+
Kind: clusterv1.ClusterKind,
610+
APIVersion: clusterv1.GroupVersion.String(),
611+
},
612+
ObjectMeta: metav1.ObjectMeta{
613+
Name: n.identity.Name,
614+
Namespace: n.identity.Namespace,
615+
},
616+
}, mutators...)
617+
if err != nil {
618+
return err
613619
}
614620

615621
if err := cFrom.Get(ctx, client.ObjectKeyFromObject(clusterObj), clusterObj); err != nil {
@@ -631,20 +637,20 @@ func pauseClusterClass(proxy Proxy, n *node, pause bool, mutators ...ResourceMut
631637
return errors.Wrap(err, "error creating client")
632638
}
633639

634-
// Get the ClusterClass from the server
635-
clusterClass := &unstructured.Unstructured{}
636-
clusterClass.SetAPIVersion(clusterv1.GroupVersion.String())
637-
clusterClass.SetKind(clusterv1.ClusterClassKind)
638-
clusterClass.SetName(n.identity.Name)
639-
clusterClass.SetNamespace(n.identity.Namespace)
640-
for _, mutator := range mutators {
641-
if err = mutator(clusterClass); err != nil {
642-
return errors.Wrapf(err, "error applying resource mutator to %q %s/%s",
643-
clusterClass.GroupVersionKind(), clusterClass.GetNamespace(), clusterClass.GetName())
644-
}
645-
}
646-
if err := cFrom.Get(ctx, client.ObjectKeyFromObject(clusterClass), clusterClass); err != nil {
647-
return errors.Wrapf(err, "error reading ClusterClass %s/%s", n.identity.Namespace, n.identity.Name)
640+
// Since the patch has been generated already in caller of this function, the ONLY affect that mutators can have
641+
// here is on namespace of the resource.
642+
clusterClass, err := applyMutators(&clusterv1.ClusterClass{
643+
TypeMeta: metav1.TypeMeta{
644+
Kind: clusterv1.ClusterClassKind,
645+
APIVersion: clusterv1.GroupVersion.String(),
646+
},
647+
ObjectMeta: metav1.ObjectMeta{
648+
Name: n.identity.Name,
649+
Namespace: n.identity.Namespace,
650+
},
651+
}, mutators...)
652+
if err != nil {
653+
return err
648654
}
649655

650656
patchHelper, err := patch.NewHelper(clusterClass, cFrom)
@@ -892,17 +898,16 @@ func (o *objectMover) createTargetObject(nodeToCreate *node, toProxy Proxy, muta
892898
return err
893899
}
894900

895-
for _, mutator := range mutators {
896-
if err = mutator(obj); err != nil {
897-
return errors.Wrapf(err, "error applying resource mutator to %q %s/%s",
898-
obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName())
899-
}
901+
obj, err = applyMutators(obj, mutators...)
902+
if err != nil {
903+
return err
900904
}
901905
// Applying mutators MAY change the namespace, so ensure the namespace exists before creating the resource.
902906
if !nodeToCreate.isGlobal && !existingNamespaces.Has(obj.GetNamespace()) {
903907
if err = o.ensureNamespace(toProxy, obj.GetNamespace()); err != nil {
904908
return err
905909
}
910+
existingNamespaces.Insert(obj.GetNamespace())
906911
}
907912
oldManagedFields := obj.GetManagedFields()
908913
if err := cTo.Create(ctx, obj); err != nil {
@@ -1224,3 +1229,22 @@ func patchTopologyManagedFields(ctx context.Context, oldManagedFields []metav1.M
12241229
}
12251230
return nil
12261231
}
1232+
1233+
func applyMutators(object client.Object, mutators ...ResourceMutatorFunc) (*unstructured.Unstructured, error) {
1234+
if object == nil {
1235+
return nil, nil
1236+
}
1237+
u := &unstructured.Unstructured{}
1238+
to, err := runtime.DefaultUnstructuredConverter.ToUnstructured(object)
1239+
if err != nil {
1240+
return nil, err
1241+
}
1242+
u.SetUnstructuredContent(to)
1243+
for _, mutator := range mutators {
1244+
if err := mutator(u); err != nil {
1245+
return nil, errors.Wrapf(err, "error applying resource mutator to %q %s/%s",
1246+
u.GroupVersionKind(), object.GetNamespace(), object.GetName())
1247+
}
1248+
}
1249+
return u, nil
1250+
}

cmd/clusterctl/client/cluster/mover_test.go

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package cluster
1818

1919
import (
2020
"fmt"
21-
"math/rand"
2221
"os"
2322
"path/filepath"
2423
"strings"
@@ -30,6 +29,7 @@ import (
3029
apierrors "k8s.io/apimachinery/pkg/api/errors"
3130
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3231
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32+
"k8s.io/apimachinery/pkg/util/sets"
3333
"k8s.io/utils/pointer"
3434
"sigs.k8s.io/controller-runtime/pkg/client"
3535

@@ -50,6 +50,32 @@ var moveTests = []struct {
5050
wantMoveGroups [][]string
5151
wantErr bool
5252
}{
53+
{
54+
name: "Cluster with ClusterClass",
55+
fields: moveTestsFields{
56+
objs: func() []client.Object {
57+
objs := test.NewFakeClusterClass("ns1", "class1").Objs()
58+
objs = append(objs, test.NewFakeCluster("ns1", "foo").WithTopologyClass("class1").Objs()...)
59+
return deduplicateObjects(objs)
60+
}(),
61+
},
62+
wantMoveGroups: [][]string{
63+
{ // group 1
64+
"cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/class1",
65+
},
66+
{ // group 2
67+
"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/class1",
68+
"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/class1",
69+
"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/foo",
70+
},
71+
{ // group 3
72+
"/v1, Kind=Secret, ns1/foo-ca",
73+
"/v1, Kind=Secret, ns1/foo-kubeconfig",
74+
"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/foo",
75+
},
76+
},
77+
wantErr: false,
78+
},
5379
{
5480
name: "Cluster",
5581
fields: moveTestsFields{
@@ -1163,9 +1189,82 @@ func Test_objectMover_move_dryRun(t *testing.T) {
11631189
}
11641190

11651191
func Test_objectMover_move(t *testing.T) {
1192+
// NB. we are testing the move and move sequence using the same set of moveTests, but checking the results at different stages of the move process
1193+
for _, tt := range moveTests {
1194+
t.Run(tt.name, func(t *testing.T) {
1195+
g := NewWithT(t)
1196+
1197+
// Create an objectGraph bound a source cluster with all the CRDs for the types involved in the test.
1198+
graph := getObjectGraphWithObjs(tt.fields.objs)
1199+
1200+
// Get all the types to be considered for discovery
1201+
g.Expect(getFakeDiscoveryTypes(graph)).To(Succeed())
1202+
1203+
// trigger discovery the content of the source cluster
1204+
g.Expect(graph.Discovery("")).To(Succeed())
1205+
1206+
// gets a fakeProxy to an empty cluster with all the required CRDs
1207+
toProxy := getFakeProxyWithCRDs()
1208+
1209+
// Run move
1210+
mover := objectMover{
1211+
fromProxy: graph.proxy,
1212+
}
1213+
err := mover.move(graph, toProxy)
1214+
1215+
if tt.wantErr {
1216+
g.Expect(err).To(HaveOccurred())
1217+
return
1218+
}
1219+
1220+
g.Expect(err).NotTo(HaveOccurred())
1221+
1222+
// check that the objects are removed from the source cluster and are created in the target cluster
1223+
csFrom, err := graph.proxy.NewClient()
1224+
g.Expect(err).NotTo(HaveOccurred())
1225+
1226+
csTo, err := toProxy.NewClient()
1227+
g.Expect(err).NotTo(HaveOccurred())
1228+
1229+
for _, node := range graph.uidToNode {
1230+
key := client.ObjectKey{
1231+
Namespace: node.identity.Namespace,
1232+
Name: node.identity.Name,
1233+
}
1234+
1235+
// objects are deleted from the source cluster
1236+
oFrom := &unstructured.Unstructured{}
1237+
oFrom.SetAPIVersion(node.identity.APIVersion)
1238+
oFrom.SetKind(node.identity.Kind)
1239+
1240+
err := csFrom.Get(ctx, key, oFrom)
1241+
if err == nil {
1242+
if !node.isGlobal && !node.isGlobalHierarchy {
1243+
t.Errorf("%v not deleted in source cluster", key)
1244+
continue
1245+
}
1246+
} else if !apierrors.IsNotFound(err) {
1247+
t.Errorf("error = %v when checking for %v deleted in source cluster", err, key)
1248+
continue
1249+
}
1250+
1251+
// objects are created in the target cluster
1252+
oTo := &unstructured.Unstructured{}
1253+
oTo.SetAPIVersion(node.identity.APIVersion)
1254+
oTo.SetKind(node.identity.Kind)
1255+
1256+
if err := csTo.Get(ctx, key, oTo); err != nil {
1257+
t.Errorf("error = %v when checking for %v created in target cluster", err, key)
1258+
continue
1259+
}
1260+
}
1261+
})
1262+
}
1263+
}
1264+
1265+
func Test_objectMover_move_with_Mutator(t *testing.T) {
11661266
// NB. we are testing the move and move sequence using the same set of moveTests, but checking the results at different stages of the move process
11671267
// we use same mutator function for all tests and validate outcome based on input.
1168-
randSource := rand.NewSource(time.Now().Unix())
11691268
for _, tt := range moveTests {
11701269
t.Run(tt.name, func(t *testing.T) {
11711270
g := NewWithT(t)
@@ -1216,20 +1315,12 @@ func Test_objectMover_move(t *testing.T) {
12161315
// gets a fakeProxy to an empty cluster with all the required CRDs
12171316
toProxy := getFakeProxyWithCRDs()
12181317

1219-
// Run move
1318+
// Run move with mutators
12201319
mover := objectMover{
12211320
fromProxy: graph.proxy,
12221321
}
12231322

1224-
// choose to include/exclude mutator randomly
1225-
includeMutator := randSource.Int63()%2 == 0
1226-
var mutators []ResourceMutatorFunc
1227-
if includeMutator {
1228-
mutators = append(mutators, namespaceMutator)
1229-
}
1230-
1231-
err := mover.move(graph, toProxy, mutators...)
1232-
1323+
err := mover.move(graph, toProxy, namespaceMutator)
12331324
if tt.wantErr {
12341325
g.Expect(err).To(HaveOccurred())
12351326
return
@@ -1270,24 +1361,20 @@ func Test_objectMover_move(t *testing.T) {
12701361
oTo := &unstructured.Unstructured{}
12711362
oTo.SetAPIVersion(node.identity.APIVersion)
12721363
oTo.SetKind(node.identity.Kind)
1273-
if includeMutator {
1274-
if key.Namespace != "" {
1275-
key.Namespace = toNamespace
1276-
}
1364+
if !node.isGlobal {
1365+
key.Namespace = toNamespace
12771366
}
12781367

12791368
if err := csTo.Get(ctx, key, oTo); err != nil {
12801369
t.Errorf("error = %v when checking for %v created in target cluster", err, key)
12811370
continue
12821371
}
1283-
if includeMutator {
1284-
if fields, knownKind := updateKnownKinds[oTo.GetKind()]; knownKind {
1285-
for _, nsField := range fields {
1286-
value, exists, err := unstructured.NestedFieldNoCopy(oTo.Object, nsField...)
1287-
g.Expect(err).To(BeNil())
1288-
if exists {
1289-
g.Expect(value).To(Equal(toNamespace))
1290-
}
1372+
if fields, knownKind := updateKnownKinds[oTo.GetKind()]; knownKind {
1373+
for _, nsField := range fields {
1374+
value, exists, err := unstructured.NestedFieldNoCopy(oTo.Object, nsField...)
1375+
g.Expect(err).To(BeNil())
1376+
if exists {
1377+
g.Expect(value).To(Equal(toNamespace))
12911378
}
12921379
}
12931380
}
@@ -1998,7 +2085,7 @@ func Test_createTargetObject(t *testing.T) {
19982085
fromProxy: tt.args.fromProxy,
19992086
}
20002087

2001-
err := mover.createTargetObject(tt.args.node, tt.args.toProxy, nil, nil)
2088+
err := mover.createTargetObject(tt.args.node, tt.args.toProxy, nil, sets.New[string]())
20022089
if tt.wantErr {
20032090
g.Expect(err).To(HaveOccurred())
20042091
return

0 commit comments

Comments
 (0)