Skip to content

Commit

Permalink
Merge pull request #399 from Danil-Grigorev/provider-upgrader
Browse files Browse the repository at this point in the history
✨ Execute provider upgrades when a version changes
  • Loading branch information
k8s-ci-robot authored Jan 24, 2024
2 parents e3f4480 + be5bcf1 commit 986ff0e
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 59 deletions.
6 changes: 6 additions & 0 deletions api/v1alpha2/conditions_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const (
// ComponentsFetchErrorReason documents that an error occurred fetching the components.
ComponentsFetchErrorReason = "ComponentsFetchError"

// ComponentsUpgradeErrorReason documents that an error occurred while upgrading the components.
ComponentsUpgradeErrorReason = "ComponentsUpgradeError"

// OldComponentsDeletionErrorReason documents that an error occurred deleting the old components prior to upgrading.
OldComponentsDeletionErrorReason = "OldComponentsDeletionError"

Expand All @@ -63,4 +66,7 @@ const (
const (
// ProviderInstalledCondition documents a Provider that has been installed.
ProviderInstalledCondition clusterv1.ConditionType = "ProviderInstalled"

// ProviderUpgradedCondition documents a Provider that has been recently upgraded.
ProviderUpgradedCondition clusterv1.ConditionType = "ProviderUpgraded"
)
90 changes: 89 additions & 1 deletion internal/controller/client_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,83 @@ import (

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"

operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// clientProxy implements the Proxy interface from the clusterctl. It is used to
// interact with the management cluster.
type clientProxy struct {
client.Client
}

func (c clientProxy) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
switch l := list.(type) {
case *clusterctlv1.ProviderList:
return listProviders(ctx, c.Client, l)
default:
return c.Client.List(ctx, l, opts...)
}
}

func (c clientProxy) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
switch o := obj.(type) {
case *clusterctlv1.Provider:
return nil
default:
return c.Client.Get(ctx, key, o, opts...)
}
}

func (c clientProxy) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
switch o := obj.(type) {
case *clusterctlv1.Provider:
return nil
default:
return c.Client.Patch(ctx, o, patch, opts...)
}
}

func listProviders(ctx context.Context, cl client.Client, list *clusterctlv1.ProviderList) error {
providers := []operatorv1.GenericProviderList{
&operatorv1.CoreProviderList{},
&operatorv1.InfrastructureProviderList{},
&operatorv1.BootstrapProviderList{},
&operatorv1.ControlPlaneProviderList{},
&operatorv1.AddonProviderList{},
&operatorv1.IPAMProviderList{},
}

for _, group := range providers {
g, ok := group.(client.ObjectList)
if !ok {
continue
}

if err := cl.List(ctx, g); err != nil {
return err
}

for _, p := range group.GetItems() {
list.Items = append(list.Items, getProvider(p, ""))
}
}

return nil
}

// controllerProxy implements the Proxy interface from the clusterctl. It is used to
// interact with the management cluster.
type controllerProxy struct {
ctrlClient client.Client
ctrlClient clientProxy
ctrlConfig *rest.Config
}

Expand Down Expand Up @@ -153,3 +219,25 @@ func listObjByGVK(ctx context.Context, c client.Client, groupVersion, kind strin

return objList, nil
}

type repositoryProxy struct {
repository.Client

components repository.Components
}

type repositoryClient struct {
components repository.Components
}

func (r repositoryClient) Raw(ctx context.Context, options repository.ComponentsOptions) ([]byte, error) {
return nil, nil
}

func (r repositoryClient) Get(ctx context.Context, options repository.ComponentsOptions) (repository.Components, error) {
return r.components, nil
}

func (r repositoryProxy) Components() repository.ComponentsClient {
return repositoryClient{r.components}
}
3 changes: 2 additions & 1 deletion internal/controller/genericprovider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ func (r *GenericProviderReconciler) reconcile(ctx context.Context, provider gene
reconciler.downloadManifests,
reconciler.load,
reconciler.fetch,
reconciler.preInstall,
reconciler.upgrade,
reconciler.install,
reconciler.reportStatus,
}

res := reconcile.Result{}
Expand Down
101 changes: 88 additions & 13 deletions internal/controller/genericprovider_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package controller

import (
"testing"
"time"

. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -39,7 +41,7 @@ apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3
releaseSeries:
- major: 0
minor: 4
contract: v1alpha4
contract: v1beta1
`
testComponents = `
apiVersion: apps/v1
Expand Down Expand Up @@ -215,22 +217,47 @@ func TestReconcilerPreflightConditions(t *testing.T) {
}
}

func TestUpgradeDowngradeProvider(t *testing.T) {
func TestAirGappedUpgradeDowngradeProvider(t *testing.T) {
currentVersion := "v999.9.2"
futureMetadata := `
apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3
releaseSeries:
- major: 999
minor: 9
contract: v1beta1
`

dummyFutureConfigMap := func(ns, name string) *corev1.ConfigMap {
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
Labels: map[string]string{
"test": "dummy-config",
},
},
Data: map[string]string{
"metadata": futureMetadata,
"components": testComponents,
},
}
}

