kube-router/pkg/controllers/network_routes_controller_test.go
Murali Reddy 71d16bf4d6
code restructuring as per typical golang projects (#397)
* code restructuring as per typical golang projects

* fix link in docs
2018-04-17 00:18:20 +05:30

1598 lines
38 KiB
Go

package controllers
import (
"errors"
"net"
"os"
"reflect"
"testing"
"time"
"github.com/osrg/gobgp/config"
gobgp "github.com/osrg/gobgp/server"
"github.com/osrg/gobgp/table"
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"
)
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-2",
},
Spec: v1core.ServiceSpec{
Type: "LoadBalancer",
ClusterIP: "10.0.0.2",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-3",
},
Spec: v1core.ServiceSpec{
Type: "NodePort",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-2",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "None",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-3",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
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()
err := testcase.nrc.bgpServer.Start(&config.Global{
Config: config.GlobalConfig{
As: 1,
RouterId: "10.0.0.0",
Port: 10000,
},
})
if err != nil {
t.Fatalf("failed to start BGP server: %v", err)
}
defer testcase.nrc.bgpServer.Stop()
w := testcase.nrc.bgpServer.Watch(gobgp.WatchBestPath(false))
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)
// 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)
watchEvents := waitForBGPWatchEventWithTimeout(time.Second*10, len(testcase.watchEvents), w, t)
for _, watchEvent := range watchEvents {
for _, path := range watchEvent.PathList {
if _, ok := testcase.watchEvents[path.GetNlri().String()]; ok {
continue
} else {
t.Errorf("got unexpected path: %v", path.GetNlri().String())
}
}
}
})
}
}
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
ExternalIPs: []string{"1.1.1.1"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-2",
},
Spec: v1core.ServiceSpec{
Type: "LoadBalancer",
ClusterIP: "10.0.0.2",
// ignored since LoadBalancer services don't
// advertise external IPs.
ExternalIPs: []string{"2.2.2.2"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-3",
},
Spec: v1core.ServiceSpec{
Type: "NodePort",
ClusterIP: "10.0.0.3",
ExternalIPs: []string{"3.3.3.3", "4.4.4.4"},
},
},
},
map[string]bool{
"1.1.1.1/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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
ExternalIPs: []string{"1.1.1.1"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-2",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "None",
ExternalIPs: []string{"2.2.2.2"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-3",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "LoadBalancer",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "LoadBalancer",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "LoadBalancer",
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(),
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
Annotations: map[string]string{
"kube-router.io/service.skiplbips": "true",
},
},
Spec: v1core.ServiceSpec{
Type: "LoadBalancer",
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{},
},
}
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,
},
})
if err != nil {
t.Fatalf("failed to start BGP server: %v", err)
}
defer testcase.nrc.bgpServer.Stop()
w := testcase.nrc.bgpServer.Watch(gobgp.WatchBestPath(false))
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)
watchEvents := waitForBGPWatchEventWithTimeout(time.Second*10, len(testcase.watchEvents), w, t)
for _, watchEvent := range watchEvents {
for _, path := range watchEvent.PathList {
if _, ok := testcase.watchEvents[path.GetNlri().String()]; ok {
continue
} else {
t.Errorf("got unexpected path: %v", path.GetNlri().String())
}
}
}
})
}
}
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: "ClusterIP",
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: "ClusterIP",
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(testcase.existingEndpoint)
if err != nil {
t.Fatalf("failed to create existing endpoints: %v", err)
}
_, err = clientset.CoreV1().Services("default").Create(testcase.existingService)
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(),
},
"node-1",
&v1core.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: v1core.NodeSpec{
PodCIDR: "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",
},
"",
&v1core.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: v1core.NodeSpec{
PodCIDR: "172.20.0.0/24",
},
},
map[string]bool{
"172.20.0.0/24": true,
},
nil,
},
{
"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()
err := testcase.nrc.bgpServer.Start(&config.Global{
Config: config.GlobalConfig{
As: 1,
RouterId: "10.0.0.0",
Port: 10000,
},
})
if err != nil {
t.Fatalf("failed to start BGP server: %v", err)
}
defer testcase.nrc.bgpServer.Stop()
w := testcase.nrc.bgpServer.Watch(gobgp.WatchBestPath(false))
clientset := fake.NewSimpleClientset()
_, err = clientset.CoreV1().Nodes().Create(testcase.node)
if err != nil {
t.Fatalf("failed to create node: %v", err)
}
testcase.nrc.clientset = clientset
os.Setenv("NODE_NAME", testcase.envNodeName)
defer 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")
}
watchEvents := waitForBGPWatchEventWithTimeout(time.Second*10, len(testcase.watchEvents), w, t)
for _, watchEvent := range watchEvents {
for _, path := range watchEvent.PathList {
if _, ok := testcase.watchEvents[path.GetNlri().String()]; ok {
continue
} else {
t.Errorf("got unexpected path: %v", path.GetNlri().String())
}
}
}
})
}
}
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(),
nodeIP: 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(),
nodeIP: 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(),
nodeIP: 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(),
nodeIP: 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()
err := testcase.nrc.bgpServer.Start(&config.Global{
Config: config.GlobalConfig{
As: 1,
RouterId: "10.0.0.0",
Port: 10000,
},
})
if err != nil {
t.Fatalf("failed to start BGP server: %v", err)
}
defer testcase.nrc.bgpServer.Stop()
if err = createNodes(testcase.nrc.clientset, testcase.existingNodes); err != nil {
t.Errorf("failed to create existing nodes: %v", err)
}
testcase.nrc.syncInternalPeers()
neighbors := testcase.nrc.bgpServer.GetNeighbor("", false)
for _, neighbor := range neighbors {
_, exists := testcase.neighbors[neighbor.Config.NeighborAddress]
if !exists {
t.Errorf("expected neighbor: %v doesn't exist", neighbor.Config.NeighborAddress)
}
}
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")
}
})
}
}
/* 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_addExportPolicies(t *testing.T) {
testcases := []struct {
name string
nrc *NetworkRoutingController
existingNodes []*v1core.Node
existingServices []*v1core.Service
podDefinedSet *config.DefinedSets
clusterIPDefinedSet *config.DefinedSets
externalPeerDefinedSet *config.DefinedSets
policyStatements []*config.Statement
err error
}{
{
"has nodes and services",
&NetworkRoutingController{
clientset: fake.NewSimpleClientset(),
hostnameOverride: "node-1",
bgpFullMeshMode: false,
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",
},
},
},
Spec: v1core.NodeSpec{
PodCIDR: "172.20.0.0/24",
},
},
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
ExternalIPs: []string{"1.1.1.1"},
},
},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "podcidrprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "172.20.0.0/24",
MasklengthRange: "24..24",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "clusteripprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "1.1.1.1/32",
MasklengthRange: "32..32",
},
{
IpPrefix: "10.0.0.1/32",
MasklengthRange: "32..32",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{},
[]*config.Statement{
{
Name: "kube_router_stmt0",
Conditions: config.Conditions{
MatchPrefixSet: config.MatchPrefixSet{
PrefixSet: "podcidrprefixset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
},
},
},
nil,
},
{
"has nodes, services with external peers",
&NetworkRoutingController{
clientset: fake.NewSimpleClientset(),
hostnameOverride: "node-1",
bgpFullMeshMode: false,
bgpServer: gobgp.NewBgpServer(),
activeNodes: make(map[string]bool),
globalPeerRouters: []*config.NeighborConfig{
{
NeighborAddress: "10.10.0.1",
},
{
NeighborAddress: "10.10.0.2",
},
},
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",
},
},
},
Spec: v1core.NodeSpec{
PodCIDR: "172.20.0.0/24",
},
},
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
ExternalIPs: []string{"1.1.1.1"},
},
},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "podcidrprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "172.20.0.0/24",
MasklengthRange: "24..24",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "clusteripprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "1.1.1.1/32",
MasklengthRange: "32..32",
},
{
IpPrefix: "10.0.0.1/32",
MasklengthRange: "32..32",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{},
NeighborSets: []config.NeighborSet{
{
NeighborSetName: "externalpeerset",
NeighborInfoList: []string{"10.10.0.1", "10.10.0.2"},
},
},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
[]*config.Statement{
{
Name: "kube_router_stmt0",
Conditions: config.Conditions{
MatchPrefixSet: config.MatchPrefixSet{
PrefixSet: "podcidrprefixset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
},
},
{
Name: "kube_router_stmt1",
Conditions: config.Conditions{
MatchPrefixSet: config.MatchPrefixSet{
PrefixSet: "clusteripprefixset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
MatchNeighborSet: config.MatchNeighborSet{
NeighborSet: "externalpeerset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
},
},
},
nil,
},
}
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,
},
})
if err != nil {
t.Fatalf("failed to start BGP server: %v", err)
}
defer testcase.nrc.bgpServer.Stop()
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)
}
if err = createServices(testcase.nrc.clientset, testcase.existingServices); err != nil {
t.Errorf("failed to create existing nodes: %v", err)
}
// ClusterIPs and ExternalIPs
waitForListerWithTimeout(testcase.nrc.svcLister, time.Second*10, t)
testcase.nrc.advertiseClusterIp = true
testcase.nrc.advertiseExternalIp = true
testcase.nrc.advertiseLoadBalancerIp = false
err = testcase.nrc.addExportPolicies()
if !reflect.DeepEqual(err, testcase.err) {
t.Logf("expected err %v", testcase.err)
t.Logf("actual err %v", err)
t.Error("unexpected error")
}
podDefinedSet, err := testcase.nrc.bgpServer.GetDefinedSet(table.DEFINED_TYPE_PREFIX, "podcidrprefixset")
if err != nil {
t.Fatalf("error validating defined sets: %v", err)
}
if !podDefinedSet.Equal(testcase.podDefinedSet) {
t.Logf("expected pod defined set: %+v", testcase.podDefinedSet.PrefixSets)
t.Logf("actual pod defined set: %+v", podDefinedSet.PrefixSets)
t.Error("unexpected pod defined set")
}
clusterIPDefinedSet, err := testcase.nrc.bgpServer.GetDefinedSet(table.DEFINED_TYPE_PREFIX, "clusteripprefixset")
if err != nil {
t.Fatalf("error validating defined sets: %v", err)
}
if !clusterIPDefinedSet.Equal(testcase.clusterIPDefinedSet) {
t.Logf("expected cluster ip defined set: %+v", testcase.clusterIPDefinedSet.PrefixSets)
t.Logf("actual cluster ip defined set: %+v", clusterIPDefinedSet.PrefixSets)
t.Error("unexpected cluster ip defined set")
}
externalPeerDefinedSet, err := testcase.nrc.bgpServer.GetDefinedSet(table.DEFINED_TYPE_NEIGHBOR, "externalpeerset")
if err != nil {
t.Fatalf("error validating defined sets: %v", err)
}
if !externalPeerDefinedSet.Equal(testcase.externalPeerDefinedSet) {
t.Logf("expected external peer defined set: %+v", testcase.externalPeerDefinedSet.NeighborSets)
t.Logf("actual external peer defined set: %+v", externalPeerDefinedSet.NeighborSets)
t.Error("unexpected external peer defined set")
}
policies := testcase.nrc.bgpServer.GetPolicy()
policyExists := false
for _, policy := range policies {
if policy.Name == "kube_router" {
policyExists = true
break
}
}
if !policyExists {
t.Errorf("policy 'kube_router' was not added")
}
routeType, policyAssignments, err := testcase.nrc.bgpServer.GetPolicyAssignment("", table.POLICY_DIRECTION_EXPORT)
if routeType != table.ROUTE_TYPE_REJECT {
t.Errorf("expected route type 'reject' for export policy assignment, but got %v", routeType)
}
if err != nil {
t.Fatalf("failed to get policy assignments: %v", err)
}
policyAssignmentExists := false
for _, policyAssignment := range policyAssignments {
if policyAssignment.Name == "kube_router" {
policyAssignmentExists = true
}
}
if !policyAssignmentExists {
t.Error("export policy assignment 'kube_router' was not added")
}
statements := testcase.nrc.bgpServer.GetStatement()
for _, expectedStatement := range testcase.policyStatements {
found := false
for _, statement := range statements {
if statement.Equal(expectedStatement) {
found = true
}
}
if !found {
t.Errorf("statement %v not found", expectedStatement)
}
}
})
}
}
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-10001",
},
{
"IP has 12 characters after removing '.'",
"100.200.300.400",
"tun100200300400",
},
}
for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
tunnelName := generateTunnelName(testcase.nodeIP)
if tunnelName != testcase.tunnelName {
t.Logf("actual tunnel interface name: %s", tunnelName)
t.Logf("expected tunnel interface name: %s", testcase.tunnelName)
t.Error("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(svc)
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(node)
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()
go informerFactory.Start(nil)
informerFactory.WaitForCacheSync(nil)
nrc.svcLister = svcInformer.GetIndexer()
nrc.epLister = epInformer.GetIndexer()
}
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 waitForBGPWatchEventWithTimeout(timeout time.Duration, expectedNumEvents int, w *gobgp.Watcher, t *testing.T) []*gobgp.WatchEventBestPath {
timeoutCh := time.After(timeout)
var events []*gobgp.WatchEventBestPath
for {
select {
case <-timeoutCh:
t.Fatalf("timeout exceeded waiting for %d watch events, got %d", expectedNumEvents, len(events))
case event := <-w.Event():
events = append(events, event.(*gobgp.WatchEventBestPath))
default:
if len(events) == expectedNumEvents {
return events
}
}
}
}
func ptrToString(str string) *string {
return &str
}