Files
kube-vip/testing/e2e/e2e_test.go
Marcel Fest be536eaaf7 fix: wireguard + routing-protocol sync (#1769)
* fix(kubevip): reject out-of-range routing protocol values

Netlink carries the address and route protocol in a single byte, so a
configured value above 255 was silently truncated on the wire and never
matched again on readback. Reject it during config validation instead.

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

* feat(wireguard): qualify service tunnel IDs by protocol

Sanitisation maps '-' onto the '_' separator, so "a-b/c" and "a/b-c"
shared one nftables chain, and TCP and UDP on the same port collided.
ServicePortIDs appends the protocol and, when sanitisation changed the
name or the ID grew too long, a hash of the raw name. It also returns
the previous port-only ID so existing chains can be migrated.

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

* fix(arp): guard manager state behind a single mutex

Instances were kept in a sync.Map with a per-instance mutex for the
refcount, so lookup and refcount update were not atomic: concurrent
Insert and Remove could resurrect a deleted instance or drop a live one.
Hold one manager mutex across both, and buffer link subscriptions so a
netlink sender is never parked on an unread channel during shutdown.

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

* fix(instance): roll back partially created instances

Instance creation added addresses, VLAN or macvlan links and DHCP
clients incrementally, so a failure part way through left the node
holding state nobody owned. Initialization now unwinds what it created,
and link cleanup only deletes attachments this instance created that no
remaining instance still uses.

Namespace-dependent tests now skip unless KUBE_VIP_REQUIRE_NETNS is set,
which CI sets on the privileged job so lost capabilities turn it red
instead of silently skipping.

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

* test(e2e): give docker kill more time under parallel load

The ARP suite runs four kind clusters against one Docker daemon, so
acknowledging a leader kill regularly exceeded the 5s budget and failed
the IPv6 failover specs before any assertion ran.

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

* fix: regression on preserveOnLeadershipLoss

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

* fix: use the introduced wireguard service_id

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

* fix(services): reuse link attachment ownership on service delete

deleteService removed VLAN and macvlan links unconditionally, which tore
down interfaces kube-vip had only adopted and interfaces another Service
still used. Route the delete path through CleanupLinkAttachments and pass
the remaining instances so ownership is handed over instead.

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>

---------

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>
2026-09-18 14:50:37 +02:00

1958 lines
69 KiB
Go

//go:build e2e
// +build e2e
package e2e_test
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"text/template"
"time"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"
"sigs.k8s.io/kind/pkg/apis/config/v1alpha4"
kindconfigv1alpha4 "sigs.k8s.io/kind/pkg/apis/config/v1alpha4"
"sigs.k8s.io/kind/pkg/cluster"
"sigs.k8s.io/kind/pkg/log"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/gexec"
"github.com/kube-vip/kube-vip/pkg/kubevip"
"github.com/kube-vip/kube-vip/pkg/utils"
"github.com/kube-vip/kube-vip/pkg/vip"
"github.com/kube-vip/kube-vip/testing/e2e"
)
const (
dsName = "traefik-whoami"
dsNamespace = "default"
defaultNetwork = "kind"
kindNetworkEnv = "KIND_EXPERIMENTAL_DOCKER_NETWORK"
testDirPrefix = "kube-vip-test"
)
const testJSON = `
- op: add
path: "/apiServer/certSANs/-"
value: `
var _ = Describe("kube-vip ARP/NDP broadcast neighbor", func() {
if Mode == ModeARP {
var (
ctx context.Context
cancel context.CancelFunc
logger log.Logger
imagePath string
k8sImagePath string
configPath string
kubeVIPManifestTemplate *template.Template
kubeVIPHostnameManifestTemplate *template.Template
v129 bool
tempDirPathRoot string
)
BeforeEach(OncePerOrdered, func() {
ctx, cancel = context.WithCancel(context.TODO())
klog.SetOutput(GinkgoWriter)
logger = e2e.TestLogger{}
imagePath = os.Getenv("E2E_IMAGE_PATH") // Path to kube-vip image
configPath = os.Getenv("CONFIG_PATH") // path to the api server config
k8sImagePath = os.Getenv("K8S_IMAGE_PATH") // path to the kubernetes image (version for kind)
if configPath == "" {
configPath = "/etc/kubernetes/admin.conf"
}
v129 = true
if v129env, v129set := os.LookupEnv("V129"); v129set {
var err error
v129, err = strconv.ParseBool(v129env)
Expect(err).ToNot(HaveOccurred())
}
curDir, err := os.Getwd()
Expect(err).NotTo(HaveOccurred())
templatePath := filepath.Join(curDir, "kube-vip.yaml.tmpl")
kubeVIPManifestTemplate, err = template.New("kube-vip.yaml.tmpl").ParseFiles(templatePath)
Expect(err).NotTo(HaveOccurred())
hostnameTemplatePath := filepath.Join(curDir, "kube-vip-hostname.yaml.tmpl")
kubeVIPHostnameManifestTemplate, err = template.New("kube-vip-hostname.yaml.tmpl").ParseFiles(hostnameTemplatePath)
Expect(err).NotTo(HaveOccurred())
})
BeforeEach(OncePerOrdered, func() {
tempDirPathRoot = MustMkdirTemp("", fmt.Sprintf("%s-arp", testDirPrefix))
})
AfterEach(OncePerOrdered, func() {
if os.Getenv("E2E_KEEP_LOGS") != "true" {
Expect(os.RemoveAll(tempDirPathRoot)).To(Succeed())
}
cancel()
})
Describe("kube-vip IPv4 functionality, vip_leaderelection=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv4Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv4Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "false",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ipv4", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 3, nil, 1)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
It(clusterName+" provides an IPv4 VIP address for the Kubernetes control plane nodes", func() {
testControlPlaneVIPs(ctx, []string{cpVIP}, clusterName, client)
})
})
Describe("kube-vip IPv4 functionality, svc_enable=true, svc_election=false", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv4Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv4Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-ipv4", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv4 VIP address for service",
func(svcName string, currentOffset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv4Family, currentOffset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv4Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("only removes VIP address if it was referenced by multiple services and all of them were deleted",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv4Family, offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv4Protocol}, 2, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("removes VIP if all endpoints were deleted and re-adds VIP on endpoint creation",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv4Family, offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv4Protocol}, 1, true, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("propagates dhcp-broadcast annotation to service",
func(svcName string, offset uint, dhcpBroadcast bool) {
lbAddress := e2e.GenerateVIP(utils.IPv4Family, offset, defaultNetwork)
createTestService(ctx, svcName, dsNamespace, dsName, lbAddress,
client, corev1.IPFamilyPolicyPreferDualStack, []corev1.IPFamily{corev1.IPv4Protocol},
corev1.ServiceExternalTrafficPolicyCluster, "", 80, dhcpBroadcast, false)
checkDHCPBroadcastAnnotation(ctx, svcName, dsNamespace, dhcpBroadcast, client)
err := client.CoreV1().Services(dsNamespace).Delete(ctx, svcName, metav1.DeleteOptions{})
Expect(err).ToNot(HaveOccurred())
},
Entry("sets annotation when dhcp-broadcast is true", "test-svc-dhcp-broadcast", SOffset.Get(), true),
Entry("does not set annotation when dhcp-broadcast is false", "test-svc-no-broadcast", SOffset.Get(), false),
)
})
Describe("kube-vip IPv4 functionality, svc_enable=true, svc_election=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv4Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv4Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "true",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-el-ipv4", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv4 VIP address for service",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv4Family, offset, defaultNetwork)
testService(ctx, svcName, lbAddress, fmt.Sprintf("kubevip-%s", svcName), dsNamespace, trafficPolicy, client, true,
[]corev1.IPFamily{corev1.IPv4Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip IPv4 functionality, svc_enable=true, with per-service election on demand", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv4Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv4Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
PerServiceElectionOnDemand: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-ipv4-mixed", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv4 VIP address for services when per-service election on demand is used",
func(svcName string, currentOffset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddresses := []string{}
for range 2 {
lbAddresses = append(lbAddresses, e2e.GenerateVIP(utils.IPv4Family, currentOffset, defaultNetwork))
}
testServiceMixedElection(ctx, svcName, lbAddresses, "plndr-svcs-lock", "kube-system", fmt.Sprintf("kubevip-%s", svcName), dsNamespace,
trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv4Protocol}, 1, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip IPv6 functionality, vip_leaderelection=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv6Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv6Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "false",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ipv6", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 3, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
It(clusterName+"provides an IPv6 VIP address for the Kubernetes control plane nodes", func() {
testControlPlaneVIPsWithTimeout(ctx, []string{cpVIP}, clusterName, client, 2*time.Second, 30*time.Second)
})
})
Describe("kube-vip IPv6 functionality, svc_enable=true, svc_election=false", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv6Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv6Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-ipv6", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv6 VIP address for service",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv6Family, offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv6Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("only removes VIP address if it was referenced by multiple services and all of them were deleted",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv6Family, offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv6Protocol}, 2, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("removes VIP if all endpoints were deleted and re-adds VIP on endpoint creation",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv6Family, offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv6Protocol}, 1, true, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip IPv6 functionality, svc_enable=true, svc_election=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv6Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv6Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "true",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-el-ipv6", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv6 VIP address for service",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv6Family, offset, defaultNetwork)
testService(ctx, svcName, lbAddress, fmt.Sprintf("kubevip-%s", svcName), dsNamespace, trafficPolicy, client, true,
[]corev1.IPFamily{corev1.IPv6Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip IPv6 functionality, svc_enable=true, with per-service election on demand", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv6Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv6Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
PerServiceElectionOnDemand: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-ipv6-mixed", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv6 VIP address for services when per-service election on demand is used",
func(svcName string, currentOffset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddresses := []string{}
for range 2 {
lbAddresses = append(lbAddresses, e2e.GenerateVIP(utils.IPv6Family, currentOffset, defaultNetwork))
}
testServiceMixedElection(ctx, svcName, lbAddresses, "plndr-svcs-lock", "kube-system", fmt.Sprintf("kubevip-%s", svcName), dsNamespace,
trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv6Protocol}, 1, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip DualStack functionality - IPv4 primary, vip_leaderelection=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "false",
SvcElectionEnable: "false",
EnableEndpoints: "false",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ds-ipv4", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 3, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
It(clusterName+" provides a DualStack VIP addresses for the Kubernetes control plane nodes", func() {
testControlPlaneVIPs(ctx, vip.Split(cpVIP), clusterName, client)
})
})
Describe("kube-vip DualStack functionality - IPv4 primary, svc_enable=true, svc_election=false", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "false",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ds-svc-ipv4", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv4 and IPv6 VIP addresses for service",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("only removes VIP address if it was referenced by multiple services and all of them were deleted",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}, 2, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("removes VIP if all endpoints were deleted and re-adds VIP on endpoint creation",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}, 1, true, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip DualStack functionality - IPv4 primary, svc_enable=true, svc_election=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "true",
EnableEndpoints: "false",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ds-svc-el-ipv4", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv4 and IPv6 VIP addresses for service",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, fmt.Sprintf("kubevip-%s", svcName), dsNamespace, trafficPolicy, client, true,
[]corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip DualStack functionality - IPv4 primary, svc_enable=true, with per-service election on demand", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
PerServiceElectionOnDemand: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-dual-ipv4-mixed", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv4 and IPv6 VIP address for services when per-service election on demand is used",
func(svcName string, currentOffset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddresses := []string{}
for range 2 {
lbAddresses = append(lbAddresses, e2e.GenerateDualStackVIP(currentOffset, defaultNetwork))
}
testServiceMixedElection(ctx, svcName, lbAddresses, "plndr-svcs-lock", "kube-system", fmt.Sprintf("kubevip-%s", svcName), dsNamespace,
trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}, 1, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip DualStack functionality - IPv6 primary, vip_leaderelection=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
PodSubnet: "fd00:10:244::/56,10.244.0.0/16",
ServiceSubnet: "fd00:10:96::/112,10.96.0.0/16",
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "false",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ds-ipv6", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 3, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
It(clusterName+" provides a DualStack VIP addresses for the Kubernetes control plane nodes", func() {
testControlPlaneVIPs(ctx, vip.Split(cpVIP), clusterName, client)
})
})
Describe("kube-vip DualStack functionality - IPv6 primary, svc_enable=true, svc_election=false", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
PodSubnet: "fd00:10:244::/56,10.244.0.0/16",
ServiceSubnet: "fd00:10:96::/112,10.96.0.0/16",
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "false",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ds-svc-ipv6", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv4 and IPv6 VIP addresses for service",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("only removes VIP address if it was referenced by multiple services and all of them were deleted",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}, 2, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
DescribeTable("removes VIP if all endpoints were deleted and re-adds VIP on endpoint creation",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, "plndr-svcs-lock", "kube-system", trafficPolicy, client, false,
[]corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}, 1, true, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip DualStack functionality - IPv6 primary, svc_enable=true, svc_election=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
PodSubnet: "fd00:10:244::/56,10.244.0.0/16",
ServiceSubnet: "fd00:10:96::/112,10.96.0.0/16",
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "true",
EnableEndpoints: "false",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ds-svc-el-ipv6", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv6 VIP address for service",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testService(ctx, svcName, lbAddress, fmt.Sprintf("kubevip-%s", svcName), dsNamespace, trafficPolicy, client, true,
[]corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip DualStack functionality - IPv6 primary, svc_enable=true, with per-service election on demand", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
PerServiceElectionOnDemand: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-dual-ipv6-mixed", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 1, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an IPv6 and IPv4 VIP address for services when per-service election on demand is used",
func(svcName string, currentOffset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddresses := []string{}
for range 2 {
lbAddresses = append(lbAddresses, e2e.GenerateDualStackVIP(currentOffset, defaultNetwork))
}
testServiceMixedElection(ctx, svcName, lbAddresses, "plndr-svcs-lock", "kube-system", fmt.Sprintf("kubevip-%s", svcName), dsNamespace,
trafficPolicy, client, false, []corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}, 1, 1, false, dsNumber)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
Entry("with external traffic policy - local", "test-svc-local", SOffset.Get(), corev1.ServiceExternalTrafficPolicyLocal),
)
})
Describe("kube-vip IPv4 functionality with legacy hostname", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv4Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv4Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "false",
SvcElectionEnable: "false",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 1
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "ipv4-hostname", k8sImagePath, v129,
kubeVIPHostnameManifestTemplate, logger, manifestValues, networking, 3, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
It(clusterName+" uses hostname fallback while providing an IPv4 VIP address for the Kubernetes control plane nodes", func() {
testControlPlaneVIPs(ctx, []string{cpVIP}, clusterName, client)
})
})
Describe("kube-vip IPv6 functionality with manually specified lease name, svc_enable=true, svc_election=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
dsNumber int
)
BeforeAll(func() {
cpVIP = e2e.GenerateVIP(utils.IPv6Family, SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.IPv6Family,
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "true",
EnableEndpoints: "true",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
dsNumber = 3
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-el-m-ipv6", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 2, nil, dsNumber)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an single IPv6 VIP address for multiple services",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateVIP(utils.IPv6Family, offset, defaultNetwork)
testServiceCommonLease(ctx, svcName, lbAddress, dsNamespace,
trafficPolicy,
client, []corev1.IPFamily{corev1.IPv6Protocol}, 3)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
)
})
Describe("kube-vip DualStack functionality with manually specified lease name, svc_enable=true, svc_election=true", Ordered, func() {
var (
cpVIP string
client kubernetes.Interface
clusterName string
tempDirPath string
)
BeforeAll(func() {
cpVIP = e2e.GenerateDualStackVIP(SOffset.Get(), defaultNetwork)
networking := &kindconfigv1alpha4.Networking{
IPFamily: kindconfigv1alpha4.DualStackFamily,
PodSubnet: "fd00:10:244::/56,10.244.0.0/16",
ServiceSubnet: "fd00:10:96::/112,10.96.0.0/16",
}
manifestValues := &e2e.KubevipManifestValues{
ControlPlaneVIP: cpVIP,
ControlPlaneEnable: "true",
ImagePath: imagePath,
ConfigPath: configPath,
SvcEnable: "true",
SvcElectionEnable: "true",
EnableEndpoints: "false",
EnableNodeLabeling: "false",
EnableServiceSecurity: "true",
}
tempDirPath = MustMkdirTemp(tempDirPathRoot, testDirPrefix)
clusterName, client, _ = prepareCluster(ctx, tempDirPath, "svc-el-m-ds", k8sImagePath, v129,
kubeVIPManifestTemplate, logger, manifestValues, networking, 2, nil, 3)
})
AfterAll(func() {
SaveLogsAndCleanup(ctx, client, tempDirPath, clusterName, func() {
cleanupCluster(clusterName, defaultNetwork, ConfigMtx, logger)
})
})
DescribeTable("configures an single IPv4 and IPv6 VIP address for multiple services",
func(svcName string, offset uint, trafficPolicy corev1.ServiceExternalTrafficPolicy) {
lbAddress := e2e.GenerateDualStackVIP(offset, defaultNetwork)
testServiceCommonLease(ctx, svcName, lbAddress, dsNamespace,
trafficPolicy,
client, []corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}, 3)
},
Entry("with external traffic policy - cluster", "test-svc-cluster", SOffset.Get(), corev1.ServiceExternalTrafficPolicyCluster),
)
})
}
})
func createKindCluster(logger log.Logger, config *v1alpha4.Cluster, clusterName string) (kubernetes.Interface, *rest.Config, error) {
provider := cluster.NewProvider(
cluster.ProviderWithLogger(logger),
cluster.ProviderWithDocker(),
)
format.UseStringerRepresentation = true // Otherwise error stacks have binary format.
Expect(provider.Create(
clusterName,
cluster.CreateWithV1Alpha4Config(config),
cluster.CreateWithRetain(os.Getenv("E2E_PRESERVE_CLUSTER") == "true"), // If create fails, we'll need the cluster alive to debug
)).To(Succeed())
kc, err := provider.KubeConfig(clusterName, false)
Expect(err).ToNot(HaveOccurred())
kubeconfigGetter := func() (*api.Config, error) {
return clientcmd.Load([]byte(kc))
}
cfg, err := clientcmd.BuildConfigFromKubeconfigGetter("", kubeconfigGetter)
if err != nil {
return nil, nil, fmt.Errorf("failed to build kubeconfig: %w", err)
}
client, err := kubernetes.NewForConfig(cfg)
Expect(err).ToNot(HaveOccurred())
return client, cfg, nil
}
// Assume the VIP is routable if status code is 200 or 500. Since etcd might glitch.
func assertControlPlaneIsRoutable(controlPlaneVIP string, transportTimeout, eventuallyTimeout time.Duration) {
assertConnection("https", controlPlaneVIP, "6443", "livez", transportTimeout, eventuallyTimeout)
}
// Assume connection to the provided address is possible
func assertConnection(protocol, ip, port, suffix string, transportTimeout, eventuallyTimeout time.Duration) {
if strings.Contains(ip, ":") {
ip = fmt.Sprintf("[%s]", ip)
}
By(withTimestamp(fmt.Sprintf("checking connection to %s://%s:%s/%s", protocol, ip, port, suffix)))
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // nolint
}
client := &http.Client{Transport: transport, Timeout: transportTimeout}
Eventually(func() int {
resp, _ := client.Get(fmt.Sprintf("%s://%s:%s/%s", protocol, ip, port, suffix))
if resp == nil {
return -1
}
defer resp.Body.Close()
return resp.StatusCode
}, eventuallyTimeout, transportTimeout).Should(BeElementOf([]int{http.StatusOK, http.StatusInternalServerError}), fmt.Sprintf("Failed to connect to %s", ip))
By(withTimestamp(fmt.Sprintf("estabilished connection to %s://%s:%s/%s", protocol, ip, port, suffix)))
}
// Assume connection to the provided address is possible
func assertConnectionError(protocol, ip, port, suffix string, transportTimeout time.Duration, eventuallyTimeout time.Duration) {
if strings.Contains(ip, ":") {
ip = fmt.Sprintf("[%s]", ip)
}
By(withTimestamp(fmt.Sprintf("checking connection error to %s://%s:%s/%s", protocol, ip, port, suffix)))
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // nolint
}
client := &http.Client{Transport: transport, Timeout: transportTimeout}
Eventually(func() error {
_, err := client.Get(fmt.Sprintf("%s://%s:%s/%s", protocol, ip, port, suffix))
return err
}, eventuallyTimeout, transportTimeout).Should(HaveOccurred())
By(withTimestamp(fmt.Sprintf("connection %s://%s:%s/%s error as expected", protocol, ip, port, suffix)))
}
func killLeader(leaderName string) {
cmd := exec.Command(
"docker", "kill", leaderName,
)
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
// Every parallel kind cluster shares one Docker daemon, so the kill can be acknowledged slowly.
Eventually(session, "30s").Should(gexec.Exit(0))
}
func findLeader(leaderIPAddr string, clusterName string) string {
dockerControlPlaneContainerNames := []string{
fmt.Sprintf("%s-control-plane", clusterName),
fmt.Sprintf("%s-control-plane2", clusterName),
fmt.Sprintf("%s-control-plane3", clusterName),
}
var leaderName string
for _, name := range dockerControlPlaneContainerNames {
cmdOut := new(bytes.Buffer)
cmd := exec.Command(
"docker", "exec", name, "ip", "addr",
)
cmd.Stdout = cmdOut
Eventually(cmd.Run(), "5s").Should(Succeed())
if strings.Contains(cmdOut.String(), leaderIPAddr) {
leaderName = name
break
}
}
return leaderName
}
func withTimestamp(text string) string {
return fmt.Sprintf("%s: %s", time.Now(), text)
}
func createDS(ctx context.Context, name, namespace string, client kubernetes.Interface, port int) {
By(withTimestamp(fmt.Sprintf("createing test daemonset %s/%s", namespace, name)))
labels := make(map[string]string)
labels["app"] = name
d := v1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: labels,
},
Spec: v1.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
Tolerations: []corev1.Toleration{
{
Key: "node-role.kubernetes.io/control-plane",
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "node-role.kubernetes.io/master",
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoSchedule,
},
},
Containers: []corev1.Container{
{
Name: fmt.Sprintf("%s-v4", name),
Image: "ghcr.io/traefik/whoami:v1.11",
Ports: []corev1.ContainerPort{
{
ContainerPort: int32(port),
},
},
Env: []corev1.EnvVar{
{
Name: "WHOAMI_PORT_NUMBER",
Value: strconv.Itoa(port),
},
},
},
},
},
},
},
}
Eventually(func() error {
_, err := client.AppsV1().DaemonSets(namespace).Create(ctx, &d, metav1.CreateOptions{})
return err
}, "60s").Should(Succeed())
Eventually(func() error {
return dsStarted(ctx, name, namespace, client)
}, "60s").Should(Succeed())
}
func createPod(ctx context.Context, client kubernetes.Interface, namespace, name string, port int) {
By(withTimestamp(fmt.Sprintf("createing test pod %s/%s", namespace, name)))
labels := make(map[string]string)
labels["app"] = name
p := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: labels,
},
Spec: corev1.PodSpec{
Tolerations: []corev1.Toleration{
{
Key: "node-role.kubernetes.io/control-plane",
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "node-role.kubernetes.io/master",
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoSchedule,
},
},
Containers: []corev1.Container{
{
Name: fmt.Sprintf("%s-v4", name),
Image: "ghcr.io/traefik/whoami:v1.11",
Ports: []corev1.ContainerPort{
{
ContainerPort: int32(port),
},
},
Env: []corev1.EnvVar{
{
Name: "WHOAMI_PORT_NUMBER",
Value: strconv.Itoa(port),
},
},
},
},
},
}
Eventually(func() error {
_, err := client.CoreV1().Pods(namespace).Create(ctx, &p, metav1.CreateOptions{})
return err
}, "60s").Should(Succeed())
Eventually(func() error {
return podStarted(ctx, name, namespace, client)
}, "60s").Should(Succeed())
}
func dsStarted(ctx context.Context, name, namespace string, client kubernetes.Interface) error {
ds, err := client.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return err
}
if ds.Status.NumberReady != ds.Status.DesiredNumberScheduled {
return fmt.Errorf("not all DS pods are ready")
}
return nil
}
func podStarted(ctx context.Context, name, namespace string, client kubernetes.Interface) error {
pod, err := client.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return err
}
if pod.Status.Phase != corev1.PodRunning {
return fmt.Errorf("pod %s/%s is not ready", namespace, name)
}
return nil
}
func removePod(ctx context.Context, name, namespace string, client kubernetes.Interface) error {
propagationPolicy := metav1.DeletePropagationForeground
if err := client.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{
PropagationPolicy: &propagationPolicy,
}); err != nil && errors.IsNotFound(err) {
return nil
} else {
return err
}
}
func createTestService(ctx context.Context, name, namespace, target, lbAddress string, client kubernetes.Interface, ipfPolicy corev1.IPFamilyPolicy,
ipFamiles []corev1.IPFamily, externalPolicy corev1.ServiceExternalTrafficPolicy, leaseName string, port int, dhcpBroadcast bool, forceElection bool,
) {
svcAnnotations := make(map[string]string)
svcAnnotations[kubevip.LoadbalancerIPAnnotation] = lbAddress
if leaseName != "" {
svcAnnotations[kubevip.ServiceLease] = leaseName
}
if dhcpBroadcast {
svcAnnotations[kubevip.DHCPBroadcast] = "true"
}
if forceElection {
svcAnnotations[kubevip.ForcePerServiceElection] = "true"
}
labels := make(map[string]string)
labels["app"] = target
By(withTimestamp(fmt.Sprintf("creating service %s/%s", namespace, name)))
s := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: labels,
Annotations: svcAnnotations,
},
Spec: corev1.ServiceSpec{
IPFamilies: ipFamiles,
IPFamilyPolicy: &ipfPolicy,
Type: corev1.ServiceTypeLoadBalancer,
ExternalTrafficPolicy: externalPolicy,
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolTCP,
Port: int32(port),
},
},
Selector: labels,
},
}
Eventually(func() error {
_, err := client.CoreV1().Services(namespace).Create(ctx, &s, metav1.CreateOptions{})
return err
}, time.Second*60, time.Second).Should(Succeed())
By(withTimestamp(fmt.Sprintf("service %s/%s created", namespace, name)))
Eventually(func() error {
By(withTimestamp(fmt.Sprintf("getting service %s/%s\n", namespace, name)))
_, err := client.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
return err
}, time.Second*60, time.Second).Should(Succeed())
}
func checkIPAddress(lbAddress, container string, expected bool) bool {
By(withTimestamp(fmt.Sprintf("checking LB %q, should exist: %t", lbAddress, expected)))
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
to := time.NewTimer(time.Second * 30)
defer to.Stop()
for {
select {
case <-to.C:
return false
case <-ticker.C:
if e2e.CheckIPAddressPresence(lbAddress, container, expected) {
return true
}
}
}
}
func checkDHCPBroadcastAnnotation(ctx context.Context, name, namespace string, expected bool, client kubernetes.Interface) {
svc, err := client.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
_, annotationSet := svc.Annotations[kubevip.DHCPBroadcast]
Expect(annotationSet).To(Equal(expected))
}
func checkIPAddressByLease(ctx context.Context, name, namespace, lbAddress string, expected bool, client kubernetes.Interface) bool {
By(withTimestamp(fmt.Sprintf("checking LB %q by lease %s/%s, should exist: %t", lbAddress, namespace, name, expected)))
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
to := time.NewTimer(time.Second * 360)
defer to.Stop()
for {
select {
case <-to.C:
return false
case <-ticker.C:
if e2e.CheckIPAddressPresenceByLease(ctx, name, namespace, lbAddress, client, expected) {
return true
}
}
}
}
func prepareCluster(ctx context.Context, tempDirPath, clusterNameSuffix, k8sImagePath string,
v129 bool, kubeVIPManifestTemplate *template.Template, logger log.Logger,
manifestValues *e2e.KubevipManifestValues, networking *kindconfigv1alpha4.Networking, nodesNum int,
addSAN *san, dsNumber int) (string, kubernetes.Interface, *rest.Config) {
manifestPath := filepath.Join(tempDirPath, fmt.Sprintf("kube-vip-%s.yaml", clusterNameSuffix))
manifestFile, err := os.Create(manifestPath)
Expect(err).NotTo(HaveOccurred())
defer manifestFile.Close()
clusterConfig := kindconfigv1alpha4.Cluster{
Networking: *networking,
Nodes: []kindconfigv1alpha4.Node{},
}
kubeadmPatches := []kindconfigv1alpha4.PatchJSON6902{}
if addSAN != nil {
for i := 0; i < 64; i++ {
(*addSAN.ip)[len(*addSAN.ip)-1]++
if addSAN.ipnet.Contains(*addSAN.ip) {
kubeadmPatches = append(kubeadmPatches, kindconfigv1alpha4.PatchJSON6902{
Group: "kubeadm.k8s.io",
Version: "v1beta3",
Kind: "ClusterConfiguration",
Patch: testJSON + addSAN.ip.String(),
})
}
}
}
clusterConfig.KubeadmConfigPatchesJSON6902 = kubeadmPatches
for range nodesNum {
nodeConfig := kindconfigv1alpha4.Node{
Role: kindconfigv1alpha4.ControlPlaneRole,
ExtraMounts: []kindconfigv1alpha4.Mount{
{
HostPath: manifestPath,
ContainerPath: "/etc/kubernetes/manifests/kube-vip.yaml",
},
},
}
// Override the kind image version
if k8sImagePath != "" {
nodeConfig.Image = k8sImagePath
}
clusterConfig.Nodes = append(clusterConfig.Nodes, nodeConfig)
}
Expect(kubeVIPManifestTemplate.Execute(manifestFile, *manifestValues)).To(Succeed())
if v129 {
// create a seperate manifest
manifestPath2 := filepath.Join(tempDirPath, fmt.Sprintf("kube-vip-%s-first.yaml", clusterNameSuffix))
// change the path of the mount to the new file
clusterConfig.Nodes[0].ExtraMounts[0].HostPath = manifestPath2
manifestFile2, err := os.Create(manifestPath2)
Expect(err).NotTo(HaveOccurred())
defer manifestFile2.Close()
manifestValues.ConfigPath = "/etc/kubernetes/super-admin.conf"
Expect(kubeVIPManifestTemplate.Execute(manifestFile2, manifestValues)).To(Succeed())
}
clusterName := fmt.Sprintf("%s-%s", filepath.Base(tempDirPath), clusterNameSuffix)
// os.Unsetenv is process-wide; guard the brief window where it's observed by Kind.
ConfigMtx.Lock()
err = os.Unsetenv(kindNetworkEnv)
ConfigMtx.Unlock()
Expect(err).ToNot(HaveOccurred())
By(withTimestamp("creating a kind cluster with multiple control plane nodes"))
client, cfg, err := createKindCluster(logger, &clusterConfig, clusterName)
Expect(err).ToNot(HaveOccurred())
createTestDS(ctx, client, dsNamespace, dsName, dsNumber)
By(withTimestamp("loading local docker image to kind cluster"))
Expect(e2e.LoadDockerImageToKind(logger, manifestValues.ImagePath, clusterName)).To(Succeed())
By(withTimestamp("loading traefik/whoami image to kind cluster"))
if err := e2e.LoadDockerImageToKind(logger, "ghcr.io/traefik/whoami:v1.11", clusterName); err != nil {
By(withTimestamp(fmt.Sprintf(
"failed to load image ghcr.io/traefik/whoami:v1.11 into kind cluster, image will be downloaded after pod deployment, error: %s",
err.Error(),
)))
}
return clusterName, client, cfg
}
func cleanupCluster(clusterName, network string, configMtx *sync.Mutex, logger log.Logger) {
if network != defaultNetwork {
err := os.Unsetenv(kindNetworkEnv)
Expect(err).ToNot(HaveOccurred())
}
if os.Getenv("E2E_PRESERVE_CLUSTER") == "true" {
return
}
provider := cluster.NewProvider(
cluster.ProviderWithLogger(logger),
cluster.ProviderWithDocker(),
)
By(withTimestamp(fmt.Sprintf("deleting cluster: %s", clusterName)))
Eventually(func() error {
configMtx.Lock()
defer configMtx.Unlock()
return provider.Delete(clusterName, "")
}, "60s", "200ms").Should(Succeed())
if network != defaultNetwork {
cmd := exec.Command("docker", "network", "rm", network)
err := cmd.Run()
Expect(err).ToNot(HaveOccurred())
}
}
func testControlPlaneVIPs(ctx context.Context, cpVIPs []string, clusterName string, client kubernetes.Interface) {
testControlPlaneVIPsWithTimeout(ctx, cpVIPs, clusterName, client, time.Duration(0), 20*time.Second)
}
func testControlPlaneVIPsWithTimeout(ctx context.Context, cpVIPs []string, clusterName string, client kubernetes.Interface,
transportTimeout, eventuallyTimeout time.Duration) {
Expect(cpVIPs).ToNot(BeEmpty())
By(withTimestamp("checking that the Kubernetes control plane nodes are accessible via the assigned VIP"))
// Allow enough time for control plane nodes to load the docker image and
// use the default timeout for establishing a connection to the VIP
for _, cpVIP := range cpVIPs {
By(withTimestamp(fmt.Sprintf("testing connection to VIP: %s", cpVIP)))
assertControlPlaneIsRoutable(cpVIP, transportTimeout, eventuallyTimeout)
}
var leaderName string
Eventually(func() string {
leaderName = findLeader(cpVIPs[0], clusterName)
return leaderName
}, "600s").ShouldNot(BeEmpty())
Eventually(client.CoreV1().Nodes().Delete(ctx, leaderName, metav1.DeleteOptions{}), "60s", "1s").Should(Succeed())
By(withTimestamp("killing the leader Kubernetes control plane node to trigger a fail-over scenario"))
killLeader(leaderName)
By(withTimestamp("checking that the Kubernetes control plane nodes are still accessible via the assigned VIP with little downtime"))
for _, cpVIP := range cpVIPs {
By(withTimestamp(fmt.Sprintf("testing connection to VIP: %s", cpVIP)))
// Allow at most 30 seconds of downtime when polling the control plane nodes
assertControlPlaneIsRoutable(cpVIP, transportTimeout, eventuallyTimeout)
}
}
func testService(ctx context.Context, svcName, lbAddress, leaseName, leaseNamespace string, trafficPolicy corev1.ServiceExternalTrafficPolicy,
client kubernetes.Interface, serviceElection bool, ipFamily []corev1.IPFamily, numberOfServices int, deleteDS bool, dsNumber int,
) {
lbAddresses := vip.Split(lbAddress)
services := []string{}
leases := []string{}
for i := range numberOfServices {
services = append(services, fmt.Sprintf("%s-%d", svcName, i))
if serviceElection {
leases = append(leases, fmt.Sprintf("%s-%d", leaseName, i))
} else {
leases = append(leases, leaseName)
}
}
for _, svc := range services {
createTestService(ctx, svc, dsNamespace, dsName, lbAddress,
client, corev1.IPFamilyPolicyPreferDualStack, ipFamily, trafficPolicy, "", 80, false, false)
time.Sleep(time.Second)
}
for _, addr := range lbAddresses {
Expect(checkIPAddressByLease(ctx, leases[0], leaseNamespace, addr, true, client)).To(BeTrue())
assertConnection("http", addr, "80", "", 3*time.Second, 60*time.Second)
}
container := e2e.GetLeaseHolder(ctx, leases[0], leaseNamespace, client)
if deleteDS {
removeTestDS(ctx, client, dsNamespace, dsName, dsNumber)
time.Sleep(time.Second)
for _, addr := range lbAddresses {
if serviceElection {
Expect(checkIPAddress(addr, container, false)).To(BeTrue())
} else {
Expect(checkIPAddressByLease(ctx, leases[0], leaseNamespace, addr, false, client)).To(BeTrue())
}
assertConnectionError("http", addr, "80", "", 3*time.Second, 30*time.Second)
}
createTestDS(ctx, client, dsNamespace, dsName, dsNumber)
time.Sleep(time.Second)
for _, addr := range lbAddresses {
Expect(checkIPAddressByLease(ctx, leases[0], leaseNamespace, addr, true, client)).To(BeTrue())
assertConnection("http", addr, "80", "", 3*time.Second, 60*time.Second)
}
}
for i := range numberOfServices {
expected := i < numberOfServices-1
err := client.CoreV1().Services(dsNamespace).Delete(ctx, services[i], metav1.DeleteOptions{})
Expect(err).ToNot(HaveOccurred())
time.Sleep(time.Second)
for _, addr := range lbAddresses {
if serviceElection {
Expect(checkIPAddress(addr, container, expected)).To(BeTrue())
} else {
Expect(checkIPAddressByLease(ctx, leases[i], leaseNamespace, addr, expected, client)).To(BeTrue())
}
if expected {
assertConnection("http", addr, "80", "", 3*time.Second, 60*time.Second)
} else {
assertConnectionError("http", addr, "80", "", 3*time.Second, 30*time.Second)
}
}
}
}
type serviceWithLease struct {
name string
lease string
leaseNamespace string
election bool
lbAddress string
}
func testServiceMixedElection(ctx context.Context, svcName string, lbAddress []string, globalLeaseName, globalLeaseNamespace, leaseName, leaseNamespace string, trafficPolicy corev1.ServiceExternalTrafficPolicy,
client kubernetes.Interface, serviceElection bool, ipFamily []corev1.IPFamily, numberOfGlobalServices, numberOfElectedServices int, deleteDS bool, dsNumber int,
) {
services := []serviceWithLease{}
for i := range numberOfGlobalServices {
services = append(services, serviceWithLease{
name: fmt.Sprintf("%s-global-%d", svcName, i),
lease: globalLeaseName,
leaseNamespace: globalLeaseNamespace,
election: false,
lbAddress: lbAddress[i],
})
}
for i := range numberOfElectedServices {
services = append(services, serviceWithLease{
name: fmt.Sprintf("%s-%d", svcName, i),
lease: fmt.Sprintf("%s-%d", leaseName, i),
leaseNamespace: leaseNamespace,
election: true,
lbAddress: lbAddress[i+numberOfGlobalServices],
})
}
for _, svc := range services {
createTestService(ctx, svc.name, dsNamespace, dsName, svc.lbAddress,
client, corev1.IPFamilyPolicyPreferDualStack, ipFamily, trafficPolicy, "", 80, false, svc.election)
time.Sleep(time.Second)
}
for _, svc := range services {
for addr := range strings.SplitSeq(svc.lbAddress, ",") {
Expect(checkIPAddressByLease(ctx, svc.lease, svc.leaseNamespace, addr, true, client)).To(BeTrue())
assertConnection("http", addr, "80", "", 3*time.Second, 60*time.Second)
}
}
for i := range services {
expected := i < len(services)-1
container := e2e.GetLeaseHolder(ctx, services[i].lease, services[i].leaseNamespace, client)
err := client.CoreV1().Services(dsNamespace).Delete(ctx, services[i].name, metav1.DeleteOptions{})
Expect(err).ToNot(HaveOccurred())
time.Sleep(time.Second)
for addr := range strings.SplitSeq(services[i].lbAddress, ",") {
if serviceElection {
Expect(checkIPAddress(addr, container, expected)).To(BeTrue())
}
if expected {
assertConnection("http", addr, "80", "", 3*time.Second, 60*time.Second)
} else {
assertConnectionError("http", addr, "80", "", 3*time.Second, 30*time.Second)
}
}
}
}
func testServiceCommonLease(ctx context.Context, svcName, lbAddress, leaseNamespace string, trafficPolicy corev1.ServiceExternalTrafficPolicy,
client kubernetes.Interface, ipFamily []corev1.IPFamily, numberOfServices int,
) {
lbAddresses := vip.Split(lbAddress)
services := []string{}
for i := range numberOfServices {
services = append(services, fmt.Sprintf("%s-%d", svcName, i))
}
lease := "common-lease"
for i, svc := range services {
tmpDsName := dsName
if i > 0 {
tmpDsName = fmt.Sprintf("%s-%d", tmpDsName, i)
}
createTestService(ctx, svc, dsNamespace, tmpDsName, lbAddress,
client, corev1.IPFamilyPolicyPreferDualStack, ipFamily, trafficPolicy, lease, 80+i, false, false)
time.Sleep(time.Second)
}
for i := range services {
for _, addr := range lbAddresses {
checkIPAddressByLease(ctx, lease, leaseNamespace, addr, true, client)
assertConnection("http", addr, strconv.Itoa(80+i), "", 3*time.Second, 60*time.Second)
}
}
container := e2e.GetLeaseHolder(ctx, lease, leaseNamespace, client)
nodes, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
Expect(err).ToNot(HaveOccurred())
for _, node := range nodes.Items {
expected := node.Name == container
for _, addr := range lbAddresses {
Expect(checkIPAddress(addr, node.Name, expected)).To(BeTrue())
}
}
for i := range numberOfServices {
expected := i < numberOfServices-1
By(fmt.Sprintf("deleting service %q", services[i]))
err := client.CoreV1().Services(dsNamespace).Delete(ctx, services[i], metav1.DeleteOptions{})
Expect(err).ToNot(HaveOccurred())
time.Sleep(time.Second)
Eventually(func() error {
_, err := client.CoreV1().Services(dsNamespace).Get(ctx, services[i], metav1.GetOptions{})
return err
}).ShouldNot(Succeed())
for _, addr := range lbAddresses {
for _, node := range nodes.Items {
if node.Name == container {
Expect(checkIPAddress(addr, node.Name, expected)).To(BeTrue())
} else {
Expect(checkIPAddress(addr, node.Name, false)).To(BeTrue())
}
}
}
}
}
func SaveLogsAndCleanup(ctx context.Context, client kubernetes.Interface, tempDirPath, clusterName string, cleanup func()) {
By(fmt.Sprintf("saving logs to %q", tempDirPath))
Eventually(func() error {
return e2e.GetLogs(ctx, client, tempDirPath, clusterName)
}, "60s", "5s").Should(Succeed())
cleanup()
}
func MustMkdirTemp(dir, prefix string) string {
tempDirPath, err := os.MkdirTemp(dir, prefix)
Expect(err).NotTo(HaveOccurred())
return tempDirPath
}