Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cherry-Pick]Add unit test in Subnet/SubnetSet controllers related with SubnetConnectionBindingMaps (#964) #985

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions pkg/controllers/subnet/subnet_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/agiledragon/gomonkey/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -803,3 +804,193 @@ func TestStartSubnetController(t *testing.T) {
})
}
}

func TestReconcileWithSubnetConnectionBindingMaps(t *testing.T) {
subnetName := "subnet1"
ns := "ns1"
testSubnet1 := &v1alpha1.Subnet{
ObjectMeta: metav1.ObjectMeta{Name: subnetName, Namespace: ns},
Spec: v1alpha1.SubnetSpec{
AccessMode: v1alpha1.AccessMode(v1alpha1.AccessModePrivate),
IPv4SubnetSize: 16,
},
}
testSubnet2 := &v1alpha1.Subnet{
ObjectMeta: metav1.ObjectMeta{Name: subnetName, Namespace: ns, Finalizers: []string{common.SubnetFinalizerName}},
Spec: v1alpha1.SubnetSpec{
AccessMode: v1alpha1.AccessMode(v1alpha1.AccessModePrivate),
IPv4SubnetSize: 16,
},
}
deletionTime := metav1.Now()
testSubnet3 := &v1alpha1.Subnet{
ObjectMeta: metav1.ObjectMeta{
Name: subnetName,
Namespace: ns,
Finalizers: []string{common.SubnetFinalizerName},
DeletionTimestamp: &deletionTime,
},
Spec: v1alpha1.SubnetSpec{
AccessMode: v1alpha1.AccessMode(v1alpha1.AccessModePrivate),
IPv4SubnetSize: 16,
},
}
for _, tc := range []struct {
name string
existingSubnet *v1alpha1.Subnet
patches func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches
expectErrStr string
expectRes ctrl.Result
}{
{
name: "Successfully add finalizer after a Subnet is used by SubnetConnectionBindingMap",
existingSubnet: testSubnet1,
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
return []v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
})
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
return nil
})
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
},
expectRes: ctrl.Result{},
}, {
name: "Failed to add finalizer after a Subnet is used by SubnetConnectionBindingMap",
existingSubnet: testSubnet1,
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
return []v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
})
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
return fmt.Errorf("failed to update CR")
})
patches.ApplyFunc(updateSubnetStatusConditions, func(_ client.Client, _ context.Context, _ *v1alpha1.Subnet, newConditions []v1alpha1.Condition) {
require.Equal(t, 1, len(newConditions))
cond := newConditions[0]
assert.Equal(t, "Failed to add the finalizer on a Subnet for the reference by SubnetConnectionBindingMap binding1", cond.Message)
})
return patches
},
expectErrStr: "failed to update CR",
expectRes: common2.ResultRequeue,
}, {
name: "Not add duplicated finalizer after a Subnet is used by SubnetConnectionBindingMap",
existingSubnet: testSubnet2,
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
return []v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
})
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
assert.FailNow(t, "Should not update Subnet CR finalizer")
return nil
})
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
},
expectRes: ctrl.Result{},
}, {
name: "Successfully remove finalizer after a Subnet is not used by any SubnetConnectionBindingMaps",
existingSubnet: testSubnet2,
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
return []v1alpha1.SubnetConnectionBindingMap{}
})
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
return nil
})
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
},
expectRes: ctrl.Result{},
}, {
name: "Failed to remove finalizer after a Subnet is not used by any SubnetConnectionBindingMaps",
existingSubnet: testSubnet2,
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
return []v1alpha1.SubnetConnectionBindingMap{}
})
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
return fmt.Errorf("failed to update CR")
})
patches.ApplyFunc(updateSubnetStatusConditions, func(_ client.Client, _ context.Context, _ *v1alpha1.Subnet, newConditions []v1alpha1.Condition) {
require.Equal(t, 1, len(newConditions))
cond := newConditions[0]
assert.Equal(t, "Failed to remove the finalizer on a Subnet when there is no reference by SubnetConnectionBindingMaps", cond.Message)
})
return patches
},
expectErrStr: "failed to update CR",
expectRes: common2.ResultRequeue,
}, {
name: "Not update finalizers if a Subnet is not used by any SubnetConnectionBindingMaps",
existingSubnet: testSubnet1,
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
return []v1alpha1.SubnetConnectionBindingMap{}
})
patches.ApplyMethod(reflect.TypeOf(r.Client), "Update", func(_ client.Client, _ context.Context, obj client.Object, opts ...client.UpdateOption) error {
assert.FailNow(t, "Should not update Subnet CR finalizer")
return nil
})
return patchSuccessfulReconcileSubnetWorkflow(r, patches)
},
expectRes: ctrl.Result{},
}, {
name: "Delete a Subnet is not allowed if it is used by SubnetConnectionBindingMap",
existingSubnet: testSubnet3,
patches: func(t *testing.T, r *SubnetReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSubnetBindingCRsBySubnet", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet) []v1alpha1.SubnetConnectionBindingMap {
return []v1alpha1.SubnetConnectionBindingMap{}
})
patches.ApplyPrivateMethod(reflect.TypeOf(r), "getNSXSubnetBindingsBySubnet", func(_ *SubnetReconciler, _ string) []*v1alpha1.SubnetConnectionBindingMap {
return []*v1alpha1.SubnetConnectionBindingMap{{ObjectMeta: metav1.ObjectMeta{Name: "binding1", Namespace: ns}}}
})
patches.ApplyPrivateMethod(reflect.TypeOf(r), "setSubnetDeletionFailedStatus", func(_ *SubnetReconciler, _ context.Context, _ *v1alpha1.Subnet, _ metav1.Time, msg string, reason string) {
assert.Equal(t, "Subnet is used by SubnetConnectionBindingMap binding1 and not able to delete", msg)
assert.Equal(t, "SubnetInUse", reason)
})
return patches
},
expectErrStr: "failed to delete Subnet CR ns1/subnet1",
expectRes: ResultRequeue,
},
} {
t.Run(tc.name, func(t *testing.T) {
ctx := context.TODO()
req := ctrl.Request{NamespacedName: types.NamespacedName{Name: subnetName, Namespace: ns}}
r := createFakeSubnetReconciler([]client.Object{tc.existingSubnet})
if tc.patches != nil {
patches := tc.patches(t, r)
defer patches.Reset()
}

res, err := r.Reconcile(ctx, req)

if tc.expectErrStr != "" {
assert.EqualError(t, err, tc.expectErrStr)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.expectRes, res)
})
}
}

func patchSuccessfulReconcileSubnetWorkflow(r *SubnetReconciler, patches *gomonkey.Patches) *gomonkey.Patches {
patches.ApplyMethod(reflect.TypeOf(r.SubnetService), "GenerateSubnetNSTags", func(_ *subnet.SubnetService, _ client.Object) []model.Tag {
return []model.Tag{{Scope: common.String("test"), Tag: common.String("subnet")}}
})
patches.ApplyMethod(reflect.TypeOf(r.VPCService), "ListVPCInfo", func(_ *vpc.VPCService, _ string) []common.VPCResourceInfo {
return []common.VPCResourceInfo{{ID: "vpc1"}}
})
patches.ApplyMethod(reflect.TypeOf(r.SubnetService), "CreateOrUpdateSubnet", func(_ *subnet.SubnetService, _ client.Object, _ common.VPCResourceInfo, _ []model.Tag) (subnet *model.VpcSubnet, err error) {
return &model.VpcSubnet{
Path: common.String("subnet-path"),
}, nil
})
patches.ApplyPrivateMethod(reflect.TypeOf(r), "updateSubnetStatus", func(_ *SubnetReconciler, _ *v1alpha1.Subnet) error {
return nil
})
patches.ApplyFunc(setSubnetReadyStatusTrue, func(_ client.Client, _ context.Context, _ client.Object, _ metav1.Time, _ ...interface{}) {
})
return patches
}
137 changes: 137 additions & 0 deletions pkg/controllers/subnet/subnetbinding_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package subnet

import (
"context"
"reflect"
"testing"

"github.com/agiledragon/gomonkey/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -16,6 +19,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/vmware-tanzu/nsx-operator/pkg/apis/vpc/v1alpha1"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnet"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetbinding"
)

var (
Expand Down Expand Up @@ -156,3 +162,134 @@ func queueItemEquals(t *testing.T, myQueue workqueue.TypedRateLimitingInterface[
assert.Equal(t, req, item)
myQueue.Done(item)
}

func TestGetSubnetBindingCRsBySubnet(t *testing.T) {
binding1 := &v1alpha1.SubnetConnectionBindingMap{
ObjectMeta: metav1.ObjectMeta{
Name: "binding1",
Namespace: "default",
},
Spec: v1alpha1.SubnetConnectionBindingMapSpec{
SubnetName: "subnet1",
TargetSubnetName: "subnet2",
VLANTrafficTag: 201,
},
}
binding2 := &v1alpha1.SubnetConnectionBindingMap{
ObjectMeta: metav1.ObjectMeta{
Name: "binding2",
Namespace: "default",
},
Spec: v1alpha1.SubnetConnectionBindingMapSpec{
SubnetName: "subnet2",
TargetSubnetName: "subnet3",
VLANTrafficTag: 201,
},
}
newScheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(newScheme))
utilruntime.Must(v1alpha1.AddToScheme(newScheme))
fakeClient := fake.NewClientBuilder().WithScheme(newScheme).WithObjects(binding1, binding2).Build()
r := &SubnetReconciler{
Client: fakeClient,
}
for _, tc := range []struct {
name string
subnetCR *v1alpha1.Subnet
expSubnetConnectionBindingMaps []v1alpha1.SubnetConnectionBindingMap
}{
{
name: "Success case to list all SubnetConnectionBindingMaps using the Subnet as either CHILD or PARENT",
subnetCR: &v1alpha1.Subnet{
ObjectMeta: metav1.ObjectMeta{
Name: "subnet2",
Namespace: "default",
},
},
expSubnetConnectionBindingMaps: []v1alpha1.SubnetConnectionBindingMap{*binding1, *binding2},
},
{
name: "No SubnetConnectionBindingMap found",
subnetCR: &v1alpha1.Subnet{
ObjectMeta: metav1.ObjectMeta{
Name: "subnet4",
Namespace: "default",
},
},
expSubnetConnectionBindingMaps: []v1alpha1.SubnetConnectionBindingMap{},
},
} {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
actBindings := r.getSubnetBindingCRsBySubnet(ctx, tc.subnetCR)
assert.ElementsMatch(t, tc.expSubnetConnectionBindingMaps, actBindings)
})
}
}

func TestGetNSXSubnetBindingsBySubnet(t *testing.T) {
for _, tc := range []struct {
name string
subnetCRUID string
patches func(r *SubnetReconciler) *gomonkey.Patches
expSubnetConnectionBindingMaps []*v1alpha1.SubnetConnectionBindingMap
}{
{
name: "No NSX VpcSubnet exists for the Subnet CR",
subnetCRUID: "uuid1",
patches: func(r *SubnetReconciler) *gomonkey.Patches {
patch := gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService), "ListSubnetCreatedBySubnet", func(_ *subnet.SubnetService, _ string) []*model.VpcSubnet {
return []*model.VpcSubnet{}
})
return patch
},
expSubnetConnectionBindingMaps: nil,
}, {
name: "No NSX SubnetConnectionBindingMap created for VpcSubnet",
subnetCRUID: "uuid1",
patches: func(r *SubnetReconciler) *gomonkey.Patches {
patch := gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService), "ListSubnetCreatedBySubnet", func(_ *subnet.SubnetService, _ string) []*model.VpcSubnet {
return []*model.VpcSubnet{
{Id: common.String("id1")},
}
})
patch.ApplyMethod(reflect.TypeOf(r.BindingService), "GetSubnetConnectionBindingMapCRsBySubnet", func(_ *subnetbinding.BindingService, _ *model.VpcSubnet) []*v1alpha1.SubnetConnectionBindingMap {
return []*v1alpha1.SubnetConnectionBindingMap{}
})
return patch
},
expSubnetConnectionBindingMaps: nil,
}, {
name: "Partial of VpcSubnets are associated with the NSX SubnetConnectionBindingMap",
subnetCRUID: "uuid1",
patches: func(r *SubnetReconciler) *gomonkey.Patches {
patch := gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService), "ListSubnetCreatedBySubnet", func(_ *subnet.SubnetService, _ string) []*model.VpcSubnet {
return []*model.VpcSubnet{
{Id: common.String("id1")},
{Id: common.String("id2")},
}
})
patch.ApplyMethod(reflect.TypeOf(r.BindingService), "GetSubnetConnectionBindingMapCRsBySubnet", func(_ *subnetbinding.BindingService, subnet *model.VpcSubnet) []*v1alpha1.SubnetConnectionBindingMap {
if *subnet.Id == "id1" {
return []*v1alpha1.SubnetConnectionBindingMap{}
}
return []*v1alpha1.SubnetConnectionBindingMap{bm1}
})
return patch
},
expSubnetConnectionBindingMaps: []*v1alpha1.SubnetConnectionBindingMap{bm1},
},
} {
t.Run(tc.name, func(t *testing.T) {
r := &SubnetReconciler{
SubnetService: &subnet.SubnetService{},
BindingService: &subnetbinding.BindingService{},
}
patches := tc.patches(r)
defer patches.Reset()

actBindings := r.getNSXSubnetBindingsBySubnet(tc.subnetCRUID)
assert.ElementsMatch(t, tc.expSubnetConnectionBindingMaps, actBindings)
})
}
}
Loading
Loading