442 lines
17 KiB
Go
442 lines
17 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/onsi/ginkgo/v2"
|
|
"github.com/onsi/gomega"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
discoveryv1 "k8s.io/api/discovery/v1"
|
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/util/rand"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"k8s.io/klog/v2"
|
|
mcsv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
|
|
|
|
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
|
"github.com/karmada-io/karmada/pkg/util"
|
|
"github.com/karmada-io/karmada/pkg/util/names"
|
|
"github.com/karmada-io/karmada/test/e2e/framework"
|
|
testhelper "github.com/karmada-io/karmada/test/helper"
|
|
)
|
|
|
|
var (
|
|
serviceExportClusterName = "member1"
|
|
serviceImportClusterName = "member2"
|
|
|
|
serviceExportResource = "serviceexports"
|
|
serviceImportResource = "serviceimports"
|
|
|
|
replicaCount = int32(1)
|
|
helloDeployment = appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Replicas: &replicaCount,
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
"app": "hello",
|
|
},
|
|
},
|
|
Template: corev1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{"app": "hello"},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "hello-tcp",
|
|
Image: "busybox",
|
|
Args: []string{"nc", "-lk", "-p", "42", "-v", "-e", "echo", "hello"},
|
|
},
|
|
{
|
|
Name: "hello-udp",
|
|
Image: "busybox",
|
|
Args: []string{"nc", "-lk", "-p", "42", "-u", "-v", "-e", "echo", "hello"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
helloService = corev1.Service{
|
|
Spec: corev1.ServiceSpec{
|
|
Selector: map[string]string{
|
|
"app": "hello",
|
|
},
|
|
Ports: []corev1.ServicePort{
|
|
{
|
|
Name: "tcp",
|
|
Port: 42,
|
|
Protocol: corev1.ProtocolTCP,
|
|
},
|
|
{
|
|
Name: "udp",
|
|
Port: 42,
|
|
Protocol: corev1.ProtocolUDP,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
helloServiceExport = mcsv1alpha1.ServiceExport{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "multicluster.x-k8s.io/v1alpha1",
|
|
Kind: "ServiceExport",
|
|
},
|
|
}
|
|
helloServiceImport = mcsv1alpha1.ServiceImport{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "multicluster.x-k8s.io/v1alpha1",
|
|
Kind: "ServiceImport",
|
|
},
|
|
Spec: mcsv1alpha1.ServiceImportSpec{
|
|
Type: mcsv1alpha1.ClusterSetIP,
|
|
Ports: []mcsv1alpha1.ServicePort{
|
|
{
|
|
Name: "tcp",
|
|
Port: 42,
|
|
Protocol: corev1.ProtocolTCP,
|
|
},
|
|
{
|
|
Name: "udp",
|
|
Port: 42,
|
|
Protocol: corev1.ProtocolUDP,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
requestPod = corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "request",
|
|
Image: "busybox",
|
|
Args: []string{"nc"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func getPrepareInfo() (serviceExport mcsv1alpha1.ServiceExport, serviceImport mcsv1alpha1.ServiceImport, exportPolicy,
|
|
importPolicy *policyv1alpha1.PropagationPolicy, deployment appsv1.Deployment, service corev1.Service) {
|
|
helloNamespace := testNamespace
|
|
helloName := fmt.Sprintf("hello-%s", rand.String(RandomStrLength))
|
|
|
|
serviceExport = helloServiceExport
|
|
serviceExport.Namespace = helloNamespace
|
|
serviceExport.Name = helloName
|
|
|
|
serviceImport = helloServiceImport
|
|
serviceImport.Namespace = helloNamespace
|
|
serviceImport.Name = helloName
|
|
|
|
exportPolicyNamespace := serviceExport.Namespace
|
|
exportPolicyName := fmt.Sprintf("export-%s-policy", serviceExport.Name)
|
|
exportPolicy = testhelper.NewPropagationPolicy(exportPolicyNamespace, exportPolicyName, []policyv1alpha1.ResourceSelector{
|
|
{
|
|
APIVersion: serviceExport.APIVersion,
|
|
Kind: serviceExport.Kind,
|
|
Name: serviceExport.Name,
|
|
Namespace: serviceExport.Namespace,
|
|
},
|
|
}, policyv1alpha1.Placement{
|
|
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
|
|
ClusterNames: []string{serviceExportClusterName},
|
|
},
|
|
})
|
|
|
|
importPolicyNamespace := serviceImport.Namespace
|
|
importPolicyName := fmt.Sprintf("import-%s-policy", serviceImport.Name)
|
|
importPolicy = testhelper.NewPropagationPolicy(importPolicyNamespace, importPolicyName, []policyv1alpha1.ResourceSelector{
|
|
{
|
|
APIVersion: serviceImport.APIVersion,
|
|
Kind: serviceImport.Kind,
|
|
Name: serviceImport.Name,
|
|
Namespace: serviceImport.Namespace,
|
|
},
|
|
}, policyv1alpha1.Placement{
|
|
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
|
|
ClusterNames: []string{serviceImportClusterName},
|
|
},
|
|
})
|
|
|
|
deployment = helloDeployment
|
|
deployment.Namespace = helloNamespace
|
|
deployment.Name = helloName
|
|
|
|
service = helloService
|
|
service.Namespace = helloNamespace
|
|
service.Name = helloName
|
|
|
|
return
|
|
}
|
|
|
|
var _ = ginkgo.Describe("Multi-Cluster Service testing", func() {
|
|
var serviceExportPolicyName, serviceImportPolicyName string
|
|
var serviceExportPolicy, serviceImportPolicy *policyv1alpha1.ClusterPropagationPolicy
|
|
var serviceExport mcsv1alpha1.ServiceExport
|
|
var serviceImport mcsv1alpha1.ServiceImport
|
|
var exportPolicy, importPolicy *policyv1alpha1.PropagationPolicy
|
|
var demoDeployment appsv1.Deployment
|
|
var demoService corev1.Service
|
|
|
|
ginkgo.BeforeEach(func() {
|
|
serviceExportPolicyName = fmt.Sprintf("%s-%s-policy", serviceExportResource, rand.String(RandomStrLength))
|
|
serviceImportPolicyName = fmt.Sprintf("%s-%s-policy", serviceImportResource, rand.String(RandomStrLength))
|
|
|
|
serviceExportPolicy = testhelper.NewClusterPropagationPolicy(serviceExportPolicyName, []policyv1alpha1.ResourceSelector{
|
|
{
|
|
APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
|
|
Kind: util.CRDKind,
|
|
Name: fmt.Sprintf("%s.%s", serviceExportResource, mcsv1alpha1.GroupName),
|
|
},
|
|
}, policyv1alpha1.Placement{
|
|
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
|
|
ClusterNames: framework.ClusterNames(),
|
|
},
|
|
})
|
|
serviceImportPolicy = testhelper.NewClusterPropagationPolicy(serviceImportPolicyName, []policyv1alpha1.ResourceSelector{
|
|
{
|
|
APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
|
|
Kind: util.CRDKind,
|
|
Name: fmt.Sprintf("%s.%s", serviceImportResource, mcsv1alpha1.GroupName),
|
|
},
|
|
}, policyv1alpha1.Placement{
|
|
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
|
|
ClusterNames: framework.ClusterNames(),
|
|
},
|
|
})
|
|
|
|
serviceExport, serviceImport, exportPolicy, importPolicy, demoDeployment, demoService = getPrepareInfo()
|
|
})
|
|
|
|
ginkgo.BeforeEach(func() {
|
|
framework.CreateClusterPropagationPolicy(karmadaClient, serviceExportPolicy)
|
|
framework.CreateClusterPropagationPolicy(karmadaClient, serviceImportPolicy)
|
|
framework.WaitCRDPresentOnClusters(karmadaClient, framework.ClusterNames(), mcsv1alpha1.GroupVersion.String(), util.ServiceExportKind)
|
|
framework.WaitCRDPresentOnClusters(karmadaClient, framework.ClusterNames(), mcsv1alpha1.GroupVersion.String(), util.ServiceImportKind)
|
|
ginkgo.DeferCleanup(func() {
|
|
framework.RemoveClusterPropagationPolicy(karmadaClient, serviceImportPolicy.Name)
|
|
framework.RemoveClusterPropagationPolicy(karmadaClient, serviceExportPolicy.Name)
|
|
|
|
// Now the deletion of ClusterPropagationPolicy will not cause the deletion of related binding and workload on member clusters,
|
|
// so we do not need to wait the disappearance of ServiceExport CRD and ServiceImport CRD
|
|
})
|
|
})
|
|
|
|
ginkgo.BeforeEach(func() {
|
|
exportClusterClient := framework.GetClusterClient(serviceExportClusterName)
|
|
gomega.Expect(exportClusterClient).ShouldNot(gomega.BeNil())
|
|
|
|
klog.Infof("Create Deployment(%s/%s) in %s cluster", demoDeployment.Namespace, demoDeployment.Name, serviceExportClusterName)
|
|
framework.CreateDeployment(exportClusterClient, &demoDeployment)
|
|
|
|
klog.Infof("Create Service(%s/%s) in %s cluster", demoService.Namespace, demoService.Name, serviceExportClusterName)
|
|
framework.CreateService(exportClusterClient, &demoService)
|
|
|
|
ginkgo.By(fmt.Sprintf("Wait Service(%s/%s)'s EndpointSlice exist in %s cluster", demoService.Namespace, demoService.Name, serviceExportClusterName), func() {
|
|
gomega.Eventually(func(g gomega.Gomega) (int, error) {
|
|
endpointSlices, err := exportClusterClient.DiscoveryV1().EndpointSlices(demoService.Namespace).List(context.TODO(), metav1.ListOptions{
|
|
LabelSelector: labels.Set{discoveryv1.LabelServiceName: demoService.Name}.AsSelector().String(),
|
|
})
|
|
g.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
epNums := 0
|
|
for _, slice := range endpointSlices.Items {
|
|
epNums += len(slice.Endpoints)
|
|
}
|
|
return epNums, nil
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal(1))
|
|
})
|
|
|
|
ginkgo.DeferCleanup(func() {
|
|
klog.Infof("Delete Deployment(%s/%s) in %s cluster", demoDeployment.Namespace, demoDeployment.Name, serviceExportClusterName)
|
|
framework.RemoveDeployment(exportClusterClient, demoDeployment.Namespace, demoDeployment.Name)
|
|
|
|
klog.Infof("Delete Service(%s/%s) in %s cluster", demoService.Namespace, demoService.Name, serviceExportClusterName)
|
|
framework.RemoveService(exportClusterClient, demoService.Namespace, demoService.Name)
|
|
})
|
|
})
|
|
|
|
ginkgo.AfterEach(func() {
|
|
ginkgo.By("Cleanup", func() {
|
|
err := controlPlaneClient.Delete(context.TODO(), &serviceExport)
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
|
|
err = controlPlaneClient.Delete(context.TODO(), &serviceImport)
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
})
|
|
|
|
framework.RemovePropagationPolicy(karmadaClient, exportPolicy.Namespace, exportPolicy.Name)
|
|
framework.RemovePropagationPolicy(karmadaClient, importPolicy.Namespace, importPolicy.Name)
|
|
})
|
|
|
|
ginkgo.Context("Connectivity testing", func() {
|
|
ginkgo.It("Export Service from source-clusters, import Service to destination-clusters", func() {
|
|
importClusterClient := framework.GetClusterClient(serviceImportClusterName)
|
|
gomega.Expect(importClusterClient).ShouldNot(gomega.BeNil())
|
|
|
|
ginkgo.By(fmt.Sprintf("Create ServiceExport(%s/%s)", serviceExport.Namespace, serviceExport.Name), func() {
|
|
err := controlPlaneClient.Create(context.TODO(), &serviceExport)
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
})
|
|
|
|
framework.CreatePropagationPolicy(karmadaClient, exportPolicy)
|
|
|
|
ginkgo.By(fmt.Sprintf("Wait EndpointSlices collected to namespace(%s) in controller-plane", demoService.Namespace), func() {
|
|
gomega.Eventually(func(g gomega.Gomega) (int, error) {
|
|
endpointSlices, err := kubeClient.DiscoveryV1().EndpointSlices(demoService.Namespace).List(context.TODO(), metav1.ListOptions{
|
|
LabelSelector: labels.Set{discoveryv1.LabelServiceName: names.GenerateDerivedServiceName(demoService.Name)}.AsSelector().String(),
|
|
})
|
|
g.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
epNums := 0
|
|
for _, slice := range endpointSlices.Items {
|
|
epNums += len(slice.Endpoints)
|
|
}
|
|
return epNums, nil
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal(1))
|
|
})
|
|
|
|
ginkgo.By(fmt.Sprintf("Create ServiceImport(%s/%s)", serviceImport.Namespace, serviceImport.Name), func() {
|
|
err := controlPlaneClient.Create(context.TODO(), &serviceImport)
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
})
|
|
|
|
framework.CreatePropagationPolicy(karmadaClient, importPolicy)
|
|
|
|
ginkgo.By(fmt.Sprintf("Wait derived-service(%s/%s) exist in %s cluster", demoService.Namespace, names.GenerateDerivedServiceName(demoService.Name), serviceImportClusterName), func() {
|
|
err := wait.PollImmediate(pollInterval, pollTimeout, func() (done bool, err error) {
|
|
_, err = importClusterClient.CoreV1().Services(demoService.Namespace).Get(context.TODO(), names.GenerateDerivedServiceName(demoService.Name), metav1.GetOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
})
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
})
|
|
|
|
ginkgo.By(fmt.Sprintf("Wait EndpointSlices have been imported to %s cluster", serviceImportClusterName), func() {
|
|
gomega.Eventually(func(g gomega.Gomega) (int, error) {
|
|
endpointSlices, err := importClusterClient.DiscoveryV1().EndpointSlices(demoService.Namespace).List(context.TODO(), metav1.ListOptions{
|
|
LabelSelector: labels.Set{discoveryv1.LabelServiceName: names.GenerateDerivedServiceName(demoService.Name)}.AsSelector().String(),
|
|
})
|
|
g.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
epNums := 0
|
|
for _, slice := range endpointSlices.Items {
|
|
epNums += len(slice.Endpoints)
|
|
}
|
|
return epNums, nil
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal(1))
|
|
})
|
|
|
|
ginkgo.By("TCP connects across clusters using the ClusterIP", func() {
|
|
derivedSvc, err := importClusterClient.CoreV1().Services(demoService.Namespace).Get(context.TODO(), names.GenerateDerivedServiceName(demoService.Name), metav1.GetOptions{})
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
|
|
pod := requestPod
|
|
pod.Name = fmt.Sprintf("request-%s", rand.String(RandomStrLength))
|
|
pod.Spec.Containers[0].Args = []string{"nc", derivedSvc.Spec.ClusterIP, "42"}
|
|
_, err = importClusterClient.CoreV1().Pods(demoService.Namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
gomega.Eventually(func() (string, error) {
|
|
return podLogs(context.TODO(), importClusterClient, demoService.Namespace, pod.Name)
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal("hello\n"))
|
|
})
|
|
|
|
ginkgo.By("UDP connects across clusters using the ClusterIP", func() {
|
|
derivedSvc, err := importClusterClient.CoreV1().Services(demoService.Namespace).Get(context.TODO(), names.GenerateDerivedServiceName(demoService.Name), metav1.GetOptions{})
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
|
|
pod := requestPod
|
|
pod.Name = fmt.Sprintf("request-%s", rand.String(RandomStrLength))
|
|
pod.Spec.Containers[0].Args = []string{"sh", "-c", fmt.Sprintf("echo hi | nc -u %s 42", derivedSvc.Spec.ClusterIP)}
|
|
_, err = importClusterClient.CoreV1().Pods(demoService.Namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
gomega.Eventually(func() (string, error) {
|
|
return podLogs(context.TODO(), importClusterClient, demoService.Namespace, pod.Name)
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal("hello\n"))
|
|
})
|
|
})
|
|
})
|
|
|
|
ginkgo.Context("EndpointSlices change testing", func() {
|
|
ginkgo.It("Update Deployment's replicas", func() {
|
|
exportClusterClient := framework.GetClusterClient(serviceExportClusterName)
|
|
gomega.Expect(exportClusterClient).ShouldNot(gomega.BeNil())
|
|
importClusterClient := framework.GetClusterClient(serviceImportClusterName)
|
|
gomega.Expect(importClusterClient).ShouldNot(gomega.BeNil())
|
|
|
|
ginkgo.By(fmt.Sprintf("Create ServiceExport(%s/%s)", serviceExport.Namespace, serviceExport.Name), func() {
|
|
err := controlPlaneClient.Create(context.TODO(), &serviceExport)
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
})
|
|
|
|
framework.CreatePropagationPolicy(karmadaClient, exportPolicy)
|
|
|
|
ginkgo.By(fmt.Sprintf("Wait EndpointSlices collected to namespace(%s) in controller-plane", demoService.Namespace), func() {
|
|
gomega.Eventually(func(g gomega.Gomega) (int, error) {
|
|
endpointSlices, err := kubeClient.DiscoveryV1().EndpointSlices(demoService.Namespace).List(context.TODO(), metav1.ListOptions{
|
|
LabelSelector: labels.Set{discoveryv1.LabelServiceName: names.GenerateDerivedServiceName(demoService.Name)}.AsSelector().String(),
|
|
})
|
|
g.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
epNums := 0
|
|
for _, slice := range endpointSlices.Items {
|
|
epNums += len(slice.Endpoints)
|
|
}
|
|
return epNums, nil
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal(1))
|
|
})
|
|
|
|
ginkgo.By(fmt.Sprintf("Create ServiceImport(%s/%s)", serviceImport.Namespace, serviceImport.Name), func() {
|
|
err := controlPlaneClient.Create(context.TODO(), &serviceImport)
|
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
|
})
|
|
|
|
framework.CreatePropagationPolicy(karmadaClient, importPolicy)
|
|
|
|
ginkgo.By(fmt.Sprintf("Wait EndpointSlice exist in %s cluster", serviceImportClusterName), func() {
|
|
gomega.Eventually(func(g gomega.Gomega) (int, error) {
|
|
endpointSlices, err := importClusterClient.DiscoveryV1().EndpointSlices(demoService.Namespace).List(context.TODO(), metav1.ListOptions{
|
|
LabelSelector: labels.Set{discoveryv1.LabelServiceName: names.GenerateDerivedServiceName(demoService.Name)}.AsSelector().String(),
|
|
})
|
|
g.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
epNums := 0
|
|
for _, slice := range endpointSlices.Items {
|
|
epNums += len(slice.Endpoints)
|
|
}
|
|
return epNums, nil
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal(1))
|
|
})
|
|
|
|
klog.Infof("Update Deployment's replicas in %s cluster", serviceExportClusterName)
|
|
framework.UpdateDeploymentReplicas(exportClusterClient, &demoDeployment, 2)
|
|
|
|
ginkgo.By(fmt.Sprintf("Wait EndpointSlice update in %s cluster", serviceImportClusterName), func() {
|
|
gomega.Eventually(func(g gomega.Gomega) (int, error) {
|
|
endpointSlices, err := importClusterClient.DiscoveryV1().EndpointSlices(demoService.Namespace).List(context.TODO(), metav1.ListOptions{
|
|
LabelSelector: labels.Set{discoveryv1.LabelServiceName: names.GenerateDerivedServiceName(demoService.Name)}.AsSelector().String(),
|
|
})
|
|
g.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
epNums := 0
|
|
for _, slice := range endpointSlices.Items {
|
|
epNums += len(slice.Endpoints)
|
|
}
|
|
return epNums, nil
|
|
}, pollTimeout, pollInterval).Should(gomega.Equal(2))
|
|
})
|
|
})
|
|
})
|
|
})
|