@@ -2,25 +2,84 @@ package servicesync
22
33import (
44 "context"
5+ "fmt"
56 "time"
67
78 "github.com/loft-sh/vcluster/pkg/util/translate"
89 "github.com/loft-sh/vcluster/test/framework"
910 "github.com/onsi/ginkgo/v2"
11+ "github.com/onsi/gomega"
1012 appsv1 "k8s.io/api/apps/v1"
1113 corev1 "k8s.io/api/core/v1"
1214 kerrors "k8s.io/apimachinery/pkg/api/errors"
1315 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+ "k8s.io/apimachinery/pkg/util/intstr"
1417 "k8s.io/apimachinery/pkg/util/wait"
1518 "k8s.io/client-go/kubernetes"
1619)
1720
18- var _ = ginkgo .Describe ("map services from host to virtual cluster and vice versa" , func () {
19- var f * framework.Framework
20-
21- ginkgo .JustBeforeEach (func () {
22- // use default framework
21+ var _ = ginkgo .Describe ("Verify mapping and syncing of services and endpoints" , ginkgo .Ordered , func () {
22+ var (
23+ f * framework.Framework
24+ testService * corev1.Service
25+ //nolint:staticcheck // SA1019: corev1.Endpoints is deprecated, but still required for compatibility
26+ testEndpoint * corev1.Endpoints
27+ serviceName = "test-service-sync"
28+ serviceNamespace = "default"
29+ endpointName = "test-service-sync"
30+ )
31+ ginkgo .BeforeAll (func () {
2332 f = framework .DefaultFramework
33+ testService = & corev1.Service {
34+ ObjectMeta : metav1.ObjectMeta {
35+ Name : serviceName ,
36+ Namespace : serviceNamespace ,
37+ },
38+ Spec : corev1.ServiceSpec {
39+ ClusterIP : "None" ,
40+ Ports : []corev1.ServicePort {
41+ {
42+ Name : "custom-port" ,
43+ Port : 8080 ,
44+ Protocol : corev1 .ProtocolTCP ,
45+ TargetPort : intstr .FromInt (5000 ),
46+ },
47+ },
48+ },
49+ }
50+ //nolint:staticcheck // SA1019: corev1.Endpoints is deprecated, but still required for compatibility
51+ testEndpoint = & corev1.Endpoints {
52+ ObjectMeta : metav1.ObjectMeta {
53+ Name : endpointName ,
54+ Namespace : serviceNamespace ,
55+ },
56+ //nolint:staticcheck // SA1019: corev1.Endpoints is deprecated, but still required for compatibility
57+ Subsets : []corev1.EndpointSubset {
58+ {
59+ Addresses : []corev1.EndpointAddress {
60+ {
61+ IP : "1.1.1.1" ,
62+ },
63+ },
64+ Ports : []corev1.EndpointPort {
65+ {
66+ Port : 5000 ,
67+ },
68+ },
69+ },
70+ },
71+ }
72+ })
73+
74+ ginkgo .AfterAll (func () {
75+ err := f .VClusterClient .CoreV1 ().Endpoints (serviceNamespace ).Delete (f .Context , endpointName , metav1.DeleteOptions {})
76+ if err != nil && ! kerrors .IsNotFound (err ) {
77+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
78+ }
79+ err = f .VClusterClient .CoreV1 ().Services (serviceNamespace ).Delete (f .Context , serviceName , metav1.DeleteOptions {})
80+ if err != nil && ! kerrors .IsNotFound (err ) {
81+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
82+ }
2483 })
2584
2685 ginkgo .It ("Test service mapping" , func () {
@@ -54,6 +113,75 @@ var _ = ginkgo.Describe("map services from host to virtual cluster and vice vers
54113 checkEndpointsSync (f .Context , f .VClusterClient , "test" , "nginx" , f .HostClient , f .VClusterNamespace , "nginx" )
55114 })
56115 })
116+
117+ ginkgo .Context ("Verify endpoint sync when endpoint is deployed before service" , func () {
118+ ginkgo .It ("Should sync Service, Endpoints, and EndpointSlice from vCluster to host cluster" , func () {
119+ ginkgo .By ("Create Service Endpoint in vCluster" )
120+ _ , err := f .VClusterClient .CoreV1 ().Endpoints (serviceNamespace ).Create (f .Context , testEndpoint , metav1.CreateOptions {})
121+ framework .ExpectNoError (err )
122+
123+ ginkgo .By ("Create Service in vCluster" )
124+ _ , err = f .VClusterClient .CoreV1 ().Services (serviceNamespace ).Create (f .Context , testService , metav1.CreateOptions {})
125+ framework .ExpectNoError (err )
126+
127+ ginkgo .By ("Verify Endpoint exists in vCluster" )
128+ _ , err = f .VClusterClient .CoreV1 ().Endpoints (serviceNamespace ).Get (f .Context , endpointName , metav1.GetOptions {})
129+ framework .ExpectNoError (err )
130+
131+ ginkgo .By ("Verify Service exists in vCluster" )
132+ _ , err = f .VClusterClient .CoreV1 ().Services (serviceNamespace ).Get (f .Context , serviceName , metav1.GetOptions {})
133+ framework .ExpectNoError (err )
134+
135+ ginkgo .By ("Verify EndpointSlice exists in vCluster" )
136+ gomega .Eventually (func (g gomega.Gomega ) {
137+ vclusterEndpointSlice , err := f .VClusterClient .DiscoveryV1 ().EndpointSlices (serviceNamespace ).List (f .Context , metav1.ListOptions {
138+ LabelSelector : fmt .Sprintf ("kubernetes.io/service-name=%s" , serviceName ),
139+ })
140+ g .Expect (err ).NotTo (gomega .HaveOccurred ())
141+ g .Expect (vclusterEndpointSlice .Items ).To (gomega .HaveLen (1 ))
142+ }).WithPolling (time .Second ).WithTimeout (framework .PollTimeout ).Should (gomega .Succeed ())
143+
144+ translatedServiceName := translate .SingleNamespaceHostName (serviceName , serviceNamespace , translate .VClusterName )
145+
146+ ginkgo .By ("Verify Service exists in Host Cluster" )
147+ gomega .Eventually (func (g gomega.Gomega ) {
148+ hostService , err := f .HostClient .CoreV1 ().Services (f .VClusterNamespace ).Get (f .Context , translatedServiceName , metav1.GetOptions {})
149+ g .Expect (err ).NotTo (gomega .HaveOccurred ())
150+ g .Expect (hostService .Spec .Ports ).To (gomega .HaveLen (1 ))
151+ if len (hostService .Spec .Ports ) > 0 {
152+ g .Expect (hostService .Spec .Ports [0 ].Name ).To (gomega .Equal ("custom-port" ))
153+ g .Expect (hostService .Spec .Ports [0 ].Port ).To (gomega .Equal (int32 (8080 )))
154+ }
155+ }).WithPolling (time .Second ).WithTimeout (framework .PollTimeout ).Should (gomega .Succeed ())
156+
157+ ginkgo .By ("Verify Endpoint exists in Host Cluster" )
158+ gomega .Eventually (func (g gomega.Gomega ) {
159+ hostEndpoint , err := f .HostClient .CoreV1 ().Endpoints (f .VClusterNamespace ).Get (f .Context , translatedServiceName , metav1.GetOptions {})
160+ g .Expect (err ).NotTo (gomega .HaveOccurred ())
161+ g .Expect (hostEndpoint .Subsets ).To (gomega .HaveLen (1 ))
162+ if len (hostEndpoint .Subsets ) > 0 {
163+ g .Expect (hostEndpoint .Subsets [0 ].Addresses ).To (gomega .HaveLen (1 ))
164+ if len (hostEndpoint .Subsets [0 ].Addresses ) > 0 {
165+ g .Expect (hostEndpoint .Subsets [0 ].Addresses [0 ].IP ).To (gomega .Equal ("1.1.1.1" ))
166+ }
167+ g .Expect (hostEndpoint .Subsets [0 ].Ports ).To (gomega .HaveLen (1 ))
168+ if len (hostEndpoint .Subsets [0 ].Ports ) > 0 {
169+ g .Expect (hostEndpoint .Subsets [0 ].Ports [0 ].Port ).To (gomega .Equal (int32 (5000 )))
170+ }
171+ }
172+ }).WithPolling (time .Second ).WithTimeout (framework .PollTimeout ).Should (gomega .Succeed ())
173+
174+ ginkgo .By ("Verify EndpointSlice exists in Host Cluster" )
175+ gomega .Eventually (func (g gomega.Gomega ) {
176+ hostEndpointSlice , err := f .HostClient .DiscoveryV1 ().EndpointSlices (f .VClusterNamespace ).List (f .Context , metav1.ListOptions {
177+ LabelSelector : fmt .Sprintf ("kubernetes.io/service-name=%s" , translatedServiceName ),
178+ })
179+ g .Expect (err ).NotTo (gomega .HaveOccurred ())
180+ g .Expect (hostEndpointSlice .Items ).To (gomega .HaveLen (1 ))
181+ }).WithPolling (time .Second ).WithTimeout (framework .PollTimeout ).Should (gomega .Succeed ())
182+
183+ })
184+ })
57185})
58186
59187func testMapping (ctx context.Context , fromClient kubernetes.Interface , fromNamespace , fromName string , toClient kubernetes.Interface , toNamespace , toName string , checkEndpoints bool ) {
0 commit comments