Skip to content

Commit 78ccb5e

Browse files
authored
[castai-umbrella] Add e2e pipeline for umbrella (#1355)
1 parent 183d6c1 commit 78ccb5e

4 files changed

Lines changed: 371 additions & 35 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Umbrella E2E Tests
2+
3+
on:
4+
# Run on PRs where the title contains "castai-umbrella".
5+
pull_request:
6+
types: [opened, synchronize, reopened, edited]
7+
# Run automatically when a castai-umbrella release is published.
8+
release:
9+
types: [published]
10+
# Allow manual runs with optional mode filter.
11+
workflow_dispatch:
12+
inputs:
13+
focus:
14+
description: "Ginkgo focus filter (leave empty to run all modes)"
15+
required: false
16+
default: ""
17+
18+
jobs:
19+
e2e:
20+
if: >
21+
github.event_name == 'workflow_dispatch' ||
22+
(github.event_name == 'release' && startsWith(github.event.release.tag_name, 'castai-')) ||
23+
(github.event_name == 'pull_request' && contains(github.event.pull_request.title, 'castai-umbrella'))
24+
runs-on: ubuntu-latest
25+
timeout-minutes: 90
26+
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v6
30+
31+
- name: Set up Go
32+
uses: actions/setup-go@v5
33+
with:
34+
go-version-file: test/e2e/go.mod
35+
36+
- name: Install Helm
37+
uses: azure/setup-helm@v4.3.1
38+
with:
39+
version: v3.17.0
40+
41+
- name: Install kind
42+
uses: helm/kind-action@v1.12.0
43+
with:
44+
install_only: true
45+
46+
- name: Install kubectl
47+
uses: azure/setup-kubectl@v4
48+
49+
- name: Add Helm repos
50+
run: |
51+
helm repo add castai-helm https://castai.github.io/helm-charts
52+
helm repo update castai-helm
53+
54+
- name: Update umbrella subchart dependencies
55+
run: |
56+
for sub in kent autoscaler autoscaler-anywhere autoscaler-openshift; do
57+
path="charts/castai-umbrella/charts/${sub}"
58+
if [ -d "$path" ]; then
59+
helm dependency update "$path"
60+
fi
61+
done
62+
helm dependency update charts/castai-umbrella
63+
64+
- name: Run e2e tests
65+
env:
66+
CASTAI_API_KEY: ${{ secrets.CASTAI_API_KEY }}
67+
API_URL: ${{ vars.CASTAI_API_URL }}
68+
run: |
69+
FOCUS="${{ github.event.inputs.focus }}"
70+
ARGS="-v -timeout 75m"
71+
if [ -n "$FOCUS" ]; then
72+
ARGS="$ARGS --ginkgo.focus=${FOCUS}"
73+
fi
74+
cd test/e2e && go test ./... $ARGS

test/e2e/Makefile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# CASTAI_API_KEY=<your-key> make e2e-autoscaler-anywhere
1414
# CASTAI_API_KEY=<your-key> make e2e-autoscaler-openshift
1515

16-
.PHONY: e2e e2e-kent e2e-autoscaler e2e-autoscaler-anywhere e2e-autoscaler-openshift e2e-upgrade e2e-uninstall deps tidy
16+
.PHONY: e2e e2e-kent e2e-autoscaler e2e-autoscaler-workload e2e-autoscaler-anywhere e2e-autoscaler-openshift e2e-upgrade e2e-uninstall deps tidy
1717

1818
# Run all e2e tests (each context creates its own Kind cluster)
1919
e2e:
@@ -27,6 +27,10 @@ e2e-kent:
2727
e2e-autoscaler:
2828
go test ./... -v -timeout 30m --ginkgo.focus="autoscaler mode"
2929

30+
# Run only the autoscaler workload-autoscaler mode tests
31+
e2e-autoscaler-workload:
32+
go test ./... -v -timeout 30m --ginkgo.focus="autoscaler workload-autoscaler mode"
33+
3034
# Run only the autoscaler-anywhere mode tests
3135
e2e-autoscaler-anywhere:
3236
go test ./... -v -timeout 30m --ginkgo.focus="autoscaler-anywhere mode"
@@ -35,9 +39,9 @@ e2e-autoscaler-anywhere:
3539
e2e-autoscaler-openshift:
3640
go test ./... -v -timeout 30m --ginkgo.focus="autoscaler-openshift mode"
3741

38-
# Run only the upgrade tests
42+
# Run only the readonly-to-full upgrade tests
3943
e2e-upgrade:
40-
go test ./... -v -timeout 30m --ginkgo.focus="in-place upgrade"
44+
go test ./... -v -timeout 30m --ginkgo.focus="autoscaler readonly-to-full upgrade"
4145

4246
# Run only the uninstall tests
4347
e2e-uninstall:

test/e2e/helpers_test.go

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"os/exec"
66
"strings"
7-
"time"
87

98
. "github.com/onsi/gomega"
109

@@ -26,19 +25,16 @@ func NewUmbrellaHelmHelper(releaseName, namespace, apiURL string) *UmbrellaHelmH
2625
}
2726

