Repository: spidernet-io/spiderpool
Pull request #1472 (2023-03-17 09:10:54)

test: Add unit tests for SubnetManager
Signed-off-by: iiiceoo <ziqian.xue@daocloud.io>


Patch:
diff --git a/pkg/reservedipmanager/mock/reservedip_manager_mock.go b/pkg/reservedipmanager/mock/reservedip_manager_mock.go
new file mode 100644
index 000000000..8adfc58ab
--- /dev/null
+++ b/pkg/reservedipmanager/mock/reservedip_manager_mock.go
@@ -0,0 +1,92 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: ./reservedip_manager.go
+
+// Package mock_reservedipmanager is a generated GoMock package.
+package mock_reservedipmanager
+
+import (
+	context "context"
+	net "net"
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+	v2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
+	types "github.com/spidernet-io/spiderpool/pkg/types"
+	client "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// MockReservedIPManager is a mock of ReservedIPManager interface.
+type MockReservedIPManager struct {
+	ctrl     *gomock.Controller
+	recorder *MockReservedIPManagerMockRecorder
+}
+
+// MockReservedIPManagerMockRecorder is the mock recorder for MockReservedIPManager.
+type MockReservedIPManagerMockRecorder struct {
+	mock *MockReservedIPManager
+}
+
+// NewMockReservedIPManager creates a new mock instance.
+func NewMockReservedIPManager(ctrl *gomock.Controller) *MockReservedIPManager {
+	mock := &MockReservedIPManager{ctrl: ctrl}
+	mock.recorder = &MockReservedIPManagerMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockReservedIPManager) EXPECT() *MockReservedIPManagerMockRecorder {
+	return m.recorder
+}
+
+// AssembleReservedIPs mocks base method.
+func (m *MockReservedIPManager) AssembleReservedIPs(ctx context.Context, version types.IPVersion) ([]net.IP, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AssembleReservedIPs", ctx, version)
+	ret0, _ := ret[0].([]net.IP)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// AssembleReservedIPs indicates an expected call of AssembleReservedIPs.
+func (mr *MockReservedIPManagerMockRecorder) AssembleReservedIPs(ctx, version interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssembleReservedIPs", reflect.TypeOf((*MockReservedIPManager)(nil).AssembleReservedIPs), ctx, version)
+}
+
+// GetReservedIPByName mocks base method.
+func (m *MockReservedIPManager) GetReservedIPByName(ctx context.Context, rIPName string, cached bool) (*v2beta1.SpiderReservedIP, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetReservedIPByName", ctx, rIPName, cached)
+	ret0, _ := ret[0].(*v2beta1.SpiderReservedIP)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetReservedIPByName indicates an expected call of GetReservedIPByName.
+func (mr *MockReservedIPManagerMockRecorder) GetReservedIPByName(ctx, rIPName, cached interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReservedIPByName", reflect.TypeOf((*MockReservedIPManager)(nil).GetReservedIPByName), ctx, rIPName, cached)
+}
+
+// ListReservedIPs mocks base method.
+func (m *MockReservedIPManager) ListReservedIPs(ctx context.Context, cached bool, opts ...client.ListOption) (*v2beta1.SpiderReservedIPList, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{ctx, cached}
+	for _, a := range opts {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "ListReservedIPs", varargs...)
+	ret0, _ := ret[0].(*v2beta1.SpiderReservedIPList)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ListReservedIPs indicates an expected call of ListReservedIPs.
+func (mr *MockReservedIPManagerMockRecorder) ListReservedIPs(ctx, cached interface{}, opts ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{ctx, cached}, opts...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListReservedIPs", reflect.TypeOf((*MockReservedIPManager)(nil).ListReservedIPs), varargs...)
+}
diff --git a/pkg/subnetmanager/subnet_manager.go b/pkg/subnetmanager/subnet_manager.go
index b25f483e1..9678a3214 100644
--- a/pkg/subnetmanager/subnet_manager.go
+++ b/pkg/subnetmanager/subnet_manager.go
@@ -34,26 +34,26 @@ type SubnetManager interface {
 }
 
 type subnetManager struct {
-	client        client.Client
-	apiReader     client.Reader
-	reservedIPMgr reservedipmanager.ReservedIPManager
+	client     client.Client
+	apiReader  client.Reader
+	rIPManager reservedipmanager.ReservedIPManager
 }
 
-func NewSubnetManager(client client.Client, apiReader client.Reader, reservedIPMgr reservedipmanager.ReservedIPManager) (SubnetManager, error) {
+func NewSubnetManager(client client.Client, apiReader client.Reader, rIPManager reservedipmanager.ReservedIPManager) (SubnetManager, error) {
 	if client == nil {
 		return nil, fmt.Errorf("k8s client %w", constant.ErrMissingRequiredParam)
 	}
 	if apiReader == nil {
 		return nil, fmt.Errorf("api reader %w", constant.ErrMissingRequiredParam)
 	}
-	if reservedIPMgr == nil {
+	if rIPManager == nil {
 		return nil, fmt.Errorf("reserved-IP manager %w", constant.ErrMissingRequiredParam)
 	}
 
 	return &subnetManager{
-		client:        client,
-		apiReader:     apiReader,
-		reservedIPMgr: reservedIPMgr,
+		client:     client,
+		apiReader:  apiReader,
+		rIPManager: rIPManager,
 	}, nil
 }
 
@@ -274,7 +274,7 @@ func (sm *subnetManager) preAllocateIPsFromSubnet(ctx context.Context, subnet *s
 	}
 
 	// filter reserved IPs
-	reservedIPs, err := sm.reservedIPMgr.AssembleReservedIPs(ctx, ipVersion)
+	reservedIPs, err := sm.rIPManager.AssembleReservedIPs(ctx, ipVersion)
 	if err != nil {
 		return nil, fmt.Errorf("%w: failed to filter reservedIPs '%v' by IP version '%d', error: %v",
 			constant.ErrWrongInput, reservedIPs, ipVersion, err)


Test patch:
diff --git a/pkg/subnetmanager/subnet_manager_suite_test.go b/pkg/subnetmanager/subnet_manager_suite_test.go
index c5e0be5a0..b2e5647cc 100644
--- a/pkg/subnetmanager/subnet_manager_suite_test.go
+++ b/pkg/subnetmanager/subnet_manager_suite_test.go
@@ -19,16 +19,19 @@ import (
 
 	electionmock "github.com/spidernet-io/spiderpool/pkg/election/mock"
 	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
+	reservedipmanagermock "github.com/spidernet-io/spiderpool/pkg/reservedipmanager/mock"
 	"github.com/spidernet-io/spiderpool/pkg/subnetmanager"
 )
 
 var mockCtrl *gomock.Controller
 var mockLeaderElector *electionmock.MockSpiderLeaseElector
+var mockRIPManager *reservedipmanagermock.MockReservedIPManager
 
 var scheme *runtime.Scheme
 var fakeClient client.Client
 var tracker k8stesting.ObjectTracker
 var fakeAPIReader client.Reader
+var subnetManager subnetmanager.SubnetManager
 var subnetWebhook *subnetmanager.SubnetWebhook
 
 func TestSubnetManager(t *testing.T) {
@@ -70,10 +73,17 @@ var _ = BeforeSuite(func() {
 		}).
 		Build()
 
+	mockLeaderElector = electionmock.NewMockSpiderLeaseElector(mockCtrl)
+	mockRIPManager = reservedipmanagermock.NewMockReservedIPManager(mockCtrl)
+	subnetManager, err = subnetmanager.NewSubnetManager(
+		fakeClient,
+		fakeAPIReader,
+		mockRIPManager,
+	)
+	Expect(err).NotTo(HaveOccurred())
+
 	subnetWebhook = &subnetmanager.SubnetWebhook{
 		Client:    fakeClient,
 		APIReader: fakeAPIReader,
 	}
-
-	mockLeaderElector = electionmock.NewMockSpiderLeaseElector(mockCtrl)
 })
diff --git a/pkg/subnetmanager/subnet_manager_test.go b/pkg/subnetmanager/subnet_manager_test.go
new file mode 100644
index 000000000..615bf05a6
--- /dev/null
+++ b/pkg/subnetmanager/subnet_manager_test.go
@@ -0,0 +1,211 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package subnetmanager_test
+
+import (
+	"context"
+	"fmt"
+	"sync/atomic"
+
+	"github.com/agiledragon/gomonkey/v2"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/utils/pointer"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
+	"github.com/spidernet-io/spiderpool/pkg/subnetmanager"
+)
+
+var _ = Describe("SubnetManager", Label("subnet_manager_test"), func() {
+	Describe("New SubnetManager", func() {
+		It("inputs nil client", func() {
+			manager, err := subnetmanager.NewSubnetManager(nil, fakeAPIReader, mockRIPManager)
+			Expect(err).To(MatchError(constant.ErrMissingRequiredParam))
+			Expect(manager).To(BeNil())
+		})
+
+		It("inputs nil API reader", func() {
+			manager, err := subnetmanager.NewSubnetManager(fakeClient, nil, mockRIPManager)
+			Expect(err).To(MatchError(constant.ErrMissingRequiredParam))
+			Expect(manager).To(BeNil())
+		})
+
+		It("inputs nil reserved-IP manager", func() {
+			manager, err := subnetmanager.NewSubnetManager(fakeClient, fakeAPIReader, nil)
+			Expect(err).To(MatchError(constant.ErrMissingRequiredParam))
+			Expect(manager).To(BeNil())
+		})
+	})
+
+	Describe("Test SubnetManager's method", func() {
+		var ctx context.Context
+
+		var count uint64
+		var subnetName string
+		var labels map[string]string
+		var subnetT *spiderpoolv2beta1.SpiderSubnet
+
+		BeforeEach(func() {
+			ctx = context.TODO()
+
+			atomic.AddUint64(&count, 1)
+			subnetName = fmt.Sprintf("node-%v", count)
+			labels = map[string]string{"foo": fmt.Sprintf("bar-%v", count)}
+			subnetT = &spiderpoolv2beta1.SpiderSubnet{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       constant.KindSpiderSubnet,
+					APIVersion: fmt.Sprintf("%s/%s", constant.SpiderpoolAPIGroup, constant.SpiderpoolAPIVersion),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:   subnetName,
+					Labels: labels,
+				},
+				Spec: spiderpoolv2beta1.SubnetSpec{},
+			}
+		})
+
+		var deleteOption *client.DeleteOptions
+
+		AfterEach(func() {
+			policy := metav1.DeletePropagationForeground
+			deleteOption = &client.DeleteOptions{
+				GracePeriodSeconds: pointer.Int64(0),
+				PropagationPolicy:  &policy,
+			}
+
+			err := fakeClient.Delete(ctx, subnetT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+
+			err = tracker.Delete(
+				schema.GroupVersionResource{
+					Group:    constant.SpiderpoolAPIGroup,
+					Version:  constant.SpiderpoolAPIVersion,
+					Resource: "spidersubnets",
+				},
+				subnetT.Namespace,
+				subnetT.Name,
+			)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+		})
+
+		Describe("GetSubnetByName", func() {
+			It("gets non-existent Subnet", func() {
+				subnet, err := subnetManager.GetSubnetByName(ctx, subnetName, constant.IgnoreCache)
+				Expect(apierrors.IsNotFound(err)).To(BeTrue())
+				Expect(subnet).To(BeNil())
+			})
+
+			It("gets an existing Subnet through cache", func() {
+				err := fakeClient.Create(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				subnet, err := subnetManager.GetSubnetByName(ctx, subnetName, constant.UseCache)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnet).NotTo(BeNil())
+				Expect(subnet).To(Equal(subnetT))
+			})
+
+			It("gets an existing Subnet through API Server", func() {
+				err := tracker.Add(subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				subnet, err := subnetManager.GetSubnetByName(ctx, subnetName, constant.IgnoreCache)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnet).NotTo(BeNil())
+				Expect(subnet).To(Equal(subnetT))
+			})
+		})
+
+		Describe("ListSubnets", func() {
+			It("failed to list Subnets due to some unknown errors", func() {
+				patches := gomonkey.ApplyMethodReturn(fakeAPIReader, "List", constant.ErrUnknown)
+				defer patches.Reset()
+
+				err := tracker.Add(subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				subnetList, err := subnetManager.ListSubnets(ctx, constant.IgnoreCache)
+				Expect(err).To(MatchError(constant.ErrUnknown))
+				Expect(subnetList).To(BeNil())
+			})
+
+			It("lists all Subnets through cache", func() {
+				err := fakeClient.Create(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				subnetList, err := subnetManager.ListSubnets(ctx, constant.UseCache)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetList.Items).NotTo(BeEmpty())
+
+				hasSubnet := false
+				for _, subnet := range subnetList.Items {
+					if subnet.Name == subnetName {
+						hasSubnet = true
+						break
+					}
+				}
+				Expect(hasSubnet).To(BeTrue())
+			})
+
+			It("lists all Subnets through API Server", func() {
+				err := tracker.Add(subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				subnetList, err := subnetManager.ListSubnets(ctx, constant.IgnoreCache)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetList.Items).NotTo(BeEmpty())
+
+				hasSubnet := false
+				for _, subnet := range subnetList.Items {
+					if subnet.Name == subnetName {
+						hasSubnet = true
+						break
+					}
+				}
+				Expect(hasSubnet).To(BeTrue())
+			})
+
+			It("filters results by label selector", func() {
+				err := tracker.Add(subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				subnetList, err := subnetManager.ListSubnets(ctx, constant.IgnoreCache, client.MatchingLabels(labels))
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetList.Items).NotTo(BeEmpty())
+
+				hasSubnet := false
+				for _, subnet := range subnetList.Items {
+					if subnet.Name == subnetName {
+						hasSubnet = true
+						break
+					}
+				}
+				Expect(hasSubnet).To(BeTrue())
+			})
+
+			It("filters results by field selector", func() {
+				err := tracker.Add(subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				subnetList, err := subnetManager.ListSubnets(ctx, constant.IgnoreCache, client.MatchingFields{metav1.ObjectNameField: subnetName})
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetList.Items).NotTo(BeEmpty())
+
+				hasSubnet := false
+				for _, subnet := range subnetList.Items {
+					if subnet.Name == subnetName {
+						hasSubnet = true
+						break
+					}
+				}
+				Expect(hasSubnet).To(BeTrue())
+			})
+		})
+	})
+})