diff --git a/controllers/limitador_controller.go b/controllers/limitador_controller.go index e61665df..c94d0aac 100644 --- a/controllers/limitador_controller.go +++ b/controllers/limitador_controller.go @@ -35,6 +35,7 @@ import ( limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" "github.com/kuadrant/limitador-operator/pkg/limitador" "github.com/kuadrant/limitador-operator/pkg/reconcilers" + upgrades "github.com/kuadrant/limitador-operator/pkg/upgrades" ) // LimitadorReconciler reconciles a Limitador object @@ -79,7 +80,7 @@ func (r *LimitadorReconciler) Reconcile(eventCtx context.Context, req ctrl.Reque return ctrl.Result{}, nil } - specErr := r.reconcileSpec(ctx, limitadorObj) + specResult, specErr := r.reconcileSpec(ctx, limitadorObj) statusResult, statusErr := r.reconcileStatus(ctx, limitadorObj, specErr) @@ -91,6 +92,11 @@ func (r *LimitadorReconciler) Reconcile(eventCtx context.Context, req ctrl.Reque return ctrl.Result{}, statusErr } + if specResult.Requeue { + logger.V(1).Info("Reconciling spec not finished. Requeueing.") + return specResult, nil + } + if statusResult.Requeue { logger.V(1).Info("Reconciling status not finished. Requeueing.") return statusResult, nil @@ -100,24 +106,31 @@ func (r *LimitadorReconciler) Reconcile(eventCtx context.Context, req ctrl.Reque return ctrl.Result{}, nil } -func (r *LimitadorReconciler) reconcileSpec(ctx context.Context, limitadorObj *limitadorv1alpha1.Limitador) error { +func (r *LimitadorReconciler) reconcileSpec(ctx context.Context, limitadorObj *limitadorv1alpha1.Limitador) (ctrl.Result, error) { if err := r.reconcileService(ctx, limitadorObj); err != nil { - return err + return ctrl.Result{}, err } if err := r.reconcilePVC(ctx, limitadorObj); err != nil { - return err + return ctrl.Result{}, err } - if err := r.reconcileDeployment(ctx, limitadorObj); err != nil { - return err + result, err := r.reconcileDeployment(ctx, limitadorObj) + if result.Requeue { + return result, nil + } + if err != nil { + return ctrl.Result{}, err } if err := r.reconcileLimitsConfigMap(ctx, limitadorObj); err != nil { - return err + return ctrl.Result{}, err } - return r.reconcilePdb(ctx, limitadorObj) + if err := r.reconcilePdb(ctx, limitadorObj); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil } func (r *LimitadorReconciler) reconcilePdb(ctx context.Context, limitadorObj *limitadorv1alpha1.Limitador) error { @@ -162,15 +175,15 @@ func (r *LimitadorReconciler) reconcilePdb(ctx context.Context, limitadorObj *li return nil } -func (r *LimitadorReconciler) reconcileDeployment(ctx context.Context, limitadorObj *limitadorv1alpha1.Limitador) error { +func (r *LimitadorReconciler) reconcileDeployment(ctx context.Context, limitadorObj *limitadorv1alpha1.Limitador) (ctrl.Result, error) { logger, err := logr.FromContext(ctx) if err != nil { - return err + return ctrl.Result{}, err } deploymentOptions, err := r.getDeploymentOptions(ctx, limitadorObj) if err != nil { - return err + return ctrl.Result{}, err } deploymentMutators := make([]reconcilers.DeploymentMutateFn, 0) @@ -192,15 +205,16 @@ func (r *LimitadorReconciler) reconcileDeployment(ctx context.Context, limitador deployment := limitador.Deployment(limitadorObj, deploymentOptions) // controller reference if err := r.SetOwnerReference(limitadorObj, deployment); err != nil { - return err + return ctrl.Result{}, err } err = r.ReconcileDeployment(ctx, deployment, reconcilers.DeploymentMutator(deploymentMutators...)) logger.V(1).Info("reconcile deployment", "error", err) if err != nil { - return err + return ctrl.Result{}, err } - return nil + // TODO: To be deleted when the upgrade path is no longer needed. + return upgrades.UpgradeDeploymentTov070(ctx, r.Client(), limitadorObj, client.ObjectKeyFromObject(deployment)) } func (r *LimitadorReconciler) reconcileService(ctx context.Context, limitadorObj *limitadorv1alpha1.Limitador) error { @@ -267,6 +281,12 @@ func (r *LimitadorReconciler) reconcileLimitsConfigMap(ctx context.Context, limi return err } + // TODO: To be deleted when the upgrade path is no longer needed. + err = upgrades.UpgradeConfigMapTov070(ctx, r.Client(), limitadorObj) + if err != nil { + return err + } + return nil } diff --git a/controllers/limitador_controller_test.go b/controllers/limitador_controller_test.go index b11071a3..7ec0a6c3 100644 --- a/controllers/limitador_controller_test.go +++ b/controllers/limitador_controller_test.go @@ -179,7 +179,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &createdLimitadorDeployment) @@ -353,7 +353,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &updatedLimitadorDeployment) @@ -396,7 +396,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &updatedLimitadorDeployment) @@ -544,7 +544,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &createdLimitadorDeployment) @@ -609,7 +609,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &updatedLimitadorDeployment) @@ -672,7 +672,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &updatedLimitadorDeployment) @@ -711,7 +711,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &deploymentObj) @@ -730,7 +730,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &updateDeploymentObj) @@ -825,7 +825,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &deploymentObj) @@ -908,7 +908,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &deploymentObj) @@ -955,7 +955,7 @@ var _ = Describe("Limitador controller", func() { context.TODO(), types.NamespacedName{ Namespace: LimitadorNamespace, - Name: limitadorObj.Name, + Name: limitador.DeploymentName(limitadorObj), }, &deploymentObj) diff --git a/pkg/limitador/k8s_objects.go b/pkg/limitador/k8s_objects.go index 914329d1..a7d676f3 100644 --- a/pkg/limitador/k8s_objects.go +++ b/pkg/limitador/k8s_objects.go @@ -71,7 +71,7 @@ func Deployment(limitador *limitadorv1alpha1.Limitador, deploymentOptions Deploy APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: limitador.ObjectMeta.Name, // TODO: revisit later. For now assume same. + Name: DeploymentName(limitador), Namespace: limitador.ObjectMeta.Namespace, // TODO: revisit later. For now assume same. Labels: Labels(limitador), }, @@ -164,7 +164,7 @@ func LimitsConfigMap(limitadorObj *limitadorv1alpha1.Limitador) (*v1.ConfigMap, } func LimitsConfigMapName(limitadorObj *limitadorv1alpha1.Limitador) string { - return fmt.Sprintf("limits-config-%s", limitadorObj.Name) + return fmt.Sprintf("limitador-limits-config-%s", limitadorObj.Name) } func ServiceName(limitadorObj *limitadorv1alpha1.Limitador) string { @@ -175,6 +175,10 @@ func PVCName(limitadorObj *limitadorv1alpha1.Limitador) string { return fmt.Sprintf("limitador-%s", limitadorObj.Name) } +func DeploymentName(limitadorObj *limitadorv1alpha1.Limitador) string { + return fmt.Sprintf("limitador-%s", limitadorObj.Name) +} + func PodDisruptionBudget(limitadorObj *limitadorv1alpha1.Limitador) *policyv1.PodDisruptionBudget { return &policyv1.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/upgrades/v0_7_0.go b/pkg/upgrades/v0_7_0.go new file mode 100644 index 00000000..e8bd1059 --- /dev/null +++ b/pkg/upgrades/v0_7_0.go @@ -0,0 +1,77 @@ +package upgrades + +import ( + "context" + "fmt" + + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + "github.com/kuadrant/limitador-operator/pkg/helpers" + + "github.com/go-logr/logr" +) + +func UpgradeDeploymentTov070(ctx context.Context, cli client.Client, limitadorObj *limitadorv1alpha1.Limitador, newDeploymentKey client.ObjectKey) (ctrl.Result, error) { + logger := logr.FromContextOrDiscard(ctx) + logger.V(1).Info("Upgrading Deployment to v0.7.0", "deployment", newDeploymentKey.Name) + + newDeployment := &appsv1.Deployment{} + if err := cli.Get(ctx, newDeploymentKey, newDeployment); err != nil { + if errors.IsNotFound(err) { + logger.V(1).Info("New deployment not found") + return ctrl.Result{Requeue: true}, nil + } + return ctrl.Result{}, err + } + + availableCondition := helpers.FindDeploymentStatusCondition(newDeployment.Status.Conditions, "Available") + + if availableCondition == nil { + return ctrl.Result{Requeue: true}, nil + } + + if availableCondition.Status == v1.ConditionTrue { + oldDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: ReleaseV060DeploymentName(limitadorObj), + Namespace: limitadorObj.Namespace, + }, + } + if err := cli.Delete(ctx, oldDeployment); err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil +} + +func UpgradeConfigMapTov070(ctx context.Context, cli client.Client, limitadorObj *limitadorv1alpha1.Limitador) error { + oldConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: ReleaseV060LimitsConfigMapName(limitadorObj), + Namespace: limitadorObj.Namespace, + }, + } + + if err := cli.Delete(ctx, oldConfigMap); err != nil && !errors.IsNotFound(err) { + return err + } + + return nil +} + +func ReleaseV060DeploymentName(limitadorObj *limitadorv1alpha1.Limitador) string { + return limitadorObj.Name +} + +func ReleaseV060LimitsConfigMapName(limitadorObj *limitadorv1alpha1.Limitador) string { + return fmt.Sprintf("limits-config-%s", limitadorObj.Name) +} diff --git a/pkg/upgrades/v0_7_0_test.go b/pkg/upgrades/v0_7_0_test.go new file mode 100644 index 00000000..68e04cf0 --- /dev/null +++ b/pkg/upgrades/v0_7_0_test.go @@ -0,0 +1,188 @@ +package upgrades + +import ( + "context" + "testing" + + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/go-logr/logr" + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + "github.com/kuadrant/limitador-operator/pkg/limitador" +) + +func TestUpgradeDeploymentTov070(t *testing.T) { + var ( + limitadorName = "test-limitador" + limitadorNamespace = "default" + ) + logger := log.Log.WithName("upgrades_test") + baseCtx := context.Background() + ctx := logr.NewContext(baseCtx, logger) + + s := runtime.NewScheme() + _ = appsv1.AddToScheme(s) + _ = limitadorv1alpha1.AddToScheme(s) + _ = v1.AddToScheme(s) + + // Create dummy Limitador object + limitadorObj := &limitadorv1alpha1.Limitador{ + ObjectMeta: metav1.ObjectMeta{ + Name: limitadorName, + Namespace: limitadorNamespace, + }, + } + + // Create a dummy Deployment object + newDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: limitador.DeploymentName(limitadorObj), + Namespace: limitadorNamespace, + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: v1.ConditionTrue, + }, + }, + }, + } + + // Old Deployment to simulate the upgrade + oldDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: ReleaseV060DeploymentName(limitadorObj), + Namespace: limitadorNamespace, + }, + } + + // Objects to track in the fake client. + objs := []client.Object{limitadorObj, newDeployment, oldDeployment} + + // Create a fake client to mock API calls. + clBuilder := fake.NewClientBuilder() + cl := clBuilder.WithScheme(s).WithObjects(objs...).Build() + + _, err := UpgradeDeploymentTov070(ctx, cl, limitadorObj, client.ObjectKeyFromObject(newDeployment)) + if err != nil { + t.Fatalf("UpgradeDeploymentTov070 failed: %v", err) + } + + // Check if the old deployment was deleted + oldDepKey := types.NamespacedName{Name: ReleaseV060DeploymentName(limitadorObj), Namespace: limitadorNamespace} + err = cl.Get(ctx, oldDepKey, &appsv1.Deployment{}) + if err == nil { + t.Fatal("Old deployment was not deleted") + } +} + +func TestUpgradeDeploymentTov070_NewDeploymentNotFound(t *testing.T) { + var ( + limitadorName = "test-limitador" + limitadorNamespace = "default" + ) + logger := log.Log.WithName("upgrades_test") + baseCtx := context.Background() + ctx := logr.NewContext(baseCtx, logger) + + s := runtime.NewScheme() + _ = appsv1.AddToScheme(s) + _ = limitadorv1alpha1.AddToScheme(s) + _ = v1.AddToScheme(s) + + // Create dummy Limitador object + limitadorObj := &limitadorv1alpha1.Limitador{ + ObjectMeta: metav1.ObjectMeta{ + Name: limitadorName, + Namespace: limitadorNamespace, + }, + } + + // Old Deployment to simulate the upgrade + oldDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: ReleaseV060DeploymentName(limitadorObj), + Namespace: limitadorNamespace, + }, + } + + newDeployment := &appsv1.Deployment{} + + // Objects to track in the fake client. + objs := []client.Object{limitadorObj, oldDeployment} + + // Create a fake client to mock API calls. + clBuilder := fake.NewClientBuilder() + cl := clBuilder.WithScheme(s).WithObjects(objs...).Build() + + // Testa situation where the new deployment does not exist + _, err := UpgradeDeploymentTov070(ctx, cl, limitadorObj, client.ObjectKeyFromObject(newDeployment)) + if err != nil { + t.Fatalf("UpgradeDeploymentTov070 failed: %v", err) + } + + // Check if the old deployment still exists + oldDepKey := types.NamespacedName{Name: ReleaseV060DeploymentName(limitadorObj), Namespace: limitadorNamespace} + err = cl.Get(ctx, oldDepKey, &appsv1.Deployment{}) + if err != nil { + t.Fatalf("Old deployment should still exist: %v", err) + } +} + +func TestUpgradeConfigMapTov070(t *testing.T) { + var ( + limitadorName = "test-limitador" + limitadorNamespace = "default" + ) + logger := log.Log.WithName("upgrades_test") + baseCtx := context.Background() + ctx := logr.NewContext(baseCtx, logger) + + s := runtime.NewScheme() + _ = v1.AddToScheme(s) + _ = limitadorv1alpha1.AddToScheme(s) + + // Create dummy Limitador object + limitadorObj := &limitadorv1alpha1.Limitador{ + ObjectMeta: metav1.ObjectMeta{ + Name: limitadorName, + Namespace: limitadorNamespace, + }, + } + + // Old ConfigMap to simulate the upgrade + oldConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: ReleaseV060LimitsConfigMapName(limitadorObj), + Namespace: limitadorNamespace, + }, + } + + // Objects to track in the fake client. + objs := []client.Object{limitadorObj, oldConfigMap} + + // Create a fake client to mock API calls. + clBuilder := fake.NewClientBuilder() + cl := clBuilder.WithScheme(s).WithObjects(objs...).Build() + + err := UpgradeConfigMapTov070(ctx, cl, limitadorObj) + if err != nil { + t.Fatalf("UpgradeConfigMapTov070 failed: %v", err) + } + + // Check if the old config map was deleted + oldCmKey := types.NamespacedName{Name: ReleaseV060LimitsConfigMapName(limitadorObj), Namespace: limitadorNamespace} + err = cl.Get(ctx, oldCmKey, &v1.ConfigMap{}) + if !apierrors.IsNotFound(err) { + t.Fatal("Old ConfigMap Found") + } +}