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 for management v3 downgrade to provisioning v1 with automigration #581

Merged
merged 3 commits into from
Jul 15, 2024
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
13 changes: 12 additions & 1 deletion charts/rancher-turtles/templates/post-upgrade-job.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and (eq (index .Values "rancherTurtles" "features" "managementv3-cluster" "enabled") true) (eq (index .Values "rancherTurtles" "features" "managementv3-cluster-migration" "enabled") true) }}
furkatgofurov7 marked this conversation as resolved.
Show resolved Hide resolved
{{- if eq (index .Values "rancherTurtles" "features" "managementv3-cluster-migration" "enabled") true }}
---
apiVersion: v1
kind: ServiceAccount
Expand All @@ -24,6 +24,13 @@ rules:
verbs:
- list
- delete
- apiGroups:
- management.cattle.io
resources:
- clusters
verbs:
- list
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down Expand Up @@ -58,7 +65,11 @@ spec:
image: {{ index .Values "rancherTurtles" "features" "rancher-webhook" "kubectlImage" }}
args:
- delete
{{- if eq (index .Values "rancherTurtles" "features" "managementv3-cluster" "enabled") true }}
- clusters.provisioning.cattle.io
{{- else }}
- clusters.management.cattle.io
{{- end }}
- --selector=cluster-api.cattle.io/owned
- -A
- --ignore-not-found=true
Expand Down
55 changes: 52 additions & 3 deletions internal/controllers/import_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"

clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
Expand All @@ -43,6 +45,7 @@ import (
"sigs.k8s.io/cluster-api/util/predicates"

"github.com/rancher/turtles/feature"
managementv3 "github.com/rancher/turtles/internal/rancher/management/v3"
provisioningv1 "github.com/rancher/turtles/internal/rancher/provisioning/v1"
"github.com/rancher/turtles/util"
turtlesannotations "github.com/rancher/turtles/util/annotations"
Expand Down Expand Up @@ -143,16 +146,23 @@ func (r *CAPIImportReconciler) Reconcile(ctx context.Context, req ctrl.Request)

log = log.WithValues("cluster", capiCluster.Name)

// Collect errors as an aggregate to return together after all patches have been performed.
var errs []error

if !capiCluster.ObjectMeta.DeletionTimestamp.IsZero() && controllerutil.RemoveFinalizer(capiCluster, managementv3.CapiClusterFinalizer) {
if err := r.Client.Patch(ctx, capiCluster, patchBase); err != nil {
log.Error(err, "failed to remove CAPI cluster finalizer "+managementv3.CapiClusterFinalizer)
errs = append(errs, err)
}
}

// Wait for controlplane to be ready. This should never be false as the predicates
// do the filtering.
if !capiCluster.Status.ControlPlaneReady && !conditions.IsTrue(capiCluster, clusterv1.ControlPlaneReadyCondition) {
log.Info("clusters control plane is not ready, requeue")
return ctrl.Result{RequeueAfter: defaultRequeueDuration}, nil
}

// Collect errors as an aggregate to return together after all patches have been performed.
var errs []error

result, err := r.reconcile(ctx, capiCluster)
if err != nil {
errs = append(errs, fmt.Errorf("error reconciling cluster: %w", err))
Expand Down Expand Up @@ -350,3 +360,42 @@ func (r *CAPIImportReconciler) reconcileDelete(ctx context.Context, capiCluster

return ctrl.Result{}, nil
}

// CAPIDowngradeReconciler is a reconciler for downgraded managementv3 clusters.
type CAPIDowngradeReconciler struct {
RancherClient client.Client
Scheme *runtime.Scheme
}

// SetupWithManager sets up reconciler with manager.
func (r *CAPIDowngradeReconciler) SetupWithManager(_ context.Context, mgr ctrl.Manager, options controller.Options) error {
if err := ctrl.NewControllerManagedBy(mgr).
For(&managementv3.Cluster{}).
WithOptions(options).
WithEventFilter(predicate.NewPredicateFuncs(func(object client.Object) bool {
_, exist := object.GetLabels()[ownedLabelName]
return exist
})).
Complete(reconcile.AsReconciler(r.RancherClient, r)); err != nil {
return fmt.Errorf("creating new downgrade controller: %w", err)
}

return nil
}

// Reconcile performs check for downgraded clusters and removes finalizer on the clusters still owned by the previous management v3 controller.
func (r *CAPIDowngradeReconciler) Reconcile(ctx context.Context, cluster *managementv3.Cluster) (res ctrl.Result, err error) {
log := log.FromContext(ctx)

patchBase := client.MergeFromWithOptions(cluster.DeepCopy(), client.MergeFromWithOptimisticLock{})

if !controllerutil.RemoveFinalizer(cluster, managementv3.CapiClusterFinalizer) {
return
}

if err = r.RancherClient.Patch(ctx, cluster, patchBase); err != nil {
log.Error(err, "Unable to remove turtles finalizer from cluster"+cluster.Name)
}

return
}
19 changes: 19 additions & 0 deletions internal/controllers/import_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ var _ = Describe("reconcile CAPI Cluster", func() {
}).Should(Succeed())
})

