From 2ca0a28bb4ff99123dfcc06b06c54d1792a9a3df Mon Sep 17 00:00:00 2001 From: Sanil2108 Date: Fri, 24 Apr 2026 19:37:49 +0530 Subject: [PATCH 1/3] test(e2e): add Helm-chart upgrade scenario Adds a dedicated e2e check that exercises the Helm-chart upgrade path of the http-add-on: 1. Install a previous release via `helm upgrade --install kedacore/http-add-on --version $HTTPADDON_UPGRADE_FROM_VERSION`. 2. Create a sample workload + HTTPScaledObject and verify it scales from 0 to maxReplicaCount under load. 3. Upgrade to the target release (`$HTTPADDON_UPGRADE_TO_VERSION`). 4. Verify the pre-existing HTTPScaledObject is still reconciled by the upgraded operator and that the deployment continues to scale correctly on load. Skipped by default; only runs when both HTTPADDON_UPGRADE_FROM_VERSION and HTTPADDON_UPGRADE_TO_VERSION env vars are set. The intent is a dedicated CI job, since the scenario swaps the cluster-wide add-on release that the rest of the e2e suite assumes is pinned. Refs #1427 Signed-off-by: Sanil2108 --- tests/checks/upgrade_httpso/upgrade_test.go | 242 ++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 tests/checks/upgrade_httpso/upgrade_test.go diff --git a/tests/checks/upgrade_httpso/upgrade_test.go b/tests/checks/upgrade_httpso/upgrade_test.go new file mode 100644 index 000000000..303425124 --- /dev/null +++ b/tests/checks/upgrade_httpso/upgrade_test.go @@ -0,0 +1,242 @@ +//go:build e2e + +// Package upgrade_test exercises a minimal Helm-chart upgrade path: +// 1. Install a previous release of the http-add-on chart. +// 2. Create a sample workload + HTTPScaledObject and verify it scales. +// 3. `helm upgrade` to the current chart version. +// 4. Verify the HTTPScaledObject still reconciles and the workload still scales. +// +// Skipped unless both HTTPADDON_UPGRADE_FROM_VERSION and HTTPADDON_UPGRADE_TO_VERSION +// are set (e.g. "0.10.0" and "0.11.0"). Intended to run as a dedicated CI job so the +// main e2e suite — which expects a single pre-installed add-on — is unaffected. +package upgrade_test + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/client-go/kubernetes" + + . "github.com/kedacore/http-add-on/tests/helper" +) + +const ( + testName = "upgrade-httpso-test" + // releaseName and chart ref match the chart documented at + // https://github.com/kedacore/charts and referenced by tests/utils/setup_test.go. + releaseName = "http-add-on" + chartRef = "kedacore/http-add-on" + + fromVersionEnv = "HTTPADDON_UPGRADE_FROM_VERSION" + toVersionEnv = "HTTPADDON_UPGRADE_TO_VERSION" +) + +var ( + testNamespace = fmt.Sprintf("%s-ns", testName) + deploymentName = fmt.Sprintf("%s-deployment", testName) + serviceName = fmt.Sprintf("%s-service", testName) + httpScaledObjectName = fmt.Sprintf("%s-http-so", testName) + host = testName + minReplicaCount = 0 + maxReplicaCount = 1 +) + +type templateData struct { + TestNamespace string + DeploymentName string + ServiceName string + HTTPScaledObjectName string + Host string + MinReplicas int + MaxReplicas int +} + +const ( + serviceTemplate = ` +apiVersion: v1 +kind: Service +metadata: + name: {{.ServiceName}} + namespace: {{.TestNamespace}} + labels: + app: {{.DeploymentName}} +spec: + ports: + - port: 8080 + targetPort: http + protocol: TCP + name: http + selector: + app: {{.DeploymentName}} +` + + deploymentTemplate = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.DeploymentName}} + namespace: {{.TestNamespace}} + labels: + app: {{.DeploymentName}} +spec: + replicas: 0 + selector: + matchLabels: + app: {{.DeploymentName}} + template: + metadata: + labels: + app: {{.DeploymentName}} + spec: + containers: + - name: {{.DeploymentName}} + image: registry.k8s.io/e2e-test-images/agnhost:2.45 + args: + - netexec + ports: + - name: http + containerPort: 8080 + protocol: TCP + readinessProbe: + httpGet: + path: / + port: http +` + + loadJobTemplate = ` +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-request + namespace: {{.TestNamespace}} +spec: + template: + spec: + containers: + - name: curl-client + image: curlimages/curl + imagePullPolicy: Always + command: ["curl", "-H", "Host: {{.Host}}", "keda-add-ons-http-interceptor-proxy.keda:8080"] + restartPolicy: Never + activeDeadlineSeconds: 600 + backoffLimit: 5 +` + + httpScaledObjectTemplate = ` +kind: HTTPScaledObject +apiVersion: http.keda.sh/v1alpha1 +metadata: + name: {{.HTTPScaledObjectName}} + namespace: {{.TestNamespace}} +spec: + hosts: + - {{.Host}} + targetPendingRequests: 100 + scaledownPeriod: 10 + scaleTargetRef: + name: {{.DeploymentName}} + service: {{.ServiceName}} + port: 8080 + replicas: + min: {{ .MinReplicas }} + max: {{ .MaxReplicas }} +` +) + +func TestUpgrade(t *testing.T) { + fromVersion := os.Getenv(fromVersionEnv) + toVersion := os.Getenv(toVersionEnv) + if fromVersion == "" || toVersion == "" { + t.Skipf("skipping upgrade scenario: set %s and %s to run", fromVersionEnv, toVersionEnv) + } + + kc := GetKubernetesClient(t) + + t.Logf("--- installing http-add-on %s (baseline) ---", fromVersion) + installOrUpgradeAddon(t, fromVersion) + waitForAddonReady(t, kc) + + data, templates := getTemplateData() + + t.Log("--- creating workload on baseline ---") + CreateKubernetesResources(t, kc, testNamespace, data, templates) + t.Cleanup(func() { + DeleteKubernetesResources(t, testNamespace, data, templates) + }) + + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, minReplicaCount, 6, 10), + "replica count should be %d after setup", minReplicaCount) + + t.Log("--- scale out (baseline) ---") + testScaleOut(t, kc, data) + + t.Logf("--- upgrading http-add-on to %s ---", toVersion) + installOrUpgradeAddon(t, toVersion) + waitForAddonReady(t, kc) + + t.Log("--- HTTPScaledObject survives upgrade ---") + // Give the new operator a moment to reconcile pre-existing HTTPScaledObject resources. + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, maxReplicaCount, 6, 10), + "expected HTTPScaledObject to still route traffic and keep the deployment at max replicas post-upgrade") + + t.Log("--- scale in (post-upgrade) ---") + testScaleIn(t, kc, data) + + t.Log("--- scale out again (post-upgrade) ---") + testScaleOut(t, kc, data) +} + +func installOrUpgradeAddon(t *testing.T, version string) { + t.Helper() + _, err := ExecuteCommand("helm repo add kedacore https://kedacore.github.io/charts") + require.NoErrorf(t, err, "cannot add kedacore helm repo - %s", err) + _, err = ExecuteCommand("helm repo update kedacore") + require.NoErrorf(t, err, "cannot update kedacore helm repo - %s", err) + _, err = ExecuteCommand(fmt.Sprintf( + "helm upgrade --install %s %s --version %s --namespace %s --wait", + releaseName, chartRef, version, KEDANamespace, + )) + require.NoErrorf(t, err, "cannot install/upgrade %s to version %s - %s", releaseName, version, err) +} + +func waitForAddonReady(t *testing.T, kc *kubernetes.Clientset) { + t.Helper() + // Same deployments TestSetupKEDA waits for — names are stable across the releases we support upgrading between. + for _, name := range []string{"keda-add-ons-http-operator", "keda-add-ons-http-interceptor", "keda-add-ons-http-external-scaler"} { + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, name, KEDANamespace, 1, 30, 6), + "%s not ready after upgrade", name) + } +} + +func testScaleOut(t *testing.T, kc *kubernetes.Clientset, data templateData) { + t.Helper() + KubectlApplyWithTemplate(t, data, "loadJobTemplate", loadJobTemplate) + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, maxReplicaCount, 6, 10), + "replica count should be %d after load", maxReplicaCount) +} + +func testScaleIn(t *testing.T, kc *kubernetes.Clientset, data templateData) { + t.Helper() + KubectlDeleteWithTemplate(t, data, "loadJobTemplate", loadJobTemplate) + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, minReplicaCount, 12, 10), + "replica count should be %d after load stops", minReplicaCount) +} + +func getTemplateData() (templateData, []Template) { + return templateData{ + TestNamespace: testNamespace, + DeploymentName: deploymentName, + ServiceName: serviceName, + HTTPScaledObjectName: httpScaledObjectName, + Host: host, + MinReplicas: minReplicaCount, + MaxReplicas: maxReplicaCount, + }, []Template{ + {Name: "deploymentTemplate", Config: deploymentTemplate}, + {Name: "serviceNameTemplate", Config: serviceTemplate}, + {Name: "httpScaledObjectTemplate", Config: httpScaledObjectTemplate}, + } +} From 9d6ed9f6fdc0039f14eef1491e0de74d7bec0e09 Mon Sep 17 00:00:00 2001 From: Sanil2108 Date: Fri, 24 Apr 2026 20:49:09 +0530 Subject: [PATCH 2/3] =?UTF-8?q?test(e2e):=20address=20review=20=E2=80=94?= =?UTF-8?q?=20tear=20down=20kustomize=20install=20first,=20re-load=20after?= =?UTF-8?q?=20upgrade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two review concerns from Copilot: 1. Installing the Helm release into the `keda` namespace conflicts with the `keda-add-ons-http-*` resources `tests/utils/setup_test.go` already created via `make deploy-e2e`. `make undeploy` them before the first Helm install so the release can take ownership cleanly. Added a matching `helm uninstall` in t.Cleanup so the cluster is left without the Helm release at the end (a subsequent `make deploy-e2e` can restore the kustomize install). 2. The post-upgrade `WaitForDeploymentReplicaReadyCount(maxReplicaCount)` assumed load from the pre-upgrade scale-out was still pending, but `scaledownPeriod: 10s` expires before the upgrade completes. Added an explicit scale-in between the baseline and upgrade blocks, then a fresh scale-out after the upgrade — which is the real assertion we want (HTTPScaledObject survives the upgrade and still routes traffic under new load). Refs #1427 Signed-off-by: Sanil2108 --- tests/checks/upgrade_httpso/upgrade_test.go | 38 +++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/tests/checks/upgrade_httpso/upgrade_test.go b/tests/checks/upgrade_httpso/upgrade_test.go index 303425124..7031ff166 100644 --- a/tests/checks/upgrade_httpso/upgrade_test.go +++ b/tests/checks/upgrade_httpso/upgrade_test.go @@ -155,6 +155,20 @@ func TestUpgrade(t *testing.T) { kc := GetKubernetesClient(t) + // The main runner (tests/run-all.go → tests/utils/setup_test.go) installs the + // add-on via `make deploy-e2e` (kustomize/ko), which creates the same + // keda-add-ons-http-* resources Helm wants to own. Tear those down first so + // the Helm release can take ownership cleanly. `make undeploy` is idempotent + // (it swallows errors if nothing is deployed) so this also handles the case + // where the test runs on a fresh cluster. + t.Log("--- tearing down any pre-existing add-on install ---") + undeployPreExistingAddon(t) + t.Cleanup(func() { + // Leave the cluster without the Helm release when we're done; a follow-up + // `make deploy-e2e` can reinstall via kustomize/ko. + _, _ = ExecuteCommand(fmt.Sprintf("helm uninstall %s --namespace %s --wait", releaseName, KEDANamespace)) + }) + t.Logf("--- installing http-add-on %s (baseline) ---", fromVersion) installOrUpgradeAddon(t, fromVersion) waitForAddonReady(t, kc) @@ -173,20 +187,32 @@ func TestUpgrade(t *testing.T) { t.Log("--- scale out (baseline) ---") testScaleOut(t, kc, data) + t.Log("--- scale in (baseline) ---") + testScaleIn(t, kc, data) + t.Logf("--- upgrading http-add-on to %s ---", toVersion) installOrUpgradeAddon(t, toVersion) waitForAddonReady(t, kc) - t.Log("--- HTTPScaledObject survives upgrade ---") - // Give the new operator a moment to reconcile pre-existing HTTPScaledObject resources. - assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, maxReplicaCount, 6, 10), - "expected HTTPScaledObject to still route traffic and keep the deployment at max replicas post-upgrade") + // The upgrade itself takes longer than scaledownPeriod (10s), so any traffic + // applied before the upgrade will have drained by the time it finishes. + // Generate fresh load post-upgrade to prove the surviving HTTPScaledObject is + // still reconciled correctly and the interceptor still routes into it. + t.Log("--- scale out (post-upgrade) ---") + testScaleOut(t, kc, data) t.Log("--- scale in (post-upgrade) ---") testScaleIn(t, kc, data) +} - t.Log("--- scale out again (post-upgrade) ---") - testScaleOut(t, kc, data) +func undeployPreExistingAddon(t *testing.T) { + t.Helper() + // Runs from tests/checks/upgrade_httpso/ — repo root is two levels up. + out, err := ExecuteCommandWithDir("make undeploy", "../../..") + if err != nil { + // Not fatal — `make undeploy` may return non-zero on a clean cluster. + t.Logf("make undeploy returned error (may be harmless on a clean cluster): %s\n%s", err, string(out)) + } } func installOrUpgradeAddon(t *testing.T, version string) { From 7cfe2eb553f6fc53581d4960d7ff936d920e173a Mon Sep 17 00:00:00 2001 From: SanilK2108 Date: Fri, 15 May 2026 12:06:00 +0530 Subject: [PATCH 3/3] test(e2e): move upgrade scenario to test/e2e/upgrade/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/ contains deprecated HTTPScaledObject tests; new tests live under test/. Move the Helm-chart upgrade check to the canonical test/e2e/upgrade/ location. The make-undeploy relative path (../../..) is unchanged — both the old tests/checks/upgrade_httpso/ and the new test/e2e/upgrade/ are three directory levels below the repo root. Signed-off-by: Sanil Khurana Signed-off-by: SanilK2108 --- .../checks/upgrade_httpso => test/e2e/upgrade}/upgrade_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {tests/checks/upgrade_httpso => test/e2e/upgrade}/upgrade_test.go (99%) diff --git a/tests/checks/upgrade_httpso/upgrade_test.go b/test/e2e/upgrade/upgrade_test.go similarity index 99% rename from tests/checks/upgrade_httpso/upgrade_test.go rename to test/e2e/upgrade/upgrade_test.go index 7031ff166..2238263e9 100644 --- a/tests/checks/upgrade_httpso/upgrade_test.go +++ b/test/e2e/upgrade/upgrade_test.go @@ -207,7 +207,7 @@ func TestUpgrade(t *testing.T) { func undeployPreExistingAddon(t *testing.T) { t.Helper() - // Runs from tests/checks/upgrade_httpso/ — repo root is two levels up. + // Runs from test/e2e/upgrade/ — repo root is three levels up. out, err := ExecuteCommandWithDir("make undeploy", "../../..") if err != nil { // Not fatal — `make undeploy` may return non-zero on a clean cluster.