@@ -17,6 +17,7 @@ limitations under the License.
17
17
package cluster
18
18
19
19
import (
20
+ "cmp"
20
21
"context"
21
22
"fmt"
22
23
"strings"
@@ -28,16 +29,22 @@ import (
28
29
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29
30
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30
31
"k8s.io/apimachinery/pkg/types"
32
+ kerrors "k8s.io/apimachinery/pkg/util/errors"
33
+ "k8s.io/apimachinery/pkg/util/sets"
34
+ "k8s.io/apimachinery/pkg/util/wait"
35
+ "k8s.io/klog/v2"
31
36
"sigs.k8s.io/controller-runtime/pkg/client"
32
37
33
38
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
34
39
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
35
40
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
41
+ "sigs.k8s.io/cluster-api/controllers/external"
36
42
addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
37
43
secretutil "sigs.k8s.io/cluster-api/util/secret"
38
44
)
39
45
40
46
const clusterTopologyNameKey = "cluster.spec.topology.class"
47
+ const clusterTopologyNamespaceKey = "cluster.spec.topology.classNamespace"
41
48
const clusterResourceSetBindingClusterNameKey = "clusterresourcesetbinding.spec.clustername"
42
49
43
50
type empty struct {}
@@ -74,6 +81,9 @@ type node struct {
74
81
// When this flag is true the object should not be deleted from the source cluster.
75
82
isGlobalHierarchy bool
76
83
84
+ // shouldNotDelete marks object and direct its descendants to not be deleted
85
+ shouldNotDelete bool
86
+
77
87
// virtual records if this node was discovered indirectly, e.g. by processing an OwnerRef, but not yet observed as a concrete object.
78
88
virtual bool
79
89
@@ -149,6 +159,7 @@ func (n *node) captureAdditionalInformation(obj *unstructured.Unstructured) erro
149
159
n .additionalInfo = map [string ]interface {}{}
150
160
}
151
161
n .additionalInfo [clusterTopologyNameKey ] = cluster .GetClassKey ().Name
162
+ n .additionalInfo [clusterTopologyNamespaceKey ] = cluster .GetClassKey ().Namespace
152
163
}
153
164
}
154
165
@@ -433,7 +444,7 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
433
444
objList := new (unstructured.UnstructuredList )
434
445
435
446
if err := retryWithExponentialBackoff (ctx , discoveryBackoff , func (ctx context.Context ) error {
436
- return getObjList (ctx , o .proxy , typeMeta , selectors , objList )
447
+ return getObjList (ctx , o .proxy , & typeMeta , selectors , objList )
437
448
}); err != nil {
438
449
return err
439
450
}
@@ -449,7 +460,7 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
449
460
providerNamespaceSelector := []client.ListOption {client .InNamespace (p .Namespace )}
450
461
providerNamespaceSecretList := new (unstructured.UnstructuredList )
451
462
if err := retryWithExponentialBackoff (ctx , discoveryBackoff , func (ctx context.Context ) error {
452
- return getObjList (ctx , o .proxy , typeMeta , providerNamespaceSelector , providerNamespaceSecretList )
463
+ return getObjList (ctx , o .proxy , & typeMeta , providerNamespaceSelector , providerNamespaceSecretList )
453
464
}); err != nil {
454
465
return err
455
466
}
@@ -471,6 +482,76 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
471
482
}
472
483
}
473
484
485
+ // On top of the object in the namespace being moved (secrets from the providers namespace),
486
+ // it is also required to discover ClusterClasses in other namespaces referenced by clusters in the namespace being moved.
487
+ discoveredExternalCC := sets.Set [string ]{}
488
+ for _ , cluster := range o .getClusters () {
489
+ className , hasName := cluster .additionalInfo [clusterTopologyNameKey ]
490
+ classNamespace , hasNamespace := cluster .additionalInfo [clusterTopologyNamespaceKey ]
491
+ // If the cluster doesn't have a reference to a ClusterClass, no-op.
492
+ if ! hasName || ! hasNamespace {
493
+ continue
494
+ }
495
+
496
+ // If the cluster reference a ClusterClass in the namespace being moved, no-op (it has been already discovered).
497
+ if classNamespace == namespace {
498
+ continue
499
+ }
500
+
501
+ // If the referenced ClusterClass has been already discovered, no-op.
502
+ externalCCKey := klog .KRef (classNamespace .(string ), className .(string ))
503
+ if discoveredExternalCC .Has (externalCCKey .String ()) {
504
+ continue
505
+ }
506
+
507
+ // Get the CC and referenced templates.
508
+ ccUnstructured , err := o .fetchRef (ctx , discoveryBackoff , & corev1.ObjectReference {
509
+ Kind : "ClusterClass" ,
510
+ Namespace : externalCCKey .Namespace ,
511
+ Name : externalCCKey .Name ,
512
+ APIVersion : clusterv1 .GroupVersion .String (),
513
+ })
514
+ if err != nil {
515
+ return errors .Wrap (err , "failed to get ClusterClass" )
516
+ }
517
+
518
+ cc := & clusterv1.ClusterClass {}
519
+ if err := localScheme .Convert (ccUnstructured , cc , nil ); err != nil {
520
+ return errors .Wrap (err , "failed to convert Unstructured to ClusterClass" )
521
+ }
522
+
523
+ errs := []error {}
524
+ _ , err = o .fetchRef (ctx , discoveryBackoff , cc .Spec .Infrastructure .Ref )
525
+ errs = append (errs , err )
526
+ _ , err = o .fetchRef (ctx , discoveryBackoff , cc .Spec .ControlPlane .Ref )
527
+ errs = append (errs , err )
528
+
529
+ if cc .Spec .ControlPlane .MachineInfrastructure != nil {
530
+ _ , err = o .fetchRef (ctx , discoveryBackoff , cc .Spec .ControlPlane .MachineInfrastructure .Ref )
531
+ errs = append (errs , err )
532
+ }
533
+
534
+ for _ , mdClass := range cc .Spec .Workers .MachineDeployments {
535
+ _ , err = o .fetchRef (ctx , discoveryBackoff , mdClass .Template .Infrastructure .Ref )
536
+ errs = append (errs , err )
537
+ _ , err = o .fetchRef (ctx , discoveryBackoff , mdClass .Template .Bootstrap .Ref )
538
+ errs = append (errs , err )
539
+ }
540
+
541
+ for _ , mpClass := range cc .Spec .Workers .MachinePools {
542
+ _ , err = o .fetchRef (ctx , discoveryBackoff , mpClass .Template .Infrastructure .Ref )
543
+ errs = append (errs , err )
544
+ _ , err = o .fetchRef (ctx , discoveryBackoff , mpClass .Template .Bootstrap .Ref )
545
+ errs = append (errs , err )
546
+ }
547
+
548
+ if err := kerrors .NewAggregate (errs ); err != nil {
549
+ return errors .Wrap (err , "failed to fetch ClusterClass references" )
550
+ }
551
+
552
+ discoveredExternalCC .Insert (externalCCKey .String ())
553
+ }
554
+
474
555
log .V (1 ).Info ("Total objects" , "count" , len (o .uidToNode ))
475
556
476
557
// Completes the graph by searching for soft ownership relations such as secrets linked to the cluster
@@ -480,23 +561,52 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
480
561
// Completes the graph by setting for each node the list of tenants the node belongs to.
481
562
o .setTenants ()
482
563
483
- return nil
564
+ // Ensure objects which are referenced across namespaces are not deleted.
565
+ return o .setShouldNotDelete (ctx , namespace )
484
566
}
485
567
486
- func getObjList (ctx context.Context , proxy Proxy , typeMeta metav1.TypeMeta , selectors []client.ListOption , objList * unstructured.UnstructuredList ) error {
568
+ // fetchRef collects specified reference and adds to moved objects.
569
+ func (o * objectGraph ) fetchRef (ctx context.Context , opts wait.Backoff , ref * corev1.ObjectReference ) (* unstructured.Unstructured , error ) {
570
+ if ref == nil {
571
+ return nil , nil
572
+ }
573
+
574
+ var obj * unstructured.Unstructured
575
+
576
+ if err := retryWithExponentialBackoff (ctx , opts , func (ctx context.Context ) error {
577
+ c , err := o .proxy .NewClient (ctx )
578
+ if err != nil {
579
+ return err
580
+ }
581
+
582
+ obj , err = external .Get (ctx , c , ref )
583
+ return err
584
+ }); err != nil {
585
+ return nil , errors .Wrapf (err , "failed to get referenced %q resource" , ref .String ())
586
+ }
587
+
588
+ if err := o .addObj (obj ); err != nil {
589
+ return nil , errors .Wrapf (err , "failed to add obj (Kind=%s, Name=%s) to graph" , obj .GetKind (), obj .GetName ())
590
+ }
591
+
592
+ return obj , nil
593
+ }
594
+
595
+ func getObjList (ctx context.Context , proxy Proxy , typeMeta * metav1.TypeMeta , selectors []client.ListOption , objList client.ObjectList ) error {
487
596
c , err := proxy .NewClient (ctx )
488
597
if err != nil {
489
598
return err
490
599
}
491
600
492
- objList .SetAPIVersion (typeMeta .APIVersion )
493
- objList .SetKind (typeMeta .Kind )
601
+ if typeMeta != nil {
602
+ objList .GetObjectKind ().SetGroupVersionKind (typeMeta .GroupVersionKind ())
603
+ }
494
604
495
605
if err := c .List (ctx , objList , selectors ... ); err != nil {
496
606
if apierrors .IsNotFound (err ) {
497
607
return nil
498
608
}
499
- return errors .Wrapf (err , "failed to list %q resources" , objList .GroupVersionKind ())
609
+ return errors .Wrapf (err , "failed to list %q resources" , objList .GetObjectKind (). GroupVersionKind ())
500
610
}
501
611
return nil
502
612
}
@@ -615,15 +725,20 @@ func (o *objectGraph) setSoftOwnership() {
615
725
}
616
726
617
727
clusterClasses := o .getClusterClasses ()
728
+
618
729
// Cluster that uses a ClusterClass are soft owned by that ClusterClass.
619
730
for _ , clusterClass := range clusterClasses {
620
731
for _ , cluster := range clusters {
621
732
// if the cluster uses a managed topology and uses the clusterclass
622
733
// set the clusterclass as a soft owner of the cluster.
623
- if className , ok := cluster .additionalInfo [clusterTopologyNameKey ]; ok {
624
- if className == clusterClass .identity .Name && clusterClass .identity .Namespace == cluster .identity .Namespace {
625
- cluster .addSoftOwner (clusterClass )
626
- }
734
+ className , hasName := cluster .additionalInfo [clusterTopologyNameKey ]
735
+ classNamespace , hasNamespace := cluster .additionalInfo [clusterTopologyNamespaceKey ]
736
+ if namespace , ok := classNamespace .(string ); ! hasNamespace || ! ok {
737
+ classNamespace = cmp .Or (namespace , cluster .identity .Namespace )
738
+ }
739
+
740
+ if hasName && className == clusterClass .identity .Name && clusterClass .identity .Namespace == classNamespace {
741
+ cluster .addSoftOwner (clusterClass )
627
742
}
628
743
}
629
744
}
@@ -649,18 +764,18 @@ func (o *objectGraph) setSoftOwnership() {
649
764
func (o * objectGraph ) setTenants () {
650
765
for _ , node := range o .getNodes () {
651
766
if node .forceMoveHierarchy {
652
- o .setTenant (node , node , node .isGlobal )
767
+ o .setTenantHierarchy (node , node , node .isGlobal )
653
768
}
654
769
}
655
770
}
656
771
657
- // setTenant sets a tenant for a node and for its own dependents/sofDependents .
658
- func (o * objectGraph ) setTenant (node , tenant * node , isGlobalHierarchy bool ) {
772
+ // setTenantHierarchy sets a tenant for a node and for its own dependents/softDependents .
773
+ func (o * objectGraph ) setTenantHierarchy (node , tenant * node , isGlobalHierarchy bool ) {
659
774
node .tenant [tenant ] = empty {}
660
775
node .isGlobalHierarchy = node .isGlobalHierarchy || isGlobalHierarchy
661
776
for _ , other := range o .getNodes () {
662
777
if other .isOwnedBy (node ) || other .isSoftOwnedBy (node ) {
663
- o .setTenant (other , tenant , isGlobalHierarchy )
778
+ o .setTenantHierarchy (other , tenant , isGlobalHierarchy )
664
779
}
665
780
}
666
781
}
@@ -674,3 +789,68 @@ func (o *objectGraph) checkVirtualNode() {
674
789
}
675
790
}
676
791
}
792
+
793
+ // Ensure objects which are referenced across namespaces are not deleted.
794
+ func (o * objectGraph ) setShouldNotDelete (ctx context.Context , namespace string ) error {
795
+ if namespace == "" {
796
+ return nil
797
+ }
798
+
799
+ // If there are ClusterClasses outside of the namespace being moved, those CC should not be deleted (we are moving a namespace).
800
+ for _ , class := range o .getClusterClasses () {
801
+ if class .identity .Namespace != namespace {
802
+ class .shouldNotDelete = true
803
+ // Ensure that also the templates referenced by the CC won't be deleted.
804
+ o .setShouldNotDeleteHierarchy (class )
805
+ }
806
+ }
807
+
808
+ // If there are clusters outside of the namespace being moved and referencing one of ClusterClass in the namespace being moved,
809
+ // ensure those ClusterClass are not deleted.
810
+ discoveryBackoff := newReadBackoff ()
811
+ allClusters := & clusterv1.ClusterList {}
812
+ if err := retryWithExponentialBackoff (ctx , discoveryBackoff , func (ctx context.Context ) error {
813
+ return getObjList (ctx , o .proxy , nil , []client.ListOption {}, allClusters )
814
+ }); err != nil {
815
+ return err
816
+ }
817
+
818
+ for _ , cluster := range allClusters .Items {
819
+ // ignore cluster in the namespace being moved.
820
+ if cluster .Namespace == namespace {
821
+ continue
822
+ }
823
+
824
+ // ignore cluster not using a CC.
825
+ if cluster .Spec .Topology == nil {
826
+ continue
827
+ }
828
+
829
+ // ignore cluster not referencing a CC in the namespace being moved.
830
+ if cluster .Spec .Topology .ClassNamespace != namespace {
831
+ continue
832
+ }
833
+
834
+ // Otherwise mark the referenced CC as should not be deleted.
835
+ for _ , class := range o .getClusterClasses () {
836
+ if class .identity .Namespace == cluster .Spec .Topology .ClassNamespace && class .identity .Name == cluster .Spec .Topology .Class {
837
+ class .shouldNotDelete = true
838
+ // Ensure that also the templates referenced by the CC won't be deleted.
839
+ o .setShouldNotDeleteHierarchy (class )
840
+ }
841
+ }
842
+ }
843
+
844
+ return nil
845
+ }
846
+
847
+ // setShouldNotDeleteHierarchy sets should not delete for a node and for its own dependents/softDependents.
848
+ func (o * objectGraph ) setShouldNotDeleteHierarchy (node * node ) {
849
+ node .shouldNotDelete = true
850
+ for _ , other := range o .getNodes () {
851
+ // Skip removal only for direct owners from the object namespace
852
+ if other .isOwnedBy (node ) {
853
+ o .setShouldNotDeleteHierarchy (other )
854
+ }
855
+ }
856
+ }
0 commit comments