Skip to content

Commit

Permalink
Merge pull request #5271 from fabriziopandini/clusterctl-support-v1al…
Browse files Browse the repository at this point in the history
…pha3-v1beta1-upgrades

✨ Clusterctl: add support for v1alpha3 to v1beta1 upgrades
  • Loading branch information
k8s-ci-robot authored Sep 22, 2021
2 parents 3a975a3 + 89df947 commit 5330150
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 36 deletions.
13 changes: 9 additions & 4 deletions cmd/clusterctl/client/cluster/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ type CheckCAPIContractOptions struct {
// AllowCAPINotInstalled instructs CheckCAPIContract to tolerate management clusters without Cluster API installed yet.
AllowCAPINotInstalled bool

// AllowCAPIContract instructs CheckCAPIContract to tolerate management clusters with Cluster API with the given contract.
AllowCAPIContract string
// AllowCAPIContracts instructs CheckCAPIContract to tolerate management clusters with Cluster API with the given contract.
AllowCAPIContracts []string
}

// AllowCAPINotInstalled instructs CheckCAPIContract to tolerate management clusters without Cluster API installed yet.
Expand All @@ -72,7 +72,7 @@ type AllowCAPIContract struct {

// Apply applies this configuration to the given CheckCAPIContractOptions.
func (t AllowCAPIContract) Apply(in *CheckCAPIContractOptions) {
in.AllowCAPIContract = t.Contract
in.AllowCAPIContracts = append(in.AllowCAPIContracts, t.Contract)
}

// InventoryClient exposes methods to interface with a cluster's provider inventory.
Expand Down Expand Up @@ -397,9 +397,14 @@ func (p *inventoryClient) CheckCAPIContract(options ...CheckCAPIContractOption)

for _, version := range crd.Spec.Versions {
if version.Storage {
if version.Name == clusterv1.GroupVersion.Version || version.Name == opt.AllowCAPIContract {
if version.Name == clusterv1.GroupVersion.Version {
return nil
}
for _, allowedContract := range opt.AllowCAPIContracts {
if version.Name == allowedContract {
return nil
}
}
return errors.Errorf("this version of clusterctl could be used only with %q management clusters, %q detected", clusterv1.GroupVersion.Version, version.Name)
}
}
Expand Down
23 changes: 22 additions & 1 deletion cmd/clusterctl/client/cluster/inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
clusterv1alpha3 "sigs.k8s.io/cluster-api/api/v1alpha3"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -276,6 +277,26 @@ func Test_CheckCAPIContract(t *testing.T) {
args: args{},
wantErr: true,
},
{
name: "Pass when Cluster API with v1alpha3 contract is installed, but this is explicitly tolerated",
fields: fields{
proxy: test.NewFakeProxy().WithObjs(&apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "clusters.cluster.x-k8s.io"},
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
{
Name: clusterv1alpha3.GroupVersion.Version,
Storage: true,
},
},
},
}),
},
args: args{
options: []CheckCAPIContractOption{AllowCAPIContract{Contract: clusterv1alpha3.GroupVersion.Version}, AllowCAPIContract{Contract: test.PreviousCAPIContractNotSupported}},
},
wantErr: false,
},
{
name: "Pass when Cluster API with previous contract is installed, but this is explicitly tolerated",
fields: fields{
Expand All @@ -295,7 +316,7 @@ func Test_CheckCAPIContract(t *testing.T) {
}),
},
args: args{
options: []CheckCAPIContractOption{AllowCAPIContract{Contract: test.PreviousCAPIContractNotSupported}},
options: []CheckCAPIContractOption{AllowCAPIContract{Contract: clusterv1alpha3.GroupVersion.Version}, AllowCAPIContract{Contract: test.PreviousCAPIContractNotSupported}},
},
wantErr: false,
},
Expand Down
83 changes: 80 additions & 3 deletions cmd/clusterctl/client/cluster/upgrader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ package cluster
import (
"testing"

"github.com/google/go-cmp/cmp"
. "github.com/onsi/gomega"

clusterv1v1alpha3 "sigs.k8s.io/cluster-api/api/v1alpha3"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
Expand Down Expand Up @@ -135,6 +137,81 @@ func Test_providerUpgrader_Plan(t *testing.T) {
},
wantErr: false,
},
{
name: "Upgrade for v1alpha3 (not supported), previous contract (not supported), current contract", // upgrade plan should report unsupported options
fields: fields{
// config for two providers
reader: test.NewFakeReader().
WithProvider("cluster-api", clusterctlv1.CoreProviderType, "https://somewhere.com").
WithProvider("infra", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
repository: map[string]repository.Repository{
"cluster-api": repository.NewMemoryRepository().
WithVersions("v1.0.0", "v1.0.1", "v2.0.0", "v3.0.0").
WithMetadata("v3.0.0", &clusterctlv1.Metadata{
ReleaseSeries: []clusterctlv1.ReleaseSeries{
{Major: 1, Minor: 0, Contract: clusterv1v1alpha3.GroupVersion.Version},
{Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported},
{Major: 3, Minor: 0, Contract: test.CurrentCAPIContract},
},
}),
"infrastructure-infra": repository.NewMemoryRepository().
WithVersions("v1.0.0", "v2.0.0", "v2.0.1", "v3.0.0").
WithMetadata("v3.0.0", &clusterctlv1.Metadata{
ReleaseSeries: []clusterctlv1.ReleaseSeries{
{Major: 1, Minor: 0, Contract: clusterv1v1alpha3.GroupVersion.Version},
{Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported},
{Major: 3, Minor: 0, Contract: test.CurrentCAPIContract},
},
}),
},
// two providers existing in the cluster
proxy: test.NewFakeProxy().
WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system").
WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra-system"),
},
want: []UpgradePlan{
{ // one upgrade plan with the latest releases in the v1alpha3 contract (not supported, but upgrade plan should report these options)
Contract: clusterv1v1alpha3.GroupVersion.Version,
Providers: []UpgradeItem{
{
Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"),
NextVersion: "v1.0.1",
},
{
Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra-system"),
NextVersion: "",
},
},
},
{ // one upgrade plan with the latest releases in the previous contract (not supported, but upgrade plan should report these options)
Contract: test.PreviousCAPIContractNotSupported,
Providers: []UpgradeItem{
{
Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"),
NextVersion: "v2.0.0",
},
{
Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra-system"),
NextVersion: "v2.0.1",
},
},
},
{ // one upgrade plan with the latest releases in the current contract
Contract: test.CurrentCAPIContract,
Providers: []UpgradeItem{
{
Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"),
NextVersion: "v3.0.0",
},
{
Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra-system"),
NextVersion: "v3.0.0",
},
},
},
},
wantErr: false,
},
{
name: "Upgrade for previous contract (not supported), current contract", // upgrade plan should report unsupported options
fields: fields{
Expand Down Expand Up @@ -323,7 +400,7 @@ func Test_providerUpgrader_Plan(t *testing.T) {
}

g.Expect(err).NotTo(HaveOccurred())
g.Expect(got).To(Equal(tt.want))
g.Expect(got).To(Equal(tt.want), cmp.Diff(got, tt.want))
})
}
}
Expand Down Expand Up @@ -813,7 +890,7 @@ func Test_providerUpgrader_ApplyPlan(t *testing.T) {
errorMsg string
}{
{
name: "fails to upgrade to v1alpha4 when there are multiple instances of the core provider",
name: "fails to upgrade to current contract when there are multiple instances of the core provider",
fields: fields{
// config for two providers
reader: test.NewFakeReader().
Expand Down Expand Up @@ -849,7 +926,7 @@ func Test_providerUpgrader_ApplyPlan(t *testing.T) {
errorMsg: "detected multiple instances of the same provider",
},
{
name: "fails to upgrade to v1alpha4 when there are multiple instances of the infra provider",
name: "fails to upgrade to current contract when there are multiple instances of the infra provider",
fields: fields{
// config for two providers
reader: test.NewFakeReader().
Expand Down
17 changes: 14 additions & 3 deletions cmd/clusterctl/client/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import (

"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1old "sigs.k8s.io/cluster-api/api/v1alpha3"
clusterv1alpha3 "sigs.k8s.io/cluster-api/api/v1alpha3"
clusterv1old "sigs.k8s.io/cluster-api/api/v1alpha4"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
Expand Down Expand Up @@ -53,7 +54,12 @@ func (c *clusterctlClient) PlanUpgrade(options PlanUpgradeOptions) ([]UpgradePla
}

// Ensure this command only runs against management clusters with the current Cluster API contract (default) or the previous one.
if err := clusterClient.ProviderInventory().CheckCAPIContract(cluster.AllowCAPIContract{Contract: clusterv1old.GroupVersion.Version}); err != nil {
// NOTE: given that v1beta1 (current) and v1alpha4 (previous) does not have breaking changes, we support also upgrades from v1alpha3 to v1beta1;
// this is an exception and support for skipping releases should be removed in future releases.
if err := clusterClient.ProviderInventory().CheckCAPIContract(
cluster.AllowCAPIContract{Contract: clusterv1alpha3.GroupVersion.Version},
cluster.AllowCAPIContract{Contract: clusterv1old.GroupVersion.Version},
); err != nil {
return nil, err
}

Expand Down Expand Up @@ -114,7 +120,12 @@ func (c *clusterctlClient) ApplyUpgrade(options ApplyUpgradeOptions) error {
}

// Ensure this command only runs against management clusters with the current Cluster API contract (default) or the previous one.
if err := clusterClient.ProviderInventory().CheckCAPIContract(cluster.AllowCAPIContract{Contract: clusterv1old.GroupVersion.Version}); err != nil {
// NOTE: given that v1beta1 (current) and v1alpha4 (previous) does not have breaking changes, we support also upgrades from v1alpha3 to v1beta1;
// this is an exception and support for skipping releases should be removed in future releases.
if err := clusterClient.ProviderInventory().CheckCAPIContract(
cluster.AllowCAPIContract{Contract: clusterv1alpha3.GroupVersion.Version},
cluster.AllowCAPIContract{Contract: clusterv1old.GroupVersion.Version},
); err != nil {
return err
}

Expand Down
18 changes: 14 additions & 4 deletions test/e2e/clusterctl_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (

const (
initWithBinaryVariableName = "INIT_WITH_BINARY"
initWithProvidersContract = "INIT_WITH_PROVIDERS_CONTRACT"
initWithKubernetesVersion = "INIT_WITH_KUBERNETES_VERSION"
)

Expand Down Expand Up @@ -155,14 +156,23 @@ func ClusterctlUpgradeSpec(ctx context.Context, inputGetter func() ClusterctlUpg
Expect(err).ToNot(HaveOccurred(), "failed to chmod temporary file")

By("Initializing the workload cluster with older versions of providers")

// NOTE: by default we are considering all the providers, no matter of the contract.
// However, given that we want to test both v1alpha3 --> v1beta1 and v1alpha4 --> v1beta1, the INIT_WITH_PROVIDERS_CONTRACT
// variable can be used to select versions with a specific contract.
contract := "*"
if input.E2EConfig.HasVariable(initWithProvidersContract) {
contract = input.E2EConfig.GetVariable(initWithProvidersContract)
}

clusterctl.InitManagementClusterAndWatchControllerLogs(ctx, clusterctl.InitManagementClusterAndWatchControllerLogsInput{
ClusterctlBinaryPath: clusterctlBinaryPath, // use older version of clusterctl to init the management cluster
ClusterProxy: managementClusterProxy,
ClusterctlConfigPath: input.ClusterctlConfigPath,
CoreProvider: input.E2EConfig.GetProvidersWithOldestVersion(config.ClusterAPIProviderName)[0],
BootstrapProviders: input.E2EConfig.GetProvidersWithOldestVersion(config.KubeadmBootstrapProviderName),
ControlPlaneProviders: input.E2EConfig.GetProvidersWithOldestVersion(config.KubeadmControlPlaneProviderName),
InfrastructureProviders: input.E2EConfig.GetProvidersWithOldestVersion(input.E2EConfig.InfrastructureProviders()...),
CoreProvider: input.E2EConfig.GetProviderLatestVersionsByContract(contract, config.ClusterAPIProviderName)[0],
BootstrapProviders: input.E2EConfig.GetProviderLatestVersionsByContract(contract, config.KubeadmBootstrapProviderName),
ControlPlaneProviders: input.E2EConfig.GetProviderLatestVersionsByContract(contract, config.KubeadmControlPlaneProviderName),
InfrastructureProviders: input.E2EConfig.GetProviderLatestVersionsByContract(contract, input.E2EConfig.InfrastructureProviders()...),
LogFolder: filepath.Join(input.ArtifactFolder, "clusters", cluster.Name),
}, input.E2EConfig.GetIntervals(specName, "wait-controllers")...)

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/clusterctl_upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
. "github.com/onsi/ginkgo"
)

var _ = Describe("When testing clusterctl upgrades", func() {
var _ = Describe("When testing clusterctl upgrades [clusterctl-Upgrade]", func() {

ClusterctlUpgradeSpec(ctx, func() ClusterctlUpgradeSpecInput {
return ClusterctlUpgradeSpecInput{
Expand Down
Loading

0 comments on commit 5330150

Please sign in to comment.