From 1c611d425a72ac480e6b205c418d341639278998 Mon Sep 17 00:00:00 2001 From: kkumar Date: Mon, 10 Feb 2025 20:29:26 +0530 Subject: [PATCH 1/2] Add test for IPAM plugin --- internal/kubernetes/client.go | 4 + plugins/ipam/plugin_test.go | 271 ++++++++++++++++++++++++++++++++++ plugins/ipam/suite_test.go | 138 +++++++++++++++++ 3 files changed, 413 insertions(+) create mode 100644 plugins/ipam/plugin_test.go create mode 100644 plugins/ipam/suite_test.go diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 1ea3944..d61e770 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -44,3 +44,7 @@ func SetClient(client *client.Client) { func GetClient() client.Client { return kubeClient } func GetConfig() *rest.Config { return cfg } + +func SetConfig(c *rest.Config) { + cfg = c +} diff --git a/plugins/ipam/plugin_test.go b/plugins/ipam/plugin_test.go new file mode 100644 index 0000000..80b6245 --- /dev/null +++ b/plugins/ipam/plugin_test.go @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: MIT + +package ipam + +import ( + "net" + "os" + "strings" + + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/ironcore-dev/fedhcp/internal/api" + ipamv1alpha1 "github.com/ironcore-dev/ipam/api/ipam/v1alpha1" + "github.com/mdlayher/netx/eui64" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" +) + +var _ = Describe("IPAM Plugin", func() { + var ( + testConfigPath string + err error + linkLocalIPV6Addr net.IP + ipv6 *ipamv1alpha1.IP + ) + + ns := SetupTest() + + BeforeEach(func(ctx SpecContext) { + // Setup temporary test config file + testConfigPath = "test_config.yaml" + config := &api.IPAMConfig{ + Namespace: ns.Name, + Subnets: []string{"ipam-subnet1", "ipam-subnet2"}, + } + configData, err := yaml.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(testConfigPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + mac := machineWithIPAddressMACAddress + m, err := net.ParseMAC(mac) + Expect(err).NotTo(HaveOccurred()) + i := net.ParseIP(linkLocalIPV6Prefix) + linkLocalIPV6Addr, err = eui64.ParseMAC(i, m) + Expect(err).NotTo(HaveOccurred()) + + sanitizedMAC := strings.Replace(mac, ":", "", -1) + ipv6Addr, err := ipamv1alpha1.IPAddrFromString(linkLocalIPV6Addr.String()) + Expect(err).NotTo(HaveOccurred()) + ipv6 = &ipamv1alpha1.IP{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "test-", + Labels: map[string]string{ + "mac": sanitizedMAC, + }, + }, + Spec: ipamv1alpha1.IPSpec{ + Subnet: corev1.LocalObjectReference{ + Name: "foo", + }, + IP: ipv6Addr, + }, + } + Expect(k8sClientTest.Create(ctx, ipv6)).To(Succeed()) + DeferCleanup(k8sClientTest.Delete, ipv6) + + Eventually(UpdateStatus(ipv6, func() { + ipv6.Status.Reserved = ipv6Addr + })).Should(Succeed()) + }) + + AfterEach(func() { + // Cleanup temporary config file + err = os.Remove(testConfigPath) + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("Configuration Loading", func() { + It("should successfully load a valid configuration file", func() { + config, err := loadConfig(testConfigPath) + Expect(err).NotTo(HaveOccurred()) + Expect(config).NotTo(BeNil()) + Expect(config.Subnets[len(config.Subnets)-1]).To(Equal("ipam-subnet2")) + }) + + It("should return an error if the configuration file is missing", func() { + _, err := loadConfig("nonexistent.yaml") + Expect(err).To(HaveOccurred()) + }) + + It("should return an error if the configuration file is invalid", func() { + err = os.WriteFile(testConfigPath, []byte("Invalid YAML"), 0644) + Expect(err).NotTo(HaveOccurred()) + _, err = loadConfig(testConfigPath) + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("Plugin Setup6", func() { + It("should successfully initialize the plugin with a valid config", func() { + handler, err := setup6(testConfigPath) + Expect(err).NotTo(HaveOccurred()) + Expect(handler).NotTo(BeNil()) + }) + + It("should not return an error for empty ipam config", func() { + invalidConfig := &api.IPAMConfig{} + invalidConfigData, err := yaml.Marshal(invalidConfig) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(testConfigPath, invalidConfigData, 0644) + Expect(err).NotTo(HaveOccurred()) + + _, err = setup6(testConfigPath) + Expect(err).NotTo(HaveOccurred()) + }) + + It("Setup6 should return error if less arguments are provided", func() { + _, err := setup6() + Expect(err).To(HaveOccurred()) + }) + + It("Setup6 should return error if more arguments are provided", func() { + _, err := setup6("foo", "bar") + Expect(err).To(HaveOccurred()) + }) + + It("Setup6 should return error if config file does not exist", func() { + _, err := setup6("does-not-exist.yaml") + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("Plugin handler6", func() { + It("Should return and break plugin chain, if getting an IPv6 DHCP request directly (no relay)", func(ctx SpecContext) { + req, _ := dhcpv6.NewMessage() + req.MessageType = dhcpv6.MessageTypeRequest + + stub, _ := dhcpv6.NewMessage() + stub.MessageType = dhcpv6.MessageTypeReply + resp, breakChain := handler6(req, stub) + + Eventually(resp).Should(BeNil()) + Eventually(breakChain).Should(BeTrue()) + }) + + It("should successfully handle request", func() { + // //sanitizedMAC := strings.Replace(mac, ":", "", -1) + // ipv6Addr, _ := ipamv1alpha1.IPAddrFromString(linkLocalIPV6Addr.String()) + + // ipv6 := &ipamv1alpha1.IP{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: ns.Name, + // GenerateName: "test-", + // }, + // Spec: ipamv1alpha1.IPSpec{ + // Subnet: corev1.LocalObjectReference{ + // Name: "foo", + // }, + // IP: ipv6Addr, + // }, + // } + + // err3 := k8sClientTest.Create(context.TODO(), ipv6) + // Expect(err3).To(BeNil()) + + // createdSubnet := &ipamv1alpha1.Subnet{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "foo", + // Namespace: ns.Name, + // }, + // } + + // err5 := k8sClientTest.Create(context.TODO(), createdSubnet) + // Expect(err5).To(BeNil()) + + // subnet := &ipamv1alpha1.Subnet{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "foo", + // Namespace: ns.Name, + // }, + // } + // existingSubnet := subnet.DeepCopy() + // err4 := k8sClientTest.Get(context.TODO(), client.ObjectKeyFromObject(subnet), existingSubnet) + // Expect(err4).To(BeNil()) + + //Expect(k8sClientTest.Create(context.TODO(), ipv6)).To(Succeed()) + // clientset, err2 := ipam.NewForConfig(cfg) + // Expect(err2).NotTo(HaveOccurred()) + // createdSubnet, err1 := clientset.IpamV1alpha1().Subnets(ns.Name).Create(context.TODO(), subnet, v1.CreateOptions{}) + // Expect(err1).NotTo(HaveOccurred()) + // Expect(createdSubnet).NotTo(BeNil()) + + //fmt.Printf("createdSubnet: %v", createdSubnet) + + // mac, _ := net.ParseMAC(machineWithIPAddressMACAddress) + // ip := net.ParseIP(linkLocalIPV6Prefix) + // linkLocalIPV6Addr, _ := eui64.ParseMAC(ip, mac) + + req, _ := dhcpv6.NewMessage() + req.MessageType = dhcpv6.MessageTypeRequest + relayedRequest, _ := dhcpv6.EncapsulateRelay(req, dhcpv6.MessageTypeRelayForward, net.IPv6loopback, linkLocalIPV6Addr) + + stub, err := dhcpv6.NewMessage() + Expect(err).To(BeNil()) + resp, stop := handler6(relayedRequest, stub) + Expect(stop).To(BeFalse()) + Expect(resp).NotTo(BeNil()) + }) + }) + + Describe("K8s Client tests", func() { + It("should successfully match the subnet", func() { + k8sClient, err := NewK8sClient(ns.Name, []string{"foo"}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + subnet, err := k8sClient.getMatchingSubnet("foo", linkLocalIPV6Addr) + Expect(err).NotTo(HaveOccurred()) + Expect(subnet).To(BeNil()) + }) + + // It("should successfully match the subnet", func() { + // err := k8sClient.doCreateIpamIP(ipv6) + // Expect(err).NotTo(HaveOccurred()) + // }) + }) + + Describe("Common tests", func() { + It("return true checks the ip in CIDR", func() { + checkIP := checkIPv6InCIDR(linkLocalIPV6Addr, "fe80::/64") + Expect(checkIP).To(BeTrue()) + }) + + It("return false, if invalid CIDR", func() { + checkIP := checkIPv6InCIDR(linkLocalIPV6Addr, "fe80::") + Expect(checkIP).To(BeFalse()) + }) + + It("return formrted string, if valid ipv6", func() { + longIP := getLongIPv6(net.ParseIP("fe80::")) + Expect(longIP).To(Equal("fe80-0000-0000-0000-0000-0000-0000-0000")) + }) + + It("return panic, if invalid ipv6", func() { + Expect(func() { + getLongIPv6(net.ParseIP("fe80::bcd::ccd::bcd")) + }).To(Panic()) + }) + + It("return pretty fromated string for ipamv1alpha1.IPSpec", func() { + ipv6Addr, err := ipamv1alpha1.IPAddrFromString(linkLocalIPV6Addr.String()) + Expect(err).NotTo(HaveOccurred()) + ipv6 := &ipamv1alpha1.IP{ + Spec: ipamv1alpha1.IPSpec{ + Subnet: corev1.LocalObjectReference{ + Name: "foo", + }, + IP: ipv6Addr, + }, + } + format := prettyFormat(ipv6.Spec) + Expect(format).ShouldNot(BeEmpty()) + }) + }) +}) diff --git a/plugins/ipam/suite_test.go b/plugins/ipam/suite_test.go new file mode 100644 index 0000000..908e401 --- /dev/null +++ b/plugins/ipam/suite_test.go @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: MIT + +package ipam + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + "time" + + "github.com/ironcore-dev/controller-utils/modutils" + "github.com/ironcore-dev/fedhcp/internal/api" + "github.com/ironcore-dev/fedhcp/internal/kubernetes" + ipamv1alpha1 "github.com/ironcore-dev/ipam/api/ipam/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + //+kubebuilder:scaffold:imports +) + +const ( + pollingInterval = 50 * time.Millisecond + eventuallyTimeout = 3 * time.Second + consistentlyDuration = 1 * time.Second + unknownMachineMACAddress = "11:11:11:11:11:11" + linkLocalIPV6Prefix = "fe80::" + subnetLabel = "subnet=dhcp" + machineWithIPAddressMACAddress = "11:22:33:44:55:66" + privateIPV4Address = "192.168.47.11" + ipamConfigFile = "config.yaml" +) + +var ( + cfg *rest.Config + k8sClientTest client.Client + testEnv *envtest.Environment +) + +func TestBluefield(t *testing.T) { + SetDefaultConsistentlyPollingInterval(pollingInterval) + SetDefaultEventuallyPollingInterval(pollingInterval) + SetDefaultEventuallyTimeout(eventuallyTimeout) + SetDefaultConsistentlyDuration(consistentlyDuration) + RegisterFailHandler(Fail) + RunSpecs(t, "IPAM Plugin Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + modutils.Dir("github.com/ironcore-dev/ipam", "config", "crd", "bases"), + }, + ErrorIfCRDPathMissing: true, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.30.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + DeferCleanup(testEnv.Stop) + + Expect(ipamv1alpha1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + k8sClientTest, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClientTest).NotTo(BeNil()) + + // set komega client + SetClient(k8sClientTest) + + // assign global k8s client in plugin + kubernetes.SetClient(&k8sClientTest) + kubernetes.SetConfig(cfg) +}) + +func SetupTest() *corev1.Namespace { + ns := &corev1.Namespace{} + + BeforeEach(func(ctx SpecContext) { + *ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-", + }, + } + + Expect(k8sClientTest.Create(ctx, ns)).To(Succeed(), "failed to create test namespace") + DeferCleanup(k8sClientTest.Delete, ns) + + configFile := ipamConfigFile + data := &api.IPAMConfig{ + Namespace: ns.Name, + Subnets: []string{"ipam-subnet1", "ipam-subnet2"}, + } + + configData, err := yaml.Marshal(data) + Expect(err).NotTo(HaveOccurred()) + + file, err := os.CreateTemp(GinkgoT().TempDir(), configFile) + Expect(err).NotTo(HaveOccurred()) + defer func() { + _ = file.Close() + }() + Expect(os.WriteFile(file.Name(), configData, 0644)).To(Succeed()) + + config, err := loadConfig(file.Name()) + Expect(err).NotTo(HaveOccurred()) + Expect(config.Namespace).To(Equal(ns.Name)) + Expect(config.Subnets[0]).To(Equal("ipam-subnet1")) + }) + + return ns +} From af39eb4f5a6242cddffb7f4b0d2f82abd7286e92 Mon Sep 17 00:00:00 2001 From: kkumar Date: Tue, 11 Feb 2025 19:16:49 +0530 Subject: [PATCH 2/2] Setup test suite --- plugins/ipam/plugin_test.go | 228 ++++++++++++++++++++---------------- plugins/ipam/suite_test.go | 7 +- 2 files changed, 133 insertions(+), 102 deletions(-) diff --git a/plugins/ipam/plugin_test.go b/plugins/ipam/plugin_test.go index 80b6245..f10e4f9 100644 --- a/plugins/ipam/plugin_test.go +++ b/plugins/ipam/plugin_test.go @@ -42,36 +42,15 @@ var _ = Describe("IPAM Plugin", func() { err = os.WriteFile(testConfigPath, configData, 0644) Expect(err).NotTo(HaveOccurred()) - mac := machineWithIPAddressMACAddress - m, err := net.ParseMAC(mac) - Expect(err).NotTo(HaveOccurred()) - i := net.ParseIP(linkLocalIPV6Prefix) - linkLocalIPV6Addr, err = eui64.ParseMAC(i, m) - Expect(err).NotTo(HaveOccurred()) + ipv6, linkLocalIPV6Addr = createTestIPAMIP(machineWithIPAddressMACAddress, *ns) + Expect(ipv6).NotTo(BeNil()) + Expect(linkLocalIPV6Addr).NotTo(BeNil()) - sanitizedMAC := strings.Replace(mac, ":", "", -1) - ipv6Addr, err := ipamv1alpha1.IPAddrFromString(linkLocalIPV6Addr.String()) - Expect(err).NotTo(HaveOccurred()) - ipv6 = &ipamv1alpha1.IP{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: ns.Name, - GenerateName: "test-", - Labels: map[string]string{ - "mac": sanitizedMAC, - }, - }, - Spec: ipamv1alpha1.IPSpec{ - Subnet: corev1.LocalObjectReference{ - Name: "foo", - }, - IP: ipv6Addr, - }, - } Expect(k8sClientTest.Create(ctx, ipv6)).To(Succeed()) DeferCleanup(k8sClientTest.Delete, ipv6) Eventually(UpdateStatus(ipv6, func() { - ipv6.Status.Reserved = ipv6Addr + ipv6.Status.Reserved = ipv6.Spec.IP })).Should(Succeed()) }) @@ -109,7 +88,7 @@ var _ = Describe("IPAM Plugin", func() { Expect(handler).NotTo(BeNil()) }) - It("should not return an error for empty ipam config", func() { + It("should not return an error for empty IPAM config", func() { invalidConfig := &api.IPAMConfig{} invalidConfigData, err := yaml.Marshal(invalidConfig) Expect(err).NotTo(HaveOccurred()) @@ -150,87 +129,18 @@ var _ = Describe("IPAM Plugin", func() { }) It("should successfully handle request", func() { - // //sanitizedMAC := strings.Replace(mac, ":", "", -1) - // ipv6Addr, _ := ipamv1alpha1.IPAddrFromString(linkLocalIPV6Addr.String()) - - // ipv6 := &ipamv1alpha1.IP{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: ns.Name, - // GenerateName: "test-", - // }, - // Spec: ipamv1alpha1.IPSpec{ - // Subnet: corev1.LocalObjectReference{ - // Name: "foo", - // }, - // IP: ipv6Addr, - // }, - // } - - // err3 := k8sClientTest.Create(context.TODO(), ipv6) - // Expect(err3).To(BeNil()) - - // createdSubnet := &ipamv1alpha1.Subnet{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "foo", - // Namespace: ns.Name, - // }, - // } - - // err5 := k8sClientTest.Create(context.TODO(), createdSubnet) - // Expect(err5).To(BeNil()) - - // subnet := &ipamv1alpha1.Subnet{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "foo", - // Namespace: ns.Name, - // }, - // } - // existingSubnet := subnet.DeepCopy() - // err4 := k8sClientTest.Get(context.TODO(), client.ObjectKeyFromObject(subnet), existingSubnet) - // Expect(err4).To(BeNil()) - - //Expect(k8sClientTest.Create(context.TODO(), ipv6)).To(Succeed()) - // clientset, err2 := ipam.NewForConfig(cfg) - // Expect(err2).NotTo(HaveOccurred()) - // createdSubnet, err1 := clientset.IpamV1alpha1().Subnets(ns.Name).Create(context.TODO(), subnet, v1.CreateOptions{}) - // Expect(err1).NotTo(HaveOccurred()) - // Expect(createdSubnet).NotTo(BeNil()) - - //fmt.Printf("createdSubnet: %v", createdSubnet) - - // mac, _ := net.ParseMAC(machineWithIPAddressMACAddress) - // ip := net.ParseIP(linkLocalIPV6Prefix) - // linkLocalIPV6Addr, _ := eui64.ParseMAC(ip, mac) - req, _ := dhcpv6.NewMessage() req.MessageType = dhcpv6.MessageTypeRequest relayedRequest, _ := dhcpv6.EncapsulateRelay(req, dhcpv6.MessageTypeRelayForward, net.IPv6loopback, linkLocalIPV6Addr) stub, err := dhcpv6.NewMessage() - Expect(err).To(BeNil()) + Expect(err).ToNot(HaveOccurred()) resp, stop := handler6(relayedRequest, stub) Expect(stop).To(BeFalse()) Expect(resp).NotTo(BeNil()) }) }) - Describe("K8s Client tests", func() { - It("should successfully match the subnet", func() { - k8sClient, err := NewK8sClient(ns.Name, []string{"foo"}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - - subnet, err := k8sClient.getMatchingSubnet("foo", linkLocalIPV6Addr) - Expect(err).NotTo(HaveOccurred()) - Expect(subnet).To(BeNil()) - }) - - // It("should successfully match the subnet", func() { - // err := k8sClient.doCreateIpamIP(ipv6) - // Expect(err).NotTo(HaveOccurred()) - // }) - }) - Describe("Common tests", func() { It("return true checks the ip in CIDR", func() { checkIP := checkIPv6InCIDR(linkLocalIPV6Addr, "fe80::/64") @@ -242,7 +152,7 @@ var _ = Describe("IPAM Plugin", func() { Expect(checkIP).To(BeFalse()) }) - It("return formrted string, if valid ipv6", func() { + It("return formatted string, if valid ipv6", func() { longIP := getLongIPv6(net.ParseIP("fe80::")) Expect(longIP).To(Equal("fe80-0000-0000-0000-0000-0000-0000-0000")) }) @@ -253,7 +163,7 @@ var _ = Describe("IPAM Plugin", func() { }).To(Panic()) }) - It("return pretty fromated string for ipamv1alpha1.IPSpec", func() { + It("return pretty formatted string for ipamv1alpha1.IPSpec", func() { ipv6Addr, err := ipamv1alpha1.IPAddrFromString(linkLocalIPV6Addr.String()) Expect(err).NotTo(HaveOccurred()) ipv6 := &ipamv1alpha1.IP{ @@ -269,3 +179,125 @@ var _ = Describe("IPAM Plugin", func() { }) }) }) + +var _ = Describe("K8s Client tests", func() { + var ( + linkLocalIPV6Addr net.IP + ipv6 *ipamv1alpha1.IP + k8sClient *K8sClient + err error + ) + + ns := SetupTest() + + BeforeEach(func() { + By("creating an IPAM IP") + ipv6, linkLocalIPV6Addr = createTestIPAMIP(machineWithMacAddress, *ns) + Expect(ipv6).NotTo(BeNil()) + Expect(linkLocalIPV6Addr).NotTo(BeNil()) + + k8sClient, err = NewK8sClient(ns.Name, []string{"foo"}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + }) + + It("should successfully match the subnet", func() { + subnet, err := k8sClient.getMatchingSubnet("foo", linkLocalIPV6Addr) + Expect(err).NotTo(HaveOccurred()) + Expect(subnet).To(BeNil()) + }) + + It("should return error if subnet not matched", func() { + subnet, err := k8sClient.getMatchingSubnet("random-subnet", linkLocalIPV6Addr) + Expect(err).ToNot(HaveOccurred()) + Expect(subnet).To(BeNil()) + }) + + It("should return (nil, nil) if CIDR mismatch", func() { + subnet, err := k8sClient.getMatchingSubnet("foo", net.IP("11:22:33:44")) + Expect(err).ToNot(HaveOccurred()) + Expect(subnet).To(BeNil()) + }) + + It("should successfully return IPAM IP for machine with mac address", func() { + ip, err := k8sClient.prepareCreateIpamIP("foo", linkLocalIPV6Addr, net.HardwareAddr(machineWithIPAddressMACAddress)) + Expect(err).NotTo(HaveOccurred()) + Expect(ip).NotTo(BeNil()) + }) + + It("should return error failed to parse IP if invalid ip prefix", func() { + m, err := net.ParseMAC(machineWithIPAddressMACAddress) + Expect(err).NotTo(HaveOccurred()) + i := net.ParseIP("fe80??::") + linkLocalIPV6Addr, err := eui64.ParseMAC(i, m) + Expect(err).To(HaveOccurred()) + + ip, err := k8sClient.prepareCreateIpamIP("foo", linkLocalIPV6Addr, net.HardwareAddr(machineWithIPAddressMACAddress)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to parse IP")) + Expect(ip).To(BeNil()) + }) + + It("should successfully return with (nil, nil) if IP already exists in subnet", func() { + m, err := net.ParseMAC(machineWithIPAddressMACAddress) + Expect(err).NotTo(HaveOccurred()) + i := net.ParseIP("fe80::") + linkLocalIPV6Addr, err := eui64.ParseMAC(i, m) + Expect(err).NotTo(HaveOccurred()) + + ip, err := k8sClient.prepareCreateIpamIP("foo", linkLocalIPV6Addr, net.HardwareAddr(machineWithIPAddressMACAddress)) + Expect(err).NotTo(HaveOccurred()) + Expect(ip).NotTo(BeNil()) + + createIPError := k8sClient.doCreateIpamIP(ip) + Expect(createIPError).NotTo(HaveOccurred()) + + sameip, err := k8sClient.prepareCreateIpamIP("foo", linkLocalIPV6Addr, net.HardwareAddr(machineWithIPAddressMACAddress)) + Expect(err).ToNot(HaveOccurred()) + Expect(sameip).To(BeNil()) + }) + + It("should successfully create IPAM IP for machine with mac address", func() { + createIPError := k8sClient.doCreateIpamIP(ipv6) + Expect(createIPError).NotTo(HaveOccurred()) + }) + + It("should successfully create IPAM IP for machine", func() { + createIPError := k8sClient.createIpamIP(linkLocalIPV6Addr, net.HardwareAddr(machineWithIPAddressMACAddress)) + Expect(createIPError).NotTo(HaveOccurred()) + }) + + It("should return timeout error, if IPAM IP not deleted", func() { + err := k8sClient.waitForDeletion(ipv6) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("timeout reached, IP not deleted")) + }) +}) + +func createTestIPAMIP(mac string, ns corev1.Namespace) (*ipamv1alpha1.IP, net.IP) { + m, err := net.ParseMAC(mac) + Expect(err).NotTo(HaveOccurred()) + i := net.ParseIP(linkLocalIPV6Prefix) + linkLocalIPV6Addr, err := eui64.ParseMAC(i, m) + Expect(err).NotTo(HaveOccurred()) + + sanitizedMAC := strings.Replace(mac, ":", "", -1) + ipv6Addr, err := ipamv1alpha1.IPAddrFromString(linkLocalIPV6Addr.String()) + Expect(err).NotTo(HaveOccurred()) + ipv6 := &ipamv1alpha1.IP{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: "test-ip", + Labels: map[string]string{ + "mac": sanitizedMAC, + }, + }, + Spec: ipamv1alpha1.IPSpec{ + Subnet: corev1.LocalObjectReference{ + Name: "foo", + }, + IP: ipv6Addr, + }, + } + return ipv6, linkLocalIPV6Addr +} diff --git a/plugins/ipam/suite_test.go b/plugins/ipam/suite_test.go index 908e401..99809c4 100644 --- a/plugins/ipam/suite_test.go +++ b/plugins/ipam/suite_test.go @@ -34,11 +34,9 @@ const ( pollingInterval = 50 * time.Millisecond eventuallyTimeout = 3 * time.Second consistentlyDuration = 1 * time.Second - unknownMachineMACAddress = "11:11:11:11:11:11" linkLocalIPV6Prefix = "fe80::" - subnetLabel = "subnet=dhcp" machineWithIPAddressMACAddress = "11:22:33:44:55:66" - privateIPV4Address = "192.168.47.11" + machineWithMacAddress = "11:22:33:44:55:77" ipamConfigFile = "config.yaml" ) @@ -73,7 +71,7 @@ var _ = BeforeSuite(func() { // Note that you must have the required binaries setup under the bin directory to perform // the tests directly. When we run make test it will be setup and used automatically. BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", - fmt.Sprintf("1.30.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + fmt.Sprintf("1.32.0-%s-%s", runtime.GOOS, runtime.GOARCH)), } var err error @@ -132,6 +130,7 @@ func SetupTest() *corev1.Namespace { Expect(err).NotTo(HaveOccurred()) Expect(config.Namespace).To(Equal(ns.Name)) Expect(config.Subnets[0]).To(Equal("ipam-subnet1")) + Expect(config.Subnets[1]).To(Equal("ipam-subnet2")) }) return ns