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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ipfailover/keepalived/tests/bin/ipfailover-tests-ext
14 changes: 14 additions & 0 deletions ipfailover/keepalived/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Test extension builder stage (added by ote-migration)
FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.22-openshift-4.19 AS test-extension-builder
RUN mkdir -p /go/src/github.com/openshift/ipfailover
WORKDIR /go/src/github.com/openshift/ipfailover
COPY . .
RUN cd ipfailover/keepalived/tests && \
make build && \
cd bin && \
tar -czvf ipfailover-test-extension.tar.gz ipfailover-tests-ext && \
rm -f ipfailover-tests-ext

#
# VIP failover monitoring container for OpenShift.
#
Expand All @@ -18,6 +29,9 @@ COPY iptables-scripts/ip6tables /usr/sbin/
COPY iptables-scripts/ip6tables-save /usr/sbin/
COPY iptables-scripts/ip6tables-restore /usr/sbin/

# Copy test extension binary (added by ote-migration)
COPY --from=test-extension-builder /go/src/github.com/openshift/ipfailover/ipfailover/keepalived/tests/bin/ipfailover-test-extension.tar.gz /usr/bin/

LABEL io.k8s.display-name="OpenShift IP Failover" \
io.k8s.description="This is a component of OpenShift and runs a clustered keepalived instance across multiple hosts to allow highly available IP addresses." \
io.openshift.tags="openshift,ha,ip,failover"
Expand Down
18 changes: 18 additions & 0 deletions ipfailover/keepalived/tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
BINARY := bin/ipfailover-tests-ext

.PHONY: build
build:
@echo "Building extension binary..."
@mkdir -p bin
GOTOOLCHAIN=auto GOSUMDB=sum.golang.org go build -o $(BINARY) ./cmd
@echo "✅ Binary built: $(BINARY)"

.PHONY: clean
clean:
@rm -f $(BINARY)

.PHONY: help
help:
@echo "Available targets:"
@echo " build - Build extension binary"
@echo " clean - Remove binaries"
60 changes: 60 additions & 0 deletions ipfailover/keepalived/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ipfailover OTE Test Extension

All 6 ipfailover tests have been successfully migrated from openshift-tests-private to the OTE (openshift-tests-extension) framework.

## Test Suites

### ipfailover/all
All 6 tests (must run with `--max-concurrency=1`)

### ipfailover/conformance/serial
4 serial tests (must run sequentially):
- 41025 - support to deploy ipfailover
- 41028 - ipfailover configuration can be customized by ENV
- 41029 - ipfailover can support up to 255 VIPs
- 49214 - Excluding existing VRRP cluster ID

### ipfailover/disruptive
2 disruptive tests:
- 41027 - pod and service automatically switched over
- 41030 - preemption strategy for keepalived ipfailover

## How to Run

```bash
export KUBECONFIG=/path/to/kubeconfig

# Build the binary
make build

# Run all tests
./bin/ipfailover-tests-ext run-suite ipfailover/all --max-concurrency=1

# Run specific suites
./bin/ipfailover-tests-ext run-suite ipfailover/conformance/serial --max-concurrency=1
./bin/ipfailover-tests-ext run-suite ipfailover/disruptive --max-concurrency=1
```

**Important**: Tests must run serially (`--max-concurrency=1`) because they use `hostNetwork: true` and will conflict if run in parallel.

## Test Execution Time

- Single test: 1-5 minutes
- Serial suite: 10-15 minutes
- Disruptive suite: 7-10 minutes
- All tests: 15-20 minutes

## Files

```
tests/
├── cmd/main.go # OTE entry point, suite definitions
├── e2e/
│ ├── ipfailover.go # 6 test implementations
│ ├── util.go # Helper functions
│ └── testdata/
│ └── router/ # Test YAML fixtures (used directly, no bindata)
├── Makefile # Simple build (no bindata generation)
├── go.mod # Dependencies
└── README.md # This file
```
130 changes: 130 additions & 0 deletions ipfailover/keepalived/tests/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"fmt"
"os"
"regexp"
"strings"

"github.com/spf13/cobra"
"k8s.io/component-base/logs"

"github.com/openshift-eng/openshift-tests-extension/pkg/cmd"
e "github.com/openshift-eng/openshift-tests-extension/pkg/extension"
et "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests"
g "github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo"
"github.com/openshift/origin/test/extended/util"
framework "k8s.io/kubernetes/test/e2e/framework"

// Import test packages from this module
_ "github.com/openshift/ipfailover-tests-extension/e2e"
)

func main() {
// Initialize test framework flags (required for kubeconfig, provider, etc.)
util.InitStandardFlags()
framework.AfterReadingAllFlags(&framework.TestContext)
Comment on lines +25 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C3 'InitStandardFlags\(|AfterReadingAllFlags\(' ipfailover/keepalived/tests/cmd/main.go
rg -n -C3 --iglob 'main.go' 'InitStandardFlags\(|AfterReadingAllFlags\(' .

Repository: openshift/images

Length of output: 972


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== main.go =="
cat -n ipfailover/keepalived/tests/cmd/main.go | sed -n '1,180p'

echo
echo "== other AfterReadingAllFlags usages =="
rg -n -C2 'AfterReadingAllFlags\(' . || true

echo
echo "== root.Execute call site =="
rg -n -C3 'root\.Execute\(\)|Execute\(\)' ipfailover/keepalived/tests/cmd/main.go

Repository: openshift/images

Length of output: 6376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== framework docs references =="
rg -n -C2 'AfterReadingAllFlags' . || true

echo
echo "== related entrypoints =="
rg -n -C2 'util.InitStandardFlags\(|logs.InitLogs\(|framework.AfterReadingAllFlags\(' . --glob 'main.go' || true

Repository: openshift/images

Length of output: 2173


Move framework.AfterReadingAllFlags after flag parsing. Calling it before root.Execute() leaves framework.TestContext on defaults, so kube/e2e flag overrides are ignored.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ipfailover/keepalived/tests/cmd/main.go` around lines 25 - 26, Move the
framework.AfterReadingAllFlags call so it runs after root.Execute() has parsed
flags, because invoking it before parsing leaves framework.TestContext at
default values and ignores kube/e2e overrides. Update the startup flow in main
so util.InitStandardFlags still happens first, then flags are parsed via
root.Execute, and only then
framework.AfterReadingAllFlags(&framework.TestContext) is called.


logs.InitLogs()
defer logs.FlushLogs()

registry := e.NewRegistry()
ext := e.NewExtension("openshift", "payload", "ipfailover")

// Register test suites (parallel, serial, disruptive, all)
registerSuites(ext)

// Build test specs from Ginkgo
// Note: ModuleTestsOnly() is applied by default, which filters out /vendor/ and k8s.io/kubernetes tests
allSpecs, err := g.BuildExtensionTestSpecsFromOpenShiftGinkgoSuite()
if err != nil {
panic(fmt.Sprintf("couldn't build extension test specs from ginkgo: %+v", err.Error()))
}

// Filter to only include tests from this module's e2e/ directory
// Excludes tests from /go/pkg/mod/ (module cache) and /vendor/
componentSpecs := allSpecs.Select(func(spec *et.ExtensionTestSpec) bool {
for _, loc := range spec.CodeLocations {
// Include tests from local e2e/ directory (not from module cache or vendor)
if strings.Contains(loc, "/e2e/") && !strings.Contains(loc, "/go/pkg/mod/") && !strings.Contains(loc, "/vendor/") {
return true
}
}
return false
})

// Initialize test framework before all tests
// Note: In OTE, we don't need the OTP-specific InitTest() call
// The framework initialization is handled by the test execution itself

// Process all specs
componentSpecs.Walk(func(spec *et.ExtensionTestSpec) {
// Apply platform filters based on Platform: labels
for label := range spec.Labels {
if strings.HasPrefix(label, "Platform:") {
platformName := strings.TrimPrefix(label, "Platform:")
spec.Include(et.PlatformEquals(platformName))
}
}

// Apply platform filters based on [platform:xxx] in test names
re := regexp.MustCompile(`\[platform:([a-z]+)\]`)
if match := re.FindStringSubmatch(spec.Name); match != nil {
platform := match[1]
spec.Include(et.PlatformEquals(platform))
}

// Set lifecycle to Blocking (tests should fail the suite if they fail)
spec.Lifecycle = et.LifecycleBlocking
})

// Add filtered component specs to extension
ext.AddSpecs(componentSpecs)

registry.Register(ext)

root := &cobra.Command{
Long: "Ipfailover Tests",
}

root.AddCommand(cmd.DefaultExtensionCommands(registry)...)

if err := func() error {
return root.Execute()
}(); err != nil {
os.Exit(1)
}
}

// registerSuites registers test suites with proper categorization
func registerSuites(ext *e.Extension) {
suites := []e.Suite{
{
Name: "ipfailover/conformance/serial",
Parents: []string{
"openshift/conformance/serial",
},
Description: "Serial conformance tests (must run sequentially)",
Qualifiers: []string{
`name.contains("[Serial]") && !name.contains("[Disruptive]")`,
},
},
{
Name: "ipfailover/disruptive",
Parents: []string{"openshift/disruptive"},
Description: "Disruptive tests (may affect cluster state)",
Qualifiers: []string{
`name.contains("[Disruptive]")`,
},
},
{
Name: "ipfailover/all",
Description: "All ipfailover tests",
// No qualifiers means all tests from this extension will be included
},
}

for _, suite := range suites {
ext.AddSuite(suite)
}
}
Loading