Skip to content

Commit b8faf2d

Browse files
committed
feat(endpoints): auto-detect provider to adjust ip mapping
Signed-off-by: SkalaNetworks <contact@skala.network>
1 parent 258200a commit b8faf2d

3 files changed

Lines changed: 239 additions & 59 deletions

File tree

pkg/controller/endpoint_slice.go

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func (c *Controller) handleUpdateEndpointSlice(key string) error {
177177
checkIP = util.MasqueradeCheckIP
178178
}
179179
isGenIPPortMapping := !ignoreHealthCheck || isPreferLocalBackend
180-
ipPortMapping, backends = getIPPortMappingBackend(endpointSlices, port, lbVip, checkIP, isGenIPPortMapping)
180+
ipPortMapping, backends = c.getIPPortMappingBackend(endpointSlices, port, lbVip, checkIP, isGenIPPortMapping)
181181
// for performance reason delete lb with no backends
182182
if len(backends) != 0 {
183183
vip = util.JoinHostPort(lbVip, port.Port)
@@ -353,7 +353,7 @@ func (c *Controller) getHealthCheckVip(subnetName, lbVip string) (string, error)
353353
return checkIP, nil
354354
}
355355

356-
func getIPPortMappingBackend(endpointSlices []*discoveryv1.EndpointSlice, servicePort v1.ServicePort, serviceIP, checkVip string, isGenIPPortMapping bool) (map[string]string, []string) {
356+
func (c *Controller) getIPPortMappingBackend(endpointSlices []*discoveryv1.EndpointSlice, servicePort v1.ServicePort, serviceIP, checkVip string, isGenIPPortMapping bool) (map[string]string, []string) {
357357
var (
358358
ipPortMapping = map[string]string{}
359359
backends = []string{}
@@ -374,7 +374,12 @@ func getIPPortMappingBackend(endpointSlices []*discoveryv1.EndpointSlice, servic
374374

375375
for _, endpoint := range endpointSlice.Endpoints {
376376
if isGenIPPortMapping && endpoint.TargetRef.Name != "" {
377-
lspName := getEndpointTargetLSP(endpoint.TargetRef.Name, endpoint.TargetRef.Namespace, util.OvnProvider)
377+
lspName, err := c.getEndpointTargetLSP(endpoint.TargetRef.Name, endpoint.TargetRef.Namespace, endpoint.Addresses)
378+
if err != nil {
379+
err := fmt.Errorf("couldn't get LSP for the endpoint's target: %w", err)
380+
klog.Error(err)
381+
}
382+
378383
for _, address := range endpoint.Addresses {
379384
ipPortMapping[address] = fmt.Sprintf(util.HealthCheckNamedVipTemplate, lspName, checkVip)
380385
}
@@ -401,30 +406,77 @@ func endpointReady(endpoint discoveryv1.Endpoint) bool {
401406
return endpoint.Conditions.Ready == nil || *endpoint.Conditions.Ready
402407
}
403408

404-
// getEndpointTargetLSP returns the name of the LSP for a given target/namespace.
409+
// getMatchingProviderForAddress returns the provider linked to a subnet in which a particular address is present
410+
func getMatchingProviderForAddress(pod *v1.Pod, providers []string, address string) string {
411+
if pod.Annotations == nil {
412+
return ""
413+
}
414+
415+
// Find which provider is linked to this address
416+
for _, provider := range providers {
417+
ipsForProvider, exists := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, provider)]
418+
if !exists {
419+
continue
420+
}
421+
422+
ips := strings.Split(ipsForProvider, ",")
423+
for _, ip := range ips {
424+
if ip == address {
425+
return provider
426+
}
427+
}
428+
}
429+
430+
return ""
431+
}
432+
433+
// getEndpointTargetLSPName returns the name of the LSP for a pod targeted by an endpoint.
405434
// A custom provider can be specified if the LSP is within a subnet that doesn't use
406435
// the default "ovn" provider.
407-
func getEndpointTargetLSP(target, namespace, provider string) string {
408-
// This pod seems to be a VM launcher pod, but we do not use the same syntax for the LSP
409-
// of normal pods and for VM pods. We need to retrieve the real name of the VM from
410-
// the pod's name to compute the LSP.
411-
if strings.HasPrefix(target, util.VMLauncherPrefix) {
412-
target = getVMNameFromLauncherPod(target)
436+
func getEndpointTargetLSPName(pod *v1.Pod, provider string) string {
437+
// If no provider is specified, use the default one
438+
if provider == "" {
439+
provider = util.OvnProvider
440+
}
441+
442+
target := pod.Name
443+
444+
// If this pod is a VM launcher pod, we need to retrieve the name of the VM. This is necessary
445+
// because we do not use the same syntax for the LSP of normal pods and for VM pods
446+
if vmName, exists := pod.Annotations[fmt.Sprintf(util.VMAnnotationTemplate, provider)]; exists {
447+
target = vmName
413448
}
414449

415-
return ovs.PodNameToPortName(target, namespace, provider)
450+
return ovs.PodNameToPortName(target, pod.Namespace, provider)
416451
}
417452

418-
// getVMNameFromLauncherPod returns the name of a VirtualMachine from the name of its launcher pod (virt-launcher)
419-
func getVMNameFromLauncherPod(podName string) string {
420-
// Remove the VM launcher pod prefix
421-
vmName := strings.TrimPrefix(podName, util.VMLauncherPrefix)
453+
// getEndpointTargetLSP returns the name of the LSP on which addresses are attached for a specific pod
454+
func (c *Controller) getEndpointTargetLSP(pod, namespace string, addresses []string) (string, error) {
455+
// Retrieve the pod object from its namespace and name
456+
podObj, err := c.podsLister.Pods(namespace).Get(pod)
457+
if err != nil {
458+
return "", fmt.Errorf("failed to get pod %s/%s: %w", namespace, pod, err)
459+
}
422460

423-
// Remove the ID of the pod
424-
slice := strings.Split(vmName, "-")
425-
if len(slice) > 0 {
426-
vmName = strings.Join(slice[:len(slice)-1], "-")
461+
// Get all the networks to which the pod is attached
462+
podNetworks, err := c.getPodKubeovnNets(podObj)
463+
if err != nil {
464+
return "", fmt.Errorf("failed to get pod networks: %w", err)
465+
}
466+
467+
// Retrieve all the providers
468+
var providers []string
469+
for _, podNetwork := range podNetworks {
470+
providers = append(providers, podNetwork.ProviderName)
471+
}
472+
473+
// Get the first matching provider for any of the address in the endpoint
474+
var provider string
475+
for _, address := range addresses {
476+
if provider = getMatchingProviderForAddress(podObj, providers, address); provider != "" {
477+
break
478+
}
427479
}
428480

429-
return vmName
481+
return getEndpointTargetLSPName(podObj, provider), nil
430482
}

pkg/controller/endpoint_slice_test.go

Lines changed: 165 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package controller
22

33
import (
4+
"fmt"
45
"testing"
56

7+
corev1 "k8s.io/api/core/v1"
68
discoveryv1 "k8s.io/api/discovery/v1"
79
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
11+
"github.com/kubeovn/kube-ovn/pkg/util"
812
)
913

1014
func TestFindServiceKey(t *testing.T) {
@@ -110,63 +114,188 @@ func TestEndpointReady(t *testing.T) {
110114
}
111115
}
112116

113-
func TestGetEndpointTargetLSP(t *testing.T) {
117+
func TestGetEndpointTargetLSPName(t *testing.T) {
118+
tests := []struct {
119+
name string
120+
pod *corev1.Pod
121+
provider string
122+
expected string
123+
}{
124+
{
125+
name: "No provider, faulty pod",
126+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
127+
Name: "",
128+
Namespace: ""},
129+
},
130+
provider: "",
131+
expected: ".",
132+
},
133+
{
134+
name: "Endpoint empty with default provider and faulty pod",
135+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
136+
Name: "",
137+
Namespace: ""},
138+
},
139+
provider: util.OvnProvider,
140+
expected: ".",
141+
},
142+
{
143+
name: "Pod target with no provider",
144+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
145+
Name: "some-pod-1d8fn",
146+
Namespace: "default"},
147+
},
148+
provider: "",
149+
expected: "some-pod-1d8fn.default",
150+
},
151+
{
152+
name: "Pod target with default provider",
153+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
154+
Name: "some-pod-1d8fn",
155+
Namespace: "default"},
156+
},
157+
provider: util.OvnProvider,
158+
expected: "some-pod-1d8fn.default",
159+
},
160+
{
161+
name: "Pod target with custom provider",
162+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
163+
Name: "some-pod-1d8fn",
164+
Namespace: "default"},
165+
},
166+
provider: "custom.provider",
167+
expected: "some-pod-1d8fn.default.custom.provider",
168+
},
169+
{
170+
name: "VM target with no provider",
171+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
172+
Name: "virt-launcher-some-vm-67jd3",
173+
Namespace: "default",
174+
Annotations: map[string]string{
175+
fmt.Sprintf(util.VMAnnotationTemplate, util.OvnProvider): "some-vm",
176+
}},
177+
},
178+
provider: "",
179+
expected: "some-vm.default",
180+
},
181+
{
182+
name: "VM target with default provider",
183+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
184+
Name: "virt-launcher-some-vm-67jd3",
185+
Namespace: "default",
186+
Annotations: map[string]string{
187+
fmt.Sprintf(util.VMAnnotationTemplate, util.OvnProvider): "some-vm",
188+
}},
189+
},
190+
provider: util.OvnProvider,
191+
expected: "some-vm.default",
192+
},
193+
{
194+
name: "VM target with custom provider",
195+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
196+
Name: "virt-launcher-some-vm-67jd3",
197+
Namespace: "default",
198+
Annotations: map[string]string{
199+
fmt.Sprintf(util.VMAnnotationTemplate, "custom.provider"): "some-vm",
200+
}},
201+
},
202+
provider: "custom.provider",
203+
expected: "some-vm.default.custom.provider",
204+
},
205+
}
206+
207+
for _, tt := range tests {
208+
t.Run(tt.name, func(t *testing.T) {
209+
result := getEndpointTargetLSPName(tt.pod, tt.provider)
210+
if result != tt.expected {
211+
t.Errorf("getEndpointTargetLSPName() = %q, want %q", result, tt.expected)
212+
}
213+
})
214+
}
215+
}
216+
217+
func TestGetMatchingProviderForAddress(t *testing.T) {
114218
tests := []struct {
115219
name string
116-
target string
117-
namespace string
118-
provider string
220+
pod *corev1.Pod
221+
providers []string
222+
address string
119223
expected string
120224
}{
121225
{
122-
name: "Endpoint empty",
123-
target: "",
124-
namespace: "",
125-
provider: "",
126-
expected: "..",
226+
name: "IP is on default provider and single stack",
227+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
228+
Annotations: map[string]string{
229+
fmt.Sprintf(util.IPAddressAnnotationTemplate, util.OvnProvider): "1.1.1.1",
230+
}},
231+
},
232+
providers: []string{util.OvnProvider},
233+
address: "1.1.1.1",
234+
expected: util.OvnProvider,
127235
},
128236
{
129-
name: "Endpoint empty with default provider",
130-
target: "",
131-
namespace: "",
132-
provider: "ovn",
133-
expected: ".",
237+
name: "IP is on default provider and dual stack",
238+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
239+
Annotations: map[string]string{
240+
fmt.Sprintf(util.IPAddressAnnotationTemplate, util.OvnProvider): "1.1.1.1,fd00::a",
241+
}},
242+
},
243+
providers: []string{util.OvnProvider},
244+
address: "fd00::a",
245+
expected: util.OvnProvider,
134246
},
135247
{
136-
name: "Pod target with default provider",
137-
target: "some-pod-1d8fn",
138-
namespace: "default",
139-
provider: "ovn",
140-
expected: "some-pod-1d8fn.default",
248+
name: "IP is on custom provider and dual stack",
249+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
250+
Annotations: map[string]string{
251+
fmt.Sprintf(util.IPAddressAnnotationTemplate, "custom.provider"): "1.1.1.1,fd00::a",
252+
}},
253+
},
254+
providers: []string{"custom.provider"},
255+
address: "fd00::a",
256+
expected: "custom.provider",
141257
},
142258
{
143-
name: "Pod target with custom provider",
144-
target: "some-pod-6xjd8",
145-
namespace: "default",
146-
provider: "custom.provider",
147-
expected: "some-pod-6xjd8.default.custom.provider",
259+
name: "IP is on custom provider with multiple other providers present on the pod",
260+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
261+
Annotations: map[string]string{
262+
fmt.Sprintf(util.IPAddressAnnotationTemplate, util.OvnProvider): "10.0.0.1,fd10:0:0::1",
263+
fmt.Sprintf(util.IPAddressAnnotationTemplate, "first.provider"): "1.1.1.1,fd00::a",
264+
fmt.Sprintf(util.IPAddressAnnotationTemplate, "second.provider"): "2.2.2.2,fd00::b",
265+
fmt.Sprintf(util.IPAddressAnnotationTemplate, "third.provider"): "3.3.3.3,fd00::c",
266+
}},
267+
},
268+
providers: []string{util.OvnProvider, "first.provider", "second.provider", "third.provider"},
269+
address: "fd00::b",
270+
expected: "second.provider",
148271
},
149272
{
150-
name: "VM target with default provider",
151-
target: "virt-launcher-some-vm-67jd3",
152-
namespace: "default",
153-
provider: "ovn",
154-
expected: "some-vm.default",
273+
name: "No provider is matching",
274+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
275+
Annotations: map[string]string{
276+
fmt.Sprintf(util.IPAddressAnnotationTemplate, util.OvnProvider): "10.0.0.1,fd10:0:0::1",
277+
}},
278+
},
279+
providers: []string{util.OvnProvider},
280+
address: "fd00::b",
281+
expected: "",
155282
},
156283
{
157-
name: "VM target with custom provider",
158-
target: "virt-launcher-some-vm-67jd3",
159-
namespace: "default",
160-
provider: "custom.provider",
161-
expected: "some-vm.default.custom.provider",
284+
name: "No annotation is present",
285+
pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
286+
Annotations: nil},
287+
},
288+
providers: []string{},
289+
address: "fd00::b",
290+
expected: "",
162291
},
163292
}
164293

165294
for _, tt := range tests {
166295
t.Run(tt.name, func(t *testing.T) {
167-
result := getEndpointTargetLSP(tt.target, tt.namespace, tt.provider)
296+
result := getMatchingProviderForAddress(tt.pod, tt.providers, tt.address)
168297
if result != tt.expected {
169-
t.Errorf("getEndpointTargetLSP() = %q, want %q", result, tt.expected)
298+
t.Errorf("getMatchingProviderForAddress() = %q, want %q", result, tt.expected)
170299
}
171300
})
172301
}

pkg/util/const.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,8 @@ const (
240240
ChassisCniDaemonRetryInterval = 1
241241
ChassisControllerRetryInterval = 3
242242

243-
VM = "VirtualMachine"
244-
VMInstance = "VirtualMachineInstance"
245-
VMLauncherPrefix = "virt-launcher-"
243+
VM = "VirtualMachine"
244+
VMInstance = "VirtualMachineInstance"
246245

247246
StatefulSet = "StatefulSet"
248247

0 commit comments

Comments
 (0)