Skip to content

Commit 774afd2

Browse files
committed
Add kustomization to e2e suite
Add a dedicated kustomization overlay (config/test-e2e) that includes Prometheus, allowing e2e tests to use 'make deploy-e2e' without modifying config/default/kustomization.yaml. Signed-off-by: Pujol <enric.pujol@sap.com>
1 parent 556f847 commit 774afd2

5 files changed

Lines changed: 43 additions & 231 deletions

File tree

‎Makefile‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ deploy: FORCE generate install-kustomize
125125
@printf "\e[1;36m>> kustomize build config/default | kubectl apply -f -\e[0m\n"
126126
@kustomize build config/default | kubectl apply -f -
127127

128+
# Deploy controller for e2e tests (includes Prometheus)
129+
deploy-e2e: FORCE generate install-kustomize
130+
@printf "\e[1;36m>> kustomize build config/test-e2e | kubectl apply -f -\e[0m\n"
131+
@kustomize build config/test-e2e | kubectl apply -f -
132+
128133
# Undeploy controller from the k8s cluster
129134
undeploy: FORCE install-kustomize
130135
@printf "\e[1;36m>> kustomize build config/default | kubectl delete -f -\e[0m\n"

‎config/test-e2e/kustomization.yaml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
apiVersion: kustomize.config.k8s.io/v1beta1
5+
kind: Kustomization
6+
7+
namespace: network-operator-system
8+
9+
resources:
10+
- ../default
11+
- ../prometheus