2827
func (h *UmbrellaHelmHelper) InstallKentMode(apiKey string) error {
28+
// preflight checks for Karpenter deployment + CRDs which don't exist on Kind.
29+
// castai-aws-vpc-cni runs a pre-install hook that patches the aws-node daemonset,
30+
// which also doesn't exist on Kind.
2931
cmd := exec.Command("helm", "upgrade", "--install", h.releaseName,
3032
chartPath,
3133
"--namespace", h.namespace,
3234
"--create-namespace",
3335
"--set", "kent.enabled=true",
34-
"--set", "kent.castai-agent.enabled=true",
35-
"--set", "kent.castai-kentroller.enabled=true",
36-
"--set", "kent.castai-cluster-controller.enabled=true",
37-
"--set", "kent.castai-evictor.enabled=true",
38-
"--set", "kent.castai-live.enabled=false",
39-
"--set", "kent.castai-pod-mutator.enabled=false",
40-
"--set", "kent.castai-workload-autoscaler.enabled=false",
41-
"--set", "kent.metrics-server.enabled=false",
36+
"--set", "kent.preflight.enabled=false",
37+
"--set", "kent.castai-live.castai-aws-vpc-cni.enabled=false",
4238
"--set", fmt.Sprintf("global.castai.apiKey=%s", apiKey),
4339
"--set", fmt.Sprintf("global.castai.apiURL=%s", h.apiURL),
4440
"--timeout", defaultHelmTimeout,
@@ -50,19 +46,74 @@ func (h *UmbrellaHelmHelper) InstallKentMode(apiKey string) error {
5046
return nil
5147
}
5248

49+
func (h *UmbrellaHelmHelper) InstallAutoscalerReadonlyMode(apiKey, provider string) error {
50+
// readonly tag activates: castai-agent, castai-spot-handler, castai-kvisor, gpu-metrics-exporter.
51+
cmd := exec.Command("helm", "upgrade", "--install", h.releaseName,
52+
chartPath,
53+
"--namespace", h.namespace,
54+
"--create-namespace",
55+
"--set", "tags.readonly=true",
56+
"--set", fmt.Sprintf("global.castai.apiKey=%s", apiKey),
57+
"--set", fmt.Sprintf("global.castai.apiURL=%s", h.apiURL),
58+
"--set", fmt.Sprintf("global.castai.provider=%s", provider),
59+
"--timeout", defaultHelmTimeout,
60+
)
61+
_, err := utils.Run(cmd)
62+
if err != nil {
63+
return fmt.Errorf("helm install autoscaler readonly mode failed: %w", err)
64+
}
65+
return nil
66+
}
67+
68+
func (h *UmbrellaHelmHelper) UpgradeToFullMode() error {
69+
// Swap readonly tag for full. --reuse-values preserves apiKey/apiURL/provider.
70+
// readonly → full is not an additive path so both tags must be set explicitly.
71+
cmd := exec.Command("helm", "upgrade", h.releaseName,
72+
chartPath,
73+
"--namespace", h.namespace,
74+
"--reuse-values",
75+
"--set", "tags.readonly=false",
76+
"--set", "tags.full=true",
77+
"--timeout", defaultHelmTimeout,
78+
)
79+
_, err := utils.Run(cmd)
80+
if err != nil {
81+
return fmt.Errorf("helm upgrade to full mode failed: %w", err)
82+
}
83+
return nil
84+
}
85+
86+
func (h *UmbrellaHelmHelper) InstallAutoscalerWorkloadMode(apiKey, provider string) error {
87+
// workload-autoscaler tag activates: castai-agent, castai-spot-handler, castai-kvisor,
88+
// gpu-metrics-exporter, castai-cluster-controller, castai-evictor, castai-pod-mutator,
89+
// castai-workload-autoscaler, castai-workload-autoscaler-exporter.
90+
// NOT included: castai-pod-pinner, castai-live (those are node-autoscaler + full only).
91+
cmd := exec.Command("helm", "upgrade", "--install", h.releaseName,
92+
chartPath,
93+
"--namespace", h.namespace,
94+
"--create-namespace",
95+
"--set", "tags.workload-autoscaler=true",
96+
"--set", fmt.Sprintf("global.castai.apiKey=%s", apiKey),
97+
"--set", fmt.Sprintf("global.castai.apiURL=%s", h.apiURL),
98+
"--set", fmt.Sprintf("global.castai.provider=%s", provider),
99+
"--timeout", defaultHelmTimeout,
100+
)
101+
_, err := utils.Run(cmd)
102+
if err != nil {
103+
return fmt.Errorf("helm install autoscaler workload mode failed: %w", err)
104+
}
105+
return nil
106+
}
107+
53108
func (h *UmbrellaHelmHelper) InstallAutoscalerMode(apiKey, provider string) error {
109+
// node-autoscaler tag activates: castai-agent, castai-spot-handler, castai-kvisor,
110+
// gpu-metrics-exporter, castai-cluster-controller, castai-evictor, castai-pod-mutator,
111+
// castai-pod-pinner, castai-live (see autoscaler/Chart.yaml tag definitions).
54112
cmd := exec.Command("helm", "upgrade", "--install", h.releaseName,
55113
chartPath,
56114
"--namespace", h.namespace,
57115
"--create-namespace",
58-
"--set", "autoscaler.enabled=true",
59-
"--set", "autoscaler.castai-agent.enabled=true",
60-
"--set", "autoscaler.castai-cluster-controller.enabled=true",
61-
"--set", "autoscaler.castai-evictor.enabled=true",
62-
"--set", "autoscaler.castai-live.enabled=false",
63-
"--set", "autoscaler.castai-pod-mutator.enabled=false",
64-
"--set", "autoscaler.castai-workload-autoscaler.enabled=false",
65-
"--set", "autoscaler.castai-kvisor.enabled=true",
116+
"--set", "tags.node-autoscaler=true",
66117
"--set", fmt.Sprintf("global.castai.apiKey=%s", apiKey),
67118
"--set", fmt.Sprintf("global.castai.apiURL=%s", h.apiURL),
68119
"--set", fmt.Sprintf("global.castai.provider=%s", provider),
@@ -76,17 +127,14 @@ func (h *UmbrellaHelmHelper) InstallAutoscalerMode(apiKey, provider string) erro
76127
}
77128

78129
func (h *UmbrellaHelmHelper) InstallAutoscalerAnywhereMode(apiKey, clusterName string) error {
130+
// tags.autoscaler-anywhere includes the autoscaler-anywhere sub-chart.
131+
// Components within that sub-chart are enabled by default in its values.yaml;
132+
// ANYWHERE_CLUSTER_NAME is passed via additionalEnv so the agent can register.
79133
cmd := exec.Command("helm", "upgrade", "--install", h.releaseName,
80134
chartPath,
81135
"--namespace", h.namespace,
82136
"--create-namespace",
83-
"--set", "autoscaler-anywhere.enabled=true",
84-
"--set", "autoscaler-anywhere.castai-agent.enabled=true",
85-
"--set", "autoscaler-anywhere.castai-cluster-controller.enabled=true",
86-
"--set", "autoscaler-anywhere.castai-evictor.enabled=true",
87-
"--set", "autoscaler-anywhere.castai-pod-mutator.enabled=true",
88-
"--set", "autoscaler-anywhere.castai-workload-autoscaler.enabled=false",
89-
"--set", "autoscaler-anywhere.castai-workload-autoscaler-exporter.enabled=false",
137+
"--set", "tags.autoscaler-anywhere=true",
90138
"--set", fmt.Sprintf("autoscaler-anywhere.castai-agent.additionalEnv.ANYWHERE_CLUSTER_NAME=%s", clusterName),
91139
"--set", fmt.Sprintf("global.castai.apiKey=%s", apiKey),
92140
"--set", fmt.Sprintf("global.castai.apiURL=%s", h.apiURL),
@@ -120,9 +168,13 @@ func (h *UmbrellaHelmHelper) InstallAutoscalerOpenshiftMode(apiKey string) error
120168
}
121169

122170
func (h *UmbrellaHelmHelper) Uninstall() error {
171+
// --no-hooks skips pre/post-delete hooks (e.g. webhook teardown calls) that
172+
// block when pods are unhealthy. The namespace and Kind cluster are deleted
173+
// immediately after, so hook-managed resources are cleaned up anyway.
123174
cmd := exec.Command("helm", "uninstall", h.releaseName,
124175
"--namespace", h.namespace,
125176
"--ignore-not-found",
177+
"--no-hooks",
126178
"--timeout", defaultHelmTimeout,
127179
)
128180
_, err := utils.Run(cmd)
@@ -236,12 +288,6 @@ func (n *NamespaceHelper) VerifyExists(g Gomega, namespace string) {
236288
g.Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Namespace %s should exist", namespace))
237289
}
238290

239-
func waitForDeployment(podHelper *PodHelper, name string, timeout time.Duration) {
240-
Eventually(func(g Gomega) {
241-
podHelper.VerifyDeploymentExists(g, name)
242-
}, timeout, 5*time.Second).Should(Succeed())
243-
}
244-
245291
// VerifyAtLeastOnePodReady checks that the named deployment has at least one
246292
// available replica according to the deployment status — no label guessing needed.
247293
func (p *PodHelper) VerifyAtLeastOnePodReady(g Gomega, deploymentName string) {

0 commit comments

Comments
 (0)