diff --git a/pkg/provider/loadBalancer.go b/pkg/provider/loadBalancer.go index c95b646..1b4a199 100644 --- a/pkg/provider/loadBalancer.go +++ b/pkg/provider/loadBalancer.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "strings" "github.com/kube-vip/kube-vip-cloud-provider/pkg/ipam" v1 "k8s.io/api/core/v1" @@ -82,12 +83,6 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi return &service.Status.LoadBalancer, nil } - // Get all services in this namespace, that have the correct label - svcs, err := k.kubeClient.CoreV1().Services(service.Namespace).List(ctx, metav1.ListOptions{LabelSelector: "implementation=kube-vip"}) - if err != nil { - return &service.Status.LoadBalancer, err - } - // Get the clound controller configuration map controllerCM, err := k.GetConfigMap(ctx, KubeVipClientConfig, "kube-system") if err != nil { @@ -99,13 +94,34 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi } } + // Get ip pool from configmap and determine if it is namespace specific or global + pool, global, err := discoverPool(controllerCM, service.Namespace, k.cloudConfigMap) + + if err != nil { + return nil, err + } + + // Get all services in this namespace or globally, that have the correct label + var svcs *v1.ServiceList + if global { + svcs, err = k.kubeClient.CoreV1().Services("").List(ctx, metav1.ListOptions{LabelSelector: "implementation=kube-vip"}) + if err != nil { + return &service.Status.LoadBalancer, err + } + } else { + svcs, err = k.kubeClient.CoreV1().Services(service.Namespace).List(ctx, metav1.ListOptions{LabelSelector: "implementation=kube-vip"}) + if err != nil { + return &service.Status.LoadBalancer, err + } + } + var existingServiceIPS []string for x := range svcs.Items { existingServiceIPS = append(existingServiceIPS, svcs.Items[x].Labels["ipam-address"]) } // If the LoadBalancer address is empty, then do a local IPAM lookup - loadBalancerIP, err := discoverAddress(controllerCM, service.Namespace, k.cloudConfigMap, existingServiceIPS) + loadBalancerIP, err := discoverAddress(service.Namespace, pool, existingServiceIPS) if err != nil { return nil, err @@ -142,7 +158,7 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi return &service.Status.LoadBalancer, nil } -func discoverAddress(cm *v1.ConfigMap, namespace, configMapName string, existingServiceIPS []string) (vip string, err error) { +func discoverPool(cm *v1.ConfigMap, namespace, configMapName string) (pool string, global bool, err error) { var cidr, ipRange string var ok bool @@ -156,16 +172,11 @@ func discoverAddress(cm *v1.ConfigMap, namespace, configMapName string, existing klog.Info(fmt.Errorf("no global cidr config exists [cidr-global]")) } else { klog.Infof("Taking address from [cidr-global] pool") + return cidr, true, nil } } else { klog.Infof("Taking address from [%s] pool", cidrKey) - } - if ok { - vip, err = ipam.FindAvailableHostFromCidr(namespace, cidr, existingServiceIPS) - if err != nil { - return "", err - } - return + return cidr, false, nil } // Find Range @@ -178,16 +189,29 @@ func discoverAddress(cm *v1.ConfigMap, namespace, configMapName string, existing klog.Info(fmt.Errorf("no global range config exists [range-global]")) } else { klog.Infof("Taking address from [range-global] pool") + return ipRange, true, nil } } else { klog.Infof("Taking address from [%s] pool", rangeKey) + return ipRange, false, nil } - if ok { - vip, err = ipam.FindAvailableHostFromRange(namespace, ipRange, existingServiceIPS) + + return "", false, fmt.Errorf("no address pools could be found") +} + +func discoverAddress(namespace, pool string, existingServiceIPS []string) (vip string, err error) { + // Check if ip pool contains a cidr, if not assume it is a range + if strings.Contains(pool, "/") { + vip, err = ipam.FindAvailableHostFromCidr(namespace, pool, existingServiceIPS) if err != nil { - return vip, err + return "", err + } + } else { + vip, err = ipam.FindAvailableHostFromRange(namespace, pool, existingServiceIPS) + if err != nil { + return "", err } - return } - return "", fmt.Errorf("no IP address ranges could be found either range-global or range-") + + return vip, err } diff --git a/pkg/provider/loadBalancer_test.go b/pkg/provider/loadBalancer_test.go new file mode 100644 index 0000000..eebf244 --- /dev/null +++ b/pkg/provider/loadBalancer_test.go @@ -0,0 +1,218 @@ +package provider + +import ( + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" +) + +func Test_DiscoveryPoolCIDR(t *testing.T) { + type args struct { + data v1.ConfigMap + cidr string + } + + dummy := new(v1.ConfigMap) + dummy.Data = map[string]string{} + dummy.Data["cidr-dummystart"] = "172.16.0.1/24" + dummy.Data["cidr-global"] = "192.168.1.1/24" + dummy.Data["cidr-system"] = "10.10.10.8/29" + dummy.Data["cidr-dummyend"] = "172.16.0.2/24" + + tests := []struct { + name string + args args + want string + wantBool bool + wantErr bool + }{ + { + name: "cidr lookup for known namespace", + args: args{ + *dummy, + "system", + }, + want: "10.10.10.8/29", + wantBool: false, + wantErr: false, + }, + { + name: "cidr lookup for unknown namespace", + args: args{ + *dummy, + "basic", + }, + want: "192.168.1.1/24", + wantBool: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotString, gotBool, err := discoverPool(&tt.args.data, tt.args.cidr, "") + if (err != nil) != tt.wantErr { + t.Errorf("discoverPool() error: %v, expected: %v", err, tt.wantErr) + return + } + if !assert.EqualValues(t, gotString, tt.want) && !assert.EqualValues(t, gotBool, tt.wantBool) { + t.Errorf("discoverPool() returned: %s : %v, expected: %s : %v", gotString, gotBool, tt.want, tt.wantBool) + } + }) + } +} + +func Test_DiscoveryPoolRange(t *testing.T) { + type args struct { + data v1.ConfigMap + ipRange string + } + + dummy := new(v1.ConfigMap) + dummy.Data = map[string]string{} + dummy.Data["range-dummystart"] = "172.16.0.1-172.16.0.254" + dummy.Data["range-global"] = "192.168.1.1-192.168.1.254" + dummy.Data["range-system"] = "10.10.10.8-10.10.10.15" + dummy.Data["range-dummyend"] = "172.16.1.1-172.16.1.254" + + tests := []struct { + name string + args args + want string + wantBool bool + wantErr bool + }{ + { + name: "range lookup for known namespace", + args: args{ + *dummy, + "system", + }, + want: "10.10.10.8-10.10.10.15", + wantBool: false, + wantErr: false, + }, + { + name: "range lookup for unknown namespace", + args: args{ + *dummy, + "basic", + }, + want: "192.168.1.1-192.168.1.254", + wantBool: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotString, gotBool, err := discoverPool(&tt.args.data, tt.args.ipRange, "") + if (err != nil) != tt.wantErr { + t.Errorf("discoverPool() error: %v, expected: %v", err, tt.wantErr) + return + } + if !assert.EqualValues(t, gotString, tt.want) && !assert.EqualValues(t, gotBool, tt.wantBool) { + t.Errorf("discoverPool() returned: %s : %v, expected: %s : %v", gotString, gotBool, tt.want, tt.wantBool) + } + }) + } +} + +func Test_DiscoveryAddressCIDR(t *testing.T) { + type args struct { + namespace string + pool string + existingServiceIPS []string + } + + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "available ip search for known namespace", + args: args{ + "system", + "10.10.10.8/29", + []string{"10.10.10.8", "10.10.10.9", "10.10.10.10", "10.10.10.12"}, + }, + want: "10.10.10.11", + wantErr: false, + }, + { + name: "available ip search for unknown namespace", + args: args{ + "system", + "192.168.1.1/24", + []string{"10.10.10.8", "172.16.0.3", "192.168.1.1", "10.10.10.9", "10.10.10.10", "172.16.0.4", "192.168.1.2", "10.10.10.12"}, + }, + want: "192.168.1.3", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotString, err := discoverAddress(tt.args.namespace, tt.args.pool, tt.args.existingServiceIPS) + if (err != nil) != tt.wantErr { + t.Errorf("discoverAddress() error: %v, expected: %v", err, tt.wantErr) + return + } + if !assert.EqualValues(t, gotString, tt.want) { + t.Errorf("discoverAddress() returned: %s, expected: %s", gotString, tt.want) + } + }) + } +} + +func Test_DiscoveryAddressRange(t *testing.T) { + type args struct { + namespace string + pool string + existingServiceIPS []string + } + + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "available ip search for known namespace", + args: args{ + "system", + "10.10.10.8-10.10.10.15", + []string{"10.10.10.8", "10.10.10.9", "10.10.10.10", "10.10.10.12"}, + }, + want: "10.10.10.11", + wantErr: false, + }, + { + name: "available ip search for unknown namespace", + args: args{ + "system", + "192.168.1.1-192.168.1.254", + []string{"10.10.10.8", "172.16.0.3", "192.168.1.1", "10.10.10.9", "10.10.10.10", "172.16.0.4", "192.168.1.2", "10.10.10.12"}, + }, + want: "192.168.1.3", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotString, err := discoverAddress(tt.args.namespace, tt.args.pool, tt.args.existingServiceIPS) + if (err != nil) != tt.wantErr { + t.Errorf("discoverAddress() error: %v, expected: %v", err, tt.wantErr) + return + } + if !assert.EqualValues(t, gotString, tt.want) { + t.Errorf("discoverAddress() returned: %s, expected: %s", gotString, tt.want) + } + }) + } +}