// +build !integration,!e2e

// Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package engine

import (
	"errors"
	"fmt"
	"net"
	"syscall"
	"testing"
	"time"

	"github.com/containernetworking/cni/pkg/skel"

	mock_cninswrapper "github.com/aws/amazon-ecs-cni-plugins/pkg/cninswrapper/mocks"
	mock_ns "github.com/aws/amazon-ecs-cni-plugins/pkg/cninswrapper/mocks_netns"
	mock_ec2metadata "github.com/aws/amazon-ecs-cni-plugins/pkg/ec2metadata/mocks"
	mock_netlinkwrapper "github.com/aws/amazon-ecs-cni-plugins/pkg/netlinkwrapper/mocks"
	mock_netlink "github.com/aws/amazon-ecs-cni-plugins/pkg/netlinkwrapper/mocks_link"
	"github.com/aws/amazon-ecs-cni-plugins/pkg/utils"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/vishvananda/netlink"
)

const (
	deviceName                = "eth1"
	firstENIID                = "eni1"
	secondENIID               = "eni2"
	firstMACAddress           = "mac1/"
	firstMACAddressSanitized  = "mac1"
	secondMACAddress          = "mac2/"
	secondMACAddressSanitized = "mac2"
	eniIPV4Address            = "10.11.12.13"
	eniIPV6Address            = "2001:db8::68"
	eniIPV4Gateway            = "10.10.10.10"
	eniIPV6Gateway            = "2001:db9::68"
	eniSubnetMask             = "20"
	eniIPV4CIDRBlock          = "10.10.10.10/20"
	eniIPV6CIDRBlock          = "2001:db8::68/32"
	eniMACAddress             = "01:23:45:67:89:ab"
	unknownMACAddress         = "01:23:45:67:89:cd"
	loMACAddress              = "00:00:00:00:00:00"
	invalidMACAddress         = "01:23:45:67:89"
)

func setup(t *testing.T) (*gomock.Controller,
	*mock_ec2metadata.MockEC2Metadata,
	*mock_cninswrapper.MockNS,
	*mock_netlinkwrapper.MockNetLink) {
	ctrl := gomock.NewController(t)
	return ctrl,
		mock_ec2metadata.NewMockEC2Metadata(ctrl),
		mock_cninswrapper.NewMockNS(ctrl),
		mock_netlinkwrapper.NewMockNetLink(ctrl)
}

func TestCreate(t *testing.T) {
	ctrl, mockMetadata, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	engine := create(mockMetadata, mockNetLink, mockNS)
	assert.NotNil(t, engine)
}

func TestGetAllMACAddressesReturnsErrorOnGetMetadataError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath).Return("", errors.New("error"))
	engine := &engine{metadata: mockMetadata}

	_, err := engine.GetAllMACAddresses()
	assert.Error(t, err)
}

func TestGetAllMACAddresses(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath).Return("a\nb", nil)
	engine := &engine{metadata: mockMetadata}

	macs, err := engine.GetAllMACAddresses()
	assert.NoError(t, err)
	assert.NotEmpty(t, macs)
	assert.Len(t, macs, 2)
}

func TestGetMACAddressOfENIReturnsErrorOnGetMetadataError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddress+metadataNetworkInterfaceIDPathSuffix).Return("", errors.New("error"))
	engine := &engine{metadata: mockMetadata}

	_, err := engine.GetMACAddressOfENI([]string{firstMACAddress}, firstENIID)
	assert.Error(t, err)
	_, ok := err.(*UnmappedMACAddressError)
	assert.True(t, ok)
}

func TestGetMACAddressOfENIReturnsErrorWhenNotFound(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddress+metadataNetworkInterfaceIDPathSuffix).Return(firstENIID, nil)
	engine := &engine{metadata: mockMetadata}

	_, err := engine.GetMACAddressOfENI([]string{firstMACAddress}, secondENIID)
	assert.Error(t, err)
	_, ok := err.(*UnmappedMACAddressError)
	assert.True(t, ok)
}

func TestGetMACAddressOfENI(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	gomock.InOrder(
		mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddress+metadataNetworkInterfaceIDPathSuffix).Return(firstENIID, nil),
		mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+secondMACAddress+metadataNetworkInterfaceIDPathSuffix).Return(secondENIID, nil),
	)
	engine := &engine{metadata: mockMetadata}

	addr, err := engine.GetMACAddressOfENI([]string{firstMACAddress, secondMACAddress}, secondENIID)
	assert.NoError(t, err)
	assert.Equal(t, addr, secondMACAddressSanitized)
}