‎test/e2e/e2e_suite_test.go‎

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ var (
3030
// with the code source changes to be tested.
3131
const image = "ghcr.io/ironcore-dev/network-operator:latest"
3232

33-
// serverImage is the name of the image which will be built and loaded
34-
// with the gNMI test server.
35-
const serverImage = "ghcr.io/ironcore-dev/gnmi-test-server:latest"
36-
3733
// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
3834
// temporary environment to validate project changes with the purposed to be used in CI jobs.
3935
// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs
@@ -45,31 +41,15 @@ func TestE2E(t *testing.T) {
4541
}
4642

4743
var _ = BeforeSuite(func(ctx SpecContext) {
48-
By("Ensure that Prometheus is enabled")
49-
cwd, err := GetProjectDir()
50-
Expect(err).NotTo(HaveOccurred(), "Failed to get project directory")
51-
52-
err = UncommentCode(cwd+"/config/default/kustomization.yaml", "#- ../prometheus", "#")
53-
Expect(err).NotTo(HaveOccurred(), "Failed to enable Prometheus")
54-
5544
By("building the manager(Operator) image")
5645
cmd := exec.CommandContext(ctx, "make", "docker-build", "IMG="+image)
57-
_, err = Run(cmd)
46+
_, err := Run(cmd)
5847
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image")
5948

6049
By("loading the manager(Operator) image on Kind")
6150
err = LoadImageToKindClusterWithName(ctx, image)
6251
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind")
6352

64-
By("building the gnmi-test-server image")
65-
cmd = exec.CommandContext(ctx, "make", "docker-build-test-gnmi-server", "TEST_SERVER_IMG="+serverImage)
66-
_, err = Run(cmd)
67-
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the gnmi-test-server image")
68-
69-
By("loading the gnmi-test-server image on Kind")
70-
err = LoadImageToKindClusterWithName(ctx, serverImage)
71-
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the gnmi-test-server image into Kind")
72-
7353
// The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing.
7454
// To prevent errors when tests run in environments with Prometheus or CertManager already installed,
7555
// we check for their presence before execution.

‎test/e2e/e2e_test.go‎

Lines changed: 13 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ import (
1010
"os"
1111
"os/exec"
1212
"path/filepath"
13-
"slices"
1413
"strings"
1514
"time"
1615

1716
. "github.com/onsi/ginkgo/v2"
1817
. "github.com/onsi/gomega"
19-
"golang.org/x/tools/txtar"
2018
)
2119

2220
// namespace where the project is deployed in
@@ -33,45 +31,17 @@ const metricsRoleBindingName = "network-operator-metrics-binding"
3331

3432
var _ = Describe("Manager", Ordered, func() {
3533
var controllerPodName string
36-
var gnmiServerIPAddr string
3734

3835
// Before running the tests, set up the environment by creating the namespace,
3936
// enforce the restricted security policy to the namespace, installing CRDs,
4037
// and deploying the controller.
4138
BeforeAll(func(ctx SpecContext) {
42-
By("deploying the gnmi-test-server")
43-
cmd := exec.CommandContext(
44-
ctx, "kubectl", "run", "gnmi-test-server",
45-
"--image", serverImage,
46-
"--image-pull-policy", "Never",
47-
"--namespace", "default",
48-
"--restart", "Never",
49-
"--port", "8000",
50-
"--port", "9339",
51-
)
52-
_, err := Run(cmd)
53-
Expect(err).NotTo(HaveOccurred(), "Failed to deploy the gnmi-test-server")
54-
55-
cmd = exec.CommandContext(
56-
ctx, "kubectl", "wait", "pods/gnmi-test-server",
57-
"--for", "condition=Ready",
58-
"--namespace", "default",
59-
"--timeout", "1m",
60-
)
61-
_, err = Run(cmd)
62-
Expect(err).NotTo(HaveOccurred())
63-
64-
cmd = exec.CommandContext(
65-
ctx, "kubectl", "get", "pod", "gnmi-test-server",
66-
"--output", "jsonpath='{.status.podIP}'",
67-
"--namespace", "default",
68-
)
39+
By("creating manager namespace")
40+
cmd := exec.CommandContext(ctx, "kubectl", "create", "ns", namespace, "--dry-run=client", "-o", "yaml")
6941
out, err := Run(cmd)
7042
Expect(err).NotTo(HaveOccurred())
71-
gnmiServerIPAddr = strings.ReplaceAll(strings.TrimSpace(out), "'", "")
72-
73-
By("creating manager namespace")
74-
cmd = exec.CommandContext(ctx, "kubectl", "create", "ns", namespace)
43+
cmd = exec.CommandContext(ctx, "kubectl", "apply", "-f", "-")
44+
cmd.Stdin = strings.NewReader(out)
7545
_, err = Run(cmd)
7646
Expect(err).NotTo(HaveOccurred(), "Failed to create namespace")
7747

@@ -86,21 +56,25 @@ var _ = Describe("Manager", Ordered, func() {
8656
Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs")
8757

8858
By("deploying the controller-manager")
89-
cmd = exec.CommandContext(ctx, "make", "deploy")
90-
_, err = Run(cmd)
91-
Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager")
59+
// Retry deploy because cert-manager webhook may not be ready immediately
60+
// after its deployment is Available (TLS certificate propagation delay).
61+
Eventually(func() error {
62+
cmd = exec.CommandContext(ctx, "make", "deploy-e2e")
63+
_, err = Run(cmd)
64+
return err
65+
}).WithTimeout(2*time.Minute).WithPolling(10*time.Second).Should(Succeed(), "Failed to deploy the controller-manager")
9266
})
9367

9468
// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,
9569
// and deleting the namespace.
9670
AfterAll(func(ctx SpecContext) {
9771
By("cleaning up the ClusterRoleBinding of the service account to allow access to metrics")
98-
cmd := exec.CommandContext(ctx, "kubectl", "delete", "clusterrolebinding", metricsRoleBindingName)
72+
cmd := exec.CommandContext(ctx, "kubectl", "delete", "clusterrolebinding", metricsRoleBindingName, "--ignore-not-found")
9973
_, err := Run(cmd)
10074
Expect(err).NotTo(HaveOccurred(), "Failed to delete ClusterRoleBinding")
10175

10276
By("cleaning up the curl pod for metrics")
103-
cmd = exec.CommandContext(ctx, "kubectl", "delete", "pod", "curl-metrics", "-n", namespace)
77+
cmd = exec.CommandContext(ctx, "kubectl", "delete", "pod", "curl-metrics", "-n", namespace, "--ignore-not-found")
10478
_, err = Run(cmd)
10579
Expect(err).NotTo(HaveOccurred(), "Failed to delete curl-metrics pod")
10680

@@ -118,11 +92,6 @@ var _ = Describe("Manager", Ordered, func() {
11892
cmd = exec.CommandContext(ctx, "kubectl", "delete", "ns", namespace, "--ignore-not-found")
11993
_, err = Run(cmd)
12094
Expect(err).NotTo(HaveOccurred(), "Failed to delete namespace")
121-
122-
By("cleaning up the gnmi-test-server pod")
123-
cmd = exec.CommandContext(ctx, "kubectl", "delete", "pod", "gnmi-test-server", "-n", "default")
124-
_, err = Run(cmd)
125-
Expect(err).NotTo(HaveOccurred(), "Failed to delete gnmi-test-server pod")
12695
})
12796

12897
// After each test, check for failures and collect logs, events,
@@ -311,113 +280,6 @@ var _ = Describe("Manager", Ordered, func() {
311280
})
312281

313282
// +kubebuilder:scaffold:e2e-webhooks-checks
314-
315-
// TODO: Customize the e2e test suite with scenarios specific to your project.
316-
// Consider applying sample/CR(s) and check their status and/or verifying
317-
// the reconciliation by using the metrics, i.e.:
318-
// metricsOutput := getMetricsOutput()
319-
// Expect(metricsOutput).To(ContainSubstring(
320-
// fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`,
321-
// strings.ToLower(<Kind>),
322-
// ))
323-
324-
DescribeTable(
325-
"Should reconcile the api objects",
326-
func(ctx SpecContext, file string, numFiles int) {
327-
device := `
328-
apiVersion: networking.metal.ironcore.dev/v1alpha1
329-
kind: Device
330-
metadata:
331-
name: device
332-
namespace: default
333-
spec:
334-
endpoint:
335-
address: "%s"`
336-
err := Apply(ctx, fmt.Sprintf(device, gnmiServerIPAddr+":9339"))
337-
Expect(err).NotTo(HaveOccurred(), "Failed to apply Device")
338-
339-
dir, err := GetProjectDir()
340-
Expect(err).NotTo(HaveOccurred(), "Failed to get project directory")
341-
342-
a, err := txtar.ParseFile(filepath.Join(dir, "test", "e2e", "testdata", file))
343-
Expect(err).NotTo(HaveOccurred(), "Failed to parse test file")
344-
Expect(a.Files).To(HaveLen(numFiles), "Unexpected number of files in the test archive")
345-
346-
// All sections except the last are resource manifests; last is expected state.
347-
resources := a.Files[:len(a.Files)-1]
348-
stateFile := a.Files[len(a.Files)-1]
349-
350-
for _, res := range resources {
351-
err = Apply(ctx, string(res.Data))
352-
Expect(err).NotTo(HaveOccurred(), "Failed to apply resource %s", res.Name)
353-
354-
// Determine wait condition from resource type prefix.
355-
// vlans/ have no provider in openconfig — skip wait.
356-
var condition string
357-
switch {
358-
case strings.HasPrefix(res.Name, "banners/"):
359-
condition = "Ready"
360-
case strings.HasPrefix(res.Name, "vlans/"):
361-
continue
362-
default:
363-
condition = "Configured"
364-
}
365-
366-
// #nosec G204
367-
cmd := exec.CommandContext(
368-
ctx, "kubectl", "wait", res.Name,
369-
"--for", "condition="+condition,
370-
"--namespace", "default",
371-
"--timeout", "5m",
372-
)
373-
_, err = Run(cmd)
374-
Expect(err).NotTo(HaveOccurred())
375-
}
376-
377-
cmd := exec.CommandContext(
378-
ctx, "kubectl", "exec", "gnmi-test-server",
379-
"--namespace", "default",
380-
"--",
381-
"wget", "-qO-", "http://localhost:8000/v1/state",
382-
)
383-
got, err := Run(cmd)
384-
Expect(err).NotTo(HaveOccurred(), "Failed to execute command on gnmi-test-server")
385-
386-
err = CompareJSON(got, string(stateFile.Data))
387-
Expect(err).NotTo(HaveOccurred(), "State output does not match expected JSON")
388-
389-
// Delete resources in reverse order.
390-
for _, res := range slices.Backward(resources) {
391-
// #nosec G204
392-
cmd = exec.CommandContext(ctx, "kubectl", "delete", res.Name)
393-
_, err = Run(cmd)
394-
Expect(err).NotTo(HaveOccurred(), "Failed to delete object")
395-
}
396-
397-
cmd = exec.CommandContext(ctx, "kubectl", "delete", "devices/device", "--cascade=foreground")
398-
_, err = Run(cmd)
399-
Expect(err).NotTo(HaveOccurred(), "Failed to delete object")
400-
401-
cmd = exec.CommandContext(
402-
ctx, "kubectl", "exec", "gnmi-test-server",
403-
"--namespace", "default",
404-
"--",
405-
"wget", "-qO-", "--header", "X-HTTP-Method-Override: DELETE", "http://localhost:8000/v1/state",
406-
)
407-
_, err = Run(cmd)
408-
Expect(err).NotTo(HaveOccurred(), "Failed to execute command on gnmi-test-server")
409-
},
410-
Entry("Loopback Interface", "interface.txt", 2),
411-
Entry("Loopback Multi-Address", "interface_loopback_multi_addr.txt", 2),
412-
Entry("Physical IPv4 Address", "interface_physical_ipv4.txt", 2),
413-
Entry("Physical Unnumbered", "interface_physical_unnumbered.txt", 3),
414-
Entry("Physical Switchport Access", "interface_physical_switchport_access.txt", 2),
415-
Entry("Physical Switchport Trunk", "interface_physical_switchport_trunk.txt", 2),
416-
Entry("Aggregate L2 Trunk", "interface_aggregate_l2_trunk.txt", 3),
417-
Entry("Aggregate L3 Address", "interface_aggregate_l3.txt", 3),
418-
Entry("Routed VLAN", "interface_routed_vlan.txt", 3),
419-
Entry("Banner PreLogin", "banner.txt", 2),
420-
)
421283
})
422284
})
423285

‎test/e2e/util_test.go‎

Lines changed: 13 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package e2e
55

66
import (
7-
"bufio"
87
"bytes"
98
"context"
109
"encoding/json"
@@ -164,17 +163,19 @@ func InstallCertManager(ctx context.Context) error {
164163
if _, err := Run(cmd); err != nil {
165164
return err
166165
}
167-
// Wait for cert-manager-webhook to be ready, which can take time if cert-manager
168-
// was re-installed after uninstalling on a cluster.
169-
cmd = exec.CommandContext(
170-
ctx, "kubectl", "wait", "deployment.apps/cert-manager-webhook",
171-
"--for", "condition=Available",
172-
"--namespace", "cert-manager",
173-
"--timeout", "5m",
174-
)
175-
176-
_, err := Run(cmd)
177-
return err
166+
// Wait for all cert-manager deployments to be ready.
167+
for _, deploy := range []string{"cert-manager", "cert-manager-cainjector", "cert-manager-webhook"} {
168+
cmd = exec.CommandContext(
169+
ctx, "kubectl", "wait", "deployment.apps/"+deploy,
170+
"--for", "condition=Available",
171+
"--namespace", "cert-manager",
172+
"--timeout", "5m",
173+
)
174+
if _, err := Run(cmd); err != nil {
175+
return err
176+
}
177+
}
178+
return nil
178179
}
179180

180181
// UninstallCertManager uninstalls the cert manager
@@ -276,50 +277,3 @@ func GetProjectDir() (string, error) {
276277
wd = strings.ReplaceAll(wd, "/test/e2e", "")
277278
return wd, nil
278279
}
279-
280-
// UncommentCode searches for target in the file and remove the comment prefix
281-
// of the target content. The target content may span multiple lines.
282-
func UncommentCode(filename, target, prefix string) error {
283-
content, err := os.ReadFile(filename)
284-
if err != nil {
285-
return err
286-
}
287-
288-
before, after, ok := bytes.Cut(content, []byte(target))
289-
if !ok {
290-
if bytes.Contains(content, []byte(target)[len(prefix):]) {
291-
return nil // already uncommented
292-
}
293-
294-
return fmt.Errorf("unable to find the code %s to be uncomment", target)
295-
}
296-
297-
out := new(bytes.Buffer)
298-
if _, err = out.Write(before); err != nil {
299-
return err
300-
}
301-
302-
scanner := bufio.NewScanner(bytes.NewBufferString(target))
303-
if !scanner.Scan() {
304-
return nil
305-
}
306-
for {
307-
_, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix))
308-
if err != nil {
309-
return err
310-
}
311-
// Avoid writing a newline in case the previous line was the last in target.
312-
if !scanner.Scan() {
313-
break
314-
}
315-
if _, err = out.WriteString("\n"); err != nil {
316-
return err
317-
}
318-
}
319-
320-
if _, err = out.Write(after); err != nil {
321-
return err
322-
}
323-
324-
return os.WriteFile(filename, out.Bytes(), 0o644)
325-
}

0 commit comments

Comments
 (0)