Skip to content

Commit ccc66e7

Browse files
authored
Add unit test in Subnet/SubnetSet controllers related with SubnetConnectionBindingMaps (vmware-tanzu#964) (vmware-tanzu#989)
1 parent 4cc0aee commit ccc66e7

File tree

5 files changed

+891
-2
lines changed

5 files changed

+891
-2
lines changed

pkg/controllers/subnet/subnet_controller_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/agiledragon/gomonkey/v2"
1212
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
1314
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1516
"k8s.io/apimachinery/pkg/runtime"
@@ -803,3 +804,193 @@ func TestStartSubnetController(t *testing.T) {
803804
})
804805
}
805806
}
807+
808+
func TestReconcileWithSubnetConnectionBindingMaps(t *testing.T) {
809+
subnetName := "subnet1"
810+
ns := "ns1"
811+
testSubnet1 := &v1alpha1.Subnet{
812+
ObjectMeta: metav1.ObjectMeta{Name: subnetName, Namespace: ns},
813+
Spec: v1alpha1.SubnetSpec{
814+
AccessMode: v1alpha1.AccessMode(v1alpha1.AccessModePrivate),
815+
IPv4SubnetSize: 16,
816+
},
817+
}
818+
testSubnet2 := &v1alpha1.Subnet{
819+
ObjectMeta: metav1.ObjectMeta{Name: subnetName, Namespace: ns, Finalizers: []string{common.SubnetFinalizerName}},
820+
Spec: v1alpha1.SubnetSpec{
821+
AccessMode: v1alpha1.AccessMode(v1alpha1.AccessModePrivate),
822+
IPv4SubnetSize: 16,
823+
},
824+
}
825+
deletionTime := metav1.Now()
826+
testSubnet3 := &v1alpha1.Subnet{
827+
ObjectMeta: metav1.ObjectMeta{
828+
Name: subnetName,
829+
Namespace: ns,
830+
Finalizers: []string{common.SubnetFinalizerName},
831+
DeletionTimestamp: &deletionTime,
832+
},
833+
Spec: v1alpha1.SubnetSpec{
834+
AccessMode: v1alpha1.AccessMode(v1alpha1.AccessModePrivate),
835+
IPv4SubnetSize: 16,
836+
},
837+
}
838+
for _, tc := range []struct {
839+
name string
840+
existingSubnet *v1alpha1.Subnet
841+
patches func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches
842+
expectErrStr string
843+
expectRes ctrl.Result
844+
}{
845+
{
846+
name: "Successfully add finalizer after a Subnet is used by SubnetConnectionBindingMap",
847+
existingSubnet: testSubnet1,
848+
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
849+
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
850+
return []v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
851+
})
852+
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
853+
return nil
854+
})
855+
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
856+
},
857+
expectRes: ctrl.Result{},
858+
}, {
859+
name: "Failed to add finalizer after a Subnet is used by SubnetConnectionBindingMap",
860+
existingSubnet: testSubnet1,
861+
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
862+
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
863+
return []v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
864+
})
865+
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
866+
return fmt.Errorf("failed to update CR")
867+
})
868+
patches.ApplyFunc(updateSubnetStatusConditions, func(_ client.Client, _ context.Context, _ *v1alpha1.Subnet, newConditions []v1alpha1.Condition) {
869+
require.Equal(t, 1, len(newConditions))
870+
cond := newConditions[0]
871+
assert.Equal(t, "Failed to add the finalizer on a Subnet for the reference by SubnetConnectionBindingMap binding1", cond.Message)
872+
})
873+
return patches
874+
},
875+
expectErrStr: "failed to update CR",
876+
expectRes: common2.ResultRequeue,
877+
}, {
878+
name: "Not add duplicated finalizer after a Subnet is used by SubnetConnectionBindingMap",
879+
existingSubnet: testSubnet2,
880+
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
881+
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
882+
return []v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
883+
})
884+
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
885+
assert.FailNow(t, "Should not update Subnet CR finalizer")
886+
return nil
887+
})
888+
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
889+
},
890+
expectRes: ctrl.Result{},
891+
}, {
892+
name: "Successfully remove finalizer after a Subnet is not used by any SubnetConnectionBindingMaps",
893+
existingSubnet: testSubnet2,
894+
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
895+
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
896+
return []v1alpha1.SubnetConnectionBindingMap{}
897+
})
898+
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
899+
return nil
900+
})
901+
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
902+
},
903+
expectRes: ctrl.Result{},
904+
}, {
905+
name: "Failed to remove finalizer after a Subnet is not used by any SubnetConnectionBindingMaps",
906+
existingSubnet: testSubnet2,
907+
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
908+
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
909+
return []v1alpha1.SubnetConnectionBindingMap{}
910+
})
911+
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
912+
return fmt.Errorf("failed to update CR")
913+
})
914+
patches.ApplyFunc(updateSubnetStatusConditions, func(_ client.Client, _ context.Context, _ *v1alpha1.Subnet, newConditions []v1alpha1.Condition) {
915+
require.Equal(t, 1, len(newConditions))
916+
cond := newConditions[0]
917+
assert.Equal(t, "Failed to remove the finalizer on a Subnet when there is no reference by SubnetConnectionBindingMaps", cond.Message)
918+
})
919+
return patches
920+
},
921+
expectErrStr: "failed to update CR",
922+
expectRes: common2.ResultRequeue,
923+
}, {
924+
name: "Not update finalizers if a Subnet is not used by any SubnetConnectionBindingMaps",
925+
existingSubnet: testSubnet1,
926+
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
927+
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
928+
return []v1alpha1.SubnetConnectionBindingMap{}
929+
})
930+
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
931+
assert.FailNow(t, "Should not update Subnet CR finalizer")
932+
return nil
933+
})
934+
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
935+
},
936+
expectRes: ctrl.Result{},
937+
}, {
938+
name: "Delete a Subnet is not allowed if it is used by SubnetConnectionBindingMap",
939+
existingSubnet: testSubnet3,
940+
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
941+
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
942+
return []v1alpha1.SubnetConnectionBindingMap{}
943+
})
944+
patches.ApplyPrivateMethod(reflect.TypeOf(r), "getNSXSubnetBindingsBySubnet", func(_ *SubnetReconciler, _ string) []*v1alpha1.SubnetConnectionBindingMap {
945+
return []*v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
946+
})
947+
patches.ApplyPrivateMethod(reflect.TypeOf(r), "setSubnetDeletionFailedStatus", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet, _ metav1.Time, msg string, reason string) {
948+
assert.Equal(t, "Subnet is used by SubnetConnectionBindingMap binding1 and not able to delete", msg)
949+
assert.Equal(t, "SubnetInUse", reason)
950+
})
951+
return patches
952+
},
953+
expectErrStr: "failed to delete Subnet CR ns1/subnet1",
954+
expectRes: ResultRequeue,
955+
},
956+
} {
957+
t.Run(tc.name, func(t *testing.T) {
958+
ctx := context.TODO()
959+
req := ctrl.Request{NamespacedName: types.NamespacedName{Name: subnetName, Namespace: ns}}
960+
r := createFakeSubnetReconciler([]client.Object{tc.existingSubnet})
961+
if tc.patches != nil {
962+
patches := tc.patches(t, r)
963+
defer patches.Reset()
964+
}
965+
966+
res, err := r.Reconcile(ctx, req)
967+
968+
if tc.expectErrStr != "" {
969+
assert.EqualError(t, err, tc.expectErrStr)
970+
} else {
971+
assert.NoError(t, err)
972+
}
973+
assert.Equal(t, tc.expectRes, res)
974+
})
975+
}
976+
}
977+
978+
func patchSuccessfulReconcileSubnetWorkflow(r *SubnetReconciler, patches *gomonkey.Patches) *gomonkey.Patches {
979+
patches.ApplyMethod(reflect.TypeOf(r.SubnetService), "GenerateSubnetNSTags", func(_ *subnet.SubnetService, _ client.Object) []model.Tag {
980+
return []model.Tag{{Scope: common.String("test"), Tag: common.String("subnet")}}
981+
})
982+
patches.ApplyMethod(reflect.TypeOf(r.VPCService), "ListVPCInfo", func(_ *vpc.VPCService, _ string) []common.VPCResourceInfo {
983+
return []common.VPCResourceInfo{{ID: "vpc1"}}
984+
})
985+
patches.ApplyMethod(reflect.TypeOf(r.SubnetService), "CreateOrUpdateSubnet", func(_ *subnet.SubnetService, _ client.Object, _ common.VPCResourceInfo, _ []model.Tag) (subnet *model.VpcSubnet, err error) {
986+
return &model.VpcSubnet{
987+
Path: common.String("subnet-path"),
988+
}, nil
989+
})
990+
patches.ApplyPrivateMethod(reflect.TypeOf(r), "updateSubnetStatus", func(_ *SubnetReconciler, _ *v1alpha1.Subnet) error {
991+
return nil
992+
})
993+
patches.ApplyFunc(setSubnetReadyStatusTrue, func(_ client.Client, _ context.Context, _ client.Object, _ metav1.Time, _ ...interface{}) {
994+
})
995+
return patches
996+
}

pkg/controllers/subnet/subnetbinding_handler_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package subnet
22

33
import (
44
"context"
5+
"reflect"
56
"testing"
67

8+
"github.com/agiledragon/gomonkey/v2"
79
"github.com/stretchr/testify/assert"
810
"github.com/stretchr/testify/require"
11+
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
912
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1013
"k8s.io/apimachinery/pkg/runtime"
1114
"k8s.io/apimachinery/pkg/types"
@@ -16,6 +19,9 @@ import (
1619
"sigs.k8s.io/controller-runtime/pkg/reconcile"
1720

1821
"github.com/vmware-tanzu/nsx-operator/pkg/apis/vpc/v1alpha1"
22+
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common"
23+
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnet"
24+
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetbinding"
1925
)
2026

2127
var (
@@ -156,3 +162,134 @@ func queueItemEquals(t *testing.T, myQueue workqueue.TypedRateLimitingInterface[
156162
assert.Equal(t, req, item)
157163
myQueue.Done(item)
158164
}
165+
166+
func TestGetSubnetBindingCRsBySubnet(t *testing.T) {
167+
binding1 := &v1alpha1.SubnetConnectionBindingMap{
168+
ObjectMeta: metav1.ObjectMeta{
169+
Name: "binding1",
170+
Namespace: "default",
171+
},
172+
Spec: v1alpha1.SubnetConnectionBindingMapSpec{
173+
SubnetName: "subnet1",
174+
TargetSubnetName: "subnet2",
175+
VLANTrafficTag: 201,
176+
},
177+
}
178+
binding2 := &v1alpha1.SubnetConnectionBindingMap{
179+
ObjectMeta: metav1.ObjectMeta{
180+
Name: "binding2",
181+
Namespace: "default",
182+
},
183+
Spec: v1alpha1.SubnetConnectionBindingMapSpec{
184+
SubnetName: "subnet2",
185+
TargetSubnetName: "subnet3",
186+
VLANTrafficTag: 201,
187+
},
188+
}
189+
newScheme := runtime.NewScheme()
190+
utilruntime.Must(clientgoscheme.AddToScheme(newScheme))
191+
utilruntime.Must(v1alpha1.AddToScheme(newScheme))
192+
fakeClient := fake.NewClientBuilder().WithScheme(newScheme).WithObjects(binding1, binding2).Build()
193+
r := &SubnetReconciler{
194+
Client: fakeClient,
195+
}
196+
for _, tc := range []struct {
197+
name string
198+
subnetCR *v1alpha1.Subnet
199+
expSubnetConnectionBindingMaps []v1alpha1.SubnetConnectionBindingMap
200+
}{
201+
{
202+
name: "Success case to list all SubnetConnectionBindingMaps using the Subnet as either CHILD or PARENT",
203+
subnetCR: &v1alpha1.Subnet{
204+
ObjectMeta: metav1.ObjectMeta{
205+
Name: "subnet2",
206+
Namespace: "default",
207+
},
208+
},
209+
expSubnetConnectionBindingMaps: []v1alpha1.SubnetConnectionBindingMap{*binding1, *binding2},
210+
},
211+
{
212+
name: "No SubnetConnectionBindingMap found",
213+
subnetCR: &v1alpha1.Subnet{
214+
ObjectMeta: metav1.ObjectMeta{
215+
Name: "subnet4",
216+
Namespace: "default",
217+
},
218+
},
219+
expSubnetConnectionBindingMaps: []v1alpha1.SubnetConnectionBindingMap{},
220+
},
221+
} {
222+
t.Run(tc.name, func(t *testing.T) {
223+
ctx := context.Background()
224+
actBindings := r.getSubnetBindingCRsBySubnet(ctx, tc.subnetCR)
225+
assert.ElementsMatch(t, tc.expSubnetConnectionBindingMaps, actBindings)
226+
})
227+
}
228+
}
229+
230+
func TestGetNSXSubnetBindingsBySubnet(t *testing.T) {
231+
for _, tc := range []struct {
232+
name string
233+
subnetCRUID string
234+
patches func(r *SubnetReconciler) *gomonkey.Patches
235+
expSubnetConnectionBindingMaps []*v1alpha1.SubnetConnectionBindingMap
236+
}{
237+
{
238+
name: "No NSX VpcSubnet exists for the Subnet CR",
239+
subnetCRUID: "uuid1",
240+
patches: func(r *SubnetReconciler) *gomonkey.Patches {
241+
patch := gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService), "ListSubnetCreatedBySubnet", func(_ *subnet.SubnetService, _ string) []*model.VpcSubnet {
242+
return []*model.VpcSubnet{}
243+
})
244+
return patch
245+
},
246+
expSubnetConnectionBindingMaps: nil,
247+
}, {
248+
name: "No NSX SubnetConnectionBindingMap created for VpcSubnet",
249+
subnetCRUID: "uuid1",
250+
patches: func(r *SubnetReconciler) *gomonkey.Patches {
251+
patch := gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService), "ListSubnetCreatedBySubnet", func(_ *subnet.SubnetService, _ string) []*model.VpcSubnet {
252+
return []*model.VpcSubnet{
253+
{Id: common.String("id1")},
254+
}
255+
})
256+
patch.ApplyMethod(reflect.TypeOf(r.BindingService), "GetSubnetConnectionBindingMapCRsBySubnet", func(_ *subnetbinding.BindingService, _ *model.VpcSubnet) []*v1alpha1.SubnetConnectionBindingMap {
257+
return []*v1alpha1.SubnetConnectionBindingMap{}
258+
})
259+
return patch
260+
},
261+
expSubnetConnectionBindingMaps: nil,
262+
}, {
263+
name: "Partial of VpcSubnets are associated with the NSX SubnetConnectionBindingMap",
264+
subnetCRUID: "uuid1",
265+
patches: func(r *SubnetReconciler) *gomonkey.Patches {
266+
patch := gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService), "ListSubnetCreatedBySubnet", func(_ *subnet.SubnetService, _ string) []*model.VpcSubnet {
267+
return []*model.VpcSubnet{
268+
{Id: common.String("id1")},
269+
{Id: common.String("id2")},
270+
}
271+
})
272+
patch.ApplyMethod(reflect.TypeOf(r.BindingService), "GetSubnetConnectionBindingMapCRsBySubnet", func(_ *subnetbinding.BindingService, subnet *model.VpcSubnet) []*v1alpha1.SubnetConnectionBindingMap {
273+
if *subnet.Id == "id1" {
274+
return []*v1alpha1.SubnetConnectionBindingMap{}
275+
}
276+
return []*v1alpha1.SubnetConnectionBindingMap{bm1}
277+
})
278+
return patch
279+
},
280+
expSubnetConnectionBindingMaps: []*v1alpha1.SubnetConnectionBindingMap{bm1},
281+
},
282+
} {
283+
t.Run(tc.name, func(t *testing.T) {
284+
r := &SubnetReconciler{
285+
SubnetService: &subnet.SubnetService{},
286+
BindingService: &subnetbinding.BindingService{},
287+
}
288+
patches := tc.patches(r)
289+
defer patches.Reset()
290+
291+
actBindings := r.getNSXSubnetBindingsBySubnet(tc.subnetCRUID)
292+
assert.ElementsMatch(t, tc.expSubnetConnectionBindingMaps, actBindings)
293+
})
294+
}
295+
}

0 commit comments

Comments
 (0)