Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ WORKDIR /workspace
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,source=go.mod,target=go.mod \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=test/gnmi,target=test/gnmi \
go mod download -x

RUN --mount=type=bind,target=. \
Expand Down
40 changes: 34 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ $(LOCALBIN):
install-gofumpt: FORCE
@if ! hash gofumpt 2>/dev/null; then printf "\e[1;36m>> Installing gofumpt...\e[0m\n"; go install mvdan.cc/gofumpt@latest; fi

install-ginkgo: FORCE
@if ! hash ginkgo 2>/dev/null; then printf "\e[1;36m>> Installing ginkgo...\e[0m\n"; go install github.com/onsi/ginkgo/v2/ginkgo@latest; fi

install-kubebuilder: FORCE
@set -eou pipefail; if ! hash kubebuilder 2>/dev/null; then printf "\e[1;36m>> Installing kubebuilder...\e[0m\n"; if command -v curl >/dev/null 2>&1; then GET="curl -sLo"; elif command -v wget >/dev/null 2>&1; then GET="wget -O"; else echo "Didn't find curl or wget to download kubebuilder"; exit 2; fi; BIN=$$(go env GOBIN); if [[ -z $$BIN ]]; then BIN=$$(go env GOPATH)/bin; fi; $$GET "$$BIN/kubebuilder" "https://go.kubebuilder.io/dl/latest/$$(go env GOOS)/$$(go env GOARCH)"; chmod +x "$$BIN/kubebuilder"; fi

Expand All @@ -68,11 +71,15 @@ lint: FORCE bin/golangci-lint-custom ## Run golangci-lint linter
@bin/golangci-lint-custom config verify
@bin/golangci-lint-custom run

# PROVIDER defines which provider to test (cisco-nxos-gnmi, cisco-iosxr-gnmi, openconfig).
# Used by test-e2e-cluster and test-e2e-envtest to filter tests.
PROVIDER ?= cisco-nxos-gnmi

fmt: FORCE install-gofumpt
@printf "\e[1;36m>> gofumpt -l -w .\e[0m\n"
@gofumpt -l -w $(shell git ls-files '*.go' | grep -v '^internal/provider/openconfig')