func TestGetInterfaceDeviceNameReturnsErrorOnInvalidMACAddress(t *testing.T) {
	ctrl, _, _, _ := setup(t)
	defer ctrl.Finish()

	engine := &engine{}

	_, err := engine.GetInterfaceDeviceName("")
	assert.Error(t, err)
}

func TestGetInterfaceDeviceNameReturnsErrorOnLinkListErrort(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetLink.EXPECT().LinkList().Return(nil, errors.New("error"))
	engine := &engine{netLink: mockNetLink}

	_, err := engine.GetInterfaceDeviceName(eniMACAddress)
	assert.Error(t, err)
}

func TestGetInterfaceDeviceNameReturnsErrorWhenDeviceNotFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	lo := mock_netlink.NewMockLink(ctrl)
	eth1 := mock_netlink.NewMockLink(ctrl)

	loAddress, err := net.ParseMAC(loMACAddress)
	assert.NoError(t, err)
	eth1Address, err := net.ParseMAC(eniMACAddress)
	assert.NoError(t, err)

	gomock.InOrder(
		mockNetLink.EXPECT().LinkList().Return([]netlink.Link{lo, eth1}, nil),
		lo.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: loAddress}),
		eth1.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: eth1Address}),
	)

	engine := &engine{netLink: mockNetLink}
	_, err = engine.GetInterfaceDeviceName(unknownMACAddress)
	assert.Error(t, err)
}

func TestGetInterfaceDeviceNameReturnsDeviceWhenFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	lo := mock_netlink.NewMockLink(ctrl)
	eth1 := mock_netlink.NewMockLink(ctrl)

	loAddress, err := net.ParseMAC(loMACAddress)
	assert.NoError(t, err)
	eth1Address, err := net.ParseMAC(eniMACAddress)
	assert.NoError(t, err)

	gomock.InOrder(
		mockNetLink.EXPECT().LinkList().Return([]netlink.Link{lo, eth1}, nil),
		lo.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: loAddress}),
		eth1.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: eth1Address}),
		eth1.EXPECT().Attrs().Return(&netlink.LinkAttrs{
			HardwareAddr: eth1Address,
			Name:         deviceName,
		}),
	)

	engine := &engine{netLink: mockNetLink}
	eniDeviceName, err := engine.GetInterfaceDeviceName(eniMACAddress)
	assert.NoError(t, err)
	assert.Equal(t, eniDeviceName, deviceName)
}

func TestGetIPV4GatewayNetMaskReturnsErrorOnGetMetadataError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(
		metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV4CIDRPathSuffix).Return("", errors.New("error"))
	engine := &engine{metadata: mockMetadata}

	_, _, err := engine.GetIPV4GatewayNetmask(firstMACAddressSanitized)
	assert.Error(t, err)
	_, ok := err.(*utils.ParseIPV4GatewayNetmaskError)
	assert.False(t, ok)
}

func TestGetIPV4GatewayNetMaskReturnsErrorWhenUnableToParseCIDRNetmaskResponse(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	engine := &engine{metadata: mockMetadata}
	mockMetadata.EXPECT().GetMetadata(
		metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV4CIDRPathSuffix).Return("1.1.1.1", nil)
	_, _, err := engine.GetIPV4GatewayNetmask(firstMACAddressSanitized)
	assert.Error(t, err)
	_, ok := err.(*utils.ParseIPV4GatewayNetmaskError)
	assert.False(t, ok)
}

func TestGetIPV4GatewayNetMaskWhenUnableToParseIPV6CIDRNetmaskResponse(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(
		metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV4CIDRPathSuffix).Return("2001:db8::/32", nil)
	engine := &engine{metadata: mockMetadata}

	_, _, err := engine.GetIPV4GatewayNetmask(firstMACAddressSanitized)
	assert.Error(t, err)
	_, ok := err.(*utils.ParseIPV4GatewayNetmaskError)
	assert.True(t, ok)
}

