Skip to content

Commit d8630bb

Browse files
authored
chore e2e tests (#60)
1 parent d4d1231 commit d8630bb

10 files changed

Lines changed: 327 additions & 248 deletions

File tree

.github/workflows/test-e2e.yml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ jobs:
2121
builder: [pack, s2i]
2222
deployer: [knative, raw, keda]
2323
steps:
24+
- name: Free up disk space
25+
run: |
26+
# Remove large packages to free up disk space on GitHub runners
27+
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
28+
# Clean up Docker to start fresh
29+
docker system prune -af --volumes
30+
df -h
31+
2432
- name: Clone the code
2533
uses: actions/checkout@v6
2634

@@ -65,8 +73,21 @@ jobs:
6573
if: failure()
6674
run: |
6775
mkdir -p /tmp/k8s-artifacts
68-
kubectl logs -n func-operator-system -l control-plane=controller-manager --tail=-1 --all-containers --prefix --timestamps > /tmp/k8s-artifacts/func-operator.log
69-
kubectl get functions -A -o yaml > /tmp/functions.yaml
76+
kubectl logs -n func-operator-system -l control-plane=controller-manager --tail=-1 --all-containers --prefix --timestamps > /tmp/k8s-artifacts/func-operator.log || true
77+
for resource in functions deployments configmaps pipelineruns roles rolebindings; do
78+
kubectl get ${resource} -A -o yaml > /tmp/k8s-artifacts/${resource}.yaml || true
79+
done
80+
81+
# Install Tekton CLI for better PipelineRun log collection
82+
curl -LO https://github.com/tektoncd/cli/releases/download/v0.44.1/tkn_0.44.1_Linux_x86_64.tar.gz
83+
tar xvzf tkn_0.44.1_Linux_x86_64.tar.gz -C /tmp
84+
chmod +x /tmp/tkn
85+
86+
# Collect logs from all pipelineruns using Tekton CLI
87+
/tmp/tkn pipelinerun list -A > /tmp/k8s-artifacts/pipelinerun-list.txt 2>&1 || true
88+
kubectl get pipelineruns -A -o json | jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name)"' | while read ns name; do
89+
/tmp/tkn pipelinerun logs $name -n $ns > /tmp/k8s-artifacts/pipelinerun-logs-${ns}-${name}.log 2>&1 || true
90+
done
7091
7192
- name: Upload Kubernetes artifacts
7293
if: failure()

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ ARG FUNC_CLI_GH_REPO=knative/func
3939
ARG FUNC_CLI_BRANCH=main
4040

4141
# workaround to invalidate cache when func cli repo got updated
42-
ADD https://api.github.com/repos/${FUNC_CLI_GH_REPO}/git/refs/heads/${FUNC_CLI_BRANCH} version.json
42+
# Use git ls-remote instead of GitHub API to avoid rate limiting (60 req/hour for unauthenticated)
43+
# which caused merge queue failures due to multiple concurrent builds
44+
RUN git ls-remote https://github.com/${FUNC_CLI_GH_REPO} refs/heads/${FUNC_CLI_BRANCH} > version.json
4345

4446
WORKDIR /workspace
4547
RUN git clone --branch ${FUNC_CLI_BRANCH} --single-branch --depth 1 https://github.com/${FUNC_CLI_GH_REPO} .

Makefile

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,12 @@ test: manifests generate fmt vet setup-envtest ## Run tests.
119119
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
120120

121121
.PHONY: test-e2e ## Run e2e tests.
122-
test-e2e:
123-
go test -timeout 1h ./test/e2e/ -v -ginkgo.v -ginkgo.timeout=1h -ginkgo.label-filter="!bundle"
122+
test-e2e: ginkgo
123+
$(GINKGO) -v --timeout=1h --label-filter="!bundle" --fail-fast -p ./test/e2e/
124124

