Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ support provider downgrades #107

Merged
merged 1 commit into from
May 15, 2023
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
145 changes: 136 additions & 9 deletions internal/controller/genericprovider_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ spec:
requests:
cpu: 200m
`

testCurrentVersion = "v0.4.2"
)

func insertDummyConfig(provider genericprovider.GenericProvider) {
Expand All @@ -85,10 +87,10 @@ func insertDummyConfig(provider genericprovider.GenericProvider) {
provider.SetSpec(spec)
}

func dummyConfigMap(ns string) *corev1.ConfigMap {
func dummyConfigMap(ns, name string) *corev1.ConfigMap {
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "v0.4.2",
Name: name,
Namespace: ns,
Labels: map[string]string{
"test": "dummy-config",
Expand Down Expand Up @@ -118,7 +120,7 @@ func TestReconcilerPreflightConditions(t *testing.T) {
},
Spec: operatorv1.CoreProviderSpec{
ProviderSpec: operatorv1.ProviderSpec{
Version: "v0.4.2",
Version: testCurrentVersion,
},
},
},
Expand All @@ -136,7 +138,7 @@ func TestReconcilerPreflightConditions(t *testing.T) {
},
Spec: operatorv1.CoreProviderSpec{
ProviderSpec: operatorv1.ProviderSpec{
Version: "v0.4.2",
Version: testCurrentVersion,
},
},
Status: operatorv1.CoreProviderStatus{
Expand All @@ -158,7 +160,7 @@ func TestReconcilerPreflightConditions(t *testing.T) {
},
Spec: operatorv1.ControlPlaneProviderSpec{
ProviderSpec: operatorv1.ProviderSpec{
Version: "v0.4.2",
Version: testCurrentVersion,
},
},
},
Expand All @@ -171,10 +173,10 @@ func TestReconcilerPreflightConditions(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)

t.Log("creating namespace", tc.namespace)
namespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: tc.namespace}}
g.Expect(env.CreateAndWait(ctx, namespace)).To(Succeed())
g.Expect(env.CreateAndWait(ctx, dummyConfigMap(tc.namespace))).To(Succeed())
t.Log("Ensure namespace exists", tc.namespace)
g.Expect(env.EnsureNamespaceExists(ctx, tc.namespace)).To(Succeed())

g.Expect(env.CreateAndWait(ctx, dummyConfigMap(tc.namespace, testCurrentVersion))).To(Succeed())

for _, p := range tc.providers {
insertDummyConfig(p)
Expand Down Expand Up @@ -206,6 +208,131 @@ func TestReconcilerPreflightConditions(t *testing.T) {
for _, p := range tc.providers {
objs = append(objs, p.GetObject())
}

objs = append(objs, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: testCurrentVersion,
Namespace: tc.namespace,
},
})

g.Expect(env.CleanupAndWait(ctx, objs...)).To(Succeed())
})
}
}

func TestUpgradeDowngradeProvider(t *testing.T) {
testCases := []struct {
name string
namespace string
newVersion string
}{
{
name: "upgrade provider version",
namespace: "test-core-provider",
newVersion: "v0.4.3",
},
{
name: "downgrade provider version",
namespace: "test-core-provider",
newVersion: "v0.4.1",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)

provider := &genericprovider.CoreProviderWrapper{
CoreProvider: &operatorv1.CoreProvider{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-api",
},
Spec: operatorv1.CoreProviderSpec{
ProviderSpec: operatorv1.ProviderSpec{
Version: testCurrentVersion,
},
},
},
}

t.Log("Ensure namespace exists", tc.namespace)
g.Expect(env.EnsureNamespaceExists(ctx, tc.namespace)).To(Succeed())

g.Expect(env.CreateAndWait(ctx, dummyConfigMap(tc.namespace, testCurrentVersion))).To(Succeed())

insertDummyConfig(provider)
provider.SetNamespace(tc.namespace)
t.Log("creating test provider", provider.GetName())
g.Expect(env.CreateAndWait(ctx, provider.GetObject())).To(Succeed())

g.Eventually(func() bool {
if err := env.Get(ctx, client.ObjectKeyFromObject(provider.GetObject()), provider.GetObject()); err != nil {
return false
}

if provider.GetStatus().InstalledVersion == nil || *provider.GetStatus().InstalledVersion != testCurrentVersion {
return false
}

for _, cond := range provider.GetStatus().Conditions {
if cond.Type == operatorv1.PreflightCheckCondition {
t.Log(t.Name(), provider.GetName(), cond)
if cond.Status == corev1.ConditionTrue {
return true
}
}
}

return false
}, timeout).Should(BeEquivalentTo(true))

// creating another configmap with another version
g.Expect(env.CreateAndWait(ctx, dummyConfigMap(tc.namespace, tc.newVersion))).To(Succeed())

// Change provider version
providerSpec := provider.GetSpec()
providerSpec.Version = tc.newVersion
provider.SetSpec(providerSpec)
g.Expect(env.Client.Update(ctx, provider.GetObject())).To(Succeed())

g.Eventually(func() bool {
if err := env.Get(ctx, client.ObjectKeyFromObject(provider.GetObject()), provider.GetObject()); err != nil {
return false
}

if provider.GetStatus().InstalledVersion == nil || *provider.GetStatus().InstalledVersion != tc.newVersion {
return false
}

for _, cond := range provider.GetStatus().Conditions {
if cond.Type == operatorv1.PreflightCheckCondition {
t.Log(t.Name(), provider.GetName(), cond)
if cond.Status == corev1.ConditionTrue {
return true
}
}
}

return false
}, timeout).Should(BeEquivalentTo(true))

// Clean up
objs := []client.Object{provider.GetObject()}
objs = append(objs, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: testCurrentVersion,
Namespace: tc.namespace,
},
})

objs = append(objs, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: tc.newVersion,
Namespace: tc.namespace,
},
})

g.Expect(env.CleanupAndWait(ctx, objs...)).To(Succeed())
})
}
Expand Down
9 changes: 6 additions & 3 deletions internal/controller/phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,17 +351,20 @@ func (s *phaseReconciler) updateRequiresPreDeletion() (bool, error) {
return false, nil
}

nextVersion, err := versionutil.ParseSemantic(s.components.Version())
currentVersion, err := versionutil.ParseSemantic(*installedVersion)
if err != nil {
return false, err
}

currentVersion, err := versionutil.ParseSemantic(*installedVersion)
res, err := currentVersion.Compare(s.components.Version())
if err != nil {
return false, err
}

return currentVersion.LessThan(nextVersion), nil
// we need to delete installed components if versions are different
needPreDelete := res != 0

return needPreDelete, nil
}

// install installs the provider components using clusterctl library.
Expand Down
27 changes: 27 additions & 0 deletions internal/envtest/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,33 @@ func (e *Environment) CreateNamespace(ctx context.Context, generateName string)
return ns, nil
}

func (e *Environment) EnsureNamespaceExists(ctx context.Context, namespace string) error {
// Check if the namespace exists
ns := &corev1.Namespace{}

err := e.Client.Get(ctx, client.ObjectKey{Name: namespace}, ns)
if err == nil {
return nil
}

if !apierrors.IsNotFound(err) {
return fmt.Errorf("unexpected error during namespace checking: %w", err)
}

// Create the namespace if it doesn't exist
newNamespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}

if err := e.Client.Create(ctx, newNamespace); err != nil {
return fmt.Errorf("unable to create namespace %s: %w", namespace, err)
}

return nil
}

func getFilePathToClusterctlCRDs(root string) string {
if clusterctlCRDPath := os.Getenv("CLUSTERCTL_CRD_PATH"); clusterctlCRDPath != "" {
return clusterctlCRDPath
Expand Down