From ecd0b21f23b76463bf719d974476a3c9e124f963 Mon Sep 17 00:00:00 2001 From: Ryan Emerson Date: Mon, 9 Sep 2024 10:51:16 +0100 Subject: [PATCH] Changes to spec.image now trigger upgrades. Fixes #1223 --- .../infinispan/handler/manage/upgrades.go | 9 +++- test/e2e/infinispan/upgrade_operand_test.go | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go b/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go index 6b429fa2f..a3b581428 100644 --- a/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go +++ b/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go @@ -89,7 +89,14 @@ func UpgradeRequired(i *ispnv1.Infinispan, ctx pipeline.Context) bool { if requestedOperand.CVE && installedOperand.UpstreamVersion.EQ(*requestedOperand.UpstreamVersion) { return false } - return !requestedOperand.EQ(installedOperand) + + if requestedOperand.EQ(installedOperand) { + // If operand versions match, but the FQN of the image differ, then we must schedule an upgrade so the user + // can transition to a custom/patched version of the Operand without having to recreate the Infinispan CR. + // We must ignore the "Pending" phase to ensure that we don't restart the GracefulShutdown process midway + return i.Spec.Image != nil && *i.Spec.Image != installedOperand.Image && i.Status.Operand.Phase != ispnv1.OperandPhasePending + } + return true } } diff --git a/test/e2e/infinispan/upgrade_operand_test.go b/test/e2e/infinispan/upgrade_operand_test.go index 9e6c2bef7..7992497d1 100644 --- a/test/e2e/infinispan/upgrade_operand_test.go +++ b/test/e2e/infinispan/upgrade_operand_test.go @@ -9,6 +9,7 @@ import ( "github.com/infinispan/infinispan-operator/pkg/kubernetes" "github.com/infinispan/infinispan-operator/pkg/reconcile/pipeline/infinispan/handler/provision" tutils "github.com/infinispan/infinispan-operator/test/e2e/utils" + "k8s.io/utils/pointer" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" @@ -210,3 +211,56 @@ func TestOperandCVEHotRodRolling(t *testing.T) { } genericTestForContainerUpdated(*spec, modifier, verifier) } + +func TestSpecImageUpdate(t *testing.T) { + defer testKube.CleanNamespaceAndLogOnPanic(t, tutils.Namespace) + + // Create Infinispan Cluster using the penultimate Operand release as this will have a different image name to the latest + // release. We can then manually specify spec.image using the FQN of the latest image to simulate a user specifying + // custom images + replicas := 1 + versionManager := tutils.VersionManager() + operand := versionManager.Operands[len(versionManager.Operands)-2] + spec := tutils.DefaultSpec(t, testKube, func(i *ispnv1.Infinispan) { + i.Spec.Replicas = int32(replicas) + i.Spec.Version = operand.Ref() + }) + + testKube.CreateInfinispan(spec, tutils.Namespace) + testKube.WaitForInfinispanPods(replicas, tutils.SinglePodTimeout, spec.Name, tutils.Namespace) + ispn := testKube.WaitForInfinispanCondition(spec.Name, spec.Namespace, ispnv1.ConditionWellFormed) + + customImage := versionManager.Latest().Image + tutils.ExpectNoError( + testKube.UpdateInfinispan(ispn, func() { + // Update the spec to install the custom image + ispn.Spec.Image = pointer.String(customImage) + }), + ) + testKube.WaitForInfinispanState(spec.Name, spec.Namespace, func(i *ispnv1.Infinispan) bool { + return !i.IsConditionTrue(ispnv1.ConditionWellFormed) && + i.Status.Operand.Version == operand.Ref() && + i.Status.Operand.Image == customImage && + i.Status.Operand.Phase == ispnv1.OperandPhasePending + }) + + testKube.WaitForInfinispanState(spec.Name, spec.Namespace, func(i *ispnv1.Infinispan) bool { + return i.IsConditionTrue(ispnv1.ConditionWellFormed) && + i.Status.Operand.Version == operand.Ref() && + i.Status.Operand.Image == customImage && + i.Status.Operand.Phase == ispnv1.OperandPhaseRunning + }) + + // Ensure that the newly created cluster pods have the correct Operand image + podList := &corev1.PodList{} + tutils.ExpectNoError(testKube.Kubernetes.ResourcesList(tutils.Namespace, ispn.PodSelectorLabels(), podList, context.TODO())) + for _, pod := range podList.Items { + container := kubernetes.GetContainer(provision.InfinispanContainer, &pod.Spec) + assert.Equal(t, customImage, container.Image) + } + + // Ensure that the StatefulSet is on its first generation, i.e. a RollingUpgrade has not been performed + ss := appsv1.StatefulSet{} + tutils.ExpectNoError(testKube.Kubernetes.Client.Get(context.TODO(), types.NamespacedName{Namespace: ispn.Namespace, Name: ispn.GetStatefulSetName()}, &ss)) + assert.EqualValues(t, 1, ss.Status.ObservedGeneration) +}