125125
.PHONY: test-e2e-bundle ## Run bundle e2e tests.
126-
test-e2e-bundle: operator-sdk docker-build docker-push bundle bundle-build bundle-push install-olm-in-cluster
127-
OPERATOR_SDK=$(OPERATOR_SDK) BUNDLE_IMG=$(BUNDLE_IMG) go test -timeout 1h ./test/e2e/ -v -ginkgo.v -ginkgo.timeout=1h -ginkgo.label-filter="bundle"
126+
test-e2e-bundle: operator-sdk docker-build docker-push bundle bundle-build bundle-push install-olm-in-cluster ginkgo
127+
OPERATOR_SDK=$(OPERATOR_SDK) BUNDLE_IMG=$(BUNDLE_IMG) $(GINKGO) -v --timeout=1h --label-filter="bundle" --fail-fast ./test/e2e/
128128

129129
.PHONY: install-olm-in-cluster
130130
install-olm-in-cluster: operator-sdk ## Install OLM in cluster if not already installed.
@@ -277,10 +277,12 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
277277
ENVTEST ?= $(LOCALBIN)/setup-envtest
278278
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
279279
MOCKERY = $(LOCALBIN)/mockery
280+
GINKGO = $(LOCALBIN)/ginkgo
280281

281282
## Tool Versions
282283
KUSTOMIZE_VERSION ?= v5.6.0
283284
CONTROLLER_TOOLS_VERSION ?= v0.18.0
285+
GINKGO_VERSION ?= v2.28.1
284286
#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)
285287
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
286288
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
@@ -321,6 +323,11 @@ mockery: ${MOCKERY} ## Download mockery locally if necessary.
321323
${MOCKERY}: $(LOCALBIN)
322324
$(call go-install-tool,${MOCKERY},github.com/vektra/mockery/v3,${MOCKERY_VERSION})
323325

326+
.PHONY: ginkgo
327+
ginkgo: $(GINKGO) ## Download ginkgo locally if necessary.
328+
$(GINKGO): $(LOCALBIN)
329+
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo,$(GINKGO_VERSION))
330+
324331
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
325332
# $1 - target path with name of binary
326333
# $2 - package url which can be installed

test/e2e/bundle_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() {
4747
testNamespaces []TestNamespace
4848
)
4949

