Add IPPool CR-based persistance
This adds persistance (to CRs) for Vino IPAM functionality. This will be integrated with the controller in a follow-on patchset. Change-Id: I5b2695df72d52890d315d3ab32c36f12377f857f
This commit is contained in:
parent
40d4804ad1
commit
1f6be5c0e4
3
go.mod
3
go.mod
@ -7,6 +7,9 @@ require (
|
||||
github.com/go-logr/logr v0.3.0
|
||||
github.com/go-logr/zapr v0.2.0
|
||||
github.com/metal3-io/baremetal-operator v0.0.0-20210111093319-93a6fd209b9a
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/onsi/ginkgo v1.14.2
|
||||
github.com/onsi/gomega v1.10.2
|
||||
github.com/prometheus/common v0.10.0
|
||||
|
3
go.sum
3
go.sum
@ -253,9 +253,12 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
136
pkg/ipam/ipam.go
136
pkg/ipam/ipam.go
@ -15,33 +15,34 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
vinov1 "vino/pkg/api/v1"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
vinov1 "vino/pkg/api/v1"
|
||||
)
|
||||
|
||||
// Ipam provides IPAM reservation, backed by IPPool CRs
|
||||
type Ipam struct {
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Client client.Client
|
||||
|
||||
ippools map[string]*vinov1.IPPoolSpec
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// NewIpam initializes an empty IPAM configuration.
|
||||
// TODO: persist and refresh state from the API server
|
||||
// TODO: add ability to remove IP addresses and ranges
|
||||
func NewIpam() *Ipam {
|
||||
ippools := make(map[string]*vinov1.IPPoolSpec)
|
||||
func NewIpam(logger logr.Logger, client client.Client, namespace string) *Ipam {
|
||||
return &Ipam{
|
||||
ippools: ippools,
|
||||
Log: logger,
|
||||
Client: client,
|
||||
Namespace: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,39 +66,47 @@ func NewRange(start string, stop string) (vinov1.Range, error) {
|
||||
// AddSubnetRange adds a range within a subnet for IP allocation
|
||||
// TODO error: range overlaps with existing range or subnet overlaps with existing subnet
|
||||
// TODO error: invalid range for subnet
|
||||
func (i *Ipam) AddSubnetRange(subnet string, subnetRange vinov1.Range) error {
|
||||
func (i *Ipam) AddSubnetRange(ctx context.Context, subnet string, subnetRange vinov1.Range) error {
|
||||
logger := i.Log.WithValues("subnet", subnet, "subnetRange", subnetRange)
|
||||
// Does the subnet already exist? (this is fine)
|
||||
ippool, exists := i.ippools[subnet]
|
||||
ippools, err := i.getIPPools(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ippool, exists := ippools[subnet]
|
||||
if !exists {
|
||||
logger.Info("IPAM creating subnet and adding range")
|
||||
ippool = &vinov1.IPPoolSpec{
|
||||
Subnet: subnet,
|
||||
Ranges: []vinov1.Range{subnetRange},
|
||||
Ranges: []vinov1.Range{},
|
||||
AllocatedIPs: []string{},
|
||||
}
|
||||
i.ippools[subnet] = ippool
|
||||
ippools[subnet] = ippool
|
||||
} else {
|
||||
logger.Info("IPAM subnet already exists; adding range")
|
||||
// Does the subnet's requested range already exist? (this should fail)
|
||||
exists = false
|
||||
for _, r := range ippool.Ranges {
|
||||
if r == subnetRange {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if exists {
|
||||
return ErrSubnetRangeOverlapsWithExistingRange{Subnet: subnet, SubnetRange: subnetRange}
|
||||
}
|
||||
}
|
||||
}
|
||||
ippool.Ranges = append(ippool.Ranges, subnetRange)
|
||||
err = i.applyIPPool(ctx, *ippool)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocateIP allocates an IP from a range and return it
|
||||
func (i *Ipam) AllocateIP(subnet string, subnetRange vinov1.Range) (string, error) {
|
||||
// NOTE/TODO: this is not threadsafe, which is fine because
|
||||
// the final impl will use the api server as the backend, not local.
|
||||
ippool, exists := i.ippools[subnet]
|
||||
func (i *Ipam) AllocateIP(ctx context.Context, subnet string, subnetRange vinov1.Range) (string, error) {
|
||||
ippools, err := i.getIPPools(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ippool, exists := ippools[subnet]
|
||||
if !exists {
|
||||
return "", ErrSubnetNotAllocated{Subnet: subnet}
|
||||
}
|
||||
@ -117,7 +126,13 @@ func (i *Ipam) AllocateIP(subnet string, subnetRange vinov1.Range) (string, erro
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
i.Log.Info("Allocating IP", "ip", ip, "subnet", subnet, "subnetRange", subnetRange)
|
||||
ippool.AllocatedIPs = append(ippool.AllocatedIPs, ip)
|
||||
err = i.applyIPPool(ctx, *ippool)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
@ -126,7 +141,10 @@ func (i *Ipam) AllocateIP(subnet string, subnetRange vinov1.Range) (string, erro
|
||||
// in use, converts it back to a string and returns it.
|
||||
// It does not itself add it to the list of assigned IPs.
|
||||
func findFreeIPInRange(ippool *vinov1.IPPoolSpec, subnetRange vinov1.Range) (string, error) {
|
||||
allocatedIPSet := sliceToMap(ippool.AllocatedIPs)
|
||||
allocatedIPSet, err := sliceToMap(ippool.AllocatedIPs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
intToString := intToIPv4String
|
||||
if strings.Contains(ippool.Subnet, ":") {
|
||||
intToString = intToIPv6String
|
||||
@ -143,7 +161,7 @@ func findFreeIPInRange(ippool *vinov1.IPPoolSpec, subnetRange vinov1.Range) (str
|
||||
}
|
||||
|
||||
for ip := start; ip <= stop; ip++ {
|
||||
_, in := allocatedIPSet[intToString(ip)]
|
||||
_, in := allocatedIPSet[ip]
|
||||
if !in {
|
||||
// Found an unallocated IP
|
||||
return intToString(ip), nil
|
||||
@ -152,14 +170,18 @@ func findFreeIPInRange(ippool *vinov1.IPPoolSpec, subnetRange vinov1.Range) (str
|
||||
return "", ErrSubnetRangeExhausted{ippool.Subnet, subnetRange}
|
||||
}
|
||||
|
||||
// Create a map[string]struct{} representation of a string slice,
|
||||
// Create a map[uint64]struct{} representation of a string slice,
|
||||
// for efficient set lookups
|
||||
func sliceToMap(slice []string) map[string]struct{} {
|
||||
m := map[string]struct{}{}
|
||||
func sliceToMap(slice []string) (map[uint64]struct{}, error) {
|
||||
m := map[uint64]struct{}{}
|
||||
for _, s := range slice {
|
||||
m[s] = struct{}{}
|
||||
i, err := ipStringToInt(s)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
return m
|
||||
m[i] = struct{}{}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Convert an IPV4 or IPV6 address string to an easily iterable uint64.
|
||||
@ -220,3 +242,55 @@ func byteArrayToInt(arr []byte) uint64 {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Transforms a subnet into k8s-friendly resource name
|
||||
func subnetResourceName(subnet string) string {
|
||||
regex := regexp.MustCompile(`[:./]`)
|
||||
return "ippool-" + regex.ReplaceAllString(subnet, "-")
|
||||
}
|
||||
|
||||
// Persist a pool to the API server (Create or Update)
|
||||
func (i *Ipam) applyIPPool(ctx context.Context, spec vinov1.IPPoolSpec) error {
|
||||
logger := i.Log.WithValues("subnet", spec.Subnet)
|
||||
|
||||
ippool := &vinov1.IPPool{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: i.Namespace,
|
||||
Name: subnetResourceName(spec.Subnet),
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
existingPool := &vinov1.IPPool{}
|
||||
err := i.Client.Get(ctx, client.ObjectKeyFromObject(ippool), existingPool)
|
||||
if err != nil {
|
||||
// Is it an unexpected error?
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
// The error is a warning that the resource doesn't exist yet, so we should create it
|
||||
logger.Info("IPAM creating IPPool")
|
||||
err = i.Client.Create(ctx, ippool)
|
||||
} else {
|
||||
logger.Info("IPAM IPPool already exists; updating it")
|
||||
ippool.ObjectMeta.ResourceVersion = existingPool.ObjectMeta.ResourceVersion
|
||||
err = i.Client.Update(ctx, ippool)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Return a mapping of all allocated subnets to their IPPoolSpecs.
|
||||
func (i *Ipam) getIPPools(ctx context.Context) (map[string]*vinov1.IPPoolSpec, error) {
|
||||
list := &vinov1.IPPoolList{}
|
||||
err := i.Client.List(ctx, list, client.InNamespace(i.Namespace))
|
||||
ippools := make(map[string]*vinov1.IPPoolSpec)
|
||||
if err != nil {
|
||||
return map[string]*vinov1.IPPoolSpec{}, err
|
||||
}
|
||||
for _, ippool := range list.Items {
|
||||
ippools[ippool.Spec.Subnet] = ippool.Spec.DeepCopy()
|
||||
}
|
||||
return ippools, nil
|
||||
}
|
||||
|
@ -15,15 +15,72 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
vinov1 "vino/pkg/api/v1"
|
||||
test "vino/pkg/test"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
// Sets up a mock client that will serve up
|
||||
func SetUpMockClient(ctx context.Context, ctrl *gomock.Controller) *test.MockClient {
|
||||
m := test.NewMockClient(ctrl)
|
||||
// Pre-populate IPAM with some precondition test data
|
||||
preExistingIpam := vinov1.IPPoolList{
|
||||
Items: []vinov1.IPPool{
|
||||
{
|
||||
Spec: vinov1.IPPoolSpec{
|
||||
Subnet: "10.0.0.0/16",
|
||||
Ranges: []vinov1.Range{
|
||||
{Start: "10.0.1.0", Stop: "10.0.1.9"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: vinov1.IPPoolSpec{
|
||||
Subnet: "2600:1700:b030:0000::/72",
|
||||
Ranges: []vinov1.Range{
|
||||
{Start: "2600:1700:b030:0000::", Stop: "2600:1700:b030:0009::"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: vinov1.IPPoolSpec{
|
||||
Subnet: "192.168.0.0/1",
|
||||
Ranges: []vinov1.Range{
|
||||
{Start: "192.168.0.0", Stop: "192.168.0.0"},
|
||||
},
|
||||
AllocatedIPs: []string{"192.168.0.0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: vinov1.IPPoolSpec{
|
||||
Subnet: "2600:1700:b031:0000::/64",
|
||||
Ranges: []vinov1.Range{
|
||||
{Start: "2600:1700:b031:0000::", Stop: "2600:1700:b031:0000::"},
|
||||
},
|
||||
AllocatedIPs: []string{"2600:1700:b031:0000::"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m.EXPECT().List(ctx, gomock.Any(), gomock.Any()).SetArg(1, preExistingIpam)
|
||||
m.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).AnyTimes()
|
||||
m.EXPECT().Create(ctx, gomock.Any(), gomock.Any()).AnyTimes()
|
||||
m.EXPECT().Update(ctx, gomock.Any(), gomock.Any()).AnyTimes()
|
||||
return m
|
||||
}
|
||||
|
||||
func TestAllocateIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, subnet, expectedErr string
|
||||
@ -79,32 +136,21 @@ func TestAllocateIP(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ipammer := NewIpam()
|
||||
m := SetUpMockClient(ctx, ctrl)
|
||||
ipammer := NewIpam(log.Log, m, "vino-system")
|
||||
ipammer.Log = log.Log
|
||||
|
||||
// Pre-populate IPAM with some precondition test data
|
||||
err := ipammer.AddSubnetRange("10.0.0.0/16", vinov1.Range{Start: "10.0.1.0", Stop: "10.0.1.9"})
|
||||
require.NoError(t, err)
|
||||
err = ipammer.AddSubnetRange("2600:1700:b030:0000::/72",
|
||||
vinov1.Range{Start: "2600:1700:b030:0000::", Stop: "2600:1700:b030:0009::"})
|
||||
require.NoError(t, err)
|
||||
err = ipammer.AddSubnetRange("192.168.0.0/1",
|
||||
vinov1.Range{Start: "192.168.0.0", Stop: "192.168.0.0"})
|
||||
require.NoError(t, err)
|
||||
err = ipammer.AddSubnetRange("2600:1700:b031:0000::/64",
|
||||
vinov1.Range{Start: "2600:1700:b031:0000::", Stop: "2600:1700:b031:0000::"})
|
||||
require.NoError(t, err)
|
||||
_, err = ipammer.AllocateIP("192.168.0.0/1", vinov1.Range{Start: "192.168.0.0", Stop: "192.168.0.0"})
|
||||
require.NoError(t, err)
|
||||
_, err = ipammer.AllocateIP("2600:1700:b031:0000::/64",
|
||||
vinov1.Range{Start: "2600:1700:b031:0000::", Stop: "2600:1700:b031:0000::"})
|
||||
require.NoError(t, err)
|
||||
ip, err := ipammer.AllocateIP(tt.subnet, tt.subnetRange)
|
||||
ip, err := ipammer.AllocateIP(ctx, tt.subnet, tt.subnetRange)
|
||||
if tt.expectedErr != "" {
|
||||
assert.Equal(t, "", ip)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "", ip)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
@ -180,15 +226,17 @@ func TestAddSubnetRange(t *testing.T) {
|
||||
// TODO: check for partially overlapping ranges and subnets
|
||||
}
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ipammer := NewIpam()
|
||||
m := SetUpMockClient(ctx, ctrl)
|
||||
ipammer := NewIpam(log.Log, m, "vino-system")
|
||||
|
||||
// Pre-populate IPAM with some precondition test data
|
||||
err := ipammer.AddSubnetRange("10.0.0.0/16", vinov1.Range{Start: "10.0.1.0", Stop: "10.0.1.9"})
|
||||
require.NoError(t, err)
|
||||
err = ipammer.AddSubnetRange(tt.subnet, tt.subnetRange)
|
||||
err := ipammer.AddSubnetRange(ctx, tt.subnet, tt.subnetRange)
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
@ -264,30 +312,31 @@ func TestSliceToMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in []string
|
||||
out map[string]struct{}
|
||||
out map[uint64]struct{}
|
||||
}{
|
||||
{
|
||||
name: "empty slice",
|
||||
in: []string{},
|
||||
out: map[string]struct{}{},
|
||||
out: map[uint64]struct{}{},
|
||||
},
|
||||
{
|
||||
name: "one-element slice",
|
||||
in: []string{"foo"},
|
||||
out: map[string]struct{}{"foo": {}},
|
||||
in: []string{"0.0.0.1"},
|
||||
out: map[uint64]struct{}{1: {}},
|
||||
},
|
||||
{
|
||||
name: "two-element slice",
|
||||
in: []string{"foo", "bar"},
|
||||
out: map[string]struct{}{"foo": {}, "bar": {}},
|
||||
in: []string{"0.0.0.1", "0.0.0.2"},
|
||||
out: map[uint64]struct{}{1: {}, 2: {}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := sliceToMap(tt.in)
|
||||
actual, err := sliceToMap(tt.in)
|
||||
assert.Equal(t, tt.out, actual)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -402,3 +451,119 @@ func TestByteArrayToInt(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubnetResourceName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
name: "ipv4",
|
||||
in: "192.168.0.0/24",
|
||||
out: "ippool-192-168-0-0-24",
|
||||
},
|
||||
{
|
||||
name: "ipv6",
|
||||
in: "0001:0000:0000:0001::/32",
|
||||
out: "ippool-0001-0000-0000-0001---32",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := subnetResourceName(tt.in)
|
||||
assert.Equal(t, tt.out, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIPPool(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
m := test.NewMockClient(ctrl)
|
||||
ipammer := NewIpam(log.Log, m, "vino-system")
|
||||
ctx := context.Background()
|
||||
|
||||
spec := vinov1.IPPoolSpec{
|
||||
Subnet: "192.168.0.0/24",
|
||||
Ranges: []vinov1.Range{
|
||||
{
|
||||
Start: "192.168.1.10",
|
||||
Stop: "192.168.1.20",
|
||||
},
|
||||
},
|
||||
}
|
||||
pool := vinov1.IPPool{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "vino-system",
|
||||
Name: "ippool-192-168-0-0-24",
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
emptyPool := &vinov1.IPPool{}
|
||||
|
||||
// Test Create scenario
|
||||
m = test.NewMockClient(ctrl)
|
||||
ipammer.Client = m
|
||||
m.EXPECT().Get(ctx, client.ObjectKeyFromObject(&pool), emptyPool).Return(
|
||||
apierrors.NewNotFound(schema.GroupResource{
|
||||
Group: "airship.airshipit.org", Resource: "ippools"}, "ippool-192-168-0-0-24"))
|
||||
m.EXPECT().Create(ctx, &pool)
|
||||
err := ipammer.applyIPPool(ctx, spec)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test Update scenario
|
||||
existingPool := pool.DeepCopy()
|
||||
existingPool.Generation = 1
|
||||
m = test.NewMockClient(ctrl)
|
||||
ipammer.Client = m
|
||||
m.EXPECT().Get(ctx, client.ObjectKeyFromObject(&pool), emptyPool).SetArg(2, *existingPool)
|
||||
m.EXPECT().Update(ctx, &pool)
|
||||
err = ipammer.applyIPPool(ctx, spec)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test non-already-exists error scenario
|
||||
m = test.NewMockClient(ctrl)
|
||||
ipammer.Client = m
|
||||
m.EXPECT().Get(ctx, client.ObjectKeyFromObject(&pool), emptyPool).Return(
|
||||
apierrors.NewBadRequest("bad things happened"))
|
||||
err = ipammer.applyIPPool(ctx, spec)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetIPPools(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
spec := vinov1.IPPoolSpec{
|
||||
Subnet: "192.168.0.0/24",
|
||||
Ranges: []vinov1.Range{
|
||||
{
|
||||
Start: "192.168.1.10",
|
||||
Stop: "192.168.1.20",
|
||||
},
|
||||
},
|
||||
}
|
||||
pool := vinov1.IPPool{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "vino-system",
|
||||
Name: "ippool-192-168-0-0-24",
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
fullList := vinov1.IPPoolList{Items: []vinov1.IPPool{pool}}
|
||||
expectedResult := map[string]*vinov1.IPPoolSpec{"192.168.0.0/24": &spec}
|
||||
|
||||
m := test.NewMockClient(ctrl)
|
||||
ipammer := NewIpam(log.Log, m, "vino-system")
|
||||
ipammer.Client = m
|
||||
m.EXPECT().List(ctx, gomock.Any(), client.InNamespace("vino-system")).SetArg(1, fullList)
|
||||
actualResult, err := ipammer.getIPPools(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, actualResult)
|
||||
}
|
||||
|
208
pkg/test/mock_client.go
Normal file
208
pkg/test/mock_client.go
Normal file
@ -0,0 +1,208 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: sigs.k8s.io/controller-runtime/pkg/client (interfaces: Client)
|
||||
|
||||
// Package mock_client is a generated GoMock package.
|
||||
package ipam
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
meta "k8s.io/apimachinery/pkg/api/meta"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
reflect "reflect"
|
||||
client "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface
|
||||
type MockClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockClientMockRecorder
|
||||
}
|
||||
|
||||
// MockClientMockRecorder is the mock recorder for MockClient
|
||||
type MockClientMockRecorder struct {
|
||||
mock *MockClient
|
||||
}
|
||||
|
||||
// NewMockClient creates a new mock instance
|
||||
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
||||
mock := &MockClient{ctrl: ctrl}
|
||||
mock.recorder = &MockClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Create mocks base method
|
||||
func (m *MockClient) Create(arg0 context.Context, arg1 client.Object, arg2 ...client.CreateOption) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Create", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Create indicates an expected call of Create
|
||||
func (mr *MockClientMockRecorder) Create(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...)
|
||||
}
|
||||
|
||||
// Delete mocks base method
|
||||
func (m *MockClient) Delete(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteOption) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Delete", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete
|
||||
func (mr *MockClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...)
|
||||
}
|
||||
|
||||
// DeleteAllOf mocks base method
|
||||
func (m *MockClient) DeleteAllOf(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteAllOfOption) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DeleteAllOf", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAllOf indicates an expected call of DeleteAllOf
|
||||
func (mr *MockClientMockRecorder) DeleteAllOf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...)
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockClient) Get(arg0 context.Context, arg1 types.NamespacedName, arg2 client.Object) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockClientMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockClient) List(arg0 context.Context, arg1 client.ObjectList, arg2 ...client.ListOption) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "List", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// List indicates an expected call of List
|
||||
func (mr *MockClientMockRecorder) List(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...)
|
||||
}
|
||||
|
||||
// Patch mocks base method
|
||||
func (m *MockClient) Patch(arg0 context.Context, arg1 client.Object, arg2 client.Patch, arg3 ...client.PatchOption) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2}
|
||||
for _, a := range arg3 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Patch", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Patch indicates an expected call of Patch
|
||||
func (mr *MockClientMockRecorder) Patch(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...)
|
||||
}
|
||||
|
||||
// RESTMapper mocks base method
|
||||
func (m *MockClient) RESTMapper() meta.RESTMapper {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RESTMapper")
|
||||
ret0, _ := ret[0].(meta.RESTMapper)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RESTMapper indicates an expected call of RESTMapper
|
||||
func (mr *MockClientMockRecorder) RESTMapper() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockClient)(nil).RESTMapper))
|
||||
}
|
||||
|
||||
// Scheme mocks base method
|
||||
func (m *MockClient) Scheme() *runtime.Scheme {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Scheme")
|
||||
ret0, _ := ret[0].(*runtime.Scheme)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Scheme indicates an expected call of Scheme
|
||||
func (mr *MockClientMockRecorder) Scheme() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockClient)(nil).Scheme))
|
||||
}
|
||||
|
||||
// Status mocks base method
|
||||
func (m *MockClient) Status() client.StatusWriter {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Status")
|
||||
ret0, _ := ret[0].(client.StatusWriter)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Status indicates an expected call of Status
|
||||
func (mr *MockClientMockRecorder) Status() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockClient) Update(arg0 context.Context, arg1 client.Object, arg2 ...client.UpdateOption) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Update", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockClientMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user