func TestGetIPV4GatewayNetMask(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(
		metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV4CIDRPathSuffix).Return("172.31.32.0/20", nil)
	engine := &engine{metadata: mockMetadata}

	gateway, netmask, err := engine.GetIPV4GatewayNetmask(firstMACAddressSanitized)
	assert.NoError(t, err)
	assert.Equal(t, "172.31.32.1", gateway)
	assert.Equal(t, "20", netmask)
}

func TestGetIPV6NetmaskInternalReturnsErrorOnMalformedCIDR(t *testing.T) {
	_, err := getIPV6PrefixLength("2001:db8::")
	assert.Error(t, err)
}

func TestGetIPV6NetmaskInternalReturnsErrorOnMalformedNetmaskInCIDR(t *testing.T) {
	_, err := getIPV6PrefixLength("2001:db8::/")
	assert.Error(t, err)
}

func TestGetIPV6NetmaskInternalReturnsErrorOnMalformedCIDRBlockInCIDR(t *testing.T) {
	_, err := getIPV6PrefixLength("2001:::/32")
	assert.Error(t, err)
}

func TestGetIPV6NetMaskInternal(t *testing.T) {
	netmask, err := getIPV6PrefixLength("2001:db8::/32")
	assert.NoError(t, err)
	assert.Equal(t, netmask, "32")
}

func TestGetIPV6NetMaskReturnsErrorOnGetMetadataError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(
		metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV6CIDRPathSuffix).Return("", errors.New("error"))
	engine := &engine{metadata: mockMetadata}

	_, err := engine.GetIPV6PrefixLength(firstMACAddressSanitized)
	assert.Error(t, err)
}

func TestGetIPV6NetMaskReturnsErrorWhenUnableToParseCIDRNetmaskResponse(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	engine := &engine{metadata: mockMetadata}
	mockMetadata.EXPECT().GetMetadata(
		metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV6CIDRPathSuffix).Return("2001:db8::", nil)
	_, err := engine.GetIPV6PrefixLength(firstMACAddressSanitized)
	assert.Error(t, err)
}

func TestGetIPV6NetMask(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(
		metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV6CIDRPathSuffix).Return("2001:db8::/32", nil)
	engine := &engine{metadata: mockMetadata}

	netmask, err := engine.GetIPV6PrefixLength(firstMACAddressSanitized)
	assert.NoError(t, err)
	assert.Equal(t, "32", netmask)
}

func TestGetIPV6GatewayIPFromRoutesOnceRouteListReturnsError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(nil, errors.New(""))
	engine := &engine{netLink: mockNetLink}

	_, _, err := engine.getIPV6GatewayIPFromRoutesOnce(mockLink, deviceName)
	assert.Error(t, err)
}

func TestGetIPV6GatewayIPFromRoutesOnceRouteListReturnsFalseWhenNotFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	ipv6Addr := net.ParseIP(eniIPV6Address)
	ipNet := &net.IPNet{
		IP: ipv6Addr,
	}
	ipv6Gw := net.ParseIP(eniIPV6Gateway)
	routes := []netlink.Route{
		// Dst is set, nothing else is
		netlink.Route{
			Dst: ipNet,
		},
		// Dst is not set, but other fields are
		netlink.Route{
			Dst: &net.IPNet{},
			Src: ipv6Addr,
			Gw:  ipv6Gw,
		},
		// Dst, Src and Gw are all set
		netlink.Route{
			Dst: ipNet,
			Src: ipv6Addr,
			Gw:  ipv6Gw,
		},
	}
	mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(routes, nil)
	engine := &engine{netLink: mockNetLink}

	_, ok, err := engine.getIPV6GatewayIPFromRoutesOnce(mockLink, deviceName)
	assert.NoError(t, err)
	assert.False(t, ok)
}

func TestGetIPV6GatewayIPFromRoutesOnceRouteListReturnsTrueWhenFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	ipv6Addr := net.ParseIP(eniIPV6Address)
	ipv6Gw := net.ParseIP(eniIPV6Gateway)
	ipNet := &net.IPNet{
		IP: ipv6Addr,
	}
	routes := []netlink.Route{
		// Dst is set, nothing else is
		netlink.Route{
			Dst: ipNet,
		},
		// Dst is not set, but other fields are
		netlink.Route{
			Dst: &net.IPNet{},
			Src: ipv6Addr,
			Gw:  ipv6Gw,
		},
		// Dst, Src and Gw are all set
		netlink.Route{
			Dst: ipNet,
			Src: ipv6Addr,
			Gw:  ipv6Gw,
		},
		// Only Gw is set, this is what we're looking for
		netlink.Route{
			Gw: ipv6Gw,
		},
	}
	mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(routes, nil)
	engine := &engine{netLink: mockNetLink}

	gateway, ok, err := engine.getIPV6GatewayIPFromRoutesOnce(mockLink, deviceName)
	assert.NoError(t, err)
	assert.True(t, ok)
	assert.Equal(t, gateway, eniIPV6Gateway)
}

