diff --git a/Makefile b/Makefile index f8f968f2e5..e67e54fc50 100644 --- a/Makefile +++ b/Makefile @@ -371,6 +371,7 @@ test-all: ./ipam/ \ ./log/ \ ./netlink/ \ + ./network/ \ ./store/ \ ./telemetry/ \ ./aitelemetry/ \ diff --git a/cni/ipam/ipam_test.go b/cni/ipam/ipam_test.go index 6c0887fe3b..5a36ec15b7 100644 --- a/cni/ipam/ipam_test.go +++ b/cni/ipam/ipam_test.go @@ -6,14 +6,14 @@ package ipam import ( "encoding/json" "fmt" - "net/http" - "net/url" - "testing" - cniSkel "github.com/containernetworking/cni/pkg/skel" cniTypesCurr "github.com/containernetworking/cni/pkg/types/current" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "net/http" + "net/url" + "testing" + "time" "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/platform" @@ -70,6 +70,8 @@ var ( err error _ = BeforeSuite(func() { + // TODO: Ensure that the other testAgent has bees released. + time.Sleep(1 * time.Second) // Create a fake local agent to handle requests from IPAM plugin. u, _ := url.Parse("tcp://" + ipamQueryUrl) testAgent, err = common.NewListener(u) diff --git a/ipam/ipv6Ipam_test.go b/ipam/ipv6Ipam_test.go index 546c185dd9..0b2cfddd09 100644 --- a/ipam/ipv6Ipam_test.go +++ b/ipam/ipv6Ipam_test.go @@ -6,13 +6,17 @@ package ipam import ( "context" "reflect" + "runtime" "testing" - "github.com/Azure/azure-container-networking/common" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" testclient "k8s.io/client-go/kubernetes/fake" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/Azure/azure-container-networking/common" ) const ( @@ -36,59 +40,110 @@ func newKubernetesTestClient() kubernetes.Interface { return client } -func TestIPv6Ipam(t *testing.T) { - options := make(map[string]interface{}) - options[common.OptEnvironment] = common.OptEnvironmentIPv6NodeIpam - - client := newKubernetesTestClient() - node, _ := client.CoreV1().Nodes().Get(context.TODO(), testNodeName, metav1.GetOptions{}) - - testInterfaces, err := retrieveKubernetesPodIPs(node, testSubnetSize) - if err != nil { - t.Fatalf("Failed to carve addresses: %+v", err) - } +func TestIpv6Ipam(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Ipv6Ipam Suite") +} - correctInterfaces := &NetworkInterfaces{ - Interfaces: []Interface{ - { - IsPrimary: true, - IPSubnets: []IPSubnet{ - { - Prefix: "ace:cab:deca:deed::/126", - IPAddresses: []IPAddress{ - {Address: "ace:cab:deca:deed::2", IsPrimary: false}, - {Address: "ace:cab:deca:deed::3", IsPrimary: false}, +var ( + _ = Describe("Test ipv6Ipam", func() { + + Describe("Test newIPv6IpamSource", func() { + + Context("When creating with current environment", func() { + It("Should create successfully", func() { + name := common.OptEnvironmentIPv6NodeIpam + options := map[string]interface{}{} + options[common.OptEnvironment] = name + kubeConfigPath := defaultLinuxKubeConfigFilePath + if runtime.GOOS == windows { + kubeConfigPath = defaultWindowsKubeConfigFilePath + } + isLoaded := true + ipv6IpamSource, err := newIPv6IpamSource(options, isLoaded) + Expect(err).NotTo(HaveOccurred()) + Expect(ipv6IpamSource.name).To(Equal(name)) + Expect(ipv6IpamSource.kubeConfigPath).To(Equal(kubeConfigPath)) + Expect(ipv6IpamSource.isLoaded).To(Equal(isLoaded)) + }) + }) + }) + + Describe("Test start and stop", func() { + + source := &ipv6IpamSource{} + + Context("Start the source with sink", func() { + It("Should set the sink of source", func() { + sink := &addressManagerMock{} + err := source.start(sink) + Expect(err).NotTo(HaveOccurred()) + Expect(source.sink).NotTo(BeNil()) + }) + }) + + Context("Stop the source", func() { + It("Should remove the sink of source", func() { + source.stop() + Expect(source.sink).To(BeNil()) + }) + }) + }) + + Describe("TestIPv6Ipam", func() { + Context("When node have IPv6 subnet", func() { + It("Carve addresses successfully and Network interface match", func() { + options := make(map[string]interface{}) + options[common.OptEnvironment] = common.OptEnvironmentIPv6NodeIpam + + client := newKubernetesTestClient() + node, _ := client.CoreV1().Nodes().Get(context.TODO(), testNodeName, metav1.GetOptions{}) + + testInterfaces, err := retrieveKubernetesPodIPs(node, testSubnetSize) + Expect(err).NotTo(HaveOccurred()) + + correctInterfaces := &NetworkInterfaces{ + Interfaces: []Interface{ + { + IsPrimary: true, + IPSubnets: []IPSubnet{ + { + Prefix: "ace:cab:deca:deed::/126", + IPAddresses: []IPAddress{ + {Address: "ace:cab:deca:deed::2", IsPrimary: false}, + {Address: "ace:cab:deca:deed::3", IsPrimary: false}, + }, + }, + }, + }, }, - }, - }, - }, - }, - } - - equal := reflect.DeepEqual(testInterfaces, correctInterfaces) - - if !equal { - t.Fatalf("Network interface mismatch, expected: %+v actual: %+v", correctInterfaces, testInterfaces) - } -} + } -func TestIPv6IpamWithoutIPv6SubnetAllocated(t *testing.T) { - options := make(map[string]interface{}) - options[common.OptEnvironment] = common.OptEnvironmentIPv6NodeIpam + equal := reflect.DeepEqual(testInterfaces, correctInterfaces) + Expect(equal).To(BeTrue()) + }) + }) - testnode := &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: testNodeName, - }, - Spec: v1.NodeSpec{ - PodCIDR: "10.0.0.1/24", - PodCIDRs: []string{"10.0.0.1/24"}, - }, - } + Context("When node doesn't have IPv6 subnet", func() { + It("Should fail to retrieve the IPv6 address", func() { + options := make(map[string]interface{}) + options[common.OptEnvironment] = common.OptEnvironmentIPv6NodeIpam - _, err := retrieveKubernetesPodIPs(testnode, testSubnetSize) + testnode := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: testNodeName, + }, + Spec: v1.NodeSpec{ + PodCIDR: "10.0.0.1/24", + PodCIDRs: []string{"10.0.0.1/24"}, + }, + } + + _, err := retrieveKubernetesPodIPs(testnode, testSubnetSize) + Expect(err).To(HaveOccurred()) + }) + }) + }) + }) +) - if err == nil { - t.Fatal("Expected to fail IPv6 IP retrieval when node doesn't have IPv6 subnet") - } -} diff --git a/ipam/manager_ipv6Ipam_test.go b/ipam/manager_ipv6Ipam_test.go index 4836837f71..9c2f7f9b47 100644 --- a/ipam/manager_ipv6Ipam_test.go +++ b/ipam/manager_ipv6Ipam_test.go @@ -6,17 +6,25 @@ package ipam import ( "testing" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/Azure/azure-container-networking/common" ) var ( - // Pools and addresses used by tests. ipv6subnet1 = "ace:cab:deca:deed::" + testSubnetSize ipv6addr2 = "ace:cab:deca:deed::2" ipv6addr3 = "ace:cab:deca:deed::3" ) +func TestManagerIpv6Ipam(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Manager ipv6ipam Suite") +} + + func createTestIpv6AddressManager() (AddressManager, error) { var config common.PluginConfig var options map[string]interface{} @@ -43,69 +51,74 @@ func createTestIpv6AddressManager() (AddressManager, error) { return am, nil } -// -// Address manager tests. -// -// request pool, request address with no address specified, request address with address specified, -// release both addresses, release pool -func TestIPv6GetAddressPoolAndAddress(t *testing.T) { - // Start with the test address space. - am, err := createTestIpv6AddressManager() - if err != nil { - t.Fatalf("createAddressManager failed, err:%+v.", err) - } - - // Test if the address spaces are returned correctly. - local, _ := am.GetDefaultAddressSpaces() - - if local != LocalDefaultAddressSpaceId { - t.Errorf("GetDefaultAddressSpaces returned invalid local address space.") - } - - // Request two separate address pools. - poolID1, subnet1, err := am.RequestPool(LocalDefaultAddressSpaceId, "", "", nil, true) - if err != nil { - t.Errorf("RequestPool failed, err:%v", err) - } - - if subnet1 != ipv6subnet1 { - t.Errorf("Mismatched retrieved subnet, expected:%+v, actual %+v", ipv6subnet1, subnet1) - } - - // test with a specified address - address2, err := am.RequestAddress(LocalDefaultAddressSpaceId, poolID1, ipv6addr2, nil) - if err != nil { - t.Errorf("RequestAddress failed, err:%v", err) - } - - if address2 != ipv6addr2+testSubnetSize { - t.Errorf("RequestAddress failed, expected: %v, actual: %v", ipv6addr2+testSubnetSize, address2) - } - - // test with a specified address - address3, err := am.RequestAddress(LocalDefaultAddressSpaceId, poolID1, "", nil) - if err != nil { - t.Errorf("RequestAddress failed, err:%v", err) - } - - if address3 != ipv6addr3+testSubnetSize { - t.Errorf("RequestAddress failed, expected: %v, actual: %v", ipv6addr3+testSubnetSize, address3) - } - - // Release addresses and the pool. - err = am.ReleaseAddress(LocalDefaultAddressSpaceId, poolID1, address2, nil) - if err != nil { - t.Errorf("ReleaseAddress failed, err:%v", err) - } - - // Release addresses and the pool. - err = am.ReleaseAddress(LocalDefaultAddressSpaceId, poolID1, address3, nil) - if err != nil { - t.Errorf("ReleaseAddress failed, err:%v", err) - } - - err = am.ReleasePool(LocalDefaultAddressSpaceId, poolID1) - if err != nil { - t.Errorf("ReleasePool failed, err:%v", err) - } -} +var ( + _ = Describe("Test manager ipv6Ipam", func() { + + Describe("Test IPv6 get address pool and address", func() { + + var ( + am AddressManager + err error + poolID1 string + subnet1 string + address2 string + address3 string + ) + + Context("Start with the test address space", func() { + It("Should create AddressManager successfully", func() { + am, err = createTestIpv6AddressManager() + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When test if the address spaces are returned correctly", func() { + It("GetDefaultAddressSpaces returned valid local address space", func() { + local, _ := am.GetDefaultAddressSpaces() + Expect(local).To(Equal(LocalDefaultAddressSpaceId)) + }) + }) + + Context("When request two separate address pools", func() { + It("Should request pool successfully and return subnet matched ipv6subnet1", func() { + poolID1, subnet1, err = am.RequestPool(LocalDefaultAddressSpaceId, "", "", nil, true) + Expect(err).NotTo(HaveOccurred()) + Expect(subnet1).To(Equal(ipv6subnet1)) + + }) + }) + + Context("When test with a specified address", func() { + It("Should request address successfully", func() { + address2, err := am.RequestAddress(LocalDefaultAddressSpaceId, poolID1, ipv6addr2, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(address2).To(Equal(ipv6addr2+testSubnetSize)) + }) + }) + + Context("When test without requesting address explicitly", func() { + It("Should request address successfully", func() { + address3, err := am.RequestAddress(LocalDefaultAddressSpaceId, poolID1, "", nil) + Expect(err).NotTo(HaveOccurred()) + Expect(address3).To(Equal(ipv6addr3+testSubnetSize)) + }) + }) + + Context("When release address2", func() { + It("Should release successfully", func() { + err = am.ReleaseAddress(LocalDefaultAddressSpaceId, poolID1, address2, nil) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When release address3 and the pool", func() { + It("Should release successfully", func() { + err = am.ReleaseAddress(LocalDefaultAddressSpaceId, poolID1, address3, nil) + Expect(err).NotTo(HaveOccurred()) + err = am.ReleasePool(LocalDefaultAddressSpaceId, poolID1) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + }) +) diff --git a/ipam/pool_test.go b/ipam/pool_test.go new file mode 100644 index 0000000000..99eb1e1018 --- /dev/null +++ b/ipam/pool_test.go @@ -0,0 +1,1008 @@ +package ipam + +import ( + "net" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/Azure/azure-container-networking/testutils" +) + +func TestPool(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Pool Suite") +} + +var ( + _ = Describe("Test Pool", func() { + + Describe("Test addressPoolId", func() { + Context("Creates a new address pool ID object", func() { + It("Should create a pool ID with given parameters", func() { + asId := "eth0" + subnet := "10.0.0.0/16" + childSubnet := "10.0.1.0/8" + apId := NewAddressPoolId(asId, subnet, childSubnet) + Expect(apId.AsId).To(Equal(asId)) + Expect(apId.Subnet).To(Equal(subnet)) + Expect(apId.ChildSubnet).To(Equal(childSubnet)) + }) + }) + + Context("Create a new pool ID when string format is incorrect", func() { + It("Should raise an error", func() { + s := "eth0|10.0.0.0/16|10.0.1.0/8|test" + apId, err := NewAddressPoolIdFromString(s) + Expect(apId).To(BeNil()) + Expect(err).To(HaveOccurred()) + }) + }) + + Context("Create a new pool ID when string only contains addressSpace Id", func() { + It("Should create a pool ID by parsing the string", func() { + s := "local" + apId, err := NewAddressPoolIdFromString(s) + Expect(apId).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + Expect(apId.AsId).To(Equal(s)) + Expect(apId.Subnet).To(BeEmpty()) + Expect(apId.ChildSubnet).To(BeEmpty()) + }) + }) + + Context("Create a new pool ID when the string has addrspace and subnet", func() { + It("Should create a pool ID by parsing the string", func() { + s := "eth0|10.0.0.0/16" + apId, err := NewAddressPoolIdFromString(s) + Expect(apId).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + Expect(apId.AsId).To(Equal("eth0")) + Expect(apId.Subnet).To(Equal("10.0.0.0/16")) + Expect(apId.ChildSubnet).To(BeEmpty()) + }) + }) + + Context("Create a new pool ID when string has addrspace, subnet and child subnet", func() { + It("Should create a pool ID by parsing the string", func() { + s := "eth0|10.0.0.0/16|10.0.1.0/8" + apId, err := NewAddressPoolIdFromString(s) + Expect(apId).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + Expect(apId.AsId).To(Equal("eth0")) + Expect(apId.Subnet).To(Equal("10.0.0.0/16")) + Expect(apId.ChildSubnet).To(Equal("10.0.1.0/8")) + }) + }) + + Context("Returns the string representation of a pool ID with childSubnet", func() { + It("Should return string with asID|subnet|childsubnet", func() { + apId := &addressPoolId{ + AsId: "eth0", + Subnet: "10.0.0.0/16", + ChildSubnet: "10.0.1.0/8", + } + s := apId.String() + Expect(s).To(Equal("eth0|10.0.0.0/16|10.0.1.0/8")) + }) + }) + + Context("Returns the string representation of a pool ID without childSubnet", func() { + It("Should return a string without childSubnet", func() { + apId := &addressPoolId{ + AsId: "eth0", + Subnet: "10.0.0.0/16", + } + s := apId.String() + Expect(s).To(Equal("eth0|10.0.0.0/16")) + }) + }) + }) + + Describe("Test newAddressSpace", func() { + + am := &addressManager{} + + Context("When scope is LocalScope", func() { + It("Should create an addressSpace with LocalScope", func() { + asId := "local" + scope := LocalScope + as, err := am.newAddressSpace(asId, scope) + Expect(err).NotTo(HaveOccurred()) + Expect(as.Id).To(Equal(asId)) + Expect(as.Scope).To(Equal(scope)) + }) + }) + + Context("When scope is GlobalScope", func() { + It("Should create an addressSpace with GlobalScope", func() { + asId := "local" + scope := GlobalScope + as, err := am.newAddressSpace(asId, scope) + Expect(err).NotTo(HaveOccurred()) + Expect(as.Id).To(Equal(asId)) + Expect(as.Scope).To(Equal(scope)) + }) + }) + + Context("When scope is not GlobalScope or LocalScope", func() { + It("Should raise an error", func() { + asId := "local" + scope := 127 + as, err := am.newAddressSpace(asId, scope) + Expect(err).To(HaveOccurred()) + Expect(as).To(BeNil()) + }) + }) + }) + + Describe("Test getAddressSpace and setAddressSpace", func() { + + am := &addressManager{ + AddrSpaces: map[string]*addressSpace{}, + } + + Context("When addressSpace not exists", func() { + It("Should raise an error", func() { + as, err := am.getAddressSpace("lo") + Expect(err).To(Equal(errInvalidAddressSpace)) + Expect(as).To(BeNil()) + }) + }) + + Context("When addressSpace exists", func() { + It("Should return the addressSpace", func() { + asId := "local" + am.AddrSpaces[asId] = &addressSpace{Id: asId} + as, err := am.getAddressSpace(asId) + Expect(err).NotTo(HaveOccurred()) + Expect(as.Id).To(Equal(asId)) + }) + }) + }) + + Describe("Test setAddressSpace", func() { + + am := &addressManager{ + AddrSpaces: map[string]*addressSpace{}, + } + am.netApi = &testutils.NetApiMock{} + asId := "local" + + Context("When addressSpace not exists", func() { + It("Should be added to the am", func() { + err := am.setAddressSpace(&addressSpace{Id: asId}) + Expect(err).NotTo(HaveOccurred()) + Expect(am.AddrSpaces[asId].Id).To(Equal(asId)) + }) + }) + + Context("When addressSpace already exists", func() { + It("Should be merged", func() { + err := am.setAddressSpace(&addressSpace{Id: asId}) + Expect(err).NotTo(HaveOccurred()) + Expect(am.AddrSpaces[asId].Id).To(Equal(asId)) + }) + }) + }) + + Describe("Test merge", func() { + Context("When only new addressSpace contains the pool", func() { + It("The pool should be merged to the origin addressSpace", func() { + asId := "local" + epoch := 3 + poolId := "10.0.0.0/16" + originAs := &addressSpace{ + Id: asId, + epoch: epoch, + Pools: map[string]*addressPool{}, + } + newAs := &addressSpace{ + Id: asId, + Pools: map[string]*addressPool{}, + } + pool := &addressPool{ + Id: poolId, + as: originAs, + } + newAs.Pools[poolId] = pool + originAs.merge(newAs) + pool = originAs.Pools[poolId] + Expect(pool.as.Id).To(Equal(asId)) + Expect(pool.epoch).To(Equal(4)) + Expect(newAs.Pools[poolId]).To(BeNil()) + }) + }) + + Context("When only new addressSpace contains the addressRecord", func() { + It("The addressRecord should be merged to the origin addressSpace", func() { + asId := "local" + epoch := 3 + poolId := "10.0.0.0/16" + arId := "10.0.0.1/16" + + originAs := &addressSpace{ + Id: asId, + epoch: epoch, + Pools: map[string]*addressPool{}, + } + pool1 := &addressPool{ + Id: poolId, + as: originAs, + Addresses: map[string]*addressRecord{}, + } + originAs.Pools[poolId] = pool1 + + newAs := &addressSpace{ + Id: asId, + Pools: map[string]*addressPool{}, + } + pool2 := &addressPool{ + Id: poolId, + as: newAs, + Addresses: map[string]*addressRecord{}, + } + pool2.Addresses[arId] = &addressRecord{InUse: true} + newAs.Pools[poolId] = pool2 + originAs.merge(newAs) + pool1 = originAs.Pools[poolId] + Expect(pool1.as.Id).To(Equal(asId)) + ar := pool1.Addresses[arId] + Expect(ar.epoch).To(Equal(4)) + Expect(ar.InUse).To(BeTrue()) + Expect(newAs.Pools[poolId]).To(BeNil()) + }) + }) + + Context("When addressRecord is contained in both new addressSpace and origin addressSpace", func() { + It("The addressRecord of origin addressSpace should be updated", func() { + asId := "local" + epoch := 3 + poolId := "10.0.0.0/16" + arId := "10.0.0.1/16" + + originAs := &addressSpace{ + Id: asId, + epoch: epoch, + Pools: map[string]*addressPool{}, + } + pool1 := &addressPool{ + Id: poolId, + as: originAs, + Addresses: map[string]*addressRecord{}, + } + pool1.Addresses[arId] = &addressRecord{InUse: true, unhealthy: true} + originAs.Pools[poolId] = pool1 + + newAs := &addressSpace{ + Id: asId, + Pools: map[string]*addressPool{}, + } + pool2 := &addressPool{ + Id: poolId, + as: newAs, + Addresses: map[string]*addressRecord{}, + } + pool2.Addresses[arId] = &addressRecord{InUse: true} + newAs.Pools[poolId] = pool2 + originAs.merge(newAs) + pool1 = originAs.Pools[poolId] + Expect(pool1.as.Id).To(Equal(asId)) + ar := pool1.Addresses[arId] + Expect(ar.epoch).To(Equal(4)) + Expect(ar.InUse).To(BeTrue()) + Expect(ar.unhealthy).To(BeFalse()) + Expect(newAs.Pools[poolId]).To(BeNil()) + }) + }) + + Context("When addressRecord epoch is correct and pool epoch is less", func() { + It("Should update the pool epoch", func() { + asId := "local" + epoch := 3 + poolId := "10.0.0.0/16" + arId := "10.0.0.1/16" + + originAs := &addressSpace{ + Id: asId, + epoch: epoch, + Pools: map[string]*addressPool{}, + } + pool1 := &addressPool{ + Id: poolId, + as: originAs, + epoch: 3, + Addresses: map[string]*addressRecord{}, + } + pool1.Addresses[arId] = &addressRecord{ + epoch: 4, + InUse: true, + } + originAs.Pools[poolId] = pool1 + newAs := &addressSpace{ + Id: asId, + Pools: map[string]*addressPool{}, + } + originAs.merge(newAs) + pool1 = originAs.Pools[poolId] + Expect(pool1.epoch).To(Equal(4)) + ar := pool1.Addresses[arId] + Expect(ar.epoch).To(Equal(4)) + Expect(ar.InUse).To(BeTrue()) + Expect(ar.unhealthy).To(BeFalse()) + }) + }) + + Context("When addressRecord is in use", func() { + It("The addressRecord should be set to unhealthy", func() { + asId := "local" + epoch := 3 + poolId := "10.0.0.0/16" + arId := "10.0.0.1/16" + + originAs := &addressSpace{ + Id: asId, + epoch: epoch, + Pools: map[string]*addressPool{}, + } + pool1 := &addressPool{ + Id: poolId, + as: originAs, + epoch: 3, + Addresses: map[string]*addressRecord{}, + } + pool1.Addresses[arId] = &addressRecord{ + epoch: 3, + InUse: true, + } + originAs.Pools[poolId] = pool1 + newAs := &addressSpace{ + Id: asId, + Pools: map[string]*addressPool{}, + } + originAs.merge(newAs) + pool1 = originAs.Pools[poolId] + Expect(pool1.epoch).To(Equal(4)) + ar := pool1.Addresses[arId] + Expect(ar.epoch).To(Equal(3)) + Expect(ar.InUse).To(BeTrue()) + Expect(ar.unhealthy).To(BeTrue()) + }) + }) + + Context("When addressRecord is not in use but pool is in use", func() { + It("The addressRecord should be deleted", func() { + asId := "local" + epoch := 3 + poolId := "10.0.0.0/16" + arId := "10.0.0.1/16" + + originAs := &addressSpace{ + Id: asId, + epoch: epoch, + Pools: map[string]*addressPool{}, + } + pool := &addressPool{ + Id: poolId, + as: originAs, + epoch: 3, + RefCount: 1, + Addresses: map[string]*addressRecord{}, + } + pool.Addresses[arId] = &addressRecord{ + epoch: 3, + InUse: false, + } + originAs.Pools[poolId] = pool + newAs := &addressSpace{ + Id: asId, + Pools: map[string]*addressPool{}, + } + originAs.merge(newAs) + pool = originAs.Pools[poolId] + Expect(pool.epoch).To(Equal(3)) + ar := pool.Addresses[arId] + Expect(ar).To(BeNil()) + }) + }) + + Context("When pool is not in use", func() { + It("The pool should be deleted", func() { + asId := "local" + epoch := 3 + poolId := "10.0.0.0/16" + + originAs := &addressSpace{ + Id: asId, + epoch: epoch, + Pools: map[string]*addressPool{}, + } + pool := &addressPool{ + Id: poolId, + as: originAs, + epoch: 3, + RefCount: 0, + Addresses: map[string]*addressRecord{}, + } + originAs.Pools[poolId] = pool + newAs := &addressSpace{ + Id: asId, + Pools: map[string]*addressPool{}, + } + originAs.merge(newAs) + pool = originAs.Pools[poolId] + Expect(pool).To(BeNil()) + }) + }) + }) + + Describe("Test newAddressPool", func() { + Context("When pool already exists", func() { + It("Should raise an error", func() { + subnet := &net.IPNet{ + IP: net.IPv4(10, 0, 0, 1), + Mask: net.IPv4Mask(255, 255, 0, 0), + } + poolId := subnet.String() + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + as.Pools[poolId] = &addressPool{Id: poolId} + pool, err := as.newAddressPool("", 0, subnet) + Expect(err).To(Equal(errAddressPoolExists)) + Expect(pool.Id).To(Equal(poolId)) + }) + }) + + Context("When pool not exists", func() { + It("Should create pool successfully", func() { + subnet := &net.IPNet{ + IP: net.IPv4(10, 0, 0, 1), + Mask: net.IPv4Mask(255, 255, 0, 0), + } + poolId := subnet.String() + as := &addressSpace{ + Id: "local", + Pools: map[string]*addressPool{}, + } + pool, err := as.newAddressPool("local", 1, subnet) + Expect(err).NotTo(HaveOccurred()) + Expect(pool.Id).To(Equal(poolId)) + Expect(pool.as.Id).To(Equal(as.Id)) + Expect(pool.IfName).To(Equal("local")) + Expect(pool.Subnet.String()).To(Equal(poolId)) + Expect(pool.IsIPv6).To(BeFalse()) + Expect(pool.Priority).To(Equal(1)) + Expect(as.Pools[poolId]).NotTo(BeNil()) + }) + }) + + Context("When pool is ipv6", func() { + It("Should create pool successfully", func() { + subnet := &net.IPNet{ + IP: net.IPv6zero, + Mask: net.IPv6zero.DefaultMask(), + } + poolId := subnet.String() + as := &addressSpace{ + Id: "local", + Pools: map[string]*addressPool{}, + } + pool, err := as.newAddressPool("local", 1, subnet) + Expect(err).NotTo(HaveOccurred()) + Expect(pool.Id).To(Equal(poolId)) + Expect(pool.as.Id).To(Equal(as.Id)) + Expect(pool.IfName).To(Equal("local")) + Expect(pool.Subnet.String()).To(Equal(poolId)) + Expect(pool.IsIPv6).To(BeTrue()) + Expect(pool.Priority).To(Equal(1)) + Expect(as.Pools[poolId]).NotTo(BeNil()) + }) + }) + }) + + Describe("Test getAddressPool", func() { + Context("When pool not find", func() { + It("Should raise an error", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + pool, err := as.getAddressPool("10.0.0.0/16") + Expect(err).To(Equal(errInvalidPoolId)) + Expect(pool).To(BeNil()) + }) + }) + + Context("When pool is found", func() { + It("Should return the pool", func() { + poolId := "10.0.0.0/16" + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + as.Pools[poolId] = &addressPool{Id: poolId} + pool, err := as.getAddressPool(poolId) + Expect(err).NotTo(HaveOccurred()) + Expect(pool.Id).To(Equal(poolId)) + }) + }) + }) + + Describe("Test requestPool", func() { + Context("When poolId is explicitly specified and not found in addressSpace", func() { + It("Should raise an error", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + ap, err := as.requestPool("10.0.0.0/16", "", nil, false) + Expect(err).To(Equal(errAddressPoolNotFound)) + Expect(ap).To(BeNil()) + }) + }) + + Context("When poolId is explicitly specified and found in addressSpace", func() { + It("Should return the pool", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + poolId := "10.0.0.0/16" + as.Pools[poolId] = &addressPool{ + Id: poolId, + RefCount: 0, + } + ap, err := as.requestPool(poolId, "", nil, false) + Expect(err).NotTo(HaveOccurred()) + Expect(ap.Id).To(Equal(poolId)) + Expect(ap.RefCount).To(Equal(1)) + }) + }) + + Context("When pool is in use", func() { + It("Should raise an error", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + poolId := "10.0.0.0/16" + as.Pools[poolId] = &addressPool{ + Id: poolId, + RefCount: 1, + } + ap, err := as.requestPool("", "", nil, false) + Expect(err).To(Equal(errNoAvailableAddressPools)) + Expect(ap).To(BeNil()) + }) + }) + + Context("When pool is ipv4 and ipv6 is wanted", func() { + It("Should raise an error", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + poolId := "10.0.0.0/16" + as.Pools[poolId] = &addressPool{ + Id: poolId, + IsIPv6: false, + } + ap, err := as.requestPool("", "", nil, true) + Expect(err).To(Equal(errNoAvailableAddressPools)) + Expect(ap).To(BeNil()) + }) + }) + + Context("When the requested interface name does not exist", func() { + It("Should raise an error", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + poolId := "10.0.0.0/16" + ifName := "local" + as.Pools[poolId] = &addressPool{ + Id: poolId, + IfName: ifName, + } + options := map[string]string{} + options[OptInterfaceName] = "en0" + ap, err := as.requestPool("", "", options, false) + Expect(err).To(Equal(errNoAvailableAddressPools)) + Expect(ap).To(BeNil()) + }) + }) + + Context("When addressSpace has one pool available", func() { + It("Should return the pool", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + poolId := "10.0.0.0/16" + as.Pools[poolId] = &addressPool{ + Id: poolId, + } + ap, err := as.requestPool("", "", nil, false) + Expect(err).NotTo(HaveOccurred()) + Expect(ap.Id).To(Equal(poolId)) + Expect(ap.RefCount).To(Equal(1)) + }) + }) + + Context("When addressSpace has pools with different priority", func() { + It("Should return the pool with the highest priority", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + as.Pools["10.0.0.0/16"] = &addressPool{ + Id: "10.0.0.0/16", + Priority: 1, + } + as.Pools["10.1.0.0/16"] = &addressPool{ + Id: "10.1.0.0/16", + Priority: 2, + } + ap, err := as.requestPool("", "", nil, false) + Expect(err).NotTo(HaveOccurred()) + Expect(ap.Id).To(Equal("10.1.0.0/16")) + Expect(ap.RefCount).To(Equal(1)) + }) + }) + + Context("When addressSpace has pools with different addresses", func() { + It("Should return the pool with the highest number of addresses", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + as.Pools["10.0.0.0/16"] = &addressPool{ + Id: "10.0.0.0/16", + Addresses: map[string]*addressRecord{}, + } + as.Pools["10.1.0.0/16"] = &addressPool{ + Id: "10.1.0.0/16", + Addresses: map[string]*addressRecord{ + "10.1.0.1/16": &addressRecord{}, + }, + } + ap, err := as.requestPool("", "", nil, false) + Expect(err).NotTo(HaveOccurred()) + Expect(ap.Id).To(Equal("10.1.0.0/16")) + Expect(ap.RefCount).To(Equal(1)) + }) + }) + }) + + Describe("Test releasePool", func() { + Context("When pool not found", func() { + It("Should raise an error", func() { + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + err := as.releasePool("10.0.0.0/16") + Expect(err).To(Equal(errAddressPoolNotFound)) + }) + }) + + Context("When pool is not in use", func() { + It("Should raise an error", func() { + poolId := "10.0.0.0/16" + as := &addressSpace{ + Pools: map[string]*addressPool{}, + } + as.Pools[poolId] = &addressPool{ + RefCount: 0, + } + err := as.releasePool("10.0.0.0/16") + Expect(err).To(Equal(errAddressPoolNotInUse)) + Expect(as.Pools[poolId]).NotTo(BeNil()) + }) + }) + + Context("When pool's epoch is less than the space's epoch and pool is never in use", func() { + It("Should release the pool ", func() { + poolId := "10.0.0.0/16" + as := &addressSpace{ + epoch: 2, + Pools: map[string]*addressPool{}, + } + as.Pools[poolId] = &addressPool{ + epoch: 1, + RefCount: 1, + } + err := as.releasePool("10.0.0.0/16") + Expect(err).NotTo(HaveOccurred()) + Expect(as.Pools[poolId]).To(BeNil()) + }) + }) + + Context("When the epoch of pool is equal to the epoch of addressSpace", func() { + It("Should not delete the pool", func() { + poolId := "10.0.0.0/16" + as := &addressSpace{ + epoch: 1, + Pools: map[string]*addressPool{}, + } + as.Pools[poolId] = &addressPool{ + epoch: 1, + RefCount: 1, + } + err := as.releasePool("10.0.0.0/16") + Expect(err).NotTo(HaveOccurred()) + Expect(as.Pools[poolId]).NotTo(BeNil()) + }) + }) + + Context("When pool is still in use", func() { + It("Should not delete the pool", func() { + poolId := "10.0.0.0/16" + as := &addressSpace{ + epoch: 2, + Pools: map[string]*addressPool{}, + } + as.Pools[poolId] = &addressPool{ + epoch: 1, + RefCount: 2, + } + err := as.releasePool("10.0.0.0/16") + Expect(err).NotTo(HaveOccurred()) + Expect(as.Pools[poolId]).NotTo(BeNil()) + }) + }) + }) + + Describe("Test getInfo", func() { + Context("When addressRecord is not in use", func() { + It("Should add the available", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + } + ap.Addresses["10.0.0.1/16"] = &addressRecord{InUse: false} + ap.Addresses["10.0.0.2/16"] = &addressRecord{InUse: true} + ap.Addresses["10.0.0.3/16"] = &addressRecord{InUse: false} + apInfo := ap.getInfo() + Expect(apInfo.Available).To(Equal(2)) + }) + }) + + Context("When addressRecords are unhealthy", func() { + It("Should append the unhealthyAddrs", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + } + ap.Addresses["10.0.0.1/16"] = &addressRecord{ + unhealthy: true, + Addr: net.IPv4zero, + } + ap.Addresses["10.0.0.2/16"] = &addressRecord{ + unhealthy: false, + Addr: net.IPv4zero, + } + ap.Addresses["10.0.0.3/16"] = &addressRecord{ + unhealthy: true, + Addr: net.IPv4zero, + } + apInfo := ap.getInfo() + Expect(len(apInfo.UnhealthyAddrs)).To(Equal(2)) + }) + }) + }) + + Describe("Test isInUse", func() { + Context("When RefCount is set to some value", func() { + It("Should return true when RefCount > 0", func() { + ap := &addressPool{RefCount: 0} + Expect(ap.isInUse()).To(BeFalse()) + ap.RefCount = 1 + Expect(ap.isInUse()).To(BeTrue()) + ap.RefCount = 10000 + Expect(ap.isInUse()).To(BeTrue()) + ap.RefCount = -1 + Expect(ap.isInUse()).To(BeFalse()) + }) + }) + }) + + Describe("Test requestAddress", func() { + Context("When addressRecord not found", func() { + It("Should raise errAddressNotFound", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + } + addr, err := ap.requestAddress("10.0.0.1/16", nil) + Expect(err).To(Equal(errAddressNotFound)) + Expect(addr).To(BeEmpty()) + }) + }) + + Context("When addressRecord is in use and id match", func() { + It("Should return the same addressRecord", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + addrsByID: map[string]*addressRecord{}, + } + arId := "10.0.0.1/16" + ap.Addresses[arId] = &addressRecord{ + ID: arId, + InUse: true, + } + options := map[string]string{} + options[OptAddressID] = arId + addr, err := ap.requestAddress("10.0.0.1/16", options) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).NotTo(BeEmpty()) + Expect(ap.addrsByID[arId].ID).To(Equal(arId)) + }) + }) + + Context("When addressRecord is in use and id is empty", func() { + It("Should raise errAddressInUse", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + } + ap.Addresses["10.0.0.1/16"] = &addressRecord{InUse: true} + addr, err := ap.requestAddress("10.0.0.1/16", nil) + Expect(err).To(Equal(errAddressInUse)) + Expect(addr).To(BeEmpty()) + }) + }) + + Context("When addressRecord is in use and id is not equal to the addressRecord's id", func() { + It("Should raise errAddressInUse", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + } + arId := "10.0.0.1/16" + ap.Addresses[arId] = &addressRecord{ + ID: arId, + InUse: true, + } + options := map[string]string{} + options[OptAddressID] = "10.0.0.2/16" + addr, err := ap.requestAddress("10.0.0.1/16", options) + Expect(err).To(Equal(errAddressInUse)) + Expect(addr).To(BeEmpty()) + }) + }) + + Context("When OptAddressType is OptAddressTypeGateway", func() { + It("Should raise errAddressInUse", func() { + ap := &addressPool{ + Gateway: net.IPv4(10, 0, 0, 1), + } + options := map[string]string{} + options[OptAddressType] = OptAddressTypeGateway + addr, err := ap.requestAddress("", options) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).NotTo(BeEmpty()) + }) + }) + + Context("When id match", func() { + It("Should return the addressRecord with given id", func() { + ap := &addressPool{ + addrsByID: map[string]*addressRecord{}, + } + arId := "10.0.0.1/16" + ap.addrsByID[arId] = &addressRecord{ + ID: arId, + InUse: true, + } + options := map[string]string{} + options[OptAddressID] = arId + addr, err := ap.requestAddress("", options) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).NotTo(BeEmpty()) + Expect(ap.addrsByID[arId].ID).To(Equal(arId)) + }) + }) + + Context("When no available address", func() { + It("Should raise errNoAvailableAddresses", func() { + ap := &addressPool{ + addrsByID: map[string]*addressRecord{}, + } + // ar.InUse is true + ap.addrsByID["10.0.0.1/16"] = &addressRecord{ + ID: "", + InUse: true, + } + // ad.Id != "" + ap.addrsByID["10.0.0.2/16"] = &addressRecord{ + ID: "10.0.0.2/16", + InUse: false, + } + addr, err := ap.requestAddress("", nil) + Expect(err).To(Equal(errNoAvailableAddresses)) + Expect(addr).To(BeEmpty()) + }) + }) + + Context("Check if the InUse is set to true", func() { + It("InUse should be set to true", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + } + arId := "10.0.0.1/16" + ap.Addresses[arId] = &addressRecord{ + InUse: false, + } + addr, err := ap.requestAddress("", nil) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).NotTo(BeEmpty()) + Expect(ap.Addresses[arId].InUse).To(BeTrue()) + }) + }) + }) + + Describe("Test releaseAddress", func() { + Context("When address is equal to the gateway", func() { + It("Should return nil", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + Gateway: net.IPv4zero, + } + ar := ap.Gateway.String() + err := ap.releaseAddress(ar, nil) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When address is equal to the gateway", func() { + It("Should return nil", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + Gateway: net.IPv4zero, + } + ar := ap.Gateway.String() + err := ap.releaseAddress(ar, nil) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When addr is not in use", func() { + It("Should return nil", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + } + ap.Addresses["10.0.0.1/16"] = &addressRecord{InUse: false} + err := ap.releaseAddress("10.0.0.1/16", nil) + Expect(err).NotTo(HaveOccurred()) + Expect(ap.Addresses["10.0.0.1/16"]).NotTo(BeNil()) + }) + }) + + Context("When delete by id", func() { + It("Should return nil", func() { + ap := &addressPool{ + addrsByID: map[string]*addressRecord{}, + Addresses: map[string]*addressRecord{}, + as: &addressSpace{epoch: 1}, + } + arId := "10.0.0.1/16" + ap.addrsByID[arId] = &addressRecord{ + ID: arId, + Addr: net.IPv4zero, + InUse: true, + epoch: 1, + } + options := map[string]string{} + options[OptAddressID] = arId + err := ap.releaseAddress("", options) + Expect(err).NotTo(HaveOccurred()) + Expect(ap.addrsByID[arId]).To(BeNil()) + }) + }) + + Context("When delete by address", func() { + It("Should return nil", func() { + ap := &addressPool{ + Addresses: map[string]*addressRecord{}, + as: &addressSpace{epoch: 2}, + } + ap.Addresses["10.0.0.1/16"] = &addressRecord{ + InUse: true, + epoch: 1, + } + err := ap.releaseAddress("10.0.0.1/16", nil) + Expect(err).NotTo(HaveOccurred()) + Expect(ap.Addresses["10.0.0.1/16"]).To(BeNil()) + }) + }) + }) + }) +) diff --git a/network/endpoint_test.go b/network/endpoint_test.go index 4a07a2ff0a..a62ce74041 100644 --- a/network/endpoint_test.go +++ b/network/endpoint_test.go @@ -5,20 +5,198 @@ package network import ( "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) -func TestGetPodName(t *testing.T) { - testData := map[string]string{ - "nginx-deployment-5c689d88bb": "nginx", - "nginx-deployment-5c689d88bb-qwq47": "nginx-deployment", - "nginx": "nginx", - } +func TestEndpoint(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Endpoint Suite") +} - for testValue, expectedPodName := range testData { - podName := GetPodNameWithoutSuffix(testValue) +var ( + _ = Describe("Test Endpoint", func() { - if podName != expectedPodName { - t.Error("Expected:", expectedPodName, ", Got: ", podName, ", For Test Value:", testValue) - } - } -} + Describe("Test getEndpoint", func() { + + Context("When endpoint not exists", func() { + It("Should raise errEndpointNotFound", func() { + nw := &network{ + Endpoints: map[string]*endpoint{}, + } + ep, err := nw.getEndpoint("invalid") + Expect(err).To(Equal(errEndpointNotFound)) + Expect(ep).To(BeNil()) + }) + }) + + Context("When endpoint exists", func() { + It("Should return endpoint with no err", func() { + epId := "epId" + nw := &network{ + Endpoints: map[string]*endpoint{}, + } + nw.Endpoints[epId] = &endpoint{ + Id: epId, + } + ep, err := nw.getEndpoint(epId) + Expect(err).NotTo(HaveOccurred()) + Expect(ep.Id).To(Equal(epId)) + }) + }) + }) + + Describe("Test getEndpointByPOD", func() { + + Context("When multiple endpoints found", func() { + It("Should raise errMultipleEndpointsFound", func() { + podName := "test" + podNS := "ns" + nw := &network{ + Endpoints: map[string]*endpoint{}, + } + nw.Endpoints["pod1"] = &endpoint{ + PODName: podName, + PODNameSpace: podNS, + } + nw.Endpoints["pod2"] = &endpoint{ + PODName: podName, + PODNameSpace: podNS, + } + ep, err := nw.getEndpointByPOD(podName, podNS, true) + Expect(err).To(Equal(errMultipleEndpointsFound)) + Expect(ep).To(BeNil()) + }) + }) + + Context("When endpoint not found", func() { + It("Should raise errEndpointNotFound", func() { + nw := &network{ + Endpoints: map[string]*endpoint{}, + } + ep, err := nw.getEndpointByPOD("invalid", "", false) + Expect(err).To(Equal(errEndpointNotFound)) + Expect(ep).To(BeNil()) + }) + }) + + Context("When one endpoint found", func() { + It("Should return endpoint", func() { + podName := "test" + podNS := "ns" + nw := &network{ + Endpoints: map[string]*endpoint{}, + } + nw.Endpoints["pod"] = &endpoint{ + PODName: podName, + PODNameSpace: podNS, + } + ep, err := nw.getEndpointByPOD(podName, podNS, true) + Expect(err).NotTo(HaveOccurred()) + Expect(ep.PODName).To(Equal(podName)) + }) + }) + }) + + Describe("Test podNameMatches", func() { + + Context("When doExactMatch flag is set", func() { + It("Should exact match", func() { + actual := "nginx" + valid := "nginx" + invalid := "nginx-deployment-5c689d88bb" + Expect(podNameMatches(valid, actual, true)).To(BeTrue()) + Expect(podNameMatches(invalid, actual, true)).To(BeFalse()) + }) + }) + + Context("When doExactMatch flag is not set", func() { + It("Should not exact match", func() { + actual := "nginx" + valid1 := "nginx" + valid2 := "nginx-deployment-5c689d88bb" + invalid := "nginx-deployment-5c689d88bb-qwq47" + Expect(podNameMatches(valid1, actual, false)).To(BeTrue()) + Expect(podNameMatches(valid2, actual, false)).To(BeTrue()) + Expect(podNameMatches(invalid, actual, false)).To(BeFalse()) + }) + }) + }) + + Describe("Test attach", func() { + + Context("When SandboxKey in use", func() { + It("Should raise errEndpointInUse", func() { + ep := &endpoint{ + SandboxKey: "key", + } + err := ep.attach("") + Expect(err).To(Equal(errEndpointInUse)) + }) + }) + + Context("When SandboxKey not in use", func() { + It("Should set SandboxKey", func() { + sandboxKey := "key" + ep := &endpoint{} + err := ep.attach(sandboxKey) + Expect(err).NotTo(HaveOccurred()) + Expect(ep.SandboxKey).To(Equal(sandboxKey)) + }) + }) + }) + + Describe("Test detach", func() { + + Context("When SandboxKey not in use", func() { + It("Should raise errEndpointNotInUse", func() { + ep := &endpoint{} + err := ep.detach() + Expect(err).To(Equal(errEndpointNotInUse)) + }) + }) + + Context("When SandboxKey in use", func() { + It("Should set SandboxKey empty", func() { + ep := &endpoint{ + SandboxKey: "key", + } + err := ep.detach() + Expect(err).NotTo(HaveOccurred()) + Expect(ep.SandboxKey).To(BeEmpty()) + }) + }) + }) + + Describe("Test updateEndpoint", func() { + Context("When endpoint not found", func() { + It("Should raise errEndpointNotFound", func() { + nw := &network{} + existingEpInfo := &EndpointInfo{ + Id: "test", + } + targetEpInfo := &EndpointInfo{} + _, err := nw.updateEndpoint(existingEpInfo, targetEpInfo) + Expect(err).To(Equal(errEndpointNotFound)) + }) + }) + }) + + Describe("Test GetPodNameWithoutSuffix", func() { + Context("When podnames have suffix or not", func() { + It("Should return podname without suffix", func() { + testData := map[string]string{ + "nginx-deployment-5c689d88bb": "nginx", + "nginx-deployment-5c689d88bb-qwq47": "nginx-deployment", + "nginx": "nginx", + } + for testValue, expectedPodName := range testData { + podName := GetPodNameWithoutSuffix(testValue) + Expect(podName).To(Equal(expectedPodName)) + } + }) + }) + }) + }) +) diff --git a/network/manager_test.go b/network/manager_test.go new file mode 100644 index 0000000000..2038998be6 --- /dev/null +++ b/network/manager_test.go @@ -0,0 +1,234 @@ +package network + +import ( + "errors" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/Azure/azure-container-networking/store" + "github.com/Azure/azure-container-networking/testutils" +) + +func TestManager(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Manager Suite") +} + +var ( + _ = Describe("Test Manager", func() { + + Describe("Test deleteExternalInterface", func() { + + Context("When external interface not found", func() { + It("Should return nil", func() { + ifName := "eth0" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + err := nm.deleteExternalInterface(ifName) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When external interface found", func() { + It("Should delete external interface", func() { + ifName := "eth0" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{} + err := nm.deleteExternalInterface(ifName) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + + Describe("Test restore", func() { + + Context("When restore is nil", func() { + It("Should return nil", func() { + nm := &networkManager{} + err := nm.restore() + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When store.Read return ErrKeyNotFound", func() { + It("Should return nil", func() { + nm := &networkManager{ + store: &testutils.KeyValueStoreMock{ + ReadError: store.ErrKeyNotFound, + }, + } + err := nm.restore() + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When store.Read return error", func() { + It("Should raise error", func() { + nm := &networkManager{ + store: &testutils.KeyValueStoreMock{ + ReadError: errors.New("error for test"), + }, + } + err := nm.restore() + Expect(err).To(HaveOccurred()) + }) + }) + + Context("When GetModificationTime error and not rebooted", func() { + It("Should populate pointers", func() { + extIfName := "eth0" + nwId := "nwId" + nm := &networkManager{ + store: &testutils.KeyValueStoreMock{ + GetModificationTimeError: errors.New("error for test"), + }, + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[extIfName] = &externalInterface{ + Name: extIfName, + Networks: map[string]*network{}, + } + nm.ExternalInterfaces[extIfName].Networks[nwId] = &network{} + err := nm.restore() + Expect(err).NotTo(HaveOccurred()) + Expect(nm.ExternalInterfaces[extIfName].Networks[nwId].extIf.Name).To(Equal(extIfName)) + }) + }) + }) + + Describe("Test save", func() { + Context("When store is nil", func() { + It("Should return nil", func(){ + nm := &networkManager{} + err := nm.save() + Expect(err).NotTo(HaveOccurred()) + Expect(nm.TimeStamp).To(Equal(time.Time{})) + }) + }) + Context("When store.Write return error", func() { + It("Should raise error", func(){ + nm := &networkManager{ + store: &testutils.KeyValueStoreMock{ + WriteError: errors.New("error for test"), + }, + } + err := nm.save() + Expect(err).To(HaveOccurred()) + Expect(nm.TimeStamp).NotTo(Equal(time.Time{}))}) + }) + }) + + Describe("Test GetNumberOfEndpoints", func() { + + Context("When ExternalInterfaces is nil", func() { + It("Should return 0", func() { + nm := &networkManager{} + num := nm.GetNumberOfEndpoints("", "") + Expect(num).To(Equal(0)) + }) + }) + + Context("When extIf not found", func() { + It("Should return 0", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + num := nm.GetNumberOfEndpoints("eth0", "") + Expect(num).To(Equal(0)) + }) + }) + + Context("When Networks is nil", func() { + It("Should return 0", func() { + ifName := "eth0" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{} + num := nm.GetNumberOfEndpoints(ifName, "") + Expect(num).To(Equal(0)) + }) + }) + + Context("When network not found", func() { + It("Should return 0", func() { + ifName := "eth0" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{ + Networks: map[string]*network{}, + } + num := nm.GetNumberOfEndpoints(ifName, "nwId") + Expect(num).To(Equal(0)) + }) + }) + + Context("When endpoints is nil", func() { + It("Should return 0", func() { + ifName := "eth0" + nwId := "nwId" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{ + Networks: map[string]*network{}, + } + nm.ExternalInterfaces[ifName].Networks[nwId] = &network{} + num := nm.GetNumberOfEndpoints(ifName, nwId) + Expect(num).To(Equal(0)) + }) + }) + + Context("When endpoints is found", func() { + It("Should return the length of endpoints", func() { + ifName := "eth0" + nwId := "nwId" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{ + Networks: map[string]*network{}, + } + nm.ExternalInterfaces[ifName].Networks[nwId] = &network{ + Endpoints: map[string]*endpoint{ + "ep1":&endpoint{}, + "ep2":&endpoint{}, + "ep3":&endpoint{}, + }, + } + num := nm.GetNumberOfEndpoints(ifName, nwId) + Expect(num).To(Equal(3)) + }) + }) + + Context("When ifName not specifed in GetNumberofEndpoints", func() { + It("Should range the nm.ExternalInterfaces", func() { + ifName := "eth0" + nwId := "nwId" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{ + Networks: map[string]*network{}, + } + nm.ExternalInterfaces[ifName].Networks[nwId] = &network{ + Endpoints: map[string]*endpoint{ + "ep1":&endpoint{}, + "ep2":&endpoint{}, + "ep3":&endpoint{}, + }, + } + num := nm.GetNumberOfEndpoints("", nwId) + Expect(num).To(Equal(3)) + }) + }) + }) + }) +) diff --git a/network/network_test.go b/network/network_test.go new file mode 100644 index 0000000000..654f49dfb0 --- /dev/null +++ b/network/network_test.go @@ -0,0 +1,196 @@ +package network + +import ( + "net" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestNetwork(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Network Suite") +} + +var ( + _ = Describe("Test Network", func() { + + Describe("Test newExternalInterface", func() { + Context("When external interface already exists", func() { + It("Should return nil without update", func() { + ifName := "eth0" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{ + Name: ifName, + Subnets: []string{"10.0.0.0/16"}, + } + err := nm.newExternalInterface(ifName, "10.1.0.0/16") + Expect(err).To(BeNil()) + Expect(nm.ExternalInterfaces[ifName].Subnets).To(ContainElement("10.0.0.0/16")) + Expect(nm.ExternalInterfaces[ifName].Subnets).NotTo(ContainElement("10.1.0.0/16")) + }) + }) + }) + + Describe("Test deleteExternalInterface", func() { + Context("Delete external interface from network manager", func() { + It("Interface should be deleted", func() { + ifName := "eth0" + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces[ifName] = &externalInterface{ + Name: ifName, + Subnets: []string{"10.0.0.0/16", "10.1.0.0/16"}, + } + err := nm.deleteExternalInterface(ifName) + Expect(err).NotTo(HaveOccurred()) + Expect(nm.ExternalInterfaces[ifName]).To(BeNil()) + }) + }) + }) + + Describe("Test findExternalInterfaceBySubnet", func() { + Context("When subnet was found or nor found in external interfaces", func() { + It("Should return the external interface when found and nil when not found", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces["eth0"] = &externalInterface{ + Name: "eth0", + Subnets: []string{"subnet1", "subnet2"}, + } + nm.ExternalInterfaces["en0"] = &externalInterface{ + Name: "en0", + Subnets: []string{"subnet3", "subnet4"}, + } + exInterface := nm.findExternalInterfaceBySubnet("subnet4") + Expect(exInterface.Name).To(Equal("en0")) + exInterface = nm.findExternalInterfaceBySubnet("subnet0") + Expect(exInterface).To(BeNil()) + }) + }) + }) + + Describe("Test findExternalInterfaceByName", func() { + Context("When ifName found or nor found", func() { + It("Should return the external interface when found and nil when not found", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces["eth0"] = &externalInterface{ + Name: "eth0", + } + nm.ExternalInterfaces["en0"] = nil + exInterface := nm.findExternalInterfaceByName("eth0") + Expect(exInterface.Name).To(Equal("eth0")) + exInterface = nm.findExternalInterfaceByName("en0") + Expect(exInterface).To(BeNil()) + exInterface = nm.findExternalInterfaceByName("lo") + Expect(exInterface).To(BeNil()) + }) + }) + }) + + Describe("Test newNetwork", func() { + Context("When nwInfo.Mode is empty", func() { + It("Should set as defalut mode", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nwInfo := &NetworkInfo{ + MasterIfName: "eth0", + } + _, _ = nm.newNetwork(nwInfo) + Expect(nwInfo.Mode).To(Equal(opModeDefault)) + }) + }) + + Context("When extIf not found by name", func() { + It("Should raise errSubnetNotFound", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nwInfo := &NetworkInfo{ + MasterIfName: "eth0", + } + nw, err := nm.newNetwork(nwInfo) + Expect(err).To(Equal(errSubnetNotFound)) + Expect(nw).To(BeNil()) + }) + }) + + Context("When extIf not found by subnet", func() { + It("Should raise errSubnetNotFound", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nwInfo := &NetworkInfo{ + Subnets: []SubnetInfo{{ + Prefix: net.IPNet{ + IP: net.IPv4(10,0,0,1), + Mask: net.IPv4Mask(255,255,0,0), + }, + }}, + } + nw, err := nm.newNetwork(nwInfo) + Expect(err).To(Equal(errSubnetNotFound)) + Expect(nw).To(BeNil()) + }) + }) + + Context("When network already exist", func() { + It("Should raise errNetworkExists", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces["eth0"] = &externalInterface{ + Networks: map[string]*network{}, + } + nm.ExternalInterfaces["eth0"].Networks["nw"] = &network{} + nwInfo := &NetworkInfo{ + Id: "nw", + MasterIfName: "eth0", + } + nw, err := nm.newNetwork(nwInfo) + Expect(err).To(Equal(errNetworkExists)) + Expect(nw).To(BeNil()) + }) + }) + }) + + Describe("Test deleteNetwork", func() { + Context("When network not found", func() { + It("Should raise errNetworkNotFound", func() { + nm := &networkManager{} + err := nm.deleteNetwork("invalid") + Expect(err).To(Equal(errNetworkNotFound)) + }) + }) + }) + + Describe("Test getNetwork", func() { + Context("When network found or nor found", func() { + It("Should return the network when found and nil when not found", func() { + nm := &networkManager{ + ExternalInterfaces: map[string]*externalInterface{}, + } + nm.ExternalInterfaces["eth0"] = &externalInterface{ + Name: "eth0", + Networks: map[string]*network{}, + } + nm.ExternalInterfaces["eth0"].Networks["nw1"] = &network{} + nw, err := nm.getNetwork("nw1") + Expect(err).NotTo(HaveOccurred()) + Expect(nw).NotTo(BeNil()) + nw, err = nm.getNetwork("invalid") + Expect(err).To(Equal(errNetworkNotFound)) + Expect(nw).To(BeNil()) + }) + }) + }) + }) +) diff --git a/testutils/netapi_mock.go b/testutils/netapi_mock.go new file mode 100644 index 0000000000..bf3515ebfe --- /dev/null +++ b/testutils/netapi_mock.go @@ -0,0 +1,9 @@ +package testutils + +type NetApiMock struct { + err error +} + +func (netApi *NetApiMock) AddExternalInterface(ifName string, subnet string) error { + return netApi.err +}