mirror of
https://github.com/cloudnativelabs/kube-router.git
synced 2025-09-28 11:31:05 +02:00
FoU implementation now properly handles a whole host of things: * It now actually handles IPv6 by changing the encapsulation protocol to GUE instead of generic FoU. I worked with generic FoU tunnels for several days and could get it to support IPv4 and IPv6 at all even when placing using it with the IPv6 proto and with iproute2 in IPv6 mode (-6) * It now handles converting between the two tunnel types seemlessly and without leaving legacy tunnel artifacts behind. Previously, you could change the encap type but it wouldn't change the tunnels * Abstracted constants
2059 lines
51 KiB
Go
2059 lines
51 KiB
Go
package routing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
v1core "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/informers"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
"k8s.io/client-go/tools/cache"
|
|
|
|
gobgpapi "github.com/osrg/gobgp/v3/api"
|
|
gobgp "github.com/osrg/gobgp/v3/pkg/server"
|
|
)
|
|
|
|
func Test_advertiseClusterIPs(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
existingServices []*v1core.Service
|
|
// the key is the subnet from the watch event
|
|
watchEvents map[string]bool
|
|
}{
|
|
|
|
{
|
|
"add bgp path for service with ClusterIP",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1/32": true,
|
|
},
|
|
},
|
|
{
|
|
"add bgp path for service with ClusterIP/NodePort/LoadBalancer",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.2",
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-3",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: NodePortST,
|
|
ClusterIP: "10.0.0.3",
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1/32": true,
|
|
"10.0.0.2/32": true,
|
|
"10.0.0.3/32": true,
|
|
},
|
|
},
|
|
{
|
|
"add bgp path for invalid service type",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: "AnotherType",
|
|
ClusterIP: "10.0.0.2",
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1/32": true,
|
|
},
|
|
},
|
|
{
|
|
"add bgp path for headless service",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "None",
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-3",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "",
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1/32": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
go testcase.nrc.bgpServer.Serve()
|
|
global := &gobgpapi.Global{
|
|
Asn: 1,
|
|
RouterId: "10.0.0.0",
|
|
ListenPort: 10000,
|
|
}
|
|
err := testcase.nrc.bgpServer.StartBgp(context.Background(), &gobgpapi.StartBgpRequest{Global: global})
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
defer func() {
|
|
if err = testcase.nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}); err != nil {
|
|
t.Fatalf("failed to stop BGP server : %s", err)
|
|
}
|
|
}()
|
|
|
|
clientset := fake.NewSimpleClientset()
|
|
startInformersForRoutes(testcase.nrc, clientset)
|
|
|
|
err = createServices(clientset, testcase.existingServices)
|
|
if err != nil {
|
|
t.Fatalf("failed to create existing services: %v", err)
|
|
}
|
|
|
|
waitForListerWithTimeout(testcase.nrc.svcLister, time.Second*10, t)
|
|
|
|
var events []*gobgpapi.Path
|
|
pathWatch := func(r *gobgpapi.WatchEventResponse) {
|
|
if table := r.GetTable(); table != nil {
|
|
for _, p := range table.Paths {
|
|
if p.Family.Afi == gobgpapi.Family_AFI_IP || p.Family.Safi == gobgpapi.Family_SAFI_UNICAST {
|
|
events = append(events, p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err = testcase.nrc.bgpServer.WatchEvent(context.Background(), &gobgpapi.WatchEventRequest{
|
|
Table: &gobgpapi.WatchEventRequest_Table{
|
|
Filters: []*gobgpapi.WatchEventRequest_Table_Filter{
|
|
{
|
|
Type: gobgpapi.WatchEventRequest_Table_Filter_BEST,
|
|
},
|
|
},
|
|
},
|
|
}, pathWatch)
|
|
if err != nil {
|
|
t.Fatalf("failed to register callback to mortor global routing table: %v", err)
|
|
}
|
|
// ClusterIPs
|
|
testcase.nrc.advertiseClusterIP = true
|
|
testcase.nrc.advertiseExternalIP = false
|
|
testcase.nrc.advertiseLoadBalancerIP = false
|
|
|
|
toAdvertise, toWithdraw, _ := testcase.nrc.getActiveVIPs()
|
|
testcase.nrc.advertiseVIPs(toAdvertise)
|
|
testcase.nrc.withdrawVIPs(toWithdraw)
|
|
|
|
timeoutCh := time.After(time.Second * 10)
|
|
L:
|
|
for {
|
|
select {
|
|
case <-timeoutCh:
|
|
t.Fatalf("timeout exceeded waiting for %d watch events, got %d", len(testcase.watchEvents), len(events))
|
|
default:
|
|
if len(events) == len(testcase.watchEvents) {
|
|
break L
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, path := range events {
|
|
nlri := path.GetNlri()
|
|
var prefix gobgpapi.IPAddressPrefix
|
|
err = nlri.UnmarshalTo(&prefix)
|
|
if err != nil {
|
|
t.Fatalf("Invalid nlri in advertised path")
|
|
}
|
|
advertisedPrefix := prefix.Prefix + "/" + fmt.Sprint(prefix.PrefixLen)
|
|
if _, ok := testcase.watchEvents[advertisedPrefix]; !ok {
|
|
t.Errorf("got unexpected path: %v", advertisedPrefix)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_advertiseExternalIPs(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
existingServices []*v1core.Service
|
|
// the key is the subnet from the watch event
|
|
watchEvents map[string]bool
|
|
}{
|
|
{
|
|
"add bgp path for service with external IPs",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1", "2.2.2.2"},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"1.1.1.1/32": true,
|
|
"2.2.2.2/32": true,
|
|
},
|
|
},
|
|
{
|
|
"add bgp path for services with external IPs of type ClusterIP/NodePort/LoadBalancer",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.2",
|
|
ExternalIPs: []string{"2.2.2.2"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-3",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: NodePortST,
|
|
ClusterIP: "10.0.0.3",
|
|
ExternalIPs: []string{"3.3.3.3", "4.4.4.4"},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"1.1.1.1/32": true,
|
|
"2.2.2.2/32": true,
|
|
"3.3.3.3/32": true,
|
|
"4.4.4.4/32": true,
|
|
},
|
|
},
|
|
{
|
|
"add bgp path for invalid service type",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: "AnotherType",
|
|
ClusterIP: "10.0.0.2",
|
|
ExternalIPs: []string{"2.2.2.2"},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"1.1.1.1/32": true,
|
|
},
|
|
},
|
|
{
|
|
"add bgp path for headless service",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "None",
|
|
ExternalIPs: []string{"2.2.2.2"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-3",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "",
|
|
ExternalIPs: []string{"3.3.3.3"},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"1.1.1.1/32": true,
|
|
},
|
|
},
|
|
{
|
|
"skip bgp path to loadbalancerIP for service without LoadBalancer IP",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{
|
|
{
|
|
Hostname: "foo-bar.zone.elb.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
},
|
|
{
|
|
"add bgp path to loadbalancerIP for service with LoadBalancer IP",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{
|
|
{
|
|
IP: "10.0.255.1",
|
|
},
|
|
{
|
|
IP: "10.0.255.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.255.1/32": true,
|
|
"10.0.255.2/32": true,
|
|
},
|
|
},
|
|
{
|
|
"no bgp path to nil loadbalancerIPs for service with LoadBalancer",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
},
|
|
{
|
|
"no bgp path to loadbalancerIPs for service with LoadBalancer and skiplbips annotation",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
Annotations: map[string]string{
|
|
svcSkipLbIpsAnnotation: "true",
|
|
},
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.1",
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{
|
|
{
|
|
IP: "10.0.255.1",
|
|
},
|
|
{
|
|
IP: "10.0.255.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
},
|
|
}
|
|
|
|
//nolint:dupl // There is no need to spend a lot of time de-duplicating test code
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
go testcase.nrc.bgpServer.Serve()
|
|
global := &gobgpapi.Global{
|
|
Asn: 1,
|
|
RouterId: "10.0.0.0",
|
|
ListenPort: 10000,
|
|
}
|
|
err := testcase.nrc.bgpServer.StartBgp(context.Background(), &gobgpapi.StartBgpRequest{Global: global})
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := testcase.nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}); err != nil {
|
|
t.Fatalf("failed to stop BGP server : %s", err)
|
|
}
|
|
}()
|
|
|
|
var events []*gobgpapi.Path
|
|
pathWatch := func(r *gobgpapi.WatchEventResponse) {
|
|
if table := r.GetTable(); table != nil {
|
|
for _, p := range table.Paths {
|
|
if p.Family.Afi == gobgpapi.Family_AFI_IP || p.Family.Safi == gobgpapi.Family_SAFI_UNICAST {
|
|
events = append(events, p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err = testcase.nrc.bgpServer.WatchEvent(context.Background(), &gobgpapi.WatchEventRequest{
|
|
Table: &gobgpapi.WatchEventRequest_Table{
|
|
Filters: []*gobgpapi.WatchEventRequest_Table_Filter{
|
|
{
|
|
Type: gobgpapi.WatchEventRequest_Table_Filter_BEST,
|
|
},
|
|
},
|
|
},
|
|
}, pathWatch)
|
|
if err != nil {
|
|
t.Fatalf("failed to register callback to mortor global routing table: %v", err)
|
|
}
|
|
clientset := fake.NewSimpleClientset()
|
|
startInformersForRoutes(testcase.nrc, clientset)
|
|
|
|
err = createServices(clientset, testcase.existingServices)
|
|
if err != nil {
|
|
t.Fatalf("failed to create existing services: %v", err)
|
|
}
|
|
|
|
waitForListerWithTimeout(testcase.nrc.svcLister, time.Second*10, t)
|
|
|
|
// ExternalIPs
|
|
testcase.nrc.advertiseClusterIP = false
|
|
testcase.nrc.advertiseExternalIP = true
|
|
testcase.nrc.advertiseLoadBalancerIP = true
|
|
|
|
toAdvertise, toWithdraw, _ := testcase.nrc.getActiveVIPs()
|
|
testcase.nrc.advertiseVIPs(toAdvertise)
|
|
testcase.nrc.withdrawVIPs(toWithdraw)
|
|
timeoutCh := time.After(time.Second * 10)
|
|
|
|
L:
|
|
for {
|
|
select {
|
|
case <-timeoutCh:
|
|
t.Fatalf("timeout exceeded waiting for %d watch events, got %d", len(testcase.watchEvents), len(events))
|
|
default:
|
|
if len(events) == len(testcase.watchEvents) {
|
|
break L
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, path := range events {
|
|
nlri := path.GetNlri()
|
|
var prefix gobgpapi.IPAddressPrefix
|
|
err = nlri.UnmarshalTo(&prefix)
|
|
if err != nil {
|
|
t.Fatalf("Invalid nlri in advertised path")
|
|
}
|
|
advertisedPrefix := prefix.Prefix + "/" + fmt.Sprint(prefix.PrefixLen)
|
|
if _, ok := testcase.watchEvents[advertisedPrefix]; !ok {
|
|
t.Errorf("got unexpected path: %v", advertisedPrefix)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_advertiseAnnotationOptOut(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
existingServices []*v1core.Service
|
|
// the key is the subnet from the watch event
|
|
watchEvents map[string]bool
|
|
}{
|
|
{
|
|
"add bgp paths for all service IPs",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.1.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: NodePortST,
|
|
ClusterIP: "10.0.0.2",
|
|
ExternalIPs: []string{"2.2.2.2", "3.3.3.3"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-3",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.3",
|
|
ExternalIPs: []string{"4.4.4.4"},
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{
|
|
{
|
|
IP: "10.0.255.1",
|
|
},
|
|
{
|
|
IP: "10.0.255.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1/32": true,
|
|
"10.0.0.2/32": true,
|
|
"10.0.0.3/32": true,
|
|
"1.1.1.1/32": true,
|
|
"2.2.2.2/32": true,
|
|
"3.3.3.3/32": true,
|
|
"4.4.4.4/32": true,
|
|
"10.0.255.1/32": true,
|
|
"10.0.255.2/32": true,
|
|
},
|
|
},
|
|
{
|
|
"opt out to advertise any IPs via annotations",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.1.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
Annotations: map[string]string{
|
|
svcAdvertiseClusterAnnotation: "false",
|
|
svcAdvertiseExternalAnnotation: "false",
|
|
svcAdvertiseLoadBalancerAnnotation: "false",
|
|
},
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1", "2.2.2.2"},
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{
|
|
{
|
|
IP: "10.0.255.1",
|
|
},
|
|
{
|
|
IP: "10.0.255.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
},
|
|
}
|
|
|
|
//nolint:dupl // There is no need to spend a lot of time de-duplicating test code
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
go testcase.nrc.bgpServer.Serve()
|
|
global := &gobgpapi.Global{
|
|
Asn: 1,
|
|
RouterId: "10.0.0.0",
|
|
ListenPort: 10000,
|
|
}
|
|
err := testcase.nrc.bgpServer.StartBgp(context.Background(), &gobgpapi.StartBgpRequest{Global: global})
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := testcase.nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}); err != nil {
|
|
t.Fatalf("failed to stop BGP server : %s", err)
|
|
}
|
|
}()
|
|
|
|
var events []*gobgpapi.Path
|
|
pathWatch := func(r *gobgpapi.WatchEventResponse) {
|
|
if table := r.GetTable(); table != nil {
|
|
for _, p := range table.Paths {
|
|
if p.Family.Afi == gobgpapi.Family_AFI_IP || p.Family.Safi == gobgpapi.Family_SAFI_UNICAST {
|
|
events = append(events, p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err = testcase.nrc.bgpServer.WatchEvent(context.Background(), &gobgpapi.WatchEventRequest{
|
|
Table: &gobgpapi.WatchEventRequest_Table{
|
|
Filters: []*gobgpapi.WatchEventRequest_Table_Filter{
|
|
{
|
|
Type: gobgpapi.WatchEventRequest_Table_Filter_BEST,
|
|
},
|
|
},
|
|
},
|
|
}, pathWatch)
|
|
if err != nil {
|
|
t.Fatalf("failed to register callback to mortor global routing table: %v", err)
|
|
}
|
|
|
|
clientset := fake.NewSimpleClientset()
|
|
startInformersForRoutes(testcase.nrc, clientset)
|
|
|
|
err = createServices(clientset, testcase.existingServices)
|
|
if err != nil {
|
|
t.Fatalf("failed to create existing services: %v", err)
|
|
}
|
|
|
|
waitForListerWithTimeout(testcase.nrc.svcLister, time.Second*10, t)
|
|
|
|
// By default advertise all IPs
|
|
testcase.nrc.advertiseClusterIP = true
|
|
testcase.nrc.advertiseExternalIP = true
|
|
testcase.nrc.advertiseLoadBalancerIP = true
|
|
|
|
toAdvertise, toWithdraw, _ := testcase.nrc.getActiveVIPs()
|
|
testcase.nrc.advertiseVIPs(toAdvertise)
|
|
testcase.nrc.withdrawVIPs(toWithdraw)
|
|
timeoutCh := time.After(time.Second * 10)
|
|
|
|
L:
|
|
for {
|
|
select {
|
|
case <-timeoutCh:
|
|
t.Fatalf("timeout exceeded waiting for %d watch events, got %d", len(testcase.watchEvents), len(events))
|
|
default:
|
|
if len(events) == len(testcase.watchEvents) {
|
|
break L
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, path := range events {
|
|
nlri := path.GetNlri()
|
|
var prefix gobgpapi.IPAddressPrefix
|
|
err = nlri.UnmarshalTo(&prefix)
|
|
if err != nil {
|
|
t.Fatalf("Invalid nlri in advertised path")
|
|
}
|
|
advertisedPrefix := prefix.Prefix + "/" + fmt.Sprint(prefix.PrefixLen)
|
|
if _, ok := testcase.watchEvents[advertisedPrefix]; !ok {
|
|
t.Errorf("got unexpected path: %v", advertisedPrefix)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_advertiseAnnotationOptIn(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
existingServices []*v1core.Service
|
|
// the key is the subnet from the watch event
|
|
watchEvents map[string]bool
|
|
}{
|
|
{
|
|
"no bgp paths for any service IPs",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.1.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: NodePortST,
|
|
ClusterIP: "10.0.0.2",
|
|
ExternalIPs: []string{"2.2.2.2", "3.3.3.3"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-3",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.3",
|
|
// ignored since LoadBalancer services don't
|
|
// advertise external IPs.
|
|
ExternalIPs: []string{"4.4.4.4"},
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{
|
|
{
|
|
IP: "10.0.255.1",
|
|
},
|
|
{
|
|
IP: "10.0.255.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
},
|
|
{
|
|
"opt in to advertise all IPs via annotations",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
primaryIP: net.ParseIP("10.0.1.1"),
|
|
},
|
|
[]*v1core.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
Annotations: map[string]string{
|
|
svcAdvertiseClusterAnnotation: "true",
|
|
svcAdvertiseExternalAnnotation: "true",
|
|
svcAdvertiseLoadBalancerAnnotation: "true",
|
|
},
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-2",
|
|
Annotations: map[string]string{
|
|
svcAdvertiseClusterAnnotation: "true",
|
|
svcAdvertiseExternalAnnotation: "true",
|
|
svcAdvertiseLoadBalancerAnnotation: "true",
|
|
},
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: NodePortST,
|
|
ClusterIP: "10.0.0.2",
|
|
ExternalIPs: []string{"2.2.2.2", "3.3.3.3"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-3",
|
|
Annotations: map[string]string{
|
|
svcAdvertiseClusterAnnotation: "true",
|
|
svcAdvertiseExternalAnnotation: "true",
|
|
svcAdvertiseLoadBalancerAnnotation: "true",
|
|
},
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: LoadBalancerST,
|
|
ClusterIP: "10.0.0.3",
|
|
ExternalIPs: []string{"4.4.4.4"},
|
|
},
|
|
Status: v1core.ServiceStatus{
|
|
LoadBalancer: v1core.LoadBalancerStatus{
|
|
Ingress: []v1core.LoadBalancerIngress{
|
|
{
|
|
IP: "10.0.255.1",
|
|
},
|
|
{
|
|
IP: "10.0.255.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1/32": true,
|
|
"10.0.0.2/32": true,
|
|
"10.0.0.3/32": true,
|
|
"1.1.1.1/32": true,
|
|
"2.2.2.2/32": true,
|
|
"3.3.3.3/32": true,
|
|
"4.4.4.4/32": true,
|
|
"10.0.255.1/32": true,
|
|
"10.0.255.2/32": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
//nolint:dupl // There is no need to spend a lot of time de-duplicating test code
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
go testcase.nrc.bgpServer.Serve()
|
|
global := &gobgpapi.Global{
|
|
Asn: 1,
|
|
RouterId: "10.0.0.0",
|
|
ListenPort: 10000,
|
|
}
|
|
err := testcase.nrc.bgpServer.StartBgp(context.Background(), &gobgpapi.StartBgpRequest{Global: global})
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := testcase.nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}); err != nil {
|
|
t.Fatalf("failed to stop BGP server : %s", err)
|
|
}
|
|
}()
|
|
|
|
var events []*gobgpapi.Path
|
|
pathWatch := func(r *gobgpapi.WatchEventResponse) {
|
|
if table := r.GetTable(); table != nil {
|
|
for _, p := range table.Paths {
|
|
if p.Family.Afi == gobgpapi.Family_AFI_IP || p.Family.Safi == gobgpapi.Family_SAFI_UNICAST {
|
|
events = append(events, p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err = testcase.nrc.bgpServer.WatchEvent(context.Background(), &gobgpapi.WatchEventRequest{
|
|
Table: &gobgpapi.WatchEventRequest_Table{
|
|
Filters: []*gobgpapi.WatchEventRequest_Table_Filter{
|
|
{
|
|
Type: gobgpapi.WatchEventRequest_Table_Filter_BEST,
|
|
},
|
|
},
|
|
},
|
|
}, pathWatch)
|
|
if err != nil {
|
|
t.Fatalf("failed to register callback to mortor global routing table: %v", err)
|
|
}
|
|
|
|
clientset := fake.NewSimpleClientset()
|
|
startInformersForRoutes(testcase.nrc, clientset)
|
|
|
|
err = createServices(clientset, testcase.existingServices)
|
|
if err != nil {
|
|
t.Fatalf("failed to create existing services: %v", err)
|
|
}
|
|
|
|
waitForListerWithTimeout(testcase.nrc.svcLister, time.Second*10, t)
|
|
|
|
// By default do not advertise any IPs
|
|
testcase.nrc.advertiseClusterIP = false
|
|
testcase.nrc.advertiseExternalIP = false
|
|
testcase.nrc.advertiseLoadBalancerIP = false
|
|
|
|
toAdvertise, toWithdraw, _ := testcase.nrc.getActiveVIPs()
|
|
testcase.nrc.advertiseVIPs(toAdvertise)
|
|
testcase.nrc.withdrawVIPs(toWithdraw)
|
|
|
|
timeoutCh := time.After(time.Second * 10)
|
|
|
|
L:
|
|
for {
|
|
select {
|
|
case <-timeoutCh:
|
|
t.Fatalf("timeout exceeded waiting for %d watch events, got %d", len(testcase.watchEvents), len(events))
|
|
default:
|
|
if len(events) == len(testcase.watchEvents) {
|
|
break L
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, path := range events {
|
|
nlri := path.GetNlri()
|
|
var prefix gobgpapi.IPAddressPrefix
|
|
err = nlri.UnmarshalTo(&prefix)
|
|
if err != nil {
|
|
t.Fatalf("Invalid nlri in advertised path")
|
|
}
|
|
advertisedPrefix := prefix.Prefix + "/" + fmt.Sprint(prefix.PrefixLen)
|
|
if _, ok := testcase.watchEvents[advertisedPrefix]; !ok {
|
|
t.Errorf("got unexpected path: %v", advertisedPrefix)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_nodeHasEndpointsForService(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
existingService *v1core.Service
|
|
existingEndpoint *v1core.Endpoints
|
|
nodeHasEndpoints bool
|
|
err error
|
|
}{
|
|
{
|
|
"node has endpoints for service",
|
|
&NetworkRoutingController{
|
|
nodeName: "node-1",
|
|
},
|
|
&v1core.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1", "2.2.2.2"},
|
|
},
|
|
},
|
|
&v1core.Endpoints{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
Namespace: "default",
|
|
},
|
|
Subsets: []v1core.EndpointSubset{
|
|
{
|
|
Addresses: []v1core.EndpointAddress{
|
|
{
|
|
IP: "172.20.1.1",
|
|
NodeName: ptrToString("node-1"),
|
|
},
|
|
{
|
|
IP: "172.20.1.2",
|
|
NodeName: ptrToString("node-2"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"node has no endpoints for service",
|
|
&NetworkRoutingController{
|
|
nodeName: "node-1",
|
|
},
|
|
&v1core.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1core.ServiceSpec{
|
|
Type: ClusterIPST,
|
|
ClusterIP: "10.0.0.1",
|
|
ExternalIPs: []string{"1.1.1.1", "2.2.2.2"},
|
|
},
|
|
},
|
|
&v1core.Endpoints{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc-1",
|
|
Namespace: "default",
|
|
},
|
|
Subsets: []v1core.EndpointSubset{
|
|
{
|
|
Addresses: []v1core.EndpointAddress{
|
|
{
|
|
IP: "172.20.1.1",
|
|
NodeName: ptrToString("node-2"),
|
|
},
|
|
{
|
|
IP: "172.20.1.2",
|
|
NodeName: ptrToString("node-3"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
clientset := fake.NewSimpleClientset()
|
|
startInformersForRoutes(testcase.nrc, clientset)
|
|
|
|
_, err := clientset.CoreV1().Endpoints("default").Create(context.Background(), testcase.existingEndpoint, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to create existing endpoints: %v", err)
|
|
}
|
|
|
|
_, err = clientset.CoreV1().Services("default").Create(context.Background(), testcase.existingService, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to create existing services: %v", err)
|
|
}
|
|
|
|
waitForListerWithTimeout(testcase.nrc.svcLister, time.Second*10, t)
|
|
waitForListerWithTimeout(testcase.nrc.epLister, time.Second*10, t)
|
|
|
|
nodeHasEndpoints, err := testcase.nrc.nodeHasEndpointsForService(testcase.existingService)
|
|
if !reflect.DeepEqual(err, testcase.err) {
|
|
t.Logf("actual err: %v", err)
|
|
t.Logf("expected err: %v", testcase.err)
|
|
t.Error("unexpected error")
|
|
}
|
|
if nodeHasEndpoints != testcase.nodeHasEndpoints {
|
|
t.Logf("expected nodeHasEndpoints: %v", testcase.nodeHasEndpoints)
|
|
t.Logf("actual nodeHasEndpoints: %v", nodeHasEndpoints)
|
|
t.Error("unexpected nodeHasEndpoints")
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_advertisePodRoute(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
envNodeName string
|
|
node *v1core.Node
|
|
// the key is the subnet from the watch event
|
|
watchEvents map[string]bool
|
|
err error
|
|
}{
|
|
{
|
|
"add bgp path for pod cidr using NODE_NAME",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
podCidr: "172.20.0.0/24",
|
|
isIPv4Capable: true,
|
|
podIPv4CIDRs: []string{"172.20.0.0/24"},
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
"node-1",
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Spec: v1core.NodeSpec{
|
|
PodCIDR: "172.20.0.0/24",
|
|
PodCIDRs: []string{
|
|
"172.20.0.0/24",
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"172.20.0.0/24": true,
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"add bgp path for pod cidr using hostname override",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
hostnameOverride: "node-1",
|
|
podCidr: "172.20.0.0/24",
|
|
isIPv4Capable: true,
|
|
podIPv4CIDRs: []string{"172.20.0.0/24"},
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
"",
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Spec: v1core.NodeSpec{
|
|
PodCIDR: "172.20.0.0/24",
|
|
PodCIDRs: []string{
|
|
"172.20.0.0/24",
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"172.20.0.0/24": true,
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"advertise IPv6 Address when enabled",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
hostnameOverride: "node-1",
|
|
podCidr: "2001:db8:42:2::/64",
|
|
podIPv6CIDRs: []string{"2001:db8:42:2::/64"},
|
|
nodeIPv6Addrs: map[v1core.NodeAddressType][]net.IP{
|
|
v1core.NodeInternalIP: {net.IPv6loopback},
|
|
},
|
|
isIPv6Capable: true,
|
|
primaryIP: net.ParseIP("10.0.0.1"),
|
|
},
|
|
"",
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Spec: v1core.NodeSpec{
|
|
PodCIDR: "2001:db8:42:2::/64",
|
|
PodCIDRs: []string{
|
|
"2001:db8:42:2::/64",
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"2001:db8:42:2::/64": true,
|
|
},
|
|
nil,
|
|
},
|
|
/* disabling tests for now, as node POD cidr is read just once at the starting of the program
|
|
Tests needs to be adopted to catch the errors when NetworkRoutingController starts
|
|
{
|
|
"add bgp path for pod cidr without NODE_NAME or hostname override",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
},
|
|
"",
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Spec: v1core.NodeSpec{
|
|
PodCIDR: "172.20.0.0/24",
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
errors.New("Failed to get pod CIDR allocated for the node due to: Failed to identify the node by NODE_NAME, hostname or --hostname-override"),
|
|
},
|
|
{
|
|
"node does not have pod cidr set",
|
|
&NetworkRoutingController{
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
},
|
|
"node-1",
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Spec: v1core.NodeSpec{
|
|
PodCIDR: "",
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
errors.New("node.Spec.PodCIDR not set for node: node-1"),
|
|
},
|
|
*/
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
go testcase.nrc.bgpServer.Serve()
|
|
global := &gobgpapi.Global{
|
|
Asn: 1,
|
|
RouterId: "10.0.0.0",
|
|
ListenPort: 10000,
|
|
}
|
|
err := testcase.nrc.bgpServer.StartBgp(context.Background(), &gobgpapi.StartBgpRequest{Global: global})
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := testcase.nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}); err != nil {
|
|
t.Fatalf("failed to stop BGP server : %s", err)
|
|
}
|
|
}()
|
|
|
|
var events []*gobgpapi.Path
|
|
pathWatch := func(r *gobgpapi.WatchEventResponse) {
|
|
if table := r.GetTable(); table != nil {
|
|
for _, p := range table.Paths {
|
|
if p.Family.Afi == gobgpapi.Family_AFI_IP || p.Family.Safi == gobgpapi.Family_SAFI_UNICAST {
|
|
events = append(events, p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err = testcase.nrc.bgpServer.WatchEvent(context.Background(), &gobgpapi.WatchEventRequest{
|
|
Table: &gobgpapi.WatchEventRequest_Table{
|
|
Filters: []*gobgpapi.WatchEventRequest_Table_Filter{
|
|
{
|
|
Type: gobgpapi.WatchEventRequest_Table_Filter_BEST,
|
|
},
|
|
},
|
|
},
|
|
}, pathWatch)
|
|
if err != nil {
|
|
t.Fatalf("failed to register callback to mortor global routing table: %v", err)
|
|
}
|
|
|
|
clientset := fake.NewSimpleClientset()
|
|
_, err = clientset.CoreV1().Nodes().Create(context.Background(), testcase.node, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to create node: %v", err)
|
|
}
|
|
testcase.nrc.clientset = clientset
|
|
|
|
_ = os.Setenv("NODE_NAME", testcase.envNodeName)
|
|
defer func() { _ = os.Unsetenv("NODE_NAME") }()
|
|
|
|
err = testcase.nrc.advertisePodRoute()
|
|
if !reflect.DeepEqual(err, testcase.err) {
|
|
t.Logf("actual error: %v", err)
|
|
t.Logf("expected error: %v", testcase.err)
|
|
t.Error("did not get expected error")
|
|
}
|
|
|
|
timeoutCh := time.After(time.Second * 10)
|
|
|
|
waitForEvents:
|
|
for {
|
|
select {
|
|
case <-timeoutCh:
|
|
t.Fatalf("timeout exceeded waiting for %d watch events, got %d", len(testcase.watchEvents), len(events))
|
|
default:
|
|
if len(events) == len(testcase.watchEvents) {
|
|
break waitForEvents
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, path := range events {
|
|
nlri := path.GetNlri()
|
|
var prefix gobgpapi.IPAddressPrefix
|
|
err = nlri.UnmarshalTo(&prefix)
|
|
if err != nil {
|
|
t.Fatalf("Invalid nlri in advertised path")
|
|
}
|
|
advertisedPrefix := prefix.Prefix + "/" + fmt.Sprint(prefix.PrefixLen)
|
|
if _, ok := testcase.watchEvents[advertisedPrefix]; !ok {
|
|
t.Errorf("got unexpected path: %v", advertisedPrefix)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_syncInternalPeers(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
existingNodes []*v1core.Node
|
|
neighbors map[string]bool
|
|
}{
|
|
{
|
|
"sync 1 peer",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: true,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
},
|
|
[]*v1core.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1": true,
|
|
},
|
|
},
|
|
{
|
|
"sync multiple peers",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: true,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
},
|
|
[]*v1core.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-2",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1": true,
|
|
"10.0.0.2": true,
|
|
},
|
|
},
|
|
{
|
|
"sync peer with removed nodes",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: true,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: map[string]bool{
|
|
"10.0.0.2": true,
|
|
},
|
|
},
|
|
[]*v1core.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1": true,
|
|
},
|
|
},
|
|
{
|
|
"sync multiple peers with full mesh disabled",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: false,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
nodeAsnNumber: 100,
|
|
},
|
|
[]*v1core.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
Annotations: map[string]string{
|
|
"kube-router.io/node.asn": "100",
|
|
},
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-2",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
go testcase.nrc.bgpServer.Serve()
|
|
global := &gobgpapi.Global{
|
|
Asn: 1,
|
|
RouterId: "10.0.0.0",
|
|
ListenPort: 10000,
|
|
}
|
|
err := testcase.nrc.bgpServer.StartBgp(context.Background(), &gobgpapi.StartBgpRequest{Global: global})
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := testcase.nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}); err != nil {
|
|
t.Fatalf("failed to stop BGP server : %s", err)
|
|
}
|
|
}()
|
|
|
|
startInformersForRoutes(testcase.nrc, testcase.nrc.clientset)
|
|
if err = createNodes(testcase.nrc.clientset, testcase.existingNodes); err != nil {
|
|
t.Errorf("failed to create existing nodes: %v", err)
|
|
}
|
|
waitForListerWithTimeout(testcase.nrc.nodeLister, time.Second*10, t)
|
|
|
|
testcase.nrc.syncInternalPeers()
|
|
|
|
neighbors := make(map[string]bool)
|
|
err = testcase.nrc.bgpServer.ListPeer(context.Background(), &gobgpapi.ListPeerRequest{}, func(peer *gobgpapi.Peer) {
|
|
if peer.Conf.NeighborAddress == "" {
|
|
return
|
|
}
|
|
neighbors[peer.Conf.NeighborAddress] = true
|
|
})
|
|
if err != nil {
|
|
t.Errorf("error listing BGP peers: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(testcase.neighbors, neighbors) {
|
|
t.Logf("actual neighbors: %v", neighbors)
|
|
t.Logf("expected neighbors: %v", testcase.neighbors)
|
|
t.Errorf("did not get expected neighbors")
|
|
}
|
|
|
|
if !reflect.DeepEqual(testcase.nrc.activeNodes, testcase.neighbors) {
|
|
t.Logf("actual active nodes: %v", testcase.nrc.activeNodes)
|
|
t.Logf("expected active nodes: %v", testcase.neighbors)
|
|
t.Errorf("did not get expected activeNodes")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_routeReflectorConfiguration(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
node *v1core.Node
|
|
expectedRRServer bool
|
|
expectedRRClient bool
|
|
expectedClusterID string
|
|
expectedBgpToStart bool
|
|
}{
|
|
{
|
|
"RR server with int cluster id",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: false,
|
|
bgpPort: 10000,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
routerID: "10.0.0.0",
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
nodeAsnNumber: 100,
|
|
hostnameOverride: "node-1",
|
|
},
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
Annotations: map[string]string{
|
|
"kube-router.io/node.asn": "100",
|
|
rrServerAnnotation: "1",
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
false,
|
|
"1",
|
|
true,
|
|
},
|
|
{
|
|
"RR server with IPv4 cluster id",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: false,
|
|
bgpPort: 10000,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
routerID: "10.0.0.0",
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
nodeAsnNumber: 100,
|
|
hostnameOverride: "node-1",
|
|
},
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
Annotations: map[string]string{
|
|
"kube-router.io/node.asn": "100",
|
|
rrServerAnnotation: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
false,
|
|
"10.0.0.1",
|
|
true,
|
|
},
|
|
{
|
|
"RR client with int cluster id",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: false,
|
|
bgpPort: 10000,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
routerID: "10.0.0.0",
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
nodeAsnNumber: 100,
|
|
hostnameOverride: "node-1",
|
|
},
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
Annotations: map[string]string{
|
|
"kube-router.io/node.asn": "100",
|
|
rrClientAnnotation: "1",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
true,
|
|
"1",
|
|
true,
|
|
},
|
|
{
|
|
"RR client with IPv4 cluster id",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: false,
|
|
bgpPort: 10000,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
routerID: "10.0.0.0",
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
nodeAsnNumber: 100,
|
|
hostnameOverride: "node-1",
|
|
},
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
Annotations: map[string]string{
|
|
"kube-router.io/node.asn": "100",
|
|
rrClientAnnotation: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
true,
|
|
"10.0.0.1",
|
|
true,
|
|
},
|
|
{
|
|
"RR server with unparseable cluster id",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: false,
|
|
bgpPort: 10000,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
nodeAsnNumber: 100,
|
|
hostnameOverride: "node-1",
|
|
},
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
Annotations: map[string]string{
|
|
"kube-router.io/node.asn": "100",
|
|
rrServerAnnotation: "hello world",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
false,
|
|
"",
|
|
false,
|
|
},
|
|
{
|
|
"RR client with unparseable cluster id",
|
|
&NetworkRoutingController{
|
|
bgpFullMeshMode: false,
|
|
bgpPort: 10000,
|
|
clientset: fake.NewSimpleClientset(),
|
|
primaryIP: net.ParseIP("10.0.0.0"),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
activeNodes: make(map[string]bool),
|
|
nodeAsnNumber: 100,
|
|
hostnameOverride: "node-1",
|
|
},
|
|
&v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
Annotations: map[string]string{
|
|
"kube-router.io/node.asn": "100",
|
|
rrClientAnnotation: "hello world",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
false,
|
|
"",
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
if err := createNodes(testcase.nrc.clientset, []*v1core.Node{testcase.node}); err != nil {
|
|
t.Errorf("failed to create existing nodes: %v", err)
|
|
}
|
|
|
|
err := testcase.nrc.startBgpServer(false)
|
|
if err == nil {
|
|
defer func() {
|
|
if err := testcase.nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}); err != nil {
|
|
t.Fatalf("failed to stop BGP server : %s", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
if testcase.expectedBgpToStart {
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
if testcase.expectedRRServer != testcase.nrc.bgpRRServer {
|
|
t.Error("Node suppose to be RR server")
|
|
}
|
|
if testcase.expectedRRClient != testcase.nrc.bgpRRClient {
|
|
t.Error("Node suppose to be RR client")
|
|
}
|
|
if testcase.expectedClusterID != testcase.nrc.bgpClusterID {
|
|
t.Errorf("Node suppose to have cluster id '%s' but got %s", testcase.expectedClusterID, testcase.nrc.bgpClusterID)
|
|
}
|
|
} else if err == nil {
|
|
t.Fatal("mis-configured BGP server is not supposed to start")
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
/* Disabling test for now. OnNodeUpdate() behaviour is changed. test needs to be adopted.
|
|
func Test_OnNodeUpdate(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nrc *NetworkRoutingController
|
|
nodeEvents []*watchers.NodeUpdate
|
|
activeNodes map[string]bool
|
|
}{
|
|
{
|
|
"node add event",
|
|
&NetworkRoutingController{
|
|
activeNodes: make(map[string]bool),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
defaultNodeAsnNumber: 1,
|
|
clientset: fake.NewSimpleClientset(),
|
|
},
|
|
[]*watchers.NodeUpdate{
|
|
{
|
|
Node: &v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Op: watchers.ADD,
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1": true,
|
|
},
|
|
},
|
|
{
|
|
"add multiple nodes",
|
|
&NetworkRoutingController{
|
|
activeNodes: make(map[string]bool),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
defaultNodeAsnNumber: 1,
|
|
clientset: fake.NewSimpleClientset(),
|
|
},
|
|
[]*watchers.NodeUpdate{
|
|
{
|
|
Node: &v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Op: watchers.ADD,
|
|
},
|
|
{
|
|
Node: &v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-2",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeExternalIP,
|
|
Address: "1.1.1.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Op: watchers.ADD,
|
|
},
|
|
},
|
|
map[string]bool{
|
|
"10.0.0.1": true,
|
|
"1.1.1.1": true,
|
|
},
|
|
},
|
|
{
|
|
"add and then delete nodes",
|
|
&NetworkRoutingController{
|
|
activeNodes: make(map[string]bool),
|
|
bgpServer: gobgp.NewBgpServer(),
|
|
defaultNodeAsnNumber: 1,
|
|
clientset: fake.NewSimpleClientset(),
|
|
},
|
|
[]*watchers.NodeUpdate{
|
|
{
|
|
Node: &v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Op: watchers.ADD,
|
|
},
|
|
{
|
|
Node: &v1core.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-1",
|
|
},
|
|
Status: v1core.NodeStatus{
|
|
Addresses: []v1core.NodeAddress{
|
|
{
|
|
Type: v1core.NodeInternalIP,
|
|
Address: "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Op: watchers.REMOVE,
|
|
},
|
|
},
|
|
map[string]bool{},
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
go testcase.nrc.bgpServer.Serve()
|
|
err := testcase.nrc.bgpServer.Start(&config.Global{
|
|
Config: config.GlobalConfig{
|
|
As: 1,
|
|
RouterId: "10.0.0.0",
|
|
Port: 10000,
|
|
},
|
|
})
|
|
testcase.nrc.bgpServerStarted = true
|
|
if err != nil {
|
|
t.Fatalf("failed to start BGP server: %v", err)
|
|
}
|
|
defer testcase.nrc.bgpServer.Stop()
|
|
|
|
for _, nodeEvent := range testcase.nodeEvents {
|
|
testcase.nrc.OnNodeUpdate(nodeEvent)
|
|
}
|
|
|
|
neighbors := testcase.nrc.bgpServer.GetNeighbor("", false)
|
|
for _, neighbor := range neighbors {
|
|
_, exists := testcase.activeNodes[neighbor.Config.NeighborAddress]
|
|
if !exists {
|
|
t.Errorf("expected neighbor: %v doesn't exist", neighbor.Config.NeighborAddress)
|
|
}
|
|
}
|
|
|
|
if !reflect.DeepEqual(testcase.nrc.activeNodes, testcase.activeNodes) {
|
|
t.Logf("actual active nodes: %v", testcase.nrc.activeNodes)
|
|
t.Logf("expected active nodes: %v", testcase.activeNodes)
|
|
t.Errorf("did not get expected activeNodes")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
*/
|
|
|
|
func Test_generateTunnelName(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
nodeIP string
|
|
tunnelName string
|
|
}{
|
|
{
|
|
"IP less than 12 characters after removing '.'",
|
|
"10.0.0.1",
|
|
"tun-e443169117a",
|
|
},
|
|
{
|
|
"IP has 12 characters after removing '.'",
|
|
"100.200.300.400",
|
|
"tun-9033d7906c7",
|
|
},
|
|
{
|
|
"IPv6 tunnel names are properly handled and consistent",
|
|
"2001:db8:42:2::/64",
|
|
"tun-ba56986ef05",
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
tunnelName := generateTunnelName(testcase.nodeIP)
|
|
assert.Lessf(t, len(tunnelName), 16, "the maximum length of the tunnel name should never exceed"+
|
|
"15 characters as 16 characters is the maximum length of a Unix interface name")
|
|
assert.Equal(t, testcase.tunnelName, tunnelName, "did not get expected tunnel interface name")
|
|
})
|
|
}
|
|
}
|
|
|
|
func createServices(clientset kubernetes.Interface, svcs []*v1core.Service) error {
|
|
for _, svc := range svcs {
|
|
_, err := clientset.CoreV1().Services("default").Create(context.Background(), svc, metav1.CreateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func createNodes(clientset kubernetes.Interface, nodes []*v1core.Node) error {
|
|
for _, node := range nodes {
|
|
_, err := clientset.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func startInformersForRoutes(nrc *NetworkRoutingController, clientset kubernetes.Interface) {
|
|
informerFactory := informers.NewSharedInformerFactory(clientset, 0)
|
|
svcInformer := informerFactory.Core().V1().Services().Informer()
|
|
epInformer := informerFactory.Core().V1().Endpoints().Informer()
|
|
nodeInformer := informerFactory.Core().V1().Nodes().Informer()
|
|
|
|
go informerFactory.Start(nil)
|
|
informerFactory.WaitForCacheSync(nil)
|
|
|
|
nrc.svcLister = svcInformer.GetIndexer()
|
|
nrc.epLister = epInformer.GetIndexer()
|
|
nrc.nodeLister = nodeInformer.GetIndexer()
|
|
}
|
|
|
|
//nolint:unparam // it doesn't hurt anything to leave timeout here, and increases future flexibility for testing
|
|
func waitForListerWithTimeout(lister cache.Indexer, timeout time.Duration, t *testing.T) {
|
|
tick := time.Tick(100 * time.Millisecond)
|
|
timeoutCh := time.After(timeout)
|
|
for {
|
|
select {
|
|
case <-timeoutCh:
|
|
t.Fatal("timeout exceeded waiting for service lister to fill cache")
|
|
case <-tick:
|
|
if len(lister.List()) != 0 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func ptrToString(str string) *string {
|
|
return &str
|
|
}
|