Skip to content

Commit

Permalink
Merge pull request #36 from JinxCappa/lb
Browse files Browse the repository at this point in the history
fix ip allocation across namespaces
  • Loading branch information
thebsdbox authored Jul 19, 2022
2 parents 0cac3eb + 99186f2 commit b36d045
Show file tree
Hide file tree
Showing 2 changed files with 262 additions and 20 deletions.
64 changes: 44 additions & 20 deletions pkg/provider/loadBalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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-<namespace>")

return vip, err
}
218 changes: 218 additions & 0 deletions pkg/provider/loadBalancer_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}

0 comments on commit b36d045

Please sign in to comment.