It("should remove a CAPI cluster with turtles finalizer", func() {
capiCluster.Finalizers = []string{managementv3.CapiClusterFinalizer}
Expect(cl.Create(ctx, capiCluster)).To(Succeed())
capiCluster.Status.ControlPlaneReady = true
Expect(cl.Status().Update(ctx, capiCluster)).To(Succeed())
Expect(cl.Delete(ctx, capiCluster)).To(Succeed())

Eventually(func(g Gomega) {
_, err := r.Reconcile(ctx, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: capiCluster.Namespace,
Name: capiCluster.Name,
},
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cl.Get(ctx, client.ObjectKeyFromObject(capiCluster), capiCluster)).To(HaveOccurred())
}).Should(Succeed())
})

It("should reconcile a CAPI cluster when rancher cluster doesn't exist", func() {
capiCluster.Labels = map[string]string{
importLabelName: "true",
Expand Down
12 changes: 6 additions & 6 deletions internal/controllers/import_controller_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,15 @@ func (r *CAPIImportManagementV3Reconciler) Reconcile(ctx context.Context, req ct
// Collect errors as an aggregate to return together after all patches have been performed.
var errs []error

patchBase := client.MergeFromWithOptions(capiCluster.DeepCopy(), client.MergeFromWithOptimisticLock{})

result, err := r.reconcile(ctx, capiCluster)
if err != nil {
errs = append(errs, fmt.Errorf("error reconciling cluster: %w", err))
}

if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
capiClusterCopy := capiCluster.DeepCopy()

patchBase := client.MergeFromWithOptions(capiCluster, client.MergeFromWithOptimisticLock{})

if err := r.Client.Patch(ctx, capiClusterCopy, patchBase); err != nil {
if err := r.Client.Patch(ctx, capiCluster, patchBase); err != nil {
errs = append(errs, fmt.Errorf("failed to patch cluster: %w", err))
}
return nil
Expand Down Expand Up @@ -456,6 +454,8 @@ func (r *CAPIImportManagementV3Reconciler) reconcileDelete(ctx context.Context,
capiCluster.Name,
turtlesannotations.ClusterImportedAnnotation))

patchBase := client.MergeFromWithOptions(capiCluster.DeepCopy(), client.MergeFromWithOptimisticLock{})

annotations := capiCluster.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
Expand All @@ -465,7 +465,7 @@ func (r *CAPIImportManagementV3Reconciler) reconcileDelete(ctx context.Context,
capiCluster.SetAnnotations(annotations)
controllerutil.RemoveFinalizer(capiCluster, managementv3.CapiClusterFinalizer)

if err := r.Client.Update(ctx, capiCluster); err != nil {
if err := r.Client.Patch(ctx, capiCluster, patchBase); err != nil {
return fmt.Errorf("error removing finalizer: %w", err)
}

Expand Down
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,15 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
setupLog.Error(err, "unable to create capi controller")
os.Exit(1)
}

if err := (&controllers.CAPIDowngradeReconciler{
RancherClient: rancherClient,
}).SetupWithManager(ctx, mgr, controller.Options{
MaxConcurrentReconciles: concurrencyNumber,
}); err != nil {
setupLog.Error(err, "unable to create rancher management v3 downgrade controller")
os.Exit(1)
}
}

if feature.Gates.Enabled(feature.RancherKubeSecretPatch) {
Expand Down
20 changes: 20 additions & 0 deletions test/e2e/specs/migrate_gitops_provv1_mgmtv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ func MigrateToV3UsingGitOpsSpec(ctx context.Context, inputGetter func() MigrateT
Image: fmt.Sprintf("ghcr.io/rancher/turtles-e2e-%s", runtime.GOARCH),
Tag: "v0.0.1",
WaitDeploymentsReadyInterval: input.E2EConfig.GetIntervals(input.BootstrapClusterProxy.GetName(), "wait-controllers"),
SkipCleanup: true,
AdditionalValues: map[string]string{},
}

Expand All @@ -387,6 +388,25 @@ func MigrateToV3UsingGitOpsSpec(ctx context.Context, inputGetter func() MigrateT

By("Rancher should be available using new cluster import")
validateRancherCluster()

By("Running downgrade on provisioningv1 cluster later")
upgradeInput.AdditionalValues["rancherTurtles.features.managementv3-cluster.enabled"] = "false"
upgradeInput.SkipCleanup = false
testenv.UpgradeRancherTurtles(ctx, upgradeInput)

By("Waiting for the new Rancher cluster record to be removed")
Eventually(komega.Get(rancherCluster), deleteClusterWait...).Should(MatchError(ContainSubstring("not found")), "Rancher cluster should be unimported (deleted)")

By("CAPI cluster should NOT have the 'imported' annotation")
Consistently(func() bool {
Eventually(komega.Get(capiCluster), input.E2EConfig.GetIntervals(input.BootstrapClusterProxy.GetName(), "wait-rancher")...).Should(Succeed())
annotations := capiCluster.GetAnnotations()
_, found := annotations["imported"]
return !found
}, 5*time.Second).Should(BeTrue(), "'imported' annotation is NOT expected on CAPI cluster")

By("Rancher should be available using old cluster import")
validateLegacyRancherCluster()
})

AfterEach(func() {
Expand Down
Loading