testCases := []struct {
name string
newVersion string
}{
{
name: "same provider version",
newVersion: "v0.4.2",
newVersion: "v999.9.2",
},
{
name: "upgrade provider version",
newVersion: "v0.4.3",
newVersion: "v999.9.3",
},
{
name: "downgrade provider version",
newVersion: "v0.4.1",
newVersion: "v999.9.1",
},
}

Expand All @@ -244,7 +271,7 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
},
Spec: operatorv1.CoreProviderSpec{
ProviderSpec: operatorv1.ProviderSpec{
Version: testCurrentVersion,
Version: currentVersion,
},
},
}
Expand All @@ -254,7 +281,7 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
t.Log("Ensure namespace exists", namespace)
g.Expect(env.EnsureNamespaceExists(ctx, namespace)).To(Succeed())

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

insertDummyConfig(provider)
provider.SetNamespace(namespace)
Expand All @@ -266,7 +293,7 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
return false
}

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

Expand All @@ -283,13 +310,16 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
}, timeout).Should(BeEquivalentTo(true))

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

// Change provider version
providerSpec := provider.GetSpec()
providerSpec.Version = tc.newVersion
providerSpec.Deployment = &operatorv1.DeploymentSpec{
Replicas: pointer.Int(2),
}
provider.SetSpec(providerSpec)

// Set label (needed to start a reconciliation of the provider)
Expand All @@ -315,23 +345,68 @@ func TestUpgradeDowngradeProvider(t *testing.T) {
return false
}

allFound := 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
allFound = true
break
}
}
}

return false
if !allFound {
return false
}

allFound = tc.newVersion == currentVersion
for _, cond := range provider.GetStatus().Conditions {
if cond.Type == operatorv1.ProviderUpgradedCondition {
t.Log(t.Name(), provider.GetName(), cond)
if cond.Status == corev1.ConditionTrue {
allFound = tc.newVersion != currentVersion
break
}
}
}

if !allFound {
return false
}

// Ensure customization occurred
dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
Namespace: provider.Namespace,
Name: "capd-controller-manager",
}}
if err := env.Get(ctx, client.ObjectKeyFromObject(dep), dep); err != nil {
return false
}

return dep.Spec.Replicas != nil && *dep.Spec.Replicas == 2
}, timeout).Should(BeEquivalentTo(true))

g.Consistently(func() bool {
allSet := tc.newVersion == currentVersion
for _, cond := range provider.GetStatus().Conditions {
if cond.Type == operatorv1.ProviderUpgradedCondition {
t.Log(t.Name(), provider.GetName(), cond)
if cond.Status == corev1.ConditionTrue {
allSet = tc.newVersion != currentVersion
break
}
}
}

return allSet
}, 2*time.Second).Should(BeTrue())

// Clean up
objs := []client.Object{provider}
objs = append(objs, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: testCurrentVersion,
Name: currentVersion,
Namespace: namespace,
},
})
Expand Down
10 changes: 5 additions & 5 deletions internal/controller/manifests_downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (p *phaseReconciler) downloadManifests(ctx context.Context) (reconcile.Resu

exists, err := p.checkConfigMapExists(ctx, labelSelector)
if err != nil {
return reconcile.Result{}, wrapPhaseError(err, "failed to check that config map with manifests exists")
return reconcile.Result{}, wrapPhaseError(err, "failed to check that config map with manifests exists", operatorv1.ProviderInstalledCondition)
}

if exists {
Expand All @@ -83,7 +83,7 @@ func (p *phaseReconciler) downloadManifests(ctx context.Context) (reconcile.Resu
if err != nil {
err = fmt.Errorf("failed to create repo from provider url for provider %q: %w", p.provider.GetName(), err)

return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason)
return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason, operatorv1.ProviderInstalledCondition)
}

spec := p.provider.GetSpec()
Expand All @@ -101,22 +101,22 @@ func (p *phaseReconciler) downloadManifests(ctx context.Context) (reconcile.Resu
if err != nil {
err = fmt.Errorf("failed to read %q from the repository for provider %q: %w", metadataFile, p.provider.GetName(), err)

return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason)
return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason, operatorv1.ProviderInstalledCondition)
}

componentsFile, err := repo.GetFile(ctx, spec.Version, repo.ComponentsPath())
if err != nil {
err = fmt.Errorf("failed to read %q from the repository for provider %q: %w", componentsFile, p.provider.GetName(), err)

return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason)
return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason, operatorv1.ProviderInstalledCondition)
}

withCompression := needToCompress(metadataFile, componentsFile)

if err := p.createManifestsConfigMap(ctx, metadataFile, componentsFile, withCompression); err != nil {
err = fmt.Errorf("failed to create config map for provider %q: %w", p.provider.GetName(), err)

return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason)
return reconcile.Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason, operatorv1.ProviderInstalledCondition)
}

return reconcile.Result{}, nil
Expand Down
Loading

0 comments on commit 986ff0e

Please sign in to comment.