diff --git a/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go b/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go index 525c638ab..9517b3d79 100644 --- a/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go +++ b/pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go @@ -89,10 +89,12 @@ func UpgradeRequired(i *ispnv1.Infinispan, ctx pipeline.Context) bool { return false } - // If the Operand is marked as a CVE base-image release, then we perform the upgrade as a StatefulSet rolling upgrade - // as the server components are not changed. - customImage := i.Status.Operand.CustomImage - if !customImage && requestedOperand.CVE && installedOperand.UpstreamVersion.EQ(*requestedOperand.UpstreamVersion) { + // If the Operand is marked as a CVE base-image release and no custom image is installed/requested, then we perform the + // upgrade as a StatefulSet rolling upgrade as the server components are not changed. + if requestedOperand.CVE && + installedOperand.UpstreamVersion.EQ(*requestedOperand.UpstreamVersion) && + i.Spec.Image == nil && + !i.Status.Operand.CustomImage { return false } @@ -100,11 +102,11 @@ func UpgradeRequired(i *ispnv1.Infinispan, ctx pipeline.Context) bool { if i.Spec.Image == nil { // If the currently installed Operand was a custom image, but spec.Image is now nil, then we need to // initiate a new upgrade to ensure that the default image associated with the Operand is installed - return customImage + return i.Status.Operand.CustomImage } // 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 - return *i.Spec.Image != installedOperand.Image + return *i.Spec.Image != i.Status.Operand.Image } return true } diff --git a/test/e2e/infinispan/upgrade_operand_test.go b/test/e2e/infinispan/upgrade_operand_test.go index 2706550e3..af6afad52 100644 --- a/test/e2e/infinispan/upgrade_operand_test.go +++ b/test/e2e/infinispan/upgrade_operand_test.go @@ -212,6 +212,28 @@ func TestOperandCVEHotRodRolling(t *testing.T) { genericTestForContainerUpdated(*spec, modifier, verifier) } +func TestSpecImage(t *testing.T) { + defer testKube.CleanNamespaceAndLogOnPanic(t, tutils.Namespace) + + // Create Infinispan Cluster using the latest Operand version, but using the image of the preceding Operand + // Ensures that creating a cluster with an initial spec.image value does not cause infinite StatefulSet updates + versionManager := tutils.VersionManager() + operand := versionManager.Operands[len(versionManager.Operands)-2] + spec := tutils.DefaultSpec(t, testKube, func(i *ispnv1.Infinispan) { + i.Spec.Image = pointer.String(operand.Image) + }) + testKube.CreateInfinispan(spec, tutils.Namespace) + testKube.WaitForInfinispanPods(1, tutils.SinglePodTimeout, spec.Name, tutils.Namespace) + testKube.WaitForInfinispanCondition(spec.Name, spec.Namespace, ispnv1.ConditionWellFormed) + testKube.WaitForInfinispanState(spec.Name, spec.Namespace, func(i *ispnv1.Infinispan) bool { + return i.IsConditionTrue(ispnv1.ConditionWellFormed) && + i.Status.Operand.CustomImage && + i.Status.Operand.Version == versionManager.Latest().Ref() && + i.Status.Operand.Image == operand.Image && + i.Status.Operand.Phase == ispnv1.OperandPhaseRunning + }) +} + func TestSpecImageUpdate(t *testing.T) { defer testKube.CleanNamespaceAndLogOnPanic(t, tutils.Namespace) @@ -237,15 +259,10 @@ func TestSpecImageUpdate(t *testing.T) { 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.CustomImage && i.Status.Operand.Version == operand.Ref() && i.Status.Operand.Image == customImage && i.Status.Operand.Phase == ispnv1.OperandPhaseRunning @@ -272,17 +289,33 @@ func TestSpecImageUpdate(t *testing.T) { ispn.Spec.Version = latestOperand.Ref() }), ) + testKube.WaitForInfinispanState(spec.Name, spec.Namespace, func(i *ispnv1.Infinispan) bool { - return !i.IsConditionTrue(ispnv1.ConditionWellFormed) && + return i.IsConditionTrue(ispnv1.ConditionWellFormed) && + !i.Status.Operand.CustomImage && i.Status.Operand.Version == latestOperand.Ref() && i.Status.Operand.Image == latestOperand.Image && - i.Status.Operand.Phase == ispnv1.OperandPhasePending + i.Status.Operand.Phase == ispnv1.OperandPhaseRunning }) + // 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) + + tutils.ExpectNoError( + testKube.UpdateInfinispan(ispn, func() { + // Update the spec to move to back to the penultimate Operand version to ensure that an upgrade is still + // triggered when the Operand is marked as CVE=true + ispn.Spec.Image = pointer.String(operand.Image) + }), + ) + testKube.WaitForInfinispanState(spec.Name, spec.Namespace, func(i *ispnv1.Infinispan) bool { return i.IsConditionTrue(ispnv1.ConditionWellFormed) && + i.Status.Operand.CustomImage && i.Status.Operand.Version == latestOperand.Ref() && - i.Status.Operand.Image == latestOperand.Image && + i.Status.Operand.Image == operand.Image && i.Status.Operand.Phase == ispnv1.OperandPhaseRunning })