diff --git a/go.mod b/go.mod index 9b5f833..429617d 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index a59721e..ee60dd6 100644 --- a/go.sum +++ b/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= diff --git a/pkg/ipam/ipam.go b/pkg/ipam/ipam.go index adcf594..6ab79c1 100644 --- a/pkg/ipam/ipam.go +++ b/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 + Log logr.Logger + Client client.Client + 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 + return ErrSubnetRangeOverlapsWithExistingRange{Subnet: subnet, SubnetRange: subnetRange} } } - 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 + } + m[i] = struct{}{} } - return m + 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 +} diff --git a/pkg/ipam/ipam_test.go b/pkg/ipam/ipam_test.go index 3496aeb..29617a8 100644 --- a/pkg/ipam/ipam_test.go +++ b/pkg/ipam/ipam_test.go @@ -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) +} diff --git a/pkg/test/mock_client.go b/pkg/test/mock_client.go new file mode 100644 index 0000000..e0b417d --- /dev/null +++ b/pkg/test/mock_client.go @@ -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...) +}