Skip to content

Commit 88d04b6

Browse files
Implement OpenShift Tests Extension (OTE) framework for CNO
Set up the OTE framework infrastructure for cluster-network-operator and migrate three test cases from openshift-tests-private: - OCP-72817: Verify internalJoinSubnet and internalTransitSwitchSubnet are configurable post-install as a Day 2 operation - OCP-51727: Verify ovsdb-server and northd do not core dump on node restart - OCP-72028: Verify join switch IP and management port IP for newly added node are synced correctly into NBDB Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c600e9f commit 88d04b6

7 files changed

Lines changed: 1260 additions & 1 deletion

File tree

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS builder
22
WORKDIR /go/src/github.com/openshift/cluster-network-operator
33
COPY . .
4-
RUN hack/build-go.sh
4+
RUN hack/build-go.sh && make build-e2e-tests && gzip -9 test/bin/cluster-network-operator-tests-ext
55

66
FROM registry.ci.openshift.org/ocp/4.22:base-rhel9
77
COPY --from=builder /go/src/github.com/openshift/cluster-network-operator/cluster-network-operator /usr/bin/
88
COPY --from=builder /go/src/github.com/openshift/cluster-network-operator/cluster-network-check-endpoints /usr/bin/
99
COPY --from=builder /go/src/github.com/openshift/cluster-network-operator/cluster-network-check-target /usr/bin/
10+
COPY --from=builder /go/src/github.com/openshift/cluster-network-operator/test/bin/cluster-network-operator-tests-ext.gz /usr/bin/cluster-network-operator-tests-ext.gz
1011

1112
COPY manifests /manifests
1213
COPY bindata /bindata

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,9 @@ clean:
4646
$(RM) cluster-network-operator cluster-network-check-endpoints cluster-network-check-target
4747
.PHONY: clean
4848

49+
.PHONY: build-e2e-tests
50+
build-e2e-tests:
51+
@echo "Building cluster-network-operator-tests-ext binary..."
52+
$(MAKE) -C test build
53+
4954
GO_TEST_PACKAGES :=./pkg/... ./cmd/...

test/Makefile

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Makefile for CNO E2E Test
2+
3+
# Binary name
4+
BINARY_NAME := cluster-network-operator-tests-ext
5+
6+
# Build directory
7+
BUILD_DIR := bin
8+
9+
# Go module and package
10+
GO_MODULE := github.com/openshift/cluster-network-operator
11+
12+
# Version information
13+
GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
14+
BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
15+
16+
# Go build flags
17+
LDFLAGS := -X '$(GO_MODULE)/pkg/version.commitFromGit=$(GIT_COMMIT)' \
18+
-X '$(GO_MODULE)/pkg/version.buildDate=$(BUILD_DATE)' \
19+
-X '$(GO_MODULE)/pkg/version.versionFromGit=$(GIT_COMMIT)'
20+
21+
# Default target
22+
.PHONY: all
23+
all: build
24+
25+
# Build the binary
26+
.PHONY: build
27+
build:
28+
@echo "Building $(BINARY_NAME)..."
29+
@mkdir -p $(BUILD_DIR)
30+
cd cmd && go build -o ../$(BUILD_DIR)/$(BINARY_NAME) -ldflags="$(LDFLAGS)" .
31+
32+
# Build for Linux (useful for container builds)
33+
.PHONY: build-linux
34+
build-linux:
35+
@echo "Building $(BINARY_NAME) for Linux..."
36+
@mkdir -p $(BUILD_DIR)
37+
cd cmd && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
38+
go build -o ../$(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 -ldflags="$(LDFLAGS)" .
39+
40+
# Clean build artifacts
41+
.PHONY: clean
42+
clean:
43+
@echo "Cleaning build artifacts..."
44+
rm -rf $(BUILD_DIR)

test/cmd/main.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"time"
7+
8+
"github.com/openshift-eng/openshift-tests-extension/pkg/cmd"
9+
10+
"github.com/spf13/cobra"
11+
12+
e "github.com/openshift-eng/openshift-tests-extension/pkg/extension"
13+
g "github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo"
14+
15+
_ "github.com/openshift/cluster-network-operator/test/e2e"
16+
)
17+
18+
func main() {
19+
registry := e.NewRegistry()
20+
21+
ext := e.NewExtension("openshift", "payload", "cluster-network-operator")
22+
testTimeout := 120 * time.Minute
23+
ext.AddSuite(e.Suite{
24+
Name: "openshift/cluster-network-operator/disruptive",
25+
Parents: []string{
26+
"openshift/disruptive",
27+
},
28+
Qualifiers: []string{
29+
"name.contains('[Suite:openshift/cluster-network-operator/disruptive]')",
30+
},
31+
ClusterStability: e.ClusterStabilityDisruptive,
32+
TestTimeout: &testTimeout,
33+
})
34+
35+
specs, err := g.BuildExtensionTestSpecsFromOpenShiftGinkgoSuite()
36+
if err != nil {
37+
panic(fmt.Sprintf("couldn't build extension test specs from ginkgo: %+v", err.Error()))
38+
}
39+
40+
ext.AddSpecs(specs)
41+
registry.Register(ext)
42+
43+
root := &cobra.Command{
44+
Long: "OpenShift Tests Extension for Cluster Network Operator",
45+
}
46+
root.AddCommand(cmd.DefaultExtensionCommands(registry)...)
47+
48+
if err := func() error {
49+
return root.Execute()
50+
}(); err != nil {
51+
os.Exit(1)
52+
}
53+
}

test/e2e/cli.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package e2e
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
o "github.com/onsi/gomega"
11+
corev1 "k8s.io/api/core/v1"
12+
"k8s.io/client-go/kubernetes"
13+
"k8s.io/client-go/rest"
14+
"k8s.io/client-go/tools/clientcmd"
15+
e2e "k8s.io/kubernetes/test/e2e/framework"
16+
admissionapi "k8s.io/pod-security-admission/api"
17+
)
18+
19+
type CLI struct {
20+
execPath string
21+
kubeconfig string
22+
namespace string
23+
withoutNamespace bool
24+
asAdmin bool
25+
verb string
26+
args []string
27+
kubeFramework *e2e.Framework
28+
namespacesToDelete []string
29+
}
30+
31+
func NewCLIWithPodSecurityLevel(baseName string, level admissionapi.Level) *CLI {
32+
cli := &CLI{
33+
execPath: "oc",
34+
kubeconfig: os.Getenv("KUBECONFIG"),
35+
kubeFramework: &e2e.Framework{
36+
BaseName: baseName,
37+
SkipNamespaceCreation: false,
38+
NamespacePodSecurityLevel: level,
39+
Options: e2e.Options{
40+
ClientQPS: 20,
41+
ClientBurst: 50,
42+
},
43+
Timeouts: e2e.NewTimeoutContext(),
44+
},
45+
}
46+
47+
config, err := cli.getConfig()
48+
if err != nil {
49+
e2e.Failf("Failed to get kubeconfig: %v", err)
50+
}
51+
clientset, err := kubernetes.NewForConfig(config)
52+
if err != nil {
53+
e2e.Failf("Failed to create Kubernetes clientset: %v", err)
54+
}
55+
cli.kubeFramework.ClientSet = clientset
56+
57+
return cli
58+
}
59+
60+
func (c *CLI) SetupNamespace() {
61+
nsName := fmt.Sprintf("e2e-test-%s-%s", c.kubeFramework.BaseName, getRandomString())
62+
63+
_, err := c.asAdminInternal().withoutNamespaceInternal().run("create", "namespace", nsName).output()
64+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to create namespace")
65+
66+
c.namespace = nsName
67+
c.namespacesToDelete = append(c.namespacesToDelete, nsName)
68+
69+
if c.kubeFramework.NamespacePodSecurityLevel != "" {
70+
level := string(c.kubeFramework.NamespacePodSecurityLevel)
71+
_, err = c.asAdminInternal().withoutNamespaceInternal().run("label", "namespace", nsName,
72+
fmt.Sprintf("pod-security.kubernetes.io/enforce=%s", level),
73+
fmt.Sprintf("pod-security.kubernetes.io/warn=%s", level),
74+
fmt.Sprintf("pod-security.kubernetes.io/audit=%s", level),
75+
"security.openshift.io/scc.podSecurityLabelSync=false",
76+
"--overwrite",
77+
).output()
78+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to label namespace")
79+
}
80+
81+
c.kubeFramework.Namespace = &corev1.Namespace{}
82+
c.kubeFramework.Namespace.Name = nsName
83+
84+
e2e.Logf("Created test namespace: %s", nsName)
85+
}
86+
87+
func (c *CLI) TeardownNamespace() {
88+
if len(c.namespacesToDelete) == 0 {
89+
return
90+
}
91+
92+
if !e2e.TestContext.DeleteNamespace {
93+
e2e.Logf("Skipping namespace deletion (DELETE_NAMESPACE=false)")
94+
return
95+
}
96+
97+
for _, ns := range c.namespacesToDelete {
98+
e2e.Logf("Deleting namespace: %s", ns)
99+
_, err := c.asAdminInternal().withoutNamespaceInternal().run("delete", "namespace", ns, "--wait=false").output()
100+
if err != nil {
101+
e2e.Logf("Warning: failed to delete namespace %s: %v", ns, err)
102+
}
103+
}
104+
}
105+
106+
func (c *CLI) Namespace() string {
107+
return c.namespace
108+
}
109+
110+
func (c *CLI) KubeFramework() *e2e.Framework {
111+
return c.kubeFramework
112+
}
113+
114+
func (c *CLI) AsAdmin() *CLI {
115+
return c.asAdminInternal()
116+
}
117+
118+
func (c *CLI) asAdminInternal() *CLI {
119+
nc := *c
120+
nc.asAdmin = true
121+
nc.namespacesToDelete = append([]string(nil), c.namespacesToDelete...)
122+
return &nc
123+
}
124+
125+
func (c *CLI) WithoutNamespace() *CLI {
126+
return c.withoutNamespaceInternal()
127+
}
128+
129+
func (c *CLI) withoutNamespaceInternal() *CLI {
130+
nc := *c
131+
nc.withoutNamespace = true
132+
nc.namespacesToDelete = append([]string(nil), c.namespacesToDelete...)
133+
return &nc
134+
}
135+
136+
func (c *CLI) Run(verb string) *CLI {
137+
nc := *c
138+
nc.verb = verb
139+
nc.args = []string{}
140+
nc.namespacesToDelete = append([]string(nil), c.namespacesToDelete...)
141+
return &nc
142+
}
143+
144+
func (c *CLI) Args(args ...string) *CLI {
145+
nc := *c
146+
nc.args = append([]string(nil), c.args...)
147+
nc.args = append(nc.args, args...)
148+
nc.namespacesToDelete = append([]string(nil), c.namespacesToDelete...)
149+
return &nc
150+
}
151+
152+
func (c *CLI) Output() (string, error) {
153+
return c.output()
154+
}
155+
156+
func (c *CLI) Execute() error {
157+
out, err := c.output()
158+
if err != nil {
159+
e2e.Logf("Command failed with output:\n%s", out)
160+
}
161+
return err
162+
}
163+
164+
func (c *CLI) run(verb string, args ...string) *CLI {
165+
nc := *c
166+
nc.verb = verb
167+
nc.args = args
168+
nc.namespacesToDelete = append([]string(nil), c.namespacesToDelete...)
169+
return &nc
170+
}
171+
172+
func (c *CLI) output() (string, error) {
173+
var cmdArgs []string
174+
175+
if c.kubeconfig != "" {
176+
cmdArgs = append(cmdArgs, fmt.Sprintf("--kubeconfig=%s", c.kubeconfig))
177+
}
178+
179+
if !c.withoutNamespace && c.namespace != "" {
180+
cmdArgs = append(cmdArgs, fmt.Sprintf("--namespace=%s", c.namespace))
181+
}
182+
183+
if c.verb != "" {
184+
cmdArgs = append(cmdArgs, c.verb)
185+
}
186+
cmdArgs = append(cmdArgs, c.args...)
187+
188+
e2e.Logf("Running: %s %s", c.execPath, strings.Join(cmdArgs, " "))
189+
190+
cmd := exec.Command(c.execPath, cmdArgs...)
191+
var stdout, stderr bytes.Buffer
192+
cmd.Stdout = &stdout
193+
cmd.Stderr = &stderr
194+
195+
err := cmd.Run()
196+
197+
outStr := strings.TrimSpace(stdout.String())
198+
errStr := strings.TrimSpace(stderr.String())
199+
200+
if err != nil {
201+
return outStr, fmt.Errorf("command failed: %w\nstdout: %s\nstderr: %s", err, outStr, errStr)
202+
}
203+
204+
return outStr, nil
205+
}
206+
207+
func (c *CLI) getConfig() (*rest.Config, error) {
208+
kubeconfig := c.kubeconfig
209+
if kubeconfig == "" {
210+
kubeconfig = os.Getenv("KUBECONFIG")
211+
}
212+
if kubeconfig == "" {
213+
return nil, fmt.Errorf("KUBECONFIG not set")
214+
}
215+
216+
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
217+
if err != nil {
218+
return nil, err
219+
}
220+
221+
config.QPS = 20
222+
config.Burst = 50
223+
224+
return config, nil
225+
}

0 commit comments

Comments
 (0)