Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/clusterctl/pkg/client/cluster/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (c *clusterClient) ProviderInstaller() ProviderInstaller {
}

func (c *clusterClient) ObjectMover() ObjectMover {
return newObjectMover(c.proxy)
return newObjectMover(c.proxy, c.ProviderInventory())
}

func (c *clusterClient) ProviderUpgrader() ProviderUpgrader {
Expand Down
86 changes: 82 additions & 4 deletions cmd/clusterctl/pkg/client/cluster/mover.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/types"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/apimachinery/pkg/util/wait"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/log"
Expand All @@ -42,7 +43,8 @@ type ObjectMover interface {

// objectMover implements the ObjectMover interface.
type objectMover struct {
fromProxy Proxy
fromProxy Proxy
fromProviderInventory InventoryClient
}

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

objectGraph := newObjectGraph(o.fromProxy)

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

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

func newObjectMover(fromProxy Proxy) *objectMover {
func newObjectMover(fromProxy Proxy, fromProviderInventory InventoryClient) *objectMover {
return &objectMover{
fromProxy: fromProxy,
fromProxy: fromProxy,
fromProviderInventory: fromProviderInventory,
}
}

Expand Down Expand Up @@ -594,3 +600,75 @@ func retry(attempts int, interval time.Duration, action func() error) error {
}
return errors.Wrapf(errorToReturn, "action failed after %d attempts", attempts)
}

// 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).
func (o *objectMover) checkTargetProviders(namespace string, toInventory InventoryClient) error {
// Gets the list of providers in the source/target cluster.
fromProviders, err := o.fromProviderInventory.List()
if err != nil {
return errors.Wrapf(err, "failed to get provider list from the source cluster")
}

toProviders, err := toInventory.List()
if err != nil {
return errors.Wrapf(err, "failed to get provider list from the target cluster")
}

// Checks all the providers installed in the source cluster
errList := []error{}
for _, sourceProvider := range fromProviders.Items {
// If we are moving objects in a namespace only, skip all the providers not watching such namespace.
if namespace != "" && !(sourceProvider.WatchedNamespace == "" || sourceProvider.WatchedNamespace == namespace) {
continue
}

sourceVersion, err := version.ParseSemantic(sourceProvider.Version)
if err != nil {
return errors.Wrapf(err, "unable to parse version %q for the %s provider in the source cluster", sourceProvider.Version, sourceProvider.InstanceName())
}

// Check corresponding providers in the target cluster and gets the latest version installed.
var maxTargetVersion *version.Version
for _, targetProvider := range toProviders.Items {
// Skips other providers.
if sourceProvider.Name != targetProvider.Name {
continue
}

// If we are moving objects in all the namespaces, skip all the providers with a different watching namespace.
// NB. This introduces a constraints for move all namespaces, that the configuration of source and target provider MUST match (except for the version);
// however this is acceptable because clusterctl supports only two models of multi-tenancy (n-Infra, n-Core).
if namespace == "" && !(targetProvider.WatchedNamespace == sourceProvider.WatchedNamespace) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if namespace == "" && !(targetProvider.WatchedNamespace == sourceProvider.WatchedNamespace) {
if namespace == "" && targetProvider.WatchedNamespace != sourceProvider.WatchedNamespace {

continue
}

// If we are moving objects in a namespace only, skip all the providers not watching such namespace.
// NB. This means that when moving a single namespace, we use a lazy matching (the watching namespace MUST overlap; exact match is not required).
if namespace != "" && !(targetProvider.WatchedNamespace == "" || targetProvider.WatchedNamespace == namespace) {
continue
}

targetVersion, err := version.ParseSemantic(targetProvider.Version)
if err != nil {
return errors.Wrapf(err, "unable to parse version %q for the %s provider in the target cluster", targetProvider.Version, targetProvider.InstanceName())
}
if maxTargetVersion == nil || maxTargetVersion.LessThan(targetVersion) {
maxTargetVersion = targetVersion
}
}
if maxTargetVersion == nil {
watching := sourceProvider.WatchedNamespace
if namespace != "" {
watching = namespace
}
errList = append(errList, errors.Errorf("provider %s watching namespace %s not found in the target cluster", sourceProvider.Name, watching))
continue
}

if !maxTargetVersion.AtLeast(sourceVersion) {
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()))
}
}

return kerrors.NewAggregate(errList)
}
126 changes: 126 additions & 0 deletions cmd/clusterctl/pkg/client/cluster/mover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/internal/test"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -670,3 +671,128 @@ func Test_objectMover_checkProvisioningCompleted(t *testing.T) {
})
}
}

func Test_objectsMoverService_checkTargetProviders(t *testing.T) {
type fields struct {
fromProxy Proxy
}
type args struct {
toProxy Proxy
namespace string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "move objects in single namespace, all the providers in place (lazy matching)",
fields: fields{
fromProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "").
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "").
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", ""),
},
args: args{
namespace: "ns1", // a single namespaces
toProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "ns1").
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "ns1").
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", "ns1"),
},
wantErr: false,
},
{
name: "move objects in single namespace, all the providers in place but with a newer version (lazy matching)",
fields: fields{
fromProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
},
args: args{
namespace: "ns1", // a single namespaces
toProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.1.0", "capi-system", "ns1"), // Lazy matching
},
wantErr: false,
},
{
name: "move objects in all namespaces, all the providers in place (exact matching)",
fields: fields{
fromProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "").
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "").
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", ""),
},
args: args{
namespace: "", // all namespaces
toProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", "").
WithProviderInventory("kubeadm", clusterctlv1.BootstrapProviderType, "v1.0.0", "cabpk-system", "").
WithProviderInventory("capa", clusterctlv1.InfrastructureProviderType, "v1.0.0", "capa-system", ""),
},
wantErr: false,
},
{
name: "move objects in all namespaces, all the providers in place but with a newer version (exact matching)",
fields: fields{
fromProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
},
args: args{
namespace: "", // all namespaces
toProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.1.0", "capi-system", ""),
},
wantErr: false,
},
{
name: "move objects in all namespaces, not exact matching",
fields: fields{
fromProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
},
args: args{
namespace: "", // all namespaces
toProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.1.0", "capi-system", "ns1"), // Lazy matching only
},
wantErr: true,
},
{
name: "fails if a provider is missing",
fields: fields{
fromProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
},
args: args{
namespace: "", // all namespaces
toProxy: test.NewFakeProxy(),
},
wantErr: true,
},
{
name: "fails if a provider version is older than expected",
fields: fields{
fromProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v2.0.0", "capi-system", ""),
},
args: args{
namespace: "", // all namespaces
toProxy: test.NewFakeProxy().
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi1-system", ""),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi1-system", ""),
WithProviderInventory("capi", clusterctlv1.CoreProviderType, "v1.0.0", "capi-system", ""),

},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &objectMover{
fromProviderInventory: newInventoryClient(tt.fields.fromProxy, nil),
}
if err := o.checkTargetProviders(tt.args.namespace, newInventoryClient(tt.args.toProxy, nil)); (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}