func TestGetIPV6GatewayIPFromRoutesDoesNotRetryOnError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	// Expect only one invocation of route list
	mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(nil, errors.New("error"))
	engine := &engine{netLink: mockNetLink}

	_, err := engine.getIPV6GatewayIPFromRoutes(mockLink, deviceName,
		maxTicksForRetrievingIPV6Gateway, ipv6GatewayTickDuration)
	assert.Error(t, err)
}

func TestGetIPV6GatewayIPFromRoutesRetriesWhenNotFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	// Expect only 2 invocation of route list
	mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(nil, nil).Times(2)
	engine := &engine{netLink: mockNetLink}

	_, err := engine.getIPV6GatewayIPFromRoutes(mockLink, deviceName, 2, time.Microsecond)
	assert.Error(t, err)
}

func TestGetIPV6GatewayIPFromRoutes(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	ipv6Gw := net.ParseIP(eniIPV6Gateway)
	routes := []netlink.Route{
		netlink.Route{
			Gw: ipv6Gw,
		},
	}

	gomock.InOrder(
		mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(nil, nil),
		mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(routes, nil),
	)
	engine := &engine{netLink: mockNetLink}

	gateway, err := engine.getIPV6GatewayIPFromRoutes(mockLink, deviceName, 2, time.Microsecond)
	assert.NoError(t, err)
	assert.Equal(t, gateway, eniIPV6Gateway)
}

func TestGetIPV6GatewayOnLinkByNameError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetLink.EXPECT().LinkByName(deviceName).Return(nil, errors.New("error"))
	engine := &engine{
		netLink:                          mockNetLink,
		ipv6GatewayTickDuration:          time.Microsecond,
		maxTicksForRetrievingIPV6Gateway: 2,
	}

	_, err := engine.GetIPV6Gateway(deviceName)
	assert.Error(t, err)
}

func TestGetIPV6GatewayOnGetIPV6GatewayIPFromRoutesError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockLink, nil),
		mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(nil, errors.New("error")),
	)
	engine := &engine{
		netLink:                          mockNetLink,
		ipv6GatewayTickDuration:          time.Microsecond,
		maxTicksForRetrievingIPV6Gateway: 2,
	}

	_, err := engine.GetIPV6Gateway(deviceName)
	assert.Error(t, err)
}

func TestGetIPV6GatewayOnGetIPV6GatewayIPFromRoutesNotFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockLink, nil),
		mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(nil, nil).Times(2),
	)
	engine := &engine{
		netLink:                          mockNetLink,
		ipv6GatewayTickDuration:          time.Microsecond,
		maxTicksForRetrievingIPV6Gateway: 2,
	}

	_, err := engine.GetIPV6Gateway(deviceName)
	assert.Error(t, err)
}

func TestGetIPV6GatewayOnGetIPV6GatewayIPFromRoutesFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	ipv6Gw := net.ParseIP(eniIPV6Gateway)
	routes := []netlink.Route{
		netlink.Route{
			Gw: ipv6Gw,
		},
	}
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockLink, nil),
		mockNetLink.EXPECT().RouteList(mockLink, netlink.FAMILY_V6).Return(routes, nil),
	)
	engine := &engine{
		netLink:                          mockNetLink,
		ipv6GatewayTickDuration:          time.Microsecond,
		maxTicksForRetrievingIPV6Gateway: 2,
	}

	gateway, err := engine.GetIPV6Gateway(deviceName)
	assert.NoError(t, err)
	assert.Equal(t, gateway, eniIPV6Gateway)
}

func TestIsValidGetIPAddressReturnsErrorOnGetMetadataError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	suffix := "/suffix"
	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+suffix).Return("", errors.New("error"))
	engine := &engine{metadata: mockMetadata, metadataMaxRetryCount: 1}

	_, err := engine.doesMACAddressMapToIPAddress(firstMACAddressSanitized, eniIPV4Address, "/suffix")
	assert.Error(t, err)
}

func TestDoesMACAddressMapToIPAddressReturnsFalseWhenNotFound(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	suffix := "/suffix"
	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+suffix).Return("172.31.32.3", nil)
	engine := &engine{metadata: mockMetadata, metadataMaxRetryCount: 1}

	ok, err := engine.doesMACAddressMapToIPAddress(firstMACAddressSanitized, eniIPV4Address, "/suffix")
	assert.NoError(t, err)
	assert.False(t, ok)

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+suffix).Return("2001::68", nil)
	ok, err = engine.doesMACAddressMapToIPAddress(firstMACAddressSanitized, eniIPV6Address, "/suffix")
	assert.NoError(t, err)
	assert.False(t, ok)
}

func TestDoesMACAddressMapToIPAddressReturnsTrueWhenFound(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	suffix := "/suffix"
	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+suffix).Return(eniIPV4Address, nil)
	engine := &engine{metadata: mockMetadata, metadataMaxRetryCount: 1}

	ok, err := engine.doesMACAddressMapToIPAddress(firstMACAddressSanitized, eniIPV4Address, suffix)
	assert.NoError(t, err)
	assert.True(t, ok)

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+suffix).Return(eniIPV6Address, nil)
	ok, err = engine.doesMACAddressMapToIPAddress(firstMACAddressSanitized, eniIPV6Address, suffix)
	assert.NoError(t, err)
	assert.True(t, ok)
}

func TestIsValidGetIPAddressRetriesOnGetMetadataError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	suffix := "/suffix"
	gomock.InOrder(
		// First attempt fails
		mockMetadata.EXPECT().GetMetadata(
			metadataNetworkInterfacesPath+firstMACAddressSanitized+suffix).Return(
			"", errors.New("error")),
		// Second attempt succeeds
		mockMetadata.EXPECT().GetMetadata(
			metadataNetworkInterfacesPath+firstMACAddressSanitized+suffix).Return(
			eniIPV4Address, nil),
	)
	engine := &engine{metadata: mockMetadata,
		metadataMaxRetryCount:          2,
		metadataDurationBetweenRetries: time.Microsecond}

	ok, err := engine.doesMACAddressMapToIPAddress(firstMACAddressSanitized, eniIPV4Address, "/suffix")
	assert.NoError(t, err)
	assert.True(t, ok)
}

func TestIsValidGetIPV4AddressReturnsError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV4AddressesSuffix).Return("", errors.New("error"))
	engine := &engine{metadata: mockMetadata, metadataMaxRetryCount: 1}

	_, err := engine.DoesMACAddressMapToIPV4Address(firstMACAddressSanitized, eniIPV4Address)
	assert.Error(t, err)
}

func TestIsValidGetIPV4Address(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV4AddressesSuffix).Return(eniIPV4Address, nil)
	engine := &engine{metadata: mockMetadata, metadataMaxRetryCount: 1}

	ok, err := engine.DoesMACAddressMapToIPV4Address(firstMACAddressSanitized, eniIPV4Address)
	assert.NoError(t, err)
	assert.True(t, ok)
}

func TestIsValidGetIPV6AddressReturnsError(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV6AddressesSuffix).Return("", errors.New("error"))
	engine := &engine{metadata: mockMetadata, metadataMaxRetryCount: 1}

	_, err := engine.DoesMACAddressMapToIPV6Address(firstMACAddressSanitized, eniIPV6Address)
	assert.Error(t, err)
}

func TestIsValidGetIPV6Address(t *testing.T) {
	ctrl, mockMetadata, _, _ := setup(t)
	defer ctrl.Finish()

	mockMetadata.EXPECT().GetMetadata(metadataNetworkInterfacesPath+firstMACAddressSanitized+metadataNetworkInterfaceIPV6AddressesSuffix).Return(eniIPV6Address, nil)
	engine := &engine{metadata: mockMetadata, metadataMaxRetryCount: 1}

	ok, err := engine.DoesMACAddressMapToIPV6Address(firstMACAddressSanitized, eniIPV6Address)
	assert.NoError(t, err)
	assert.True(t, ok)
}

func TestSetupContainerNamespaceFailsOnLinkByNameError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetLink.EXPECT().LinkByName(deviceName).Return(nil, errors.New("error"))
	engine := &engine{netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, false, 0)
	assert.Error(t, err)
}

func TestSetupContainerNamespaceFailsOnGetNSError(t *testing.T) {
	ctrl, _, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockLink := mock_netlink.NewMockLink(ctrl)
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockLink, nil),
		mockNS.EXPECT().GetNS("ns1").Return(nil, errors.New("error")),
	)
	engine := &engine{ns: mockNS, netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, false, 0)
	assert.Error(t, err)
}

func TestSetupContainerNamespaceFailsOnLinksetNsFdError(t *testing.T) {
	ctrl, _, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockENILink := mock_netlink.NewMockLink(ctrl)
	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	var fd uintptr
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNS.EXPECT().GetNS("ns1").Return(mockNetNS, nil),
		mockNetNS.EXPECT().Fd().Return(fd),
		mockNetLink.EXPECT().LinkSetNsFd(mockENILink, int(fd)).Return(errors.New("error")),
	)
	engine := &engine{ns: mockNS, netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, false, 0)
	assert.Error(t, err)
}

func TestSetupContainerNamespaceFailsOnParseAddrError(t *testing.T) {
	ctrl, _, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockENILink := mock_netlink.NewMockLink(ctrl)
	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	var fd uintptr
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNS.EXPECT().GetNS("ns1").Return(mockNetNS, nil),
		mockNetNS.EXPECT().Fd().Return(fd),
		mockNetLink.EXPECT().LinkSetNsFd(mockENILink, int(fd)).Return(nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(nil, errors.New("error")),
	)
	engine := &engine{ns: mockNS, netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, false, 0)
	assert.Error(t, err)
}

func TestSetupContainerNamespaceFailsOnWithNetNSPathError(t *testing.T) {
	ctrl, _, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockENILink := mock_netlink.NewMockLink(ctrl)
	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	var fd uintptr
	ipv4Addr := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNS.EXPECT().GetNS("ns1").Return(mockNetNS, nil),
		mockNetNS.EXPECT().Fd().Return(fd),
		mockNetLink.EXPECT().LinkSetNsFd(mockENILink, int(fd)).Return(nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Addr, nil),
		mockNS.EXPECT().WithNetNSPath("ns1", gomock.Any()).Return(errors.New("error")),
	)
	engine := &engine{ns: mockNS, netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, false, 0)
	assert.Error(t, err)
}

func TestSetupContainerNamespaceNoIPV6(t *testing.T) {
	ctrl, _, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockENILink := mock_netlink.NewMockLink(ctrl)
	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	var fd uintptr
	ipv4Addr := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNS.EXPECT().GetNS("ns1").Return(mockNetNS, nil),
		mockNetNS.EXPECT().Fd().Return(fd),
		mockNetLink.EXPECT().LinkSetNsFd(mockENILink, int(fd)).Return(nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Addr, nil),
		mockNS.EXPECT().WithNetNSPath("ns1", gomock.Any()).Return(nil),
	)
	engine := &engine{ns: mockNS, netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, false, 0)
	assert.NoError(t, err)
}

func TestSetupContainerNamespace(t *testing.T) {
	ctrl, _, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockENILink := mock_netlink.NewMockLink(ctrl)
	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	var fd uintptr
	ipv4Addr := &netlink.Addr{}
	ipv6Addr := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNS.EXPECT().GetNS("ns1").Return(mockNetNS, nil),
		mockNetNS.EXPECT().Fd().Return(fd),
		mockNetLink.EXPECT().LinkSetNsFd(mockENILink, int(fd)).Return(nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Addr, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(ipv6Addr, nil),
		mockNS.EXPECT().WithNetNSPath("ns1", gomock.Any()).Return(nil),
	)
	engine := &engine{ns: mockNS, netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, false, 0)
	assert.NoError(t, err)
}

func TestSetupContainerNamespaceStayDown(t *testing.T) {
	ctrl, _, mockNS, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockENILink := mock_netlink.NewMockLink(ctrl)
	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	var fd uintptr
	gomock.InOrder(
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNS.EXPECT().GetNS("ns1").Return(mockNetNS, nil),
		mockNetNS.EXPECT().Fd().Return(fd),
		mockNetLink.EXPECT().LinkSetNsFd(mockENILink, int(fd)).Return(nil),
	)
	engine := &engine{ns: mockNS, netLink: mockNetLink}
	err := engine.SetupContainerNamespace(&skel.CmdArgs{
		Netns:  "ns1",
		IfName: "eth0",
	}, deviceName, eniMACAddress, []string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, true, 0)
	assert.NoError(t, err)
}

func TestSetupNamespaceClosureCreationFailsOnIPV4ParseAddrError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(nil, errors.New("error"))
	_, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, 0)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureCreationFailsOnIPV4ParseGatewayError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	ipv4Addr := &netlink.Addr{}
	mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Addr, nil)
	// Gateway is an empty string, so we expect this method to fail
	_, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{""}, false, 0)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureCreationFailsOnIPV6ParseAddrError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	ipv4Addr := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Addr, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(nil, errors.New("error")),
	)
	_, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, 0)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureCreationFailsOnIPV6ParseGatewayError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	ipv4Addr := &netlink.Addr{}
	ipv6Addr := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Addr, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(ipv6Addr, nil),
	)
	// Gateway is an empty string, so we expect this method to fail
	_, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, ""}, false, 0)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureRunFailsOnLinkByNameError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	ipv4Addr := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Addr, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(nil, errors.New("error")),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureRunFailsOnIPV4AddrAddError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(errors.New("error")),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureRunFailsOnIPV6AddrAddError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	ipv6Address := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(ipv6Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv6Address).Return(errors.New("error")),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureRunFailsOnLinkSetupError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(errors.New("error")),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureRunFailsOnBlackholeRouteAddError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(nil),
	)
	for i, ep := range InstanceMetadataEndpoints {
		var retErr error
		if i == len(InstanceMetadataEndpoints) - 1 {
			retErr = errors.New("error")
		}
		_, imdsNetwork, err := net.ParseCIDR(ep)
		assert.NoError(t, err)
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Nil(t, route.Gw)
			assert.Equal(t, imdsNetwork.String(), route.Dst.String())
			assert.Equal(t, syscall.RTN_BLACKHOLE, route.Type)
		}).Return(retErr)
	}
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, true, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureRunFailsOnIPV4RouteAddError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	eniLinkIndex := 1
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV4Gateway)
		}).Return(errors.New("error")),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.Error(t, err)
}

func TestSetupNamespaceClosureRunFailsOnIPV6RouteAddError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	ipv6Address := &netlink.Addr{}
	eniLinkIndex := 1
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(ipv6Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv6Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV4Gateway)
		}).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV6Gateway)
			assert.Equal(t, route.LinkIndex, eniLinkIndex)
		}).Return(errors.New("error")),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.Error(t, err)
}

func TestIsRouteExistsError(t *testing.T) {
	for _, tc := range []struct {
		err      error
		expected bool
	}{
		{errors.New("error"), false},
		{syscall.Errno(syscall.EEXIST), true},
	} {
		t.Run(fmt.Sprintf("Error %v returns %t for isRouteExistsError", tc.err, tc.expected), func(t *testing.T) {
			assert.Equal(t, tc.expected, isRouteExistsError(tc.err))
		})
	}
}

func TestSetupNamespaceClosureRunNoIPV6(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	eniLinkIndex := 1
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV4Gateway)
		}).Return(nil),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock}, []string{eniIPV4Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.NoError(t, err)
}

func TestSetupNamespaceClosureRunBlockIMDS(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	ipv6Address := &netlink.Addr{}
	eniLinkIndex := 1
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(ipv6Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv6Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(nil),
	)
	for _, ep := range InstanceMetadataEndpoints {
                _, imdsNetwork, err := net.ParseCIDR(ep)
                assert.NoError(t, err)
                mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
                        assert.Nil(t, route.Gw)
                        assert.Equal(t, imdsNetwork.String(), route.Dst.String())
                        assert.Equal(t, syscall.RTN_BLACKHOLE, route.Type)
                }).Return(nil)
        }
	gomock.InOrder(
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV4Gateway)
		}).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV6Gateway)
			assert.Equal(t, route.LinkIndex, eniLinkIndex)
		}).Return(nil),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, true, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.NoError(t, err)
}

func TestSetupNamespaceClosureRun(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)
	ipv4Address := &netlink.Addr{}
	ipv6Address := &netlink.Addr{}
	eniLinkIndex := 1
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(ipv6Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv6Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV4Gateway)
		}).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV6Gateway)
			assert.Equal(t, route.LinkIndex, eniLinkIndex)
		}).Return(nil),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, 0)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.NoError(t, err)
}

func TestGetLinkByHardwareAddressFailsOnListLinkError(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	hwAddress := net.HardwareAddr{}

	mockNetLink.EXPECT().LinkList().Return(nil, errors.New("error"))
	_, err := getLinkByHardwareAddress(mockNetLink, hwAddress)
	assert.Error(t, err)
}

func TestGetLinkByHardwareAddressFailsWhenLinkNotFound(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	lo := mock_netlink.NewMockLink(ctrl)
	eth1 := mock_netlink.NewMockLink(ctrl)

	loAddress, err := net.ParseMAC(loMACAddress)
	assert.NoError(t, err)
	eth1Address, err := net.ParseMAC(eniMACAddress)
	assert.NoError(t, err)
	hwAddress, err := net.ParseMAC(unknownMACAddress)
	assert.NoError(t, err)
	gomock.InOrder(
		mockNetLink.EXPECT().LinkList().Return([]netlink.Link{lo, eth1}, nil),
		lo.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: loAddress}),
		eth1.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: eth1Address}),
	)

	_, err = getLinkByHardwareAddress(mockNetLink, hwAddress)
	assert.Error(t, err)
}

func TestGetLinkByHardwareAddress(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	lo := mock_netlink.NewMockLink(ctrl)
	eth1 := mock_netlink.NewMockLink(ctrl)

	loAddress, err := net.ParseMAC(loMACAddress)
	assert.NoError(t, err)
	eth1Address, err := net.ParseMAC(eniMACAddress)
	assert.NoError(t, err)
	gomock.InOrder(
		mockNetLink.EXPECT().LinkList().Return([]netlink.Link{lo, eth1}, nil),
		lo.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: loAddress}),
		eth1.EXPECT().Attrs().Return(&netlink.LinkAttrs{HardwareAddr: eth1Address}),
	)

	_, err = getLinkByHardwareAddress(mockNetLink, eth1Address)
	assert.NoError(t, err)
}

func TestSetupNamespaceClosureRunWithMTU(t *testing.T) {
	ctrl, _, _, mockNetLink := setup(t)
	defer ctrl.Finish()

	mockNetNS := mock_ns.NewMockNetNS(ctrl)
	mockENILink := mock_netlink.NewMockLink(ctrl)

	ipv4Address := &netlink.Addr{}
	ipv6Address := &netlink.Addr{}
	eniLinkIndex := 1
	gomock.InOrder(
		mockNetLink.EXPECT().ParseAddr(eniIPV4CIDRBlock).Return(ipv4Address, nil),
		mockNetLink.EXPECT().ParseAddr(eniIPV6CIDRBlock).Return(ipv6Address, nil),
		mockNetLink.EXPECT().LinkByName(deviceName).Return(mockENILink, nil),
		mockNetLink.EXPECT().LinkSetName(mockENILink, "eth0").Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv4Address).Return(nil),
		mockNetLink.EXPECT().AddrAdd(mockENILink, ipv6Address).Return(nil),
		mockNetLink.EXPECT().LinkSetUp(mockENILink).Return(nil),
		mockNetLink.EXPECT().LinkSetMTU(mockENILink, 9001).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV4Gateway)
		}).Return(nil),
		mockENILink.EXPECT().Attrs().Return(&netlink.LinkAttrs{Index: eniLinkIndex}),
		mockNetLink.EXPECT().RouteAdd(gomock.Any()).Do(func(route *netlink.Route) {
			assert.Equal(t, route.Gw.String(), eniIPV6Gateway)
			assert.Equal(t, route.LinkIndex, eniLinkIndex)
		}).Return(nil),
	)
	closure, err := newSetupNamespaceClosureContext(mockNetLink, "eth0", deviceName, eniMACAddress,
		[]string{eniIPV4CIDRBlock, eniIPV6CIDRBlock}, []string{eniIPV4Gateway, eniIPV6Gateway}, false, 9001)
	assert.NoError(t, err)
	assert.NotNil(t, closure)
	err = closure.run(mockNetNS)
	assert.NoError(t, err)
}