50-
SetDefaultEventuallyTimeout(5 * time.Minute)
51-
SetDefaultEventuallyPollingInterval(time.Second)
52-
5350
BeforeAll(func() {
5451
bundleImage = os.Getenv("BUNDLE_IMG")
5552
Expect(bundleImage).ToNot(BeEmpty(), "BUNDLE_IMG must be given")

test/e2e/e2e_suite_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"testing"
23+
"time"
2324

2425
. "github.com/onsi/ginkgo/v2"
2526
. "github.com/onsi/gomega"
@@ -51,6 +52,11 @@ func TestE2E(t *testing.T) {
5152
var _ = BeforeSuite(func() {
5253
ctx = context.Background()
5354

55+
// Set global timeout for Eventually assertions
56+
// Must be set here (not in Describe blocks) to avoid race conditions in parallel execution
57+
SetDefaultEventuallyTimeout(10 * time.Minute)
58+
SetDefaultEventuallyPollingInterval(1 * time.Second)
59+
5460
// Register the Function API scheme
5561
err := functionsdevv1alpha1.AddToScheme(scheme.Scheme)
5662
Expect(err).NotTo(HaveOccurred())

test/e2e/e2e_test.go

Lines changed: 1 addition & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ package e2e
1919
import (
2020
"fmt"
2121
"os/exec"
22-
"time"
23-
24-
. "github.com/onsi/ginkgo/v2"
25-
. "github.com/onsi/gomega"
2622

2723
"github.com/functions-dev/func-operator/test/utils"
24+
. "github.com/onsi/ginkgo/v2"
2825
)
2926

3027
// namespace where the project is deployed in
@@ -33,191 +30,6 @@ const namespace = "func-operator-system"
3330
// serviceAccountName created for the project
3431
const serviceAccountName = "func-operator-controller-manager"
3532

36-
// metricsServiceName is the name of the metrics service of the project
37-
const metricsServiceName = "func-operator-controller-manager-metrics-service"
38-
39-
// metricsPort is the port of the metrics service providing the managers metrics
40-
const metricsPort = "8080"
41-
42-
var _ = Describe("Manager", func() {
43-
var controllerPodName string
44-
45-
// After each test, check for failures and collect logs, events,
46-
// and pod descriptions for debugging.
47-
AfterEach(func() {
48-
specReport := CurrentSpecReport()
49-
if specReport.Failed() {
50-
By("Fetching controller manager pod logs")
51-
cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
52-
controllerLogs, err := utils.Run(cmd)
53-
if err == nil {
54-
_, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs)
55-
} else {
56-
_, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err)
57-
}
58-
59-
By("Fetching Kubernetes events")
60-
cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp")
61-
eventsOutput, err := utils.Run(cmd)
62-
if err == nil {
63-
_, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput)
64-
} else {
65-
_, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err)
66-
}
67-
68-
By("Fetching curl-metrics logs")
69-
cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace)
70-
metricsOutput, err := utils.Run(cmd)
71-
if err == nil {
72-
_, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput)
73-
} else {
74-
_, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err)
75-
}
76-
77-
By("Fetching controller manager pod description")
78-
cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace)
79-
podDescription, err := utils.Run(cmd)
80-
if err == nil {
81-
fmt.Println("Pod description:\n", podDescription)
82-
} else {
83-
fmt.Println("Failed to describe controller pod")
84-
}
85-
}
86-
})
87-
88-
SetDefaultEventuallyTimeout(2 * time.Minute)
89-
SetDefaultEventuallyPollingInterval(time.Second)
90-
91-
Context("Manager", func() {
92-
It("should run successfully", func() {
93-
By("validating that the controller-manager pod is running as expected")
94-
verifyControllerUp := func(g Gomega) {
95-
// Get the name of the controller-manager pod
96-
cmd := exec.Command("kubectl", "get",
97-
"pods", "-l", "control-plane=controller-manager",
98-
"-o", "go-template={{ range .items }}"+
99-
"{{ if not .metadata.deletionTimestamp }}"+
100-
"{{ .metadata.name }}"+
101-
"{{ \"\\n\" }}{{ end }}{{ end }}",
102-
"-n", namespace,
103-
)
104-
105-
podOutput, err := utils.Run(cmd)
106-
g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve controller-manager pod information")
107-
podNames := utils.GetNonEmptyLines(podOutput)
108-
g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running")
109-
controllerPodName = podNames[0]
110-
g.Expect(controllerPodName).To(ContainSubstring("controller-manager"))
111-
112-
// Validate the pod's status
113-
cmd = exec.Command("kubectl", "get",
114-
"pods", controllerPodName, "-o", "jsonpath={.status.phase}",
115-
"-n", namespace,
116-
)
117-
output, err := utils.Run(cmd)
118-
g.Expect(err).NotTo(HaveOccurred())
119-
g.Expect(output).To(Equal("Running"), "Incorrect controller-manager pod status")
120-
}
121-
Eventually(verifyControllerUp).Should(Succeed())
122-
})
123-
124-
Context("with curl-metrics-pod", func() {
125-
curlMetricPodName := "curl-metrics"
126-
127-
AfterEach(func() {
128-
cmd := exec.Command("kubectl", "delete", "pod", curlMetricPodName, "-n", namespace, "--ignore-not-found")
129-
_, err := utils.Run(cmd)
130-
Expect(err).NotTo(HaveOccurred())
131-
})
132-
133-
It("should ensure the metrics endpoint is serving metrics", func() {
134-
By("validating that the metrics service is available")
135-
cmd := exec.Command("kubectl", "get", "service", metricsServiceName, "-n", namespace)
136-
_, err := utils.Run(cmd)
137-
Expect(err).NotTo(HaveOccurred(), "Metrics service should exist")
138-
139-
By("waiting for the metrics endpoint to be ready")
140-
verifyMetricsEndpointReady := func(g Gomega) {
141-
cmd := exec.Command("kubectl", "get", "endpoints", metricsServiceName, "-n", namespace)
142-
output, err := utils.Run(cmd)
143-
g.Expect(err).NotTo(HaveOccurred())
144-
g.Expect(output).To(ContainSubstring(metricsPort), "Metrics endpoint is not ready")
145-
}
146-
Eventually(verifyMetricsEndpointReady).Should(Succeed())
147-
148-
By("verifying that the controller manager is serving the metrics server")
149-
verifyMetricsServerStarted := func(g Gomega) {
150-
cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
151-
output, err := utils.Run(cmd)
152-
g.Expect(err).NotTo(HaveOccurred())
153-
g.Expect(output).To(ContainSubstring("controller-runtime.metrics\tServing metrics server"),
154-
"Metrics server not yet started")
155-
}
156-
Eventually(verifyMetricsServerStarted).Should(Succeed())
157-
158-
By("creating the curl-metrics pod to access the metrics endpoint")
159-
cmd = exec.Command("kubectl", "run", curlMetricPodName, "--restart=Never",
160-
"--namespace", namespace,
161-
"--image=curlimages/curl:latest",
162-
"--overrides",
163-
fmt.Sprintf(`{
164-
"spec": {
165-
"containers": [{
166-
"name": "curl",
167-
"image": "curlimages/curl:latest",
168-
"command": ["/bin/sh", "-c"],
169-
"args": ["curl -v %s.%s.svc.cluster.local:%s/metrics"],
170-
"securityContext": {
171-
"allowPrivilegeEscalation": false,
172-
"capabilities": {
173-
"drop": ["ALL"]
174-
},
175-
"runAsNonRoot": true,
176-
"runAsUser": 1000,
177-
"seccompProfile": {
178-
"type": "RuntimeDefault"
179-
}
180-
}
181-
}],
182-
"serviceAccount": "%s"
183-
}
184-
}`, metricsServiceName, namespace, metricsPort, serviceAccountName))
185-
_, err = utils.Run(cmd)
186-
Expect(err).NotTo(HaveOccurred(), "Failed to create curl-metrics pod")
187-
188-
By("waiting for the curl-metrics pod to complete.")
189-
verifyCurlUp := func(g Gomega) {
190-
cmd := exec.Command("kubectl", "get", "pods", curlMetricPodName,
191-
"-o", "jsonpath={.status.phase}",
192-
"-n", namespace)
193-
output, err := utils.Run(cmd)
194-
g.Expect(err).NotTo(HaveOccurred())
195-
g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status")
196-
}
197-
Eventually(verifyCurlUp, 5*time.Minute).Should(Succeed())
198-
199-
By("getting the metrics by checking curl-metrics logs")
200-
metricsOutput := getMetricsOutput()
201-
Expect(metricsOutput).To(ContainSubstring(
202-
"controller_runtime_reconcile_total",
203-
))
204-
})
205-
})
206-
207-
// +kubebuilder:scaffold:e2e-webhooks-checks
208-
})
209-
})
210-
211-
// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.
212-
func getMetricsOutput() string {
213-
By("getting the curl-metrics logs")
214-
cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace)
215-
metricsOutput, err := utils.Run(cmd)
216-
Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod")
217-
Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK"))
218-
return metricsOutput
219-
}
220-
22133
// logFailedTestDetails logs function resource and controller logs on test failure
22234
func logFailedTestDetails(functionName, functionNamespace string) {
22335
specReport := CurrentSpecReport()

0 commit comments

Comments
 (0)