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

Updates to spec.image on existing Infinispan CR have no effect #2145

Merged
merged 1 commit into from
Sep 10, 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
3 changes: 3 additions & 0 deletions api/v1/infinispan_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ const (
)

type OperandStatus struct {
// Whether the Operand installed/pending is using a custom image
// +optional
CustomImage bool `json:"customImage,omitempty"`
// Whether the Operand has been deprecated and is subject for removal in a subsequent release
// +optional
Deprecated bool `json:"deprecated,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/infinispan.org_infinispans.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2347,6 +2347,10 @@ spec:
operand:
description: The Operand status
properties:
customImage:
description: Whether the Operand installed/pending is using a
custom image
type: boolean
deprecated:
description: Whether the Operand has been deprecated and is subject
for removal in a subsequent release
Expand Down
12 changes: 7 additions & 5 deletions pkg/reconcile/pipeline/infinispan/handler/manage/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,17 +250,19 @@ func getCrossSiteViewCondition(ctx pipeline.Context, podList *corev1.PodList, si
}

func OperandStatus(i *ispnv1.Infinispan, phase ispnv1.OperandPhase, operand version.Operand) ispnv1.OperandStatus {
customImg := i.Spec.Image != nil
var img string
if i.Spec.Image != nil {
if customImg {
img = *i.Spec.Image
} else {
img = operand.Image
}

return ispnv1.OperandStatus{
Deprecated: operand.Deprecated,
Image: img,
Phase: phase,
Version: operand.Ref(),
CustomImage: customImg,
Deprecated: operand.Deprecated,
Image: img,
Phase: phase,
Version: operand.Ref(),
}
}
21 changes: 19 additions & 2 deletions pkg/reconcile/pipeline/infinispan/handler/manage/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,29 @@ func UpgradeRequired(i *ispnv1.Infinispan, ctx pipeline.Context) bool {
return true
}

// Don't schedule an upgrade if one is already in progress
if i.Status.Operand.Phase == ispnv1.OperandPhasePending {
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.
if requestedOperand.CVE && installedOperand.UpstreamVersion.EQ(*requestedOperand.UpstreamVersion) {
customImage := i.Status.Operand.CustomImage
if !customImage && requestedOperand.CVE && installedOperand.UpstreamVersion.EQ(*requestedOperand.UpstreamVersion) {
return false
}
return !requestedOperand.EQ(installedOperand)

if requestedOperand.EQ(installedOperand) {
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
}
// 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 true
}
}

Expand Down
81 changes: 81 additions & 0 deletions test/e2e/infinispan/upgrade_operand_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -210,3 +211,83 @@ 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)

latestOperand := versionManager.Latest()
tutils.ExpectNoError(
testKube.UpdateInfinispan(ispn, func() {
// Update the spec to move to the latest Operand version to ensure that a new GracefulShutdown is triggered
ispn.Spec.Image = nil
ispn.Spec.Version = latestOperand.Ref()
}),
)
testKube.WaitForInfinispanState(spec.Name, spec.Namespace, func(i *ispnv1.Infinispan) bool {
return !i.IsConditionTrue(ispnv1.ConditionWellFormed) &&
i.Status.Operand.Version == latestOperand.Ref() &&
i.Status.Operand.Image == latestOperand.Image &&
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 == latestOperand.Ref() &&
i.Status.Operand.Image == latestOperand.Image &&
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)
}
Loading