From 8feef71fd3cb32ff9a6bf3a377837486872f86aa Mon Sep 17 00:00:00 2001 From: yaoice Date: Wed, 13 Jan 2021 11:24:59 +0800 Subject: [PATCH] add flannel to support dual stack ip support three mode ip stack: - only ipv4 stack - only ipv6 stack - dual stack ip Signed-off-by: yaoice --- plugins/meta/flannel/flannel.go | 22 +- plugins/meta/flannel/flannel_linux.go | 24 +- plugins/meta/flannel/flannel_linux_test.go | 584 +++++++++++++++++---- 3 files changed, 524 insertions(+), 106 deletions(-) diff --git a/plugins/meta/flannel/flannel.go b/plugins/meta/flannel/flannel.go index b3f4af9e..5d07efaf 100644 --- a/plugins/meta/flannel/flannel.go +++ b/plugins/meta/flannel/flannel.go @@ -57,6 +57,8 @@ type NetConf struct { type subnetEnv struct { nw *net.IPNet sn *net.IPNet + ip6Nw *net.IPNet + ip6Sn *net.IPNet mtu *uint ipmasq *bool } @@ -64,11 +66,11 @@ type subnetEnv struct { func (se *subnetEnv) missing() string { m := []string{} - if se.nw == nil { - m = append(m, "FLANNEL_NETWORK") + if se.nw == nil && se.ip6Nw == nil { + m = append(m, []string{"FLANNEL_NETWORK", "FLANNEL_IPV6_NETWORK"}...) } - if se.sn == nil { - m = append(m, "FLANNEL_SUBNET") + if se.sn == nil && se.ip6Sn == nil { + m = append(m, []string{"FLANNEL_SUBNET", "FLANNEL_IPV6_SUBNET"}...) } if se.mtu == nil { m = append(m, "FLANNEL_MTU") @@ -128,6 +130,18 @@ func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) { return nil, err } + case "FLANNEL_IPV6_NETWORK": + _, se.ip6Nw, err = net.ParseCIDR(parts[1]) + if err != nil { + return nil, err + } + + case "FLANNEL_IPV6_SUBNET": + _, se.ip6Sn, err = net.ParseCIDR(parts[1]) + if err != nil { + return nil, err + } + case "FLANNEL_MTU": mtu, err := strconv.ParseUint(parts[1], 10, 32) if err != nil { diff --git a/plugins/meta/flannel/flannel_linux.go b/plugins/meta/flannel/flannel_linux.go index 4f9906ee..7d1a806e 100644 --- a/plugins/meta/flannel/flannel_linux.go +++ b/plugins/meta/flannel/flannel_linux.go @@ -40,13 +40,33 @@ func getDelegateIPAM(n *NetConf, fenv *subnetEnv) (map[string]interface{}, error if !hasKey(ipam, "type") { ipam["type"] = "host-local" } - ipam["subnet"] = fenv.sn.String() + + var rangesSlice [][]map[string]interface{} + + if fenv.sn != nil && fenv.sn.String() != "" { + rangesSlice = append(rangesSlice, []map[string]interface{}{ + {"subnet": fenv.sn.String()}, + }, + ) + } + if fenv.ip6Sn != nil && fenv.ip6Sn.String() != "" { + rangesSlice = append(rangesSlice, []map[string]interface{}{ + {"subnet": fenv.ip6Sn.String()}, + }, + ) + } + ipam["ranges"] = rangesSlice rtes, err := getIPAMRoutes(n) if err != nil { return nil, fmt.Errorf("failed to read IPAM routes: %w", err) } - rtes = append(rtes, types.Route{Dst: *fenv.nw}) + if fenv.nw != nil { + rtes = append(rtes, types.Route{Dst: *fenv.nw}) + } + if fenv.ip6Nw != nil { + rtes = append(rtes, types.Route{Dst: *fenv.ip6Nw}) + } ipam["routes"] = rtes return ipam, nil diff --git a/plugins/meta/flannel/flannel_linux_test.go b/plugins/meta/flannel/flannel_linux_test.go index 83abdefd..a3b68e37 100644 --- a/plugins/meta/flannel/flannel_linux_test.go +++ b/plugins/meta/flannel/flannel_linux_test.go @@ -30,10 +30,14 @@ import ( var _ = Describe("Flannel", func() { var ( - originalNS ns.NetNS - input string - subnetFile string - dataDir string + originalNS ns.NetNS + onlyIpv4Input string + onlyIpv6Input string + dualStackInput string + onlyIpv4SubnetFile string + onlyIpv6SubnetFile string + dualStackSubnetFile string + dataDir string ) BeforeEach(func() { @@ -48,10 +52,10 @@ var _ = Describe("Flannel", func() { const inputTemplate = ` { - "name": "cni-flannel", - "type": "flannel", - "subnetFile": "%s", - "dataDir": "%s"%s + "name": "cni-flannel", + "type": "flannel", + "subnetFile": "%s", + "dataDir": "%s"%s }` const inputIPAMTemplate = ` @@ -70,11 +74,25 @@ var _ = Describe("Flannel", func() { { "dst": "10.96.0.0/12" }, { "dst": "192.168.244.0/24", "gw": "10.1.17.20" }` - const flannelSubnetEnv = ` + const onlyIpv4FlannelSubnetEnv = ` FLANNEL_NETWORK=10.1.0.0/16 FLANNEL_SUBNET=10.1.17.1/24 FLANNEL_MTU=1472 FLANNEL_IPMASQ=true +` + const onlyIpv6FlannelSubnetEnv = ` +FLANNEL_IPV6_NETWORK=fc00::/48 +FLANNEL_IPV6_SUBNET=fc00::1/64 +FLANNEL_MTU=1472 +FLANNEL_IPMASQ=true +` + const dualStackFlannelSubnetEnv = ` +FLANNEL_NETWORK=10.1.0.0/16 +FLANNEL_SUBNET=10.1.17.1/24 +FLANNEL_IPV6_NETWORK=fc00::/48 +FLANNEL_IPV6_SUBNET=fc00::1/64 +FLANNEL_MTU=1472 +FLANNEL_IPMASQ=true ` var writeSubnetEnv = func(contents string) string { @@ -96,7 +114,7 @@ FLANNEL_IPMASQ=true return c } - var makeInput = func(inputIPAM string) string { + var makeInput = func(inputIPAM string, subnetFile string) string { ipamPart := "" if len(inputIPAM) > 0 { ipamPart = ",\n \"ipam\":\n" + inputIPAM @@ -108,116 +126,350 @@ FLANNEL_IPMASQ=true BeforeEach(func() { var err error // flannel subnet.env - subnetFile = writeSubnetEnv(flannelSubnetEnv) + onlyIpv4SubnetFile = writeSubnetEnv(onlyIpv4FlannelSubnetEnv) + onlyIpv6SubnetFile = writeSubnetEnv(onlyIpv6FlannelSubnetEnv) + dualStackSubnetFile = writeSubnetEnv(dualStackFlannelSubnetEnv) // flannel state dir dataDir, err = ioutil.TempDir("", "dataDir") Expect(err).NotTo(HaveOccurred()) - input = makeInput("") + onlyIpv4Input = makeInput("", onlyIpv4SubnetFile) + onlyIpv6Input = makeInput("", onlyIpv6SubnetFile) + dualStackInput = makeInput("", dualStackSubnetFile) }) AfterEach(func() { - os.Remove(subnetFile) + os.Remove(onlyIpv4SubnetFile) + os.Remove(onlyIpv6SubnetFile) + os.Remove(dualStackSubnetFile) os.Remove(dataDir) }) Describe("CNI lifecycle", func() { - It("uses dataDir for storing network configuration", func() { - const IFNAME = "eth0" + Context("when using only ipv4 stack", func() { + It("uses dataDir for storing network configuration with ipv4 stack", func() { + const IFNAME = "eth0" - targetNs, err := testutils.NewNS() - Expect(err).NotTo(HaveOccurred()) - defer targetNs.Close() - - args := &skel.CmdArgs{ - ContainerID: "some-container-id", - Netns: targetNs.Path(), - IfName: IFNAME, - StdinData: []byte(input), - } - - err = originalNS.Do(func(ns.NetNS) error { - defer GinkgoRecover() - - By("calling ADD") - resI, _, err := testutils.CmdAddWithArgs(args, func() error { - return cmdAdd(args) - }) + targetNs, err := testutils.NewNS() Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() - By("check that plugin writes the net config to dataDir") - path := fmt.Sprintf("%s/%s", dataDir, "some-container-id") - Expect(path).Should(BeAnExistingFile()) + args := &skel.CmdArgs{ + ContainerID: "some-container-id-ipv4", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(onlyIpv4Input), + } - netConfBytes, err := ioutil.ReadFile(path) - Expect(err).NotTo(HaveOccurred()) - expected := `{ - "ipMasq" : false, - "ipam" : { - "routes" : [ - { - "dst" : "10.1.0.0/16" - } - ], - "subnet" : "10.1.17.0/24", - "type" : "host-local" - }, - "isGateway": true, - "mtu" : 1472, - "name" : "cni-flannel", - "type" : "bridge" + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + By("calling ADD with ipv4 stack") + resI, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + By("check that plugin writes the net config to dataDir with ipv4 stack") + path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-ipv4") + Expect(path).Should(BeAnExistingFile()) + + netConfBytes, err := ioutil.ReadFile(path) + Expect(err).NotTo(HaveOccurred()) + expected := `{ + "ipMasq": false, + "ipam": { + "routes": [ + { + "dst": "10.1.0.0/16" + } + ], + "ranges": [ + [{ + "subnet": "10.1.17.0/24" + }] + ], + "type": "host-local" + }, + "isGateway": true, + "mtu": 1472, + "name": "cni-flannel", + "type": "bridge" } ` - Expect(netConfBytes).Should(MatchJSON(expected)) + Expect(netConfBytes).Should(MatchJSON(expected)) - result, err := current.NewResultFromResult(resI) - Expect(err).NotTo(HaveOccurred()) - Expect(result.IPs).To(HaveLen(1)) + result, err := current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IPs).To(HaveLen(1)) - By("calling DEL") - err = testutils.CmdDelWithArgs(args, func() error { - return cmdDel(args) + By("calling DEL with ipv4 stack") + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + + By("check that plugin removes net config from state dir with ipv4 stack") + Expect(path).ShouldNot(BeAnExistingFile()) + + By("calling DEL again with ipv4 stack") + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + By("check that plugin does not fail due to missing net config with ipv4 stack") + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("when using only ipv6 stack", func() { + It("uses dataDir for storing network configuration with ipv6 stack", func() { + const IFNAME = "eth0" + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "some-container-id-ipv6", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(onlyIpv6Input), + } + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + By("calling ADD with ipv6 stack") + resI, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + By("check that plugin writes the net config to dataDir with ipv6 stack") + path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-ipv6") + Expect(path).Should(BeAnExistingFile()) + + netConfBytes, err := ioutil.ReadFile(path) + Expect(err).NotTo(HaveOccurred()) + expected := `{ + "ipMasq": false, + "ipam": { + "routes": [ + { + "dst": "fc00::/48" + } + ], + "ranges": [ + [{ + "subnet": "fc00::/64" + }] + ], + "type": "host-local" + }, + "isGateway": true, + "mtu": 1472, + "name": "cni-flannel", + "type": "bridge" +} +` + Expect(netConfBytes).Should(MatchJSON(expected)) + + result, err := current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IPs).To(HaveLen(1)) + + By("calling DEL with ipv6 stack") + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + + By("check that plugin removes net config from state dir with ipv6 stack") + Expect(path).ShouldNot(BeAnExistingFile()) + + By("calling DEL again with ipv6 stack") + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + By("check that plugin does not fail due to missing net config with ipv6 stack") + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("when using dual stack", func() { + It("uses dataDir for storing network configuration with dual stack", func() { + const IFNAME = "eth0" + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "some-container-id-dual-stack", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(dualStackInput), + } + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + By("calling ADD with dual stack") + resI, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + By("check that plugin writes the net config to dataDir with dual stack") + path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-dual-stack") + Expect(path).Should(BeAnExistingFile()) + + netConfBytes, err := ioutil.ReadFile(path) + Expect(err).NotTo(HaveOccurred()) + expected := `{ + "ipMasq": false, + "ipam": { + "routes": [ + { + "dst": "10.1.0.0/16" + }, + { + "dst": "fc00::/48" + } + ], + "ranges": [ + [{ + "subnet": "10.1.17.0/24" + }], + [{ + "subnet": "fc00::/64" + }] + ], + "type": "host-local" + }, + "isGateway": true, + "mtu": 1472, + "name": "cni-flannel", + "type": "bridge" +} +` + Expect(netConfBytes).Should(MatchJSON(expected)) + + result, err := current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IPs).To(HaveLen(2)) + + By("calling DEL with dual stack") + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + + By("check that plugin removes net config from state dir with dual stack") + Expect(path).ShouldNot(BeAnExistingFile()) + + By("calling DEL again with dual stack") + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + By("check that plugin does not fail due to missing net config with dual stack") + Expect(err).NotTo(HaveOccurred()) + + return nil }) Expect(err).NotTo(HaveOccurred()) - - By("check that plugin removes net config from state dir") - Expect(path).ShouldNot(BeAnExistingFile()) - - By("calling DEL again") - err = testutils.CmdDelWithArgs(args, func() error { - return cmdDel(args) - }) - By("check that plugin does not fail due to missing net config") - Expect(err).NotTo(HaveOccurred()) - - return nil }) - Expect(err).NotTo(HaveOccurred()) }) }) Describe("loadFlannelNetConf", func() { - Context("when subnetFile and dataDir are specified", func() { - It("loads flannel network config", func() { - conf, err := loadFlannelNetConf([]byte(input)) + Context("when subnetFile and dataDir are specified with ipv4 stack", func() { + It("loads flannel network config with ipv4 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv4Input)) Expect(err).ShouldNot(HaveOccurred()) Expect(conf.Name).To(Equal("cni-flannel")) Expect(conf.Type).To(Equal("flannel")) - Expect(conf.SubnetFile).To(Equal(subnetFile)) + Expect(conf.SubnetFile).To(Equal(onlyIpv4SubnetFile)) Expect(conf.DataDir).To(Equal(dataDir)) }) }) - Context("when defaulting subnetFile and dataDir", func() { + Context("when subnetFile and dataDir are specified with ipv6 stack", func() { + It("loads flannel network config with ipv6 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv6Input)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(conf.Name).To(Equal("cni-flannel")) + Expect(conf.Type).To(Equal("flannel")) + Expect(conf.SubnetFile).To(Equal(onlyIpv6SubnetFile)) + Expect(conf.DataDir).To(Equal(dataDir)) + }) + }) + + Context("when subnetFile and dataDir are specified with dual stack", func() { + It("loads flannel network config with dual stack", func() { + conf, err := loadFlannelNetConf([]byte(dualStackInput)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(conf.Name).To(Equal("cni-flannel")) + Expect(conf.Type).To(Equal("flannel")) + Expect(conf.SubnetFile).To(Equal(dualStackSubnetFile)) + Expect(conf.DataDir).To(Equal(dataDir)) + }) + }) + + Context("when defaulting subnetFile and dataDir with ipv4 stack", func() { BeforeEach(func() { - input = `{ + onlyIpv4Input = `{ "name": "cni-flannel", "type": "flannel" }` }) - It("loads flannel network config with defaults", func() { - conf, err := loadFlannelNetConf([]byte(input)) + It("loads flannel network config with defaults with ipv4 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv4Input)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(conf.Name).To(Equal("cni-flannel")) + Expect(conf.Type).To(Equal("flannel")) + Expect(conf.SubnetFile).To(Equal(defaultSubnetFile)) + Expect(conf.DataDir).To(Equal(defaultDataDir)) + }) + }) + + Context("when defaulting subnetFile and dataDir with ipv6 stack", func() { + BeforeEach(func() { + onlyIpv6Input = `{ +"name": "cni-flannel", +"type": "flannel" +}` + }) + + It("loads flannel network config with defaults with ipv6 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv6Input)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(conf.Name).To(Equal("cni-flannel")) + Expect(conf.Type).To(Equal("flannel")) + Expect(conf.SubnetFile).To(Equal(defaultSubnetFile)) + Expect(conf.DataDir).To(Equal(defaultDataDir)) + }) + }) + + Context("when defaulting subnetFile and dataDir with dual stack", func() { + BeforeEach(func() { + dualStackInput = `{ +"name": "cni-flannel", +"type": "flannel" +}` + }) + + It("loads flannel network config with defaults with dual stack", func() { + conf, err := loadFlannelNetConf([]byte(dualStackInput)) Expect(err).ShouldNot(HaveOccurred()) Expect(conf.Name).To(Equal("cni-flannel")) Expect(conf.Type).To(Equal("flannel")) @@ -227,9 +479,9 @@ FLANNEL_IPMASQ=true }) Describe("loadFlannelSubnetEnv", func() { - Context("when flannel subnet env is valid", func() { - It("loads flannel subnet config", func() { - conf, err := loadFlannelSubnetEnv(subnetFile) + Context("when flannel subnet env is valid with ipv4 stack", func() { + It("loads flannel subnet config with ipv4 stack", func() { + conf, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile) Expect(err).ShouldNot(HaveOccurred()) Expect(conf.nw.String()).To(Equal("10.1.0.0/16")) Expect(conf.sn.String()).To(Equal("10.1.17.0/24")) @@ -239,56 +491,188 @@ FLANNEL_IPMASQ=true }) }) - Context("when flannel subnet env is invalid", func() { + Context("when flannel subnet env is valid with ipv6 stack", func() { + It("loads flannel subnet config with ipv6 stack", func() { + conf, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile) + Expect(err).ShouldNot(HaveOccurred()) + Expect(conf.ip6Nw.String()).To(Equal("fc00::/48")) + Expect(conf.ip6Sn.String()).To(Equal("fc00::/64")) + var mtu uint = 1472 + Expect(*conf.mtu).To(Equal(mtu)) + Expect(*conf.ipmasq).To(BeTrue()) + }) + }) + + Context("when flannel subnet env is valid with dual stack", func() { + It("loads flannel subnet config with dual stack", func() { + conf, err := loadFlannelSubnetEnv(dualStackSubnetFile) + Expect(err).ShouldNot(HaveOccurred()) + Expect(conf.nw.String()).To(Equal("10.1.0.0/16")) + Expect(conf.sn.String()).To(Equal("10.1.17.0/24")) + Expect(conf.ip6Nw.String()).To(Equal("fc00::/48")) + Expect(conf.ip6Sn.String()).To(Equal("fc00::/64")) + var mtu uint = 1472 + Expect(*conf.mtu).To(Equal(mtu)) + Expect(*conf.ipmasq).To(BeTrue()) + }) + }) + + Context("when flannel subnet env is invalid with ipv4 stack", func() { BeforeEach(func() { - subnetFile = writeSubnetEnv("foo=bar") + onlyIpv4SubnetFile = writeSubnetEnv("foo=bar") }) It("returns an error", func() { - _, err := loadFlannelSubnetEnv(subnetFile) - Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ"))) + _, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile) + Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ"))) + }) + }) + + Context("when flannel subnet env is invalid with ipv6 stack", func() { + BeforeEach(func() { + onlyIpv6SubnetFile = writeSubnetEnv("foo=bar") + }) + It("returns an error", func() { + _, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile) + Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ"))) + }) + }) + + Context("when flannel subnet env is invalid with dual stack", func() { + BeforeEach(func() { + dualStackSubnetFile = writeSubnetEnv("foo=bar") + }) + It("returns an error", func() { + _, err := loadFlannelSubnetEnv(dualStackSubnetFile) + Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ"))) }) }) }) }) Describe("getDelegateIPAM", func() { - Context("when input IPAM is provided", func() { + Context("when input IPAM is provided with ipv4 stack", func() { BeforeEach(func() { inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "") - input = makeInput(inputIPAM) + onlyIpv4Input = makeInput(inputIPAM, onlyIpv4SubnetFile) }) - It("configures Delegate IPAM accordingly", func() { - conf, err := loadFlannelNetConf([]byte(input)) + It("configures Delegate IPAM accordingly with ipv4 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv4Input)) Expect(err).ShouldNot(HaveOccurred()) - fenv, err := loadFlannelSubnetEnv(subnetFile) + fenv, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile) Expect(err).ShouldNot(HaveOccurred()) ipam, err := getDelegateIPAM(conf, fenv) Expect(err).ShouldNot(HaveOccurred()) podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n" - subnet := "\"subnet\": \"10.1.17.0/24\"" + subnet := "\"ranges\": [[{\"subnet\": \"10.1.17.0/24\"}]]" expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet) buf, _ := json.Marshal(ipam) Expect(buf).Should(MatchJSON(expected)) }) }) - Context("when input IPAM is provided without 'type'", func() { + Context("when input IPAM is provided with ipv6 stack", func() { + BeforeEach(func() { + inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "") + onlyIpv6Input = makeInput(inputIPAM, onlyIpv6SubnetFile) + }) + It("configures Delegate IPAM accordingly with ipv6 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv6Input)) + Expect(err).ShouldNot(HaveOccurred()) + fenv, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile) + Expect(err).ShouldNot(HaveOccurred()) + + ipam, err := getDelegateIPAM(conf, fenv) + Expect(err).ShouldNot(HaveOccurred()) + + podsRoute := "{ \"dst\": \"fc00::/48\" }\n" + subnet := "\"ranges\": [[{ \"subnet\": \"fc00::/64\" }]]" + expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet) + buf, _ := json.Marshal(ipam) + Expect(buf).Should(MatchJSON(expected)) + }) + }) + + Context("when input IPAM is provided with dual stack", func() { + BeforeEach(func() { + inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "") + dualStackInput = makeInput(inputIPAM, dualStackSubnetFile) + }) + It("configures Delegate IPAM accordingly with dual stack", func() { + conf, err := loadFlannelNetConf([]byte(dualStackInput)) + Expect(err).ShouldNot(HaveOccurred()) + fenv, err := loadFlannelSubnetEnv(dualStackSubnetFile) + Expect(err).ShouldNot(HaveOccurred()) + + ipam, err := getDelegateIPAM(conf, fenv) + Expect(err).ShouldNot(HaveOccurred()) + + podsRoute := "{ \"dst\": \"10.1.0.0/16\" }" + ",\n" + "{ \"dst\": \"fc00::/48\" }\n" + subnet := "\"ranges\": [[{ \"subnet\": \"10.1.17.0/24\" }],\n[{ \"subnet\": \"fc00::/64\" }]]" + expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet) + buf, _ := json.Marshal(ipam) + Expect(buf).Should(MatchJSON(expected)) + }) + }) + + Context("when input IPAM is provided without 'type' with ipv4 stack", func() { BeforeEach(func() { inputIPAM := makeInputIPAM("", inputIPAMRoutes, "") - input = makeInput(inputIPAM) + onlyIpv4Input = makeInput(inputIPAM, onlyIpv4SubnetFile) }) - It("configures Delegate IPAM with 'host-local' ipam", func() { - conf, err := loadFlannelNetConf([]byte(input)) + It("configures Delegate IPAM with 'host-local' ipam with ipv4 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv4Input)) Expect(err).ShouldNot(HaveOccurred()) - fenv, err := loadFlannelSubnetEnv(subnetFile) + fenv, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile) Expect(err).ShouldNot(HaveOccurred()) ipam, err := getDelegateIPAM(conf, fenv) Expect(err).ShouldNot(HaveOccurred()) podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n" - subnet := "\"subnet\": \"10.1.17.0/24\"" + subnet := "\"ranges\": [[{\"subnet\": \"10.1.17.0/24\"}]]" + expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet) + buf, _ := json.Marshal(ipam) + Expect(buf).Should(MatchJSON(expected)) + }) + }) + + Context("when input IPAM is provided without 'type' with ipv6 stack", func() { + BeforeEach(func() { + inputIPAM := makeInputIPAM("", inputIPAMRoutes, "") + onlyIpv6Input = makeInput(inputIPAM, onlyIpv6SubnetFile) + }) + It("configures Delegate IPAM with 'host-local' ipam with ipv6 stack", func() { + conf, err := loadFlannelNetConf([]byte(onlyIpv6Input)) + Expect(err).ShouldNot(HaveOccurred()) + fenv, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile) + Expect(err).ShouldNot(HaveOccurred()) + ipam, err := getDelegateIPAM(conf, fenv) + Expect(err).ShouldNot(HaveOccurred()) + + podsRoute := "{ \"dst\": \"fc00::/48\" }\n" + subnet := "\"ranges\": [[{ \"subnet\": \"fc00::/64\" }]]" + expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet) + buf, _ := json.Marshal(ipam) + Expect(buf).Should(MatchJSON(expected)) + }) + }) + + Context("when input IPAM is provided without 'type' with dual stack", func() { + BeforeEach(func() { + inputIPAM := makeInputIPAM("", inputIPAMRoutes, "") + dualStackInput = makeInput(inputIPAM, dualStackSubnetFile) + }) + It("configures Delegate IPAM with 'host-local' ipam with dual stack", func() { + conf, err := loadFlannelNetConf([]byte(dualStackInput)) + Expect(err).ShouldNot(HaveOccurred()) + fenv, err := loadFlannelSubnetEnv(dualStackSubnetFile) + Expect(err).ShouldNot(HaveOccurred()) + ipam, err := getDelegateIPAM(conf, fenv) + Expect(err).ShouldNot(HaveOccurred()) + + podsRoute := "{ \"dst\": \"10.1.0.0/16\" }" + ",\n" + "{ \"dst\": \"fc00::/48\" }\n" + subnet := "\"ranges\": [[{ \"subnet\": \"10.1.17.0/24\" }],\n[{ \"subnet\": \"fc00::/64\" }]]" expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet) buf, _ := json.Marshal(ipam) Expect(buf).Should(MatchJSON(expected))