Skip to content

Commit 12b2539

Browse files
authored
Merge pull request #2313 from fabriziopandini/clusterctl-move-preflight-providers
🐛clusterctl: preflight for move operation
2 parents d09f59e + 4a10541 commit 12b2539

File tree

3 files changed

+209
-5
lines changed

3 files changed

+209
-5
lines changed

cmd/clusterctl/pkg/client/cluster/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func (c *clusterClient) ProviderInstaller() ProviderInstaller {
113113
}
114114

115115
func (c *clusterClient) ObjectMover() ObjectMover {
116-
return newObjectMover(c.proxy)
116+
return newObjectMover(c.proxy, c.ProviderInventory())
117117
}
118118

119119
func (c *clusterClient) ProviderUpgrader() ProviderUpgrader {

cmd/clusterctl/pkg/client/cluster/mover.go

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/types"
2929
kerrors "k8s.io/apimachinery/pkg/util/errors"
3030
"k8s.io/apimachinery/pkg/util/sets"
31+
"k8s.io/apimachinery/pkg/util/version"
3132
"k8s.io/apimachinery/pkg/util/wait"
3233
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
3334
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/log"
@@ -42,7 +43,8 @@ type ObjectMover interface {
4243

4344
// objectMover implements the ObjectMover interface.
4445
type objectMover struct {
45-
fromProxy Proxy
46+
fromProxy Proxy
47+
fromProviderInventory InventoryClient
4648
}
4749

4850
// ensure objectMover implements the ObjectMover interface.
@@ -54,7 +56,10 @@ func (o *objectMover) Move(namespace string, toCluster Client) error {
5456

5557
objectGraph := newObjectGraph(o.fromProxy)
5658

57-
//TODO: implement preflight checks ensuring the target cluster has all the required providers in place
59+
// checks that all the required providers in place in the target cluster.
60+
if err := o.checkTargetProviders(namespace, toCluster.ProviderInventory()); err != nil {
61+
return err
62+
}
5863

5964
// Gets all the types defines by the CRDs installed by clusterctl plus the ConfigMap/Secret core types.
6065
types, err := objectGraph.getDiscoveryTypes()
@@ -86,9 +91,10 @@ func (o *objectMover) Move(namespace string, toCluster Client) error {
8691
return nil
8792
}
8893

89-
func newObjectMover(fromProxy Proxy) *objectMover {
94+
func newObjectMover(fromProxy Proxy, fromProviderInventory InventoryClient) *objectMover {
9095
return &objectMover{
91-
fromProxy: fromProxy,
96+
fromProxy: fromProxy,
97+
fromProviderInventory: fromProviderInventory,
9298
}
9399
}
94100

@@ -594,3 +600,75 @@ func retry(attempts int, interval time.Duration, action func() error) error {
594600
}
595601
return errors.Wrapf(errorToReturn, "action failed after %d attempts", attempts)
596602
}
603+
604+
// checkTargetProviders checks that all the providers installed in the source cluster exists in the target cluster as well (with a version >= of the current version).
605+
func (o *objectMover) checkTargetProviders(namespace string, toInventory InventoryClient) error {
606+
// Gets the list of providers in the source/target cluster.
607+
fromProviders, err := o.fromProviderInventory.List()
608+
if err != nil {
609+
return errors.Wrapf(err, "failed to get provider list from the source cluster")
610+
}
611+
612+
toProviders, err := toInventory.List()
613+
if err != nil {
614+
return errors.Wrapf(err, "failed to get provider list from the target cluster")
615+
}
616+
617+
// Checks all the providers installed in the source cluster
618+
errList := []error{}
619+
for _, sourceProvider := range fromProviders.Items {
620+
// If we are moving objects in a namespace only, skip all the providers not watching such namespace.
621+
if namespace != "" && !(sourceProvider.WatchedNamespace == "" || sourceProvider.WatchedNamespace == namespace) {
622+
continue
623+
}
624+
625+
sourceVersion, err := version.ParseSemantic(sourceProvider.Version)
626+
if err != nil {
627+
return errors.Wrapf(err, "unable to parse version %q for the %s provider in the source cluster", sourceProvider.Version, sourceProvider.InstanceName())
628+
}
629+
630+
// Check corresponding providers in the target cluster and gets the latest version installed.
631+
var maxTargetVersion *version.Version
632+
for _, targetProvider := range toProviders.Items {
633+
// Skips other providers.
634+
if sourceProvider.Name != targetProvider.Name {
635+
continue
636+
}
637+
638+
// If we are moving objects in all the namespaces, skip all the providers with a different watching namespace.
639+
// NB. This introduces a constraints for move all namespaces, that the configuration of source and target provider MUST match (except for the version);
640+
// however this is acceptable because clusterctl supports only two models of multi-tenancy (n-Infra, n-Core).
641+
if namespace == "" && !(targetProvider.WatchedNamespace == sourceProvider.WatchedNamespace) {
642+
continue
643+
}
644+
645+
// If we are moving objects in a namespace only, skip all the providers not watching such namespace.
646+
// NB. This means that when moving a single namespace, we use a lazy matching (the watching namespace MUST overlap; exact match is not required).
647+
if namespace != "" && !(targetProvider.WatchedNamespace == "" || targetProvider.WatchedNamespace == namespace) {
648+
continue
649+
}
650+
651+
targetVersion, err := version.ParseSemantic(targetProvider.Version)
652+
if err != nil {
653+
return errors.Wrapf(err, "unable to parse version %q for the %s provider in the target cluster", targetProvider.Version, targetProvider.InstanceName())
654+
}
655+
if maxTargetVersion == nil || maxTargetVersion.LessThan(targetVersion) {
656+
maxTargetVersion = targetVersion
657+
}
658+
}
659+
if maxTargetVersion == nil {
660+
watching := sourceProvider.WatchedNamespace
661+
if namespace != "" {
662+
watching = namespace
663+
}
664+
errList = append(errList, errors.Errorf("provider %s watching namespace %s not found in the target cluster", sourceProvider.Name, watching))
665+
continue
666+
}
667+
668+
if !maxTargetVersion.AtLeast(sourceVersion) {
669+
errList = append(errList, errors.Errorf("provider %s in the target cluster is older than in the source cluster (source: %s, target: %s)", sourceProvider.Name, sourceVersion.String(), maxTargetVersion.String()))
670+
}
671+
}
672+
673+
return kerrors.NewAggregate(errList)
674+
}

cmd/clusterctl/pkg/client/cluster/mover_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2828
"k8s.io/apimachinery/pkg/runtime"
2929
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
30+
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
3031
"sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/internal/test"
3132
"sigs.k8s.io/controller-runtime/pkg/client"
3233
)
@@ -670,3 +671,128 @@ func Test_objectMover_checkProvisioningCompleted(t *testing.T) {
670671
})
671672
}
672673
}
674+
675+
func Test_objectsMoverService_checkTargetProviders(t *testing.T) {
676+
type fields struct {
677+
fromProxy Proxy
678+
}
679+
type args struct {
680+
toProxy Proxy
681+
namespace string
682+
}
683+
tests := []struct {
684+
name string
685+
fields fields
686+
args args
687+
wantErr bool
688+
}{
689+
{
690+
name: "move objects in single namespace, all the providers in place (lazy matching)",
691+
fields: fields{
692+
fromProxy: test.NewFakeProxy().
693+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "").
694+
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "").
695+
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", ""),
696+
},
697+
args: args{
698+
namespace: "ns1", // a single namespaces
699+
toProxy: test.NewFakeProxy().
700+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "ns1").
701+
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "ns1").
702+
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", "ns1"),
703+
},
704+
wantErr: false,
705+
},
706+
{
707+
name: "move objects in single namespace, all the providers in place but with a newer version (lazy matching)",
708+
fields: fields{
709+
fromProxy: test.NewFakeProxy().
710+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
711+
},
712+
args: args{
713+
namespace: "ns1", // a single namespaces
714+
toProxy: test.NewFakeProxy().
715+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.1.0", "capi-system", "ns1"), // Lazy matching
716+
},
717+
wantErr: false,
718+
},
719+
{
720+
name: "move objects in all namespaces, all the providers in place (exact matching)",
721+
fields: fields{
722+
fromProxy: test.NewFakeProxy().
723+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "").
724+
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "").
725+
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", ""),
726+
},
727+
args: args{
728+
namespace: "", // all namespaces
729+
toProxy: test.NewFakeProxy().
730+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "").
731+
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "").
732+
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", ""),
733+
},
734+
wantErr: false,
735+
},
736+
{
737+
name: "move objects in all namespaces, all the providers in place but with a newer version (exact matching)",
738+
fields: fields{
739+
fromProxy: test.NewFakeProxy().
740+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
741+
},
742+
args: args{
743+
namespace: "", // all namespaces
744+
toProxy: test.NewFakeProxy().
745+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.1.0", "capi-system", ""),
746+
},
747+
wantErr: false,
748+
},
749+
{
750+
name: "move objects in all namespaces, not exact matching",
751+
fields: fields{
752+
fromProxy: test.NewFakeProxy().
753+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
754+
},
755+
args: args{
756+
namespace: "", // all namespaces
757+
toProxy: test.NewFakeProxy().
758+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.1.0", "capi-system", "ns1"), // Lazy matching only
759+
},
760+
wantErr: true,
761+
},
762+
{
763+
name: "fails if a provider is missing",
764+
fields: fields{
765+
fromProxy: test.NewFakeProxy().
766+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
767+
},
768+
args: args{
769+
namespace: "", // all namespaces
770+
toProxy: test.NewFakeProxy(),
771+
},
772+
wantErr: true,
773+
},
774+
{
775+
name: "fails if a provider version is older than expected",
776+
fields: fields{
777+
fromProxy: test.NewFakeProxy().
778+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
779+
},
780+
args: args{
781+
namespace: "", // all namespaces
782+
toProxy: test.NewFakeProxy().
783+
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi1-system", ""),
784+
},
785+
wantErr: true,
786+
},
787+
}
788+
for _, tt := range tests {
789+
t.Run(tt.name, func(t *testing.T) {
790+
o := &objectMover{
791+
fromProviderInventory: newInventoryClient(tt.fields.fromProxy, nil),
792+
}
793+
if err := o.checkTargetProviders(tt.args.namespace, newInventoryClient(tt.args.toProxy, nil)); (err != nil) != tt.wantErr {
794+
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
795+
}
796+
})
797+
}
798+
}

0 commit comments

Comments
 (0)