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

🐛 Avoid failures on unchanged provider version #108

Merged
merged 2 commits into from
May 22, 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
38 changes: 28 additions & 10 deletions internal/controller/genericprovider_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,18 @@ func TestReconcilerPreflightConditions(t *testing.T) {
func TestUpgradeDowngradeProvider(t *testing.T) {
testCases := []struct {
name string
namespace string
newVersion string
}{
{
name: "same provider version",
newVersion: "v0.4.2",
},
{
name: "upgrade provider version",
namespace: "test-core-provider",
newVersion: "v0.4.3",
},
{
name: "downgrade provider version",
namespace: "test-core-provider",
newVersion: "v0.4.1",
},
}
Expand All @@ -256,13 +257,15 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
},
}

t.Log("Ensure namespace exists", tc.namespace)
g.Expect(env.EnsureNamespaceExists(ctx, tc.namespace)).To(Succeed())
namespace := "test-upgrades-downgrades"

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

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

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

Expand All @@ -288,12 +291,23 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
}, timeout).Should(BeEquivalentTo(true))

// creating another configmap with another version
g.Expect(env.CreateAndWait(ctx, dummyConfigMap(tc.namespace, tc.newVersion))).To(Succeed())
if tc.newVersion != testCurrentVersion {
g.Expect(env.CreateAndWait(ctx, dummyConfigMap(namespace, tc.newVersion))).To(Succeed())
}

// Change provider version
providerSpec := provider.GetSpec()
providerSpec.Version = tc.newVersion
provider.SetSpec(providerSpec)

// Set label (needed to start a reconciliation of the provider)
labels := provider.GetLabels()
if labels == nil {
labels = map[string]string{}
}
labels["provider-version"] = tc.newVersion
provider.SetLabels(labels)

g.Expect(env.Client.Update(ctx, provider.GetObject())).To(Succeed())

g.Eventually(func() bool {
Expand All @@ -305,6 +319,10 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
return false
}

if provider.GetLabels()["provider-version"] != tc.newVersion {
return false
}

for _, cond := range provider.GetStatus().Conditions {
if cond.Type == operatorv1.PreflightCheckCondition {
t.Log(t.Name(), provider.GetName(), cond)
Expand All @@ -322,14 +340,14 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
objs = append(objs, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: testCurrentVersion,
Namespace: tc.namespace,
Namespace: namespace,
},
})

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

Expand Down
43 changes: 29 additions & 14 deletions internal/controller/phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,43 +334,58 @@ func (p *phaseReconciler) fetch(ctx context.Context) (reconcile.Result, error) {
func (p *phaseReconciler) preInstall(ctx context.Context) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)

needPreDelete, err := p.updateRequiresPreDeletion()
if err != nil || !needPreDelete {
return reconcile.Result{}, wrapPhaseError(err, "failed getting clusterctl Provider", operatorv1.ProviderInstalledCondition)
needPreDelete, err := p.versionChanged()
if err != nil {
return reconcile.Result{}, wrapPhaseError(err, "failed getting clusterctl Provider version", operatorv1.ProviderInstalledCondition)
}

// we need to delete existing components only if their version changes and something has already been installed.
if !needPreDelete || p.provider.GetStatus().InstalledVersion == nil {
return reconcile.Result{}, nil
}

log.Info("Upgrade detected, deleting existing components")

return p.delete(ctx)
}

// updateRequiresPreDeletion try to get installed version from provider status and decide if it's an upgrade.
func (s *phaseReconciler) updateRequiresPreDeletion() (bool, error) {
installedVersion := s.provider.GetStatus().InstalledVersion
// versionChanged try to get installed version from provider status and decide if it has changed.
func (p *phaseReconciler) versionChanged() (bool, error) {
installedVersion := p.provider.GetStatus().InstalledVersion
if installedVersion == nil {
return false, nil
return true, nil
}

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

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

// we need to delete installed components if versions are different
needPreDelete := res != 0
versionChanged := res != 0

return needPreDelete, nil
return versionChanged, nil
}

// install installs the provider components using clusterctl library.
func (p *phaseReconciler) install(ctx context.Context) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)

versionChanged, err := p.versionChanged()
if err != nil {
return reconcile.Result{}, wrapPhaseError(err, "failed getting clusterctl Provider version", operatorv1.ProviderInstalledCondition)
}

// skip installation if the version hasn't changed.
if !versionChanged {
return reconcile.Result{}, nil
}

clusterClient := p.newClusterClient()

log.Info("Installing provider")
Expand Down Expand Up @@ -438,9 +453,9 @@ func clusterctlProviderName(provider genericprovider.GenericProvider) client.Obj
}

// newClusterClient returns a clusterctl client for interacting with management cluster.
func (s *phaseReconciler) newClusterClient() cluster.Client {
return cluster.New(cluster.Kubeconfig{}, s.configClient, cluster.InjectProxy(&controllerProxy{
ctrlClient: s.ctrlClient,
ctrlConfig: s.ctrlConfig,
func (p *phaseReconciler) newClusterClient() cluster.Client {
return cluster.New(cluster.Kubeconfig{}, p.configClient, cluster.InjectProxy(&controllerProxy{
ctrlClient: p.ctrlClient,
ctrlConfig: p.ctrlConfig,
}))
}