# Run the e2e tests against a k8s cluster.
# Run the scaffolded e2e tests (unchanged from Kubebuilder).
test-e2e: FORCE
@command -v kind >/dev/null 2>&1 || { \
echo "Kind is not installed. Please install Kind manually."; \
Expand All @@ -85,6 +92,26 @@ test-e2e: FORCE
@printf "\e[1;36m>> go test ./test/e2e/ -v -ginkgo.v\e[0m\n"
@KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v

# Run gNMI controller tests in cluster mode (requires Kind cluster).
# Uses ginkgo for parallel execution.
GINKGO_PROCS ?= 4
test-e2e-cluster: FORCE install-ginkgo
@command -v kind >/dev/null 2>&1 || { \
echo "Kind is not installed. Please install Kind manually."; \
exit 1; \
}
@kind get clusters | grep -q $(KIND_CLUSTER) || { \
echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \
exit 1; \
}
@printf "\e[1;36m>> ginkgo -procs=$(GINKGO_PROCS) -tags=cluster -timeout=15m -v ./test/e2e/ (PROVIDER=$(PROVIDER))\e[0m\n"
@KIND_CLUSTER=$(KIND_CLUSTER) E2E_PROVIDER=$(PROVIDER) ginkgo -procs=$(GINKGO_PROCS) -tags=cluster -timeout=15m -v ./test/e2e/

# Run gNMI controller tests in envtest mode (no cluster required).
test-e2e-envtest: FORCE install-setup-envtest
@printf "\e[1;36m>> go test ./test/e2e/ -tags=envtest -v -ginkgo.v (PROVIDER=$(PROVIDER))\e[0m\n"
@KUBEBUILDER_ASSETS=$$(setup-envtest use 1.32 -p path) E2E_PROVIDER=$(PROVIDER) go test ./test/e2e/ -tags=envtest -v -ginkgo.v

docker-build: FORCE
@printf "\e[1;36m>> $(CONTAINER_TOOL) build --tag=$(IMG) .\e[0m\n"
@$(CONTAINER_TOOL) build --build-arg=BININFO_BUILD_DATE=$(BININFO_BUILD_DATE) --build-arg=BININFO_COMMIT_HASH=$(BININFO_COMMIT_HASH) --build-arg=BININFO_VERSION=$(BININFO_VERSION) --tag=$(IMG) .
Expand All @@ -98,15 +125,16 @@ build-installer: FORCE generate install-kustomize
@printf "\e[1;36m>> kustomize build config/default > dist/install.yaml\e[0m\n"
@mkdir -p dist; kustomize build config/default > dist/install.yaml

# Deploy controller to the k8s cluster
# Deploy controller to the k8s cluster.
# Use PROVIDER to set the provider (default: cisco-nxos-gnmi).
deploy: FORCE generate install-kustomize
@printf "\e[1;36m>> kustomize build config/default | kubectl apply -f -\e[0m\n"
@kustomize build config/default | kubectl apply -f -
@printf "\e[1;36m>> deploying controller-manager (PROVIDER=$(PROVIDER))\e[0m\n"
@kustomize build config/develop | sed 's/--provider=openconfig/--provider=$(PROVIDER)/' | kubectl apply -f -

# Undeploy controller from the k8s cluster
undeploy: FORCE install-kustomize
@printf "\e[1;36m>> kustomize build config/default | kubectl delete -f -\e[0m\n"
@kustomize build config/default | kubectl delete --ignore-not-found=true -f -
@printf "\e[1;36m>> undeploying controller-manager\e[0m\n"
@kustomize build config/develop | kubectl delete --ignore-not-found=true -f -

# Install CRDs into the k8s cluster
deploy-crds: FORCE generate install-kustomize
Expand Down
1 change: 1 addition & 0 deletions config/develop/manager_patch.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
- op: replace
path: /spec/template/spec/containers/0/args
value:
- --metrics-bind-address=:8443
- --leader-elect=false
- --health-probe-bind-address=:8081
- --provider=openconfig
Expand Down
99 changes: 99 additions & 0 deletions test/e2e/cluster_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

//go:build cluster

package e2e

import (
"fmt"
"testing"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/ironcore-dev/network-operator/test/e2e/testutil"
)

// TestCluster runs the e2e test suite in cluster mode.
// Named differently from TestE2E to avoid conflict with scaffolded e2e_suite_test.go.
func TestCluster(t *testing.T) {
RegisterFailHandler(Fail)
_, _ = fmt.Fprintf(GinkgoWriter, "Starting network-operator tests in CLUSTER mode\n")
RunSpecs(t, "e2e suite (cluster)")
}

// SynchronizedBeforeSuite enables parallel test execution:
// - Process 1: Builds images, installs Prometheus/CertManager, deploys manager (runs first, alone)
// - All processes: Create ClusterEnvironment connection (runs after process 1 completes)
var _ = SynchronizedBeforeSuite(
// First function: runs ONLY on process 1, before other processes start
func(ctx SpecContext) []byte {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
// Note: Timeout is set in the second function for all processes

By("Ensure that Prometheus is enabled")
cwd, err := testutil.GetProjectDir()
Expect(err).NotTo(HaveOccurred(), "Failed to get project directory")

err = testutil.UncommentCode(cwd+"/config/default/kustomization.yaml", "#- ../prometheus", "#")
Expect(err).NotTo(HaveOccurred(), "Failed to enable Prometheus")

// Build and load images to Kind (only process 1)
buildAndLoadImages(ctx)

// Setup Prometheus and CertManager (only process 1)
setupClusterDependencies(ctx)

// Deploy controller-manager (includes CRDs via make deploy)
By("deploying controller-manager")
tmpEnv := testutil.NewClusterEnvironment()
Expect(tmpEnv.Setup(ctx)).To(Succeed())
Expect(tmpEnv.DeployManager(ctx)).To(Succeed())

return nil // No data to pass to other processes
},
// Second function: runs on ALL processes after the first function completes
func(ctx SpecContext, _ []byte) {
SetDefaultEventuallyTimeout(testutil.DefaultTimeout)
SetDefaultEventuallyPollingInterval(time.Second)

// All processes create their own ClusterEnvironment connection
By("initializing cluster environment")
testEnv = testutil.NewClusterEnvironment()
Expect(testEnv.Setup(ctx)).To(Succeed())
},
)

// SynchronizedAfterSuite enables parallel test cleanup:
// - All processes: Local cleanup (runs on all processes)
// - Process 1: Uninstall shared dependencies (runs last, alone)
var _ = SynchronizedAfterSuite(
// First function: runs on ALL processes
func(ctx SpecContext) {
// Perform local cleanup (will run only once even if called from signal handler)
performCleanup()

// Wait for all test namespaces to be fully deleted before returning.
// This ensures DeferCleanup hooks have finished deleting resources and their
// finalizers have been processed by the controller. Without this, the second
// function (UndeployManager) may delete the CRDs while resources still exist,
// causing finalizers to be stuck forever.
if testEnv != nil {
_ = testEnv.WaitForTestNamespacesGone(ctx) //nolint:errcheck // best-effort cleanup
}
},
// Second function: runs ONLY on process 1, after all other processes complete
func(ctx SpecContext) {
// Undeploy the controller-manager
tmpEnv := testutil.NewClusterEnvironment()
_ = tmpEnv.Setup(ctx) //nolint:errcheck // best-effort cleanup
_ = tmpEnv.UndeployManager(ctx) //nolint:errcheck // best-effort cleanup

// Uninstall Prometheus and CertManager
cleanupClusterDependencies(ctx)
},
)
Loading