diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 35e06147..2e1603ab 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -14,7 +14,7 @@ for ENI trunking before the deployment. ```sh make apply-dependencies # install the cert manager and certificate make apply # Apply your changes -make e2etest # Run the integration test suite +make test-e2e # Run the integration test suite ``` In another terminal, you can tail the logs with stern diff --git a/go.mod b/go.mod index f6a758b7..9d5e1eb3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 github.com/onsi/ginkgo/v2 v2.11.0 - github.com/onsi/gomega v1.27.8 + github.com/onsi/gomega v1.27.10 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 github.com/prometheus/client_model v0.4.0 @@ -61,11 +61,11 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/tools v0.9.3 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index 3bd1d25f..af821b05 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -217,8 +217,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= @@ -243,20 +243,20 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/hack/toolchain.sh b/hack/toolchain.sh index f5ff2a6f..939fa3d8 100755 --- a/hack/toolchain.sh +++ b/hack/toolchain.sh @@ -12,6 +12,7 @@ main() { tools() { go install sigs.k8s.io/controller-runtime/tools/setup-envtest@v0.0.0-20220421205612-c162794a9b12 go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.2 + go install github.com/google/ko@latest if ! echo "$PATH" | grep -q "${GOPATH:-undefined}/bin\|$HOME/go/bin"; then echo "Go workspace's \"bin\" directory is not in PATH. Run 'export PATH=\"\$PATH:\${GOPATH:-\$HOME/go}/bin\"'." diff --git a/pkg/aws/ec2/api/eni_cleanup_test.go b/pkg/aws/ec2/api/eni_cleanup_test.go index fe1fc6af..199f6368 100644 --- a/pkg/aws/ec2/api/eni_cleanup_test.go +++ b/pkg/aws/ec2/api/eni_cleanup_test.go @@ -19,7 +19,7 @@ import ( "reflect" "testing" - "github.com/aws/amazon-vpc-resource-controller-k8s/mocks/amazon-vcp-resource-controller-k8s/pkg/aws/ec2/api" + mock_api "github.com/aws/amazon-vpc-resource-controller-k8s/mocks/amazon-vcp-resource-controller-k8s/pkg/aws/ec2/api" "github.com/aws/amazon-vpc-resource-controller-k8s/pkg/config" "github.com/aws/aws-sdk-go/aws" diff --git a/pkg/aws/ec2/api/helper.go b/pkg/aws/ec2/api/helper.go index de08e723..3a6cb3ea 100644 --- a/pkg/aws/ec2/api/helper.go +++ b/pkg/aws/ec2/api/helper.go @@ -171,7 +171,7 @@ func (h *ec2APIHelper) CreateNetworkInterface(description *string, subnetId *str if err != nil { errDelete := h.DeleteNetworkInterface(nwInterface.NetworkInterfaceId) if errDelete != nil { - return nwInterface, fmt.Errorf("failed to attach the network interface %v: failed to delete the nw interfac %v", + return nwInterface, fmt.Errorf("failed to attach the network interface permissions %v: failed to delete the nw interfac %v", err, errDelete) } return nil, fmt.Errorf("failed to get attach network interface permissions for trunk %v", err) @@ -396,8 +396,8 @@ func (h *ec2APIHelper) DetachNetworkInterfaceFromInstance(attachmentId *string) return err } -// WaitForNetworkInterfaceStatusChange keeps on retrying with backoff to see if the current status of the network -// interface is equal to the desired state of the network interface +// WaitForNetworkInterfaceStatusChange checks if the current network interface attachment status +// equals the desired status with backoff func (h *ec2APIHelper) WaitForNetworkInterfaceStatusChange(networkInterfaceId *string, desiredStatus string) error { ErrRetryAttachmentStatusCheck := fmt.Errorf("interface not in desired status yet %s, interface id %s", @@ -607,7 +607,7 @@ func (h *ec2APIHelper) DetachAndDeleteNetworkInterface(attachmentID *string, nwI if err != nil { return err } - err = h.WaitForNetworkInterfaceStatusChange(nwInterfaceID, ec2.NetworkInterfaceStatusAvailable) + err = h.WaitForNetworkInterfaceStatusChange(nwInterfaceID, ec2.AttachmentStatusDetached) if err != nil { return err } diff --git a/pkg/aws/ec2/api/helper_test.go b/pkg/aws/ec2/api/helper_test.go index fb66b960..971e8211 100644 --- a/pkg/aws/ec2/api/helper_test.go +++ b/pkg/aws/ec2/api/helper_test.go @@ -153,7 +153,7 @@ var ( NetworkInterfaceId: &branchInterfaceId, InterfaceType: aws.String("interface"), Attachment: &ec2.NetworkInterfaceAttachment{ - Status: aws.String(ec2.NetworkInterfaceStatusAvailable), + Status: aws.String(ec2.AttachmentStatusDetached), }, }, }, @@ -941,7 +941,7 @@ func TestEc2APIHelper_DetachAndDeleteNetworkInterface(t *testing.T) { oldStatus := describeNetworkInterfaceOutputUsingOneInterfaceId.NetworkInterfaces[0].Attachment.Status describeNetworkInterfaceOutputUsingOneInterfaceId.NetworkInterfaces[0].Attachment.Status = - aws.String(ec2.NetworkInterfaceStatusAvailable) + aws.String(ec2.AttachmentStatusDetached) mockWrapper.EXPECT().DetachNetworkInterface(detachNetworkInterfaceInput).Return(nil, nil) mockWrapper.EXPECT().DescribeNetworkInterfaces(describeNetworkInterfaceInputUsingOneInterfaceId). diff --git a/pkg/provider/branch/trunk/trunk.go b/pkg/provider/branch/trunk/trunk.go index 2a607f77..06465d60 100644 --- a/pkg/provider/branch/trunk/trunk.go +++ b/pkg/provider/branch/trunk/trunk.go @@ -174,14 +174,18 @@ func (t *trunkENI) InitTrunk(instance ec2.EC2Instance, podList []v1.Pod) error { // Get trunk network interface for _, nwInterface := range nwInterfaces { - // It's possible to get an empty network interface response if the instnace - // is being deleted. + // It's possible to get an empty network interface response if the instance is being deleted. if nwInterface == nil || nwInterface.InterfaceType == nil { return fmt.Errorf("received an empty network interface response "+ "from EC2 %+v", nwInterface) } if *nwInterface.InterfaceType == "trunk" { - t.trunkENIId = *nwInterface.NetworkInterfaceId + // Check that the trunkENI is in attached state before adding to cache + if err = t.ec2ApiHelper.WaitForNetworkInterfaceStatusChange(nwInterface.NetworkInterfaceId, awsEC2.AttachmentStatusAttached); err == nil { + t.trunkENIId = *nwInterface.NetworkInterfaceId + } else { + return fmt.Errorf("failed to verify network interface status attached for %v", *nwInterface.NetworkInterfaceId) + } } } diff --git a/pkg/provider/branch/trunk/trunk_test.go b/pkg/provider/branch/trunk/trunk_test.go index 01af30c7..4c41b3c6 100644 --- a/pkg/provider/branch/trunk/trunk_test.go +++ b/pkg/provider/branch/trunk/trunk_test.go @@ -133,7 +133,13 @@ var ( // Trunk Interface trunkId = "eni-00000000000000002" - trunkInterface = &awsEc2.NetworkInterface{NetworkInterfaceId: &trunkId} + trunkInterface = &awsEc2.NetworkInterface{ + InterfaceType: aws.String("trunk"), + NetworkInterfaceId: &trunkId, + Attachment: &awsEc2.NetworkInterfaceAttachment{ + Status: aws.String(awsEc2.AttachmentStatusAttached), + }, + } trunkIDTag = &awsEc2.Tag{ Key: aws.String(config.TrunkENIIDTag), @@ -588,129 +594,159 @@ func TestTrunkENI_Reconcile_NoStateChange(t *testing.T) { assert.True(t, isPresent) } -// TestTrunkENI_InitTrunk_TrunkNotExists verifies that trunk is created if it doesn't exists -func TestTrunkENI_InitTrunk_TrunkNotExists(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - trunkENI, mockEC2APIHelper, mockInstance := getMockHelperInstanceAndTrunkObject(ctrl) - freeIndex := int64(2) - - mockInstance.EXPECT().InstanceID().Return(InstanceId) - mockInstance.EXPECT().CurrentInstanceSecurityGroups().Return(SecurityGroups) - mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return([]*awsEc2.InstanceNetworkInterface{}, nil) - mockInstance.EXPECT().GetHighestUnusedDeviceIndex().Return(freeIndex, nil) - mockInstance.EXPECT().SubnetID().Return(SubnetId) - mockEC2APIHelper.EXPECT().CreateAndAttachNetworkInterface(&InstanceId, &SubnetId, SecurityGroups, nil, - &freeIndex, &TrunkEniDescription, &InterfaceTypeTrunk, nil).Return(trunkInterface, nil) - - err := trunkENI.InitTrunk(mockInstance, []v1.Pod{*MockPod2}) - - assert.NoError(t, err) - assert.Equal(t, trunkId, trunkENI.trunkENIId) -} - -// TestTrunkENI_InitTrunk_ErrWhen_EmptyNWInterfaceResponse tests error is returned if an network -// interface without an interface type is returned -func TestTrunkENI_InitTrunk_ErrWhen_EmptyNWInterfaceResponse(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - trunkENI, mockEC2APIHelper, mockInstance := getMockHelperInstanceAndTrunkObject(ctrl) - - mockInstance.EXPECT().InstanceID().Return(InstanceId) - mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return( - []*awsEc2.InstanceNetworkInterface{{InterfaceType: nil}}, nil) - - err := trunkENI.InitTrunk(mockInstance, []v1.Pod{*MockPod2}) - - assert.NotNil(t, err) -} - -// TestTrunkENI_InitTrunk_GetTrunkError tests that error is returned if the get trunk call fails -func TestTrunkENI_InitTrunk_GetTrunkError(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - trunkENI, mockEC2APIHelper, mockInstance := getMockHelperInstanceAndTrunkObject(ctrl) - - mockInstance.EXPECT().InstanceID().Return(InstanceId) - mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return(nil, MockError) - - err := trunkENI.InitTrunk(mockInstance, []v1.Pod{*MockPod2}) - - assert.Error(t, MockError, err) -} - -// TestTrunkENI_InitTrunk_GetFreeIndexFail tests that error is returned if there are no free index -func TestTrunkENI_InitTrunk_GetFreeIndexFail(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - trunkENI, mockEC2APIHelper, mockInstance := getMockHelperInstanceAndTrunkObject(ctrl) - - mockInstance.EXPECT().InstanceID().Return(InstanceId) - mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return([]*awsEc2.InstanceNetworkInterface{}, nil) - mockInstance.EXPECT().GetHighestUnusedDeviceIndex().Return(int64(0), MockError) - - err := trunkENI.InitTrunk(mockInstance, []v1.Pod{*MockPod2}) - - assert.Error(t, MockError, err) -} - -// TestTrunkENI_InitTrunk_TrunkExists_WithBranches tests that no error is returned when trunk exists with branches -func TestTrunkENI_InitTrunk_TrunkExists_WithBranches(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - trunkENI, mockEC2APIHelper, mockInstance := getMockHelperInstanceAndTrunkObject(ctrl) - - mockInstance.EXPECT().InstanceID().Return(InstanceId) - mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return(instanceNwInterfaces, nil) - mockEC2APIHelper.EXPECT().GetBranchNetworkInterface(&trunkId).Return(branchInterfaces, nil) - err := trunkENI.InitTrunk(FakeInstance, []v1.Pod{*MockPod1, *MockPod2}) - branchENIs, isPresent := trunkENI.uidToBranchENIMap[PodUID] - - assert.NoError(t, err) - assert.True(t, isPresent) - - // Assert eni details are correct - assert.Equal(t, Branch1Id, branchENIs[0].ID) - assert.Equal(t, Branch2Id, branchENIs[1].ID) - assert.Equal(t, VlanId1, branchENIs[0].VlanID) - assert.Equal(t, VlanId2, branchENIs[1].VlanID) - - // Assert that Vlan ID's are marked as used and if you retry using then you get error - assert.True(t, trunkENI.usedVlanIds[EniDetails1.VlanID]) - assert.True(t, trunkENI.usedVlanIds[EniDetails2.VlanID]) - - // Assert no entry for pod that didn't have a branch ENI - _, isPresent = trunkENI.uidToBranchENIMap[MockNamespacedName2] - assert.False(t, isPresent) -} - -// TestTrunkENI_InitTrunk_TrunkExists_DanglingENIs tests that enis are pushed to delete queue for which there is no -// pod -func TestTrunkENI_InitTrunk_TrunkExists_DanglingENIs(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - trunkENI, mockEC2APIHelper, mockInstance := getMockHelperInstanceAndTrunkObject(ctrl) - - mockInstance.EXPECT().InstanceID().Return(InstanceId) - mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return(instanceNwInterfaces, nil) - mockEC2APIHelper.EXPECT().GetBranchNetworkInterface(&trunkId).Return(branchInterfaces, nil) - - err := trunkENI.InitTrunk(FakeInstance, []v1.Pod{*MockPod2}) - assert.NoError(t, err) - - _, isPresent := trunkENI.uidToBranchENIMap[PodUID] - assert.False(t, isPresent) - _, isPresent = trunkENI.uidToBranchENIMap[MockNamespacedName2] - assert.False(t, isPresent) - - assert.ElementsMatch(t, []string{EniDetails1.ID, EniDetails2.ID}, - []string{trunkENI.deleteQueue[0].ID, trunkENI.deleteQueue[1].ID}) +func TestTrunkENI_InitTrunk(t *testing.T) { + type args struct { + instance ec2.EC2Instance + podList []v1.Pod + } + type fields struct { + mockInstance *mock_ec2.MockEC2Instance + mockEC2APIHelper *mock_api.MockEC2APIHelper + trunkENI *trunkENI + } + testsTrunkENI_InitTrunk := []struct { + name string + prepare func(f *fields) + args args + wantErr bool + asserts func(f *fields) + }{ + { + name: "TrunkNotExists, verifies trunk is created if it does not exist with no error", + prepare: func(f *fields) { + freeIndex := int64(2) + f.mockInstance.EXPECT().InstanceID().Return(InstanceId) + f.mockInstance.EXPECT().CurrentInstanceSecurityGroups().Return(SecurityGroups) + f.mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return([]*awsEc2.InstanceNetworkInterface{}, nil) + f.mockInstance.EXPECT().GetHighestUnusedDeviceIndex().Return(freeIndex, nil) + f.mockInstance.EXPECT().SubnetID().Return(SubnetId) + f.mockEC2APIHelper.EXPECT().CreateAndAttachNetworkInterface(&InstanceId, &SubnetId, SecurityGroups, nil, + &freeIndex, &TrunkEniDescription, &InterfaceTypeTrunk, nil).Return(trunkInterface, nil) + }, + // Pass nil to set the instance to fields.mockInstance in the function later + args: args{instance: nil, podList: []v1.Pod{*MockPod2}}, + wantErr: false, + asserts: func(f *fields) { + assert.Equal(t, trunkId, f.trunkENI.trunkENIId) + }, + }, + { + name: "ErrWhen_EmptyNWInterfaceResponse, verifies error is returned when interface type is nil", + prepare: func(f *fields) { + f.mockInstance.EXPECT().InstanceID().Return(InstanceId) + f.mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return( + []*awsEc2.InstanceNetworkInterface{{InterfaceType: nil}}, nil) + + }, + args: args{instance: nil, podList: []v1.Pod{*MockPod2}}, + wantErr: true, + asserts: nil, + }, + { + name: "GetTrunkError, verifies error is returned when get trunkENI call fails", + prepare: func(f *fields) { + f.mockInstance.EXPECT().InstanceID().Return(InstanceId) + f.mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return(nil, MockError) + }, + args: args{instance: nil, podList: []v1.Pod{*MockPod2}}, + wantErr: true, + asserts: nil, + }, + { + name: "GetFreeIndexFail, verifies error is returned if no free index exists", + prepare: func(f *fields) { + f.mockInstance.EXPECT().InstanceID().Return(InstanceId) + f.mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return([]*awsEc2.InstanceNetworkInterface{}, nil) + f.mockInstance.EXPECT().GetHighestUnusedDeviceIndex().Return(int64(0), MockError) + }, + args: args{instance: nil, podList: []v1.Pod{*MockPod2}}, + wantErr: true, + asserts: nil, + }, + { + name: "TrunkExists_WithBranches, verifies no error when trunk exists with branches", + prepare: func(f *fields) { + f.mockInstance.EXPECT().InstanceID().Return(InstanceId) + f.mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return(instanceNwInterfaces, nil) + f.mockEC2APIHelper.EXPECT().WaitForNetworkInterfaceStatusChange(&trunkId, awsEc2.AttachmentStatusAttached).Return(nil) + f.mockEC2APIHelper.EXPECT().GetBranchNetworkInterface(&trunkId).Return(branchInterfaces, nil) + }, + args: args{instance: FakeInstance, podList: []v1.Pod{*MockPod1, *MockPod2}}, + wantErr: false, + asserts: func(f *fields) { + branchENIs, isPresent := f.trunkENI.uidToBranchENIMap[PodUID] + assert.True(t, isPresent) + // Assert eni details are correct + assert.Equal(t, Branch1Id, branchENIs[0].ID) + assert.Equal(t, Branch2Id, branchENIs[1].ID) + assert.Equal(t, VlanId1, branchENIs[0].VlanID) + assert.Equal(t, VlanId2, branchENIs[1].VlanID) + + // Assert that Vlan ID's are marked as used and if you retry using then you get error + assert.True(t, f.trunkENI.usedVlanIds[EniDetails1.VlanID]) + assert.True(t, f.trunkENI.usedVlanIds[EniDetails2.VlanID]) + + // Assert no entry for pod that didn't have a branch ENI + _, isPresent = f.trunkENI.uidToBranchENIMap[MockNamespacedName2] + assert.False(t, isPresent) + }, + }, + { + name: "TrunkExists_DanglingENIs, verifies ENIs are pushed to delete queue if no pod exists", + prepare: func(f *fields) { + f.mockInstance.EXPECT().InstanceID().Return(InstanceId) + f.mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return(instanceNwInterfaces, nil) + f.mockEC2APIHelper.EXPECT().WaitForNetworkInterfaceStatusChange(&trunkId, awsEc2.AttachmentStatusAttached).Return(nil) + f.mockEC2APIHelper.EXPECT().GetBranchNetworkInterface(&trunkId).Return(branchInterfaces, nil) + }, + args: args{instance: FakeInstance, podList: []v1.Pod{*MockPod2}}, + wantErr: false, + asserts: func(f *fields) { + _, isPresent := f.trunkENI.uidToBranchENIMap[PodUID] + assert.False(t, isPresent) + _, isPresent = f.trunkENI.uidToBranchENIMap[MockNamespacedName2] + assert.False(t, isPresent) + + assert.ElementsMatch(t, []string{EniDetails1.ID, EniDetails2.ID}, + []string{f.trunkENI.deleteQueue[0].ID, f.trunkENI.deleteQueue[1].ID}) + }, + }, + { + name: "TrunkExists_NotAttached, verifies error is returned if trunkENI is not attached", + prepare: func(f *fields) { + f.mockInstance.EXPECT().InstanceID().Return(InstanceId) + f.mockEC2APIHelper.EXPECT().GetInstanceNetworkInterface(&InstanceId).Return(instanceNwInterfaces, nil) + f.mockEC2APIHelper.EXPECT().WaitForNetworkInterfaceStatusChange(&trunkId, awsEc2.AttachmentStatusAttached).Return(MockError) + }, + args: args{instance: FakeInstance, podList: []v1.Pod{*MockPod1, *MockPod2}}, + wantErr: true, + asserts: nil, + }, + } + for _, tt := range testsTrunkENI_InitTrunk { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + trunkENI, mockEC2APIHelper, mockInstance := getMockHelperInstanceAndTrunkObject(ctrl) + f := fields{ + mockInstance: mockInstance, + mockEC2APIHelper: mockEC2APIHelper, + trunkENI: trunkENI, + } + if tt.prepare != nil { + tt.prepare(&f) + } + if tt.args.instance == nil { + tt.args.instance = f.mockInstance + } + err := f.trunkENI.InitTrunk(tt.args.instance, tt.args.podList) + assert.Equal(t, err != nil, tt.wantErr) + if tt.asserts != nil { + tt.asserts(&f) + } + }) + } } // TestTrunkENI_DeleteAllBranchENIs tests all branch ENI associated with the trunk are deleted