Skip to content

Commit

Permalink
refactor lb svc e2e
Browse files Browse the repository at this point in the history
Signed-off-by: zhangzujian <[email protected]>
  • Loading branch information
zhangzujian committed Jun 28, 2024
1 parent c547787 commit 81c918f
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 46 deletions.
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,6 @@ kind-install-lb-svc:
$(call kind_load_image,kube-ovn,$(VPC_NAT_GW_IMG))
@$(MAKE) ENABLE_LB_SVC=true CNI_CONFIG_PRIORITY=10 kind-install
@$(MAKE) kind-install-multus
kubectl apply -f yamls/lb-svc-attachment.yaml

.PHONY: kind-install-webhook
kind-install-webhook: kind-install
Expand Down
196 changes: 158 additions & 38 deletions test/e2e/lb-svc/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package lb_svc

import (
"context"
"encoding/json"
"flag"
"fmt"
"math/big"
"math/rand/v2"
"net"
"strconv"
"testing"
"time"

cnitypes "github.com/containernetworking/cni/pkg/types"
dockernetwork "github.com/docker/docker/api/types/network"
nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -21,17 +25,34 @@ import (
k8sframework "k8s.io/kubernetes/test/e2e/framework"
"k8s.io/kubernetes/test/e2e/framework/config"
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
"k8s.io/utils/ptr"

"github.com/onsi/ginkgo/v2"

apiv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
"github.com/kubeovn/kube-ovn/pkg/netconf"
"github.com/kubeovn/kube-ovn/pkg/util"
"github.com/kubeovn/kube-ovn/test/e2e/framework"
"github.com/kubeovn/kube-ovn/test/e2e/framework/docker"
"github.com/kubeovn/kube-ovn/test/e2e/framework/kind"
)

const subnetProvider = "lb-svc-attachment.kube-system"
const CNIVersion = "0.3.1"

// https://github.com/containernetworking/plugins/blob/main/plugins/main/macvlan/macvlan.go#L37
type MacvlanNetConf struct {
netconf.NetConf
Master string `json:"master"`
Mode string `json:"mode"`
MTU int `json:"mtu"`
Mac string `json:"mac,omitempty"`
LinkContNs bool `json:"linkInContainer,omitempty"`

RuntimeConfig struct {
Mac string `json:"mac,omitempty"`
} `json:"runtimeConfig,omitempty"`
}

func init() {
klog.SetOutput(ginkgo.GinkgoWriter)
Expand All @@ -56,20 +77,27 @@ var _ = framework.SerialDescribe("[group:lb-svc]", func() {

var skip bool
var cs clientset.Interface
var podClient *framework.PodClient
var subnetClient *framework.SubnetClient
var serviceClient *framework.ServiceClient
var deploymentClient *framework.DeploymentClient
var clusterName, subnetName, namespaceName, serviceName, deploymentName string
var nadClient *framework.NetworkAttachmentDefinitionClient
var provider, nadName, clusterName, subnetName, namespaceName, serviceName, deploymentName, serverPodName, clientPodName string
var dockerNetwork *dockernetwork.Inspect
var cidr, gateway string
ginkgo.BeforeEach(func() {
cs = f.ClientSet
podClient = f.PodClient()
subnetClient = f.SubnetClient()
serviceClient = f.ServiceClient()
deploymentClient = f.DeploymentClient()
nadClient = f.NetworkAttachmentDefinitionClient()
namespaceName = f.Namespace.Name
nadName = "nad-" + framework.RandomSuffix()
subnetName = "subnet-" + framework.RandomSuffix()
serviceName = "service-" + framework.RandomSuffix()
serverPodName = "pod-" + framework.RandomSuffix()
clientPodName = "pod-" + framework.RandomSuffix()
deploymentName = lbSvcDeploymentName(serviceName)

if skip {
Expand All @@ -96,6 +124,33 @@ var _ = framework.SerialDescribe("[group:lb-svc]", func() {
dockerNetwork = network
}

provider = fmt.Sprintf("%s.%s", nadName, namespaceName)

ginkgo.By("Constructing network attachment definition config")
config := &MacvlanNetConf{
NetConf: netconf.NetConf{
NetConf: cnitypes.NetConf{
CNIVersion: CNIVersion,
Type: "macvlan",
},
IPAM: &netconf.IPAMConf{
Type: util.CniTypeName,
ServerSocket: "/run/openvswitch/kube-ovn-daemon.sock",
Provider: provider,
},
},
Master: "eth0",
Mode: "bridge",
LinkContNs: true,
}
buf, err := json.MarshalIndent(config, "", " ")
framework.ExpectNoError(err)

ginkgo.By("Creating network attachment definition " + nadName)
nad := framework.MakeNetworkAttachmentDefinition(nadName, namespaceName, string(buf))
nad = nadClient.Create(nad)
framework.Logf("created network attachment definition config:\n%s", nad.Spec.Config)

ginkgo.By("Creating subnet " + subnetName)
for _, config := range dockerNetwork.IPAM.Config {
if util.CheckProtocol(config.Subnet) == apiv1.ProtocolIPv4 {
Expand All @@ -112,38 +167,54 @@ var _ = framework.SerialDescribe("[group:lb-svc]", func() {
}
}
subnet := framework.MakeSubnet(subnetName, "", cidr, gateway, "", "", excludeIPs, nil, []string{namespaceName})
subnet.Spec.Provider = subnetProvider
subnet.Spec.Provider = provider
_ = subnetClient.Create(subnet)
})
ginkgo.AfterEach(func() {
ginkgo.By("Deleting deployment " + deploymentName)
deploymentClient.DeleteSync(deploymentName)
ginkgo.By("Deleting pod " + clientPodName)
podClient.DeleteSync(clientPodName)

ginkgo.By("Deleting pod " + serverPodName)
podClient.DeleteSync(serverPodName)

ginkgo.By("Deleting service " + serviceName)
serviceClient.DeleteSync(serviceName)

ginkgo.By("Deleting deployment " + deploymentName)
deploymentClient.DeleteSync(deploymentName)

ginkgo.By("Deleting subnet " + subnetName)
subnetClient.DeleteSync(subnetName)

ginkgo.By("Deleting network attachment definition " + nadName)
nadClient.Delete(nadName)
})

framework.ConformanceIt("should allocate dynamic external IP for service", func() {
ginkgo.By("Creating server pod " + serverPodName)
labels := map[string]string{"app": serviceName}
port := 8000 + rand.Int32N(1000)
args := []string{"netexec", "--http-port", strconv.Itoa(int(port))}
serverPod := framework.MakePod(namespaceName, serverPodName, labels, nil, framework.AgnhostImage, nil, args)
_ = podClient.CreateSync(serverPod)

ginkgo.By("Creating service " + serviceName)
ports := []corev1.ServicePort{{
Name: "tcp",
Protocol: corev1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt32(80),
Port: port,
TargetPort: intstr.FromInt32(port),
}}
annotations := map[string]string{
subnetProvider + ".kubernetes.io/logical_switch": subnetName,
util.AttachmentProvider: provider,
}
selector := map[string]string{"app": "lb-svc-dynamic"}
service := framework.MakeService(serviceName, corev1.ServiceTypeLoadBalancer, annotations, selector, ports, corev1.ServiceAffinityNone)
_ = serviceClient.CreateSync(service, func(s *corev1.Service) (bool, error) {
service := framework.MakeService(serviceName, corev1.ServiceTypeLoadBalancer, annotations, labels, ports, corev1.ServiceAffinityNone)
service.Spec.AllocateLoadBalancerNodePorts = ptr.To(false)
service = serviceClient.CreateSync(service, func(s *corev1.Service) (bool, error) {
return len(s.Spec.ClusterIPs) != 0, nil
}, "cluster ips are not empty")

ginkgo.By("Waiting for deployment " + deploymentName + " to be ready")
ginkgo.By("Waiting for LB deployment " + deploymentName + " to be ready")
framework.WaitUntil(2*time.Second, time.Minute, func(ctx context.Context) (bool, error) {
_, err := deploymentClient.DeploymentInterface.Get(ctx, deploymentName, metav1.GetOptions{})
if err == nil {
Expand All @@ -164,44 +235,71 @@ var _ = framework.SerialDescribe("[group:lb-svc]", func() {
framework.ExpectNoError(err)
framework.ExpectHaveLen(pods.Items, 1)

ginkgo.By("Checking pod annotations")
key := fmt.Sprintf(util.AllocatedAnnotationTemplate, subnetProvider)
framework.ExpectHaveKeyWithValue(pods.Items[0].Annotations, key, "true")
cidrKey := fmt.Sprintf(util.CidrAnnotationTemplate, subnetProvider)
ipKey := fmt.Sprintf(util.IPAddressAnnotationTemplate, subnetProvider)
framework.ExpectHaveKey(pods.Items[0].Annotations, cidrKey)
framework.ExpectHaveKey(pods.Items[0].Annotations, ipKey)
cidr := pods.Items[0].Annotations[cidrKey]
ip := pods.Items[0].Annotations[ipKey]
framework.ExpectTrue(util.CIDRContainIP(cidr, ip))
ginkgo.By("Checking LB pod annotations")
pod := &pods.Items[0]
key := fmt.Sprintf(util.AllocatedAnnotationTemplate, provider)
framework.ExpectHaveKeyWithValue(pod.Annotations, key, "true")
cidrKey := fmt.Sprintf(util.CidrAnnotationTemplate, provider)
ipKey := fmt.Sprintf(util.IPAddressAnnotationTemplate, provider)
framework.ExpectHaveKey(pod.Annotations, cidrKey)
framework.ExpectHaveKey(pod.Annotations, ipKey)
lbIP := pod.Annotations[ipKey]
framework.ExpectIPInCIDR(lbIP, pod.Annotations[cidrKey])

ginkgo.By("Checking service external IP")
framework.WaitUntil(2*time.Second, time.Minute, func(_ context.Context) (bool, error) {
service = serviceClient.Get(serviceName)
return len(service.Status.LoadBalancer.Ingress) != 0, nil
}, ".status.loadBalancer.ingress is not empty")
framework.ExpectEqual(service.Status.LoadBalancer.Ingress[0].IP, ip)
framework.ExpectHaveLen(service.Status.LoadBalancer.Ingress, 1)
framework.ExpectEqual(service.Status.LoadBalancer.Ingress[0].IP, lbIP)
framework.ExpectConsistOf(service.Spec.ExternalIPs, []string{lbIP})

ginkgo.By("Creating client pod " + clientPodName)
annotations = map[string]string{nadv1.NetworkAttachmentAnnot: fmt.Sprintf("%s/%s", namespaceName, nadName)}
cmd := []string{"sh", "-c", "sleep infinity"}
clientPod := framework.MakePod(namespaceName, clientPodName, nil, annotations, f.KubeOVNImage, cmd, nil)
clientPod = podClient.CreateSync(clientPod)

ginkgo.By("Checking service connectivity from client pod " + clientPodName)
curlCmd := fmt.Sprintf("curl -q -s --connect-timeout 2 --max-time 2 %s/clientip", util.JoinHostPort(lbIP, port))
ginkgo.By(fmt.Sprintf(`Executing %q in pod %s/%s`, curlCmd, clientPod.Namespace, clientPod.Name))
_ = e2epodoutput.RunHostCmdOrDie(clientPod.Namespace, clientPod.Name, curlCmd)

ginkgo.By("Deleting service " + serviceName)
serviceClient.DeleteSync(serviceName)

ginkgo.By("Waiting for LB deployment " + deploymentName + " to be deleted automatically")
err = deploymentClient.WaitToDisappear(deploymentName, 2*time.Second, 2*time.Minute)
framework.ExpectNoError(err, "deployment failed to disappear")
})

framework.ConformanceIt("should allocate static external IP for service", func() {
ginkgo.By("Creating server pod " + serverPodName)
labels := map[string]string{"app": serviceName}
port := 8000 + rand.Int32N(1000)
args := []string{"netexec", "--http-port", strconv.Itoa(int(port))}
serverPod := framework.MakePod(namespaceName, serverPodName, labels, nil, framework.AgnhostImage, nil, args)
_ = podClient.CreateSync(serverPod)

ginkgo.By("Creating service " + serviceName)
base := util.IP2BigInt(gateway)
lbIP := util.BigInt2Ip(base.Add(base, big.NewInt(50+rand.Int64N(50))))
ports := []corev1.ServicePort{{
Name: "tcp",
Protocol: corev1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt32(80),
Port: port,
TargetPort: intstr.FromInt32(port),
}}
annotations := map[string]string{
subnetProvider + ".kubernetes.io/logical_switch": subnetName,
util.AttachmentProvider: provider,
}
selector := map[string]string{"app": "lb-svc-static"}
service := framework.MakeService(serviceName, corev1.ServiceTypeLoadBalancer, annotations, selector, ports, corev1.ServiceAffinityNone)
base := util.IP2BigInt(gateway)
lbIP := util.BigInt2Ip(base.Add(base, big.NewInt(50+rand.Int64N(50))))
service := framework.MakeService(serviceName, corev1.ServiceTypeLoadBalancer, annotations, labels, ports, corev1.ServiceAffinityNone)
service.Spec.LoadBalancerIP = lbIP
service.Spec.AllocateLoadBalancerNodePorts = ptr.To(false)
_ = serviceClient.Create(service)

ginkgo.By("Waiting for deployment " + deploymentName + " to be ready")
ginkgo.By("Waiting for LB deployment " + deploymentName + " to be ready")
framework.WaitUntil(2*time.Second, time.Minute, func(ctx context.Context) (bool, error) {
_, err := deploymentClient.DeploymentInterface.Get(ctx, deploymentName, metav1.GetOptions{})
if err == nil {
Expand All @@ -221,19 +319,41 @@ var _ = framework.SerialDescribe("[group:lb-svc]", func() {
framework.ExpectNoError(err)
framework.ExpectHaveLen(pods.Items, 1)

ginkgo.By("Checking pod annotations")
key := fmt.Sprintf(util.AllocatedAnnotationTemplate, subnetProvider)
framework.ExpectHaveKeyWithValue(pods.Items[0].Annotations, key, "true")
ipKey := fmt.Sprintf(util.IPAddressAnnotationTemplate, subnetProvider)
framework.ExpectHaveKeyWithValue(pods.Items[0].Annotations, ipKey, lbIP)
cidr := pods.Items[0].Annotations[fmt.Sprintf(util.CidrAnnotationTemplate, subnetProvider)]
framework.ExpectTrue(util.CIDRContainIP(cidr, lbIP))
ginkgo.By("Checking LB pod annotations")
pod := &pods.Items[0]
key := fmt.Sprintf(util.AllocatedAnnotationTemplate, provider)
framework.ExpectHaveKeyWithValue(pod.Annotations, key, "true")
ipKey := fmt.Sprintf(util.IPAddressAnnotationTemplate, provider)
framework.ExpectHaveKeyWithValue(pod.Annotations, ipKey, lbIP)
cidrKey := fmt.Sprintf(util.CidrAnnotationTemplate, provider)
framework.ExpectHaveKey(pod.Annotations, cidrKey)
framework.ExpectIPInCIDR(lbIP, pod.Annotations[cidrKey])

ginkgo.By("Checking service external IP")
framework.WaitUntil(2*time.Second, time.Minute, func(_ context.Context) (bool, error) {
service = serviceClient.Get(serviceName)
return len(service.Status.LoadBalancer.Ingress) != 0, nil
}, ".status.loadBalancer.ingress is not empty")
framework.ExpectHaveLen(service.Status.LoadBalancer.Ingress, 1)
framework.ExpectEqual(service.Status.LoadBalancer.Ingress[0].IP, lbIP)
framework.Logf("service external IP: %v", service.Spec.ExternalIPs)

ginkgo.By("Creating client pod " + clientPodName)
annotations = map[string]string{nadv1.NetworkAttachmentAnnot: fmt.Sprintf("%s/%s", namespaceName, nadName)}
cmd := []string{"sh", "-c", "sleep infinity"}
clientPod := framework.MakePod(namespaceName, clientPodName, nil, annotations, f.KubeOVNImage, cmd, nil)
clientPod = podClient.CreateSync(clientPod)

ginkgo.By("Checking service connectivity from client pod " + clientPodName)
curlCmd := fmt.Sprintf("curl -q -s --connect-timeout 2 --max-time 2 %s/clientip", util.JoinHostPort(lbIP, port))
ginkgo.By(fmt.Sprintf(`Executing %q in pod %s/%s`, curlCmd, clientPod.Namespace, clientPod.Name))
_ = e2epodoutput.RunHostCmdOrDie(clientPod.Namespace, clientPod.Name, curlCmd)

ginkgo.By("Deleting service " + serviceName)
serviceClient.DeleteSync(serviceName)

ginkgo.By("Waiting for LB deployment " + deploymentName + " to be deleted automatically")
err = deploymentClient.WaitToDisappear(deploymentName, 2*time.Second, 2*time.Minute)
framework.ExpectNoError(err, "deployment failed to disappear")
})
})
7 changes: 0 additions & 7 deletions yamls/lb-svc-attachment.yaml

This file was deleted.

0 comments on commit 81c918f

Please sign in to comment.