IPv6/dual-stack integration tests (#12575)

* IPv6 integration tests

This adds a new test `TestDualStack` to the deep suite that ensures requests to a dual stack service are always routed the the IPv6 endpoint.

It also amends other tests in the suite for them to work in IPv6-only clusters:

- skipports: replaced the booksapp with emojivoto, given the servers in the former don't bind to IPv6 addresses
- endpoints: amended the regexes to include IPv6 addresses
- localhost: bumped nginx for it to bind to the IPv6 loopback as well

Note the `TestDualStack` test is disabled by default because Github runners don't support IPv6. To run it locally, first deploy a dual-stack cluster via:

```
kind create cluster --config test/integration/deep/kind-dualstack.yml
```
(for testing IPv6-only clusters, use the `kind-ipv6.yml` config)

Then load the images and trigger the test with:

```
bin/tests --name deep-dual-stack --skip-cluster-create $PWD/target/cli/linux-amd64/linkerd
```
This commit is contained in:
Alejandro Pedraza 2024-05-28 16:00:26 -05:00 committed by GitHub
parent 9b4405761f
commit b21686a9be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 522 additions and 144 deletions

View File

@ -14,7 +14,9 @@ testdir="$bindir"/../test/integration
export default_test_names=(deep deep-native-sidecar viz external helm-upgrade uninstall upgrade-edge default-policy-deny rsa-ca)
export external_resource_test_names=(external-resources)
export all_test_names=(cluster-domain cni-calico-deep multicluster "${default_test_names[*]}" "${external_resource_test_names[*]}")
# TODO(alpeb): add test cni-calico-deep-dual-stack
export dual_stack_test_names=(deep-dual-stack)
export all_test_names=(cluster-domain cni-calico-deep multicluster "${default_test_names[*]}" "${external_resource_test_names[*]}" "${dual_stack_test_names[*]}")
images_load_default=(proxy controller policy-controller web metrics-api tap)
tests_usage() {
@ -448,6 +450,10 @@ run_deep-native-sidecar_test() {
run_test "$testdir/deep/..." --native-sidecar
}
run_deep-dual-stack_test() {
run_test "$testdir/deep/..." --dual-stack
}
run_default-policy-deny_test() {
export default_inbound_policy='deny'
run_test "$testdir/install/..."

View File

@ -0,0 +1,180 @@
package dualstack
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"testing"
"github.com/linkerd/linkerd2/testutil"
)
type IP struct {
IP string `json:"ip"`
}
var TestHelper *testutil.TestHelper
func TestMain(m *testing.M) {
TestHelper = testutil.NewTestHelper()
// Block test execution until control plane is running
TestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)
os.Exit(m.Run())
}
// TestDualStack creates an injected pod that starts two servers, one listening
// on the IPv4 wildcard address and serving the string "IPv4", and another
// listening on the IPv6 wildcard address and serving the string "IPv6". They
// are fronted by a DualStack Service. We test that we can reach those two IPs
// directly, and that making a request to the service's FQDN always hits the
// IPv6 endpoint.
func TestDualStack(t *testing.T) {
if !TestHelper.DualStack() {
t.Skip("Skipping Skip DualStack test")
}
TestHelper.WithDataPlaneNamespace(context.Background(), "dualstack-test", map[string]string{}, t, func(t *testing.T, ns string) {
out, err := TestHelper.Kubectl("",
"create", "configmap", "go-app",
"--from-file=main.go=testdata/ipfamilies-server.go",
"-n", ns,
)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
out, err = TestHelper.Kubectl("",
"apply", "-f", "testdata/ipfamilies-server-client.yml",
"-n", ns,
)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
checkPods(t, ns, "ipfamilies-server")
checkPods(t, ns, "client")
var clientIPv6, serverIPv4, serverIPv6 string
t.Run("Retrieve pod IPs", func(t *testing.T) {
cmd := []string{
"get", "po",
"-o", "jsonpath='{.items[*].status.podIPs}'",
"-n", ns,
}
out, err = TestHelper.Kubectl("", append(cmd, "-l", "app=server")...)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
var IPs []IP
out = strings.Trim(out, "'")
if err = json.Unmarshal([]byte(out), &IPs); err != nil {
testutil.AnnotatedFatalf(t, "error unmarshaling JSON", "error unmarshaling JSON '%s': %s", out, err)
}
if len(IPs) != 2 {
testutil.AnnotatedFatalf(t, "unexpected number of IPs", "expected 2 IPs, got %s", fmt.Sprint(len(IPs)))
}
serverIPv4 = IPs[0].IP
serverIPv6 = IPs[1].IP
out, err = TestHelper.Kubectl("", append(cmd, "-l", "app=client")...)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
out = strings.Trim(out, "'")
if err = json.Unmarshal([]byte(out), &IPs); err != nil {
testutil.AnnotatedFatalf(t, "error unmarshaling JSON", "error unmarshaling JSON '%s': %s", out, err)
}
if len(IPs) != 2 {
testutil.AnnotatedFatalf(t, "unexpected number of IPs", "expected 2 IPs, got %s", fmt.Sprint(len(IPs)))
}
clientIPv6 = IPs[1].IP
})
t.Run("Apply policy", func(t *testing.T) {
file, err := os.Open("testdata/ipfamilies-policy.yml")
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v", err)
}
defer file.Close()
manifest, err := io.ReadAll(file)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v", err)
}
in := strings.ReplaceAll(string(manifest), "{IPv6}", clientIPv6)
out, err = TestHelper.KubectlApply(in, ns)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
})
t.Run("Hit IPv4 addr directly", func(t *testing.T) {
out, err = TestHelper.Kubectl("",
"exec", "deploy/client",
"-c", "curl",
"-n", ns,
"--",
"curl", "-s", "http://"+serverIPv4+":8080",
)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
if out != "IPv4\n" {
testutil.AnnotatedFatalf(t, "unexpected output", "expected 'IPv4', received '%s'", out)
}
})
t.Run("Hit IPv6 addr directly", func(t *testing.T) {
out, err = TestHelper.Kubectl("",
"exec", "deploy/client",
"-c", "curl",
"-n", ns,
"--",
"curl", "-s", "http://["+serverIPv6+"]:8080",
)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
if out != "IPv6\n" {
testutil.AnnotatedFatalf(t, "expected 'IPv6', received '%s'", out)
}
})
t.Run("Hit FQDN directly (should always resolve to IPv6)", func(t *testing.T) {
for i := 0; i < 10; i++ {
out, err = TestHelper.Kubectl("",
"exec", "deploy/client",
"-c", "curl",
"-n", ns,
"--",
"curl", "-s", "http://ipfamilies-server:8080",
)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
}
if out != "IPv6\n" {
testutil.AnnotatedFatalf(t, "expected 'IPv6', received '%s'", out)
}
}
})
})
}
func checkPods(t *testing.T, ns, pod string) {
t.Helper()
if err := TestHelper.CheckPods(context.Background(), ns, pod, 1); err != nil {
//nolint:errorlint
if rce, ok := err.(*testutil.RestartCountError); ok {
testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
} else {
testutil.AnnotatedError(t, "CheckPods timed-out", err)
}
}
}

View File

@ -0,0 +1,32 @@
apiVersion: policy.linkerd.io/v1beta2
kind: Server
metadata:
name: ipfamilies
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: ipfamilies-server
port: http
proxyProtocol: HTTP/1
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: ipfamilies
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: ipfamilies
requiredAuthenticationRefs:
- name: ipfamilies
kind: NetworkAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: NetworkAuthentication
metadata:
name: ipfamilies
spec:
networks:
- cidr: {IPv6}/128

View File

@ -0,0 +1,73 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ipfamilies-server
spec:
selector:
matchLabels:
app: server
template:
metadata:
annotations:
linkerd.io/inject: enabled
labels:
app: server
spec:
containers:
- image: ghcr.io/alpeb/family-server:v1
image: golang:1.22-alpine
name: ipfamilies-server
ports:
- containerPort: 8080
name: http
protocol: TCP
command: ["/bin/sh"]
args:
- -c
- 'go run /go/src/app/main.go'
volumeMounts:
- name: go-app
mountPath: /go/src/app
volumes:
- name: go-app
configMap:
name: go-app
---
apiVersion: v1
kind: Service
metadata:
name: ipfamilies-server
spec:
ipFamilies:
- IPv4
- IPv6
ipFamilyPolicy: RequireDualStack
ports:
- name: http
port: 8080
protocol: TCP
targetPort: http
selector:
app: server
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
spec:
selector:
matchLabels:
app: client
template:
metadata:
annotations:
linkerd.io/inject: enabled
labels:
app: client
spec:
containers:
- name: curl
image: curlimages/curl
command: [ "sh", "-c", "--" ]
args: [ "sleep infinity" ]

View File

@ -0,0 +1,41 @@
package main
import (
"fmt"
"log"
"net"
"net/http"
)
type (
ipv4Handler struct{}
ipv6Handler struct{}
)
func (ipv4Handler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintf(w, "IPv4\n")
}
func (ipv6Handler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintf(w, "IPv6\n")
}
func main() {
log.Print("Server started")
go func() {
ln, err := net.Listen("tcp4", "0.0.0.0:8080")
if err != nil {
log.Fatal(err)
}
srv := &http.Server{Handler: ipv4Handler{}}
log.Fatal(srv.Serve(ln))
}()
ln, err := net.Listen("tcp6", "[::]:8080")
if err != nil {
log.Fatal(err)
}
srv := &http.Server{Handler: ipv6Handler{}}
log.Fatal(srv.Serve(ln))
}

View File

@ -108,7 +108,7 @@ func createTestCaseTable(controlNs, endpointNs string) []testCase {
expectedRE: `\[
\{
"namespace": "(\S*)",
"ip": "\d+\.\d+\.\d+\.\d+",
"ip": "[a-f0-9.:]+",
"port": 8086,
"pod": "linkerd-destination\-[a-f0-9]+\-[a-z0-9]+",
"service": "linkerd-dst\.\S*",
@ -125,7 +125,7 @@ func createTestCaseTable(controlNs, endpointNs string) []testCase {
expectedRE: `\[
\{
"namespace": "(\S*)",
"ip": "\d+\.\d+\.\d+\.\d+",
"ip": "[a-f0-9.:]+",
"port": 8080,
"pod": "linkerd-identity\-[a-f0-9]+\-[a-z0-9]+",
"service": "linkerd-identity\.\S*",
@ -142,7 +142,7 @@ func createTestCaseTable(controlNs, endpointNs string) []testCase {
expectedRE: `\[
\{
"namespace": "(\S*)",
"ip": "\d+\.\d+\.\d+\.\d+",
"ip": "[a-f0-9.:]+",
"port": 8443,
"pod": "linkerd-proxy-injector-[a-f0-9]+\-[a-z0-9]+",
"service": "linkerd-proxy-injector\.\S*",
@ -159,7 +159,7 @@ func createTestCaseTable(controlNs, endpointNs string) []testCase {
expectedRE: `\[
\{
"namespace": "(\S*)",
"ip": "\d+\.\d+\.\d+\.\d+",
"ip": "[a-f0-9.:]+",
"port": 8080,
"pod": "nginx-[a-f0-9]+\-[a-z0-9]+",
"service": "nginx\.\S*",

View File

@ -126,6 +126,10 @@ func TestInstall(t *testing.T) {
cmd = append(cmd, "--set", "proxy.nativeSidecar=true")
}
if TestHelper.DualStack() {
cmd = append(cmd, "--set", "disableIPv6=false")
}
// Pipe cmd & args to `linkerd`
out, err = TestHelper.LinkerdRun(cmd...)
if err != nil {

View File

@ -0,0 +1,4 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
ipFamily: dual

View File

@ -0,0 +1,4 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
ipFamily: ipv6

View File

@ -3,6 +3,7 @@ package localhost
import (
"context"
"fmt"
"net/netip"
"os"
"regexp"
"testing"
@ -163,7 +164,16 @@ func TestLocalhostRouting(t *testing.T) {
testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: no IP address found for %s/%s", ns, podName)
}
statusCode, err := TestHelper.Kubectl("", append(execCommand, podIP)...)
addr, err := netip.ParseAddr(podIP)
if err != nil {
testutil.AnnotatedFatalf(t, "Invalid IP", "Invalid IP '%s': %s", podIP, err)
}
if addr.Is6() {
podIP = fmt.Sprintf("[%s]", podIP)
}
url := fmt.Sprintf("http://%s:80", podIP)
statusCode, err := TestHelper.Kubectl("", append(execCommand, url)...)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error received when calling 'kubectl exec'", "unexpected error received when calling 'kubectl exec': %v", err)
}

View File

@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.14.2
image: nginx:1.25.5
ports:
- containerPort: 80
- name: curl

View File

@ -14,8 +14,8 @@ import (
var TestHelper *testutil.TestHelper
var (
skipPortsNs = "skip-ports-test"
booksappDeployments = []string{"books", "traffic", "authors", "webapp"}
skipPortsNs = "skip-ports-test"
emojivotoDeployments = []string{"emoji", "vote-bot", "voting", "web"}
)
func secureRequestMatcher(dst string) *prommatch.Matcher {
@ -65,8 +65,8 @@ func TestSkipInboundPorts(t *testing.T) {
"'kubectl apply' command failed\n%s", out)
}
// Check all booksapp deployments are up and running
for _, deploy := range booksappDeployments {
// Check all emojivoto deployments are up and running
for _, deploy := range emojivotoDeployments {
if err := TestHelper.CheckPods(ctx, ns, deploy, 1); err != nil {
//nolint:errorlint
if rce, ok := err.(*testutil.RestartCountError); ok {
@ -81,7 +81,7 @@ func TestSkipInboundPorts(t *testing.T) {
// Wait for slow-cookers to start sending requests by using a short
// time window through RetryFor.
err := testutil.RetryFor(30*time.Second, func() error {
pods, err := TestHelper.GetPods(ctx, ns, map[string]string{"app": "webapp"})
pods, err := TestHelper.GetPods(ctx, ns, map[string]string{"app": "web-svc"})
if err != nil {
return fmt.Errorf("error getting pods\n%w", err)
}
@ -94,10 +94,10 @@ func TestSkipInboundPorts(t *testing.T) {
return fmt.Errorf("error getting metrics for pod\n%w", err)
}
s := prommatch.Suite{}.
MustContain("secure requests to authors", secureRequestMatcher("authors")).
MustContain("insecure requests to books", insecureRequestMatcher("books")).
MustNotContain("insecure requests to authors", insecureRequestMatcher("authors")).
MustNotContain("secure requests to books", secureRequestMatcher("books"))
MustContain("secure requests to emoji-svc", secureRequestMatcher("emoji-svc")).
MustContain("insecure requests to voting-svc", insecureRequestMatcher("voting-svc")).
MustNotContain("insecure requests to emoji-svc", insecureRequestMatcher("emoji-svc")).
MustNotContain("secure requests to voting-svc", secureRequestMatcher("voting-svc"))
if err := s.CheckString(metrics); err != nil {
return fmt.Errorf("error matching metrics\n%w", err)
}

View File

@ -1,191 +1,207 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: emoji
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: voting
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: web
---
apiVersion: v1
kind: Service
metadata:
name: webapp
labels:
app: webapp
project: booksapp
name: emoji-svc
spec:
ports:
- name: grpc
port: 8080
targetPort: 8080
- name: prom
port: 8801
targetPort: 8801
selector:
app: webapp
app: emoji-svc
---
apiVersion: v1
kind: Service
metadata:
name: voting-svc
spec:
ports:
- name: grpc
port: 8080
targetPort: 8080
- name: prom
port: 8801
targetPort: 8801
selector:
app: voting-svc
---
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: web-svc
type: ClusterIP
ports:
- name: service
port: 7000
---
kind: Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
project: booksapp
app.kubernetes.io/part-of: booksapp
app.kubernetes.io/name: emoji
app.kubernetes.io/part-of: emojivoto
app.kubernetes.io/version: v11
name: emoji
spec:
replicas: 1
selector:
matchLabels:
app: webapp
project: booksapp
app: emoji-svc
version: v11
template:
metadata:
labels:
app: webapp
project: booksapp
app: emoji-svc
version: v11
spec:
dnsPolicy: ClusterFirst
containers:
- name: service
image: buoyantio/booksapp:v0.0.5
env:
- name: DATABASE_URL
value: sqlite3:db/db.sqlite3
- name: AUTHORS_SITE
value: http://authors:7001
- name: BOOKS_SITE
value: http://books:7002
args: ["prod:webapp"]
readinessProbe:
httpGet:
path: /ping
port: 7000
- env:
- name: GRPC_PORT
value: "8080"
- name: PROM_PORT
value: "8801"
image: docker.l5d.io/buoyantio/emojivoto-emoji-svc:v11
name: emoji-svc
ports:
- name: service
containerPort: 7000
- containerPort: 8080
name: grpc
- containerPort: 8801
name: prom
resources:
requests:
cpu: 100m
serviceAccountName: emoji
---
apiVersion: v1
kind: Service
metadata:
name: authors
labels:
app: authors
project: booksapp
spec:
selector:
app: authors
ports:
- name: service
port: 7001
---
kind: Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: authors
labels:
app: authors
project: booksapp
app.kubernetes.io/part-of: booksapp
app.kubernetes.io/name: vote-bot
app.kubernetes.io/part-of: emojivoto
app.kubernetes.io/version: v11
name: vote-bot
spec:
replicas: 1
selector:
matchLabels:
app: authors
project: booksapp
app: vote-bot
version: v11
template:
metadata:
labels:
app: authors
project: booksapp
app: vote-bot
version: v11
spec:
dnsPolicy: ClusterFirst
containers:
- name: service
image: buoyantio/booksapp:v0.0.5
- command:
- emojivoto-vote-bot
env:
- name: DATABASE_URL
value: sqlite3:db/db.sqlite3
- name: BOOKS_SITE
value: http://books:7002
- name: FAILURE_RATE
value: "0.0"
args: ["prod:authors"]
readinessProbe:
httpGet:
path: /ping
port: 7001
ports:
- name: service
containerPort: 7001
- name: WEB_HOST
value: web-svc:80
image: docker.l5d.io/buoyantio/emojivoto-web:v11
name: vote-bot
resources:
requests:
cpu: 10m
---
apiVersion: v1
kind: Service
metadata:
name: books
labels:
app: books
project: booksapp
spec:
selector:
app: books
ports:
- name: service
port: 7002
---
kind: Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: books
labels:
app: books
project: booksapp
app.kubernetes.io/part-of: booksapp
app.kubernetes.io/name: voting
app.kubernetes.io/part-of: emojivoto
app.kubernetes.io/version: v11
name: voting
spec:
replicas: 1
selector:
matchLabels:
app: books
project: booksapp
app: voting-svc
version: v11
template:
metadata:
annotations:
config.linkerd.io/skip-inbound-ports: "7002"
config.linkerd.io/skip-inbound-ports: "8080"
labels:
app: books
project: booksapp
app: voting-svc
version: v11
spec:
dnsPolicy: ClusterFirst
containers:
- name: service
image: buoyantio/booksapp:v0.0.5
env:
- name: DATABASE_URL
value: sqlite3:db/db.sqlite3
- name: AUTHORS_SITE
value: http://authors:7001
args: ["prod:books"]
readinessProbe:
httpGet:
path: /ping
port: 7002
- env:
- name: GRPC_PORT
value: "8080"
- name: PROM_PORT
value: "8801"
image: docker.l5d.io/buoyantio/emojivoto-voting-svc:v11
name: voting-svc
ports:
- name: service
containerPort: 7002
- containerPort: 8080
name: grpc
- containerPort: 8801
name: prom
resources:
requests:
cpu: 100m
serviceAccountName: voting
---
kind: Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: traffic
labels:
app: traffic
project: booksapp
app.kubernetes.io/part-of: booksapp
app.kubernetes.io/name: web
app.kubernetes.io/part-of: emojivoto
app.kubernetes.io/version: v11
name: web
spec:
replicas: 1
selector:
matchLabels:
app: traffic
project: booksapp
app: web-svc
version: v11
template:
metadata:
labels:
app: traffic
project: booksapp
app: web-svc
version: v11
spec:
dnsPolicy: ClusterFirst
containers:
- name: traffic
image: buoyantio/booksapp-traffic:v0.0.3
args:
- "-initial-delay=30s"
- "webapp:7000"
- env:
- name: WEB_PORT
value: "8080"
- name: EMOJISVC_HOST
value: emoji-svc:8080
- name: VOTINGSVC_HOST
value: voting-svc:8080
- name: INDEX_BUNDLE
value: dist/index_bundle.js
image: docker.l5d.io/buoyantio/emojivoto-web:v11
name: web-svc
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: 100m
serviceAccountName: web

View File

@ -38,6 +38,7 @@ type TestHelper struct {
uninstall bool
cni bool
calico bool
dualStack bool
nativeSidecar bool
defaultInboundPolicy string
httpClient http.Client
@ -208,6 +209,7 @@ func NewTestHelper() *TestHelper {
uninstall := flag.Bool("uninstall", false, "whether to run the 'linkerd uninstall' integration test")
cni := flag.Bool("cni", false, "whether to install linkerd with CNI enabled")
calico := flag.Bool("calico", false, "whether to install calico CNI plugin")
dualStack := flag.Bool("dual-stack", false, "whether to run the dual-stack tests")
nativeSidecar := flag.Bool("native-sidecar", false, "whether to install using native sidecar injection")
defaultInboundPolicy := flag.String("default-inbound-policy", "", "if non-empty, passed to --set proxy.defaultInboundPolicy at linkerd's install time")
flag.Parse()
@ -254,6 +256,7 @@ func NewTestHelper() *TestHelper {
externalPrometheus: *externalPrometheus,
cni: *cni,
calico: *calico,
dualStack: *dualStack,
nativeSidecar: *nativeSidecar,
uninstall: *uninstall,
defaultInboundPolicy: *defaultInboundPolicy,
@ -399,6 +402,11 @@ func (h *TestHelper) Calico() bool {
return h.calico
}
// DualStack determines whether the DualStack tests are run
func (h *TestHelper) DualStack() bool {
return h.dualStack
}
// NativeSidecar determines whether native sidecar injection is enabled
func (h *TestHelper) NativeSidecar() bool {
return h.nativeSidecar