From 6da1cb78768836e99a72ddf76cee2c173246117b Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Mon, 18 Jun 2018 15:46:48 +0900 Subject: [PATCH 1/7] Support CNI_ARGS in static IPAM plugin This change is to add CNI_ARGS support in static IPAM plugin. When IP/SUBNET/GATEWAY are given in CNI_ARGS, static IPAM adds these info in addition to config files. To configure ip address only from CNI_ARGS, 'address' field in config is changed to optional from required. --- plugins/ipam/static/README.md | 11 +++++- plugins/ipam/static/main.go | 51 ++++++++++++++++++++++-- plugins/ipam/static/static_test.go | 62 ++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/plugins/ipam/static/README.md b/plugins/ipam/static/README.md index 7b3c01fe..37540603 100644 --- a/plugins/ipam/static/README.md +++ b/plugins/ipam/static/README.md @@ -38,8 +38,17 @@ static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses stat ## Network configuration reference * `type` (string, required): "static" -* `addresses` (array, required): an array of arrays of ip address objects: +* `addresses` (array, optional): an array of arrays of ip address objects: * `address` (string, required): CIDR notation IP address. * `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. * `routes` (string, optional): list of routes add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used. * `dns` (string, optional): the dictionary with "nameservers", "domain" and "search". + +## Supported arguments +The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported: + +* `IP`: request a specific IP address from a subnet +* `SUBNET`: request a specific subnet +* `GATEWAY`: request a specific gateway address + + (example: CNI_ARGS="IP=10.10.0.1;SUBNET=10.10.0.0/24;GATEWAY=10.10.0.254") diff --git a/plugins/ipam/static/main.go b/plugins/ipam/static/main.go index d236c0b6..74505644 100644 --- a/plugins/ipam/static/main.go +++ b/plugins/ipam/static/main.go @@ -30,19 +30,29 @@ import ( // The top-level network config - IPAM plugins are passed the full configuration // of the calling plugin, not just the IPAM section. type Net struct { - Name string `json:"name"` - CNIVersion string `json:"cniVersion"` - IPAM *IPAMConfig `json:"ipam"` + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + IPAM *IPAMConfig `json:"ipam"` + RuntimeConfig struct { + Addresses []Address `json:"addresses,omitempty"` + } `json:"runtimeConfig,omitempty"` } type IPAMConfig struct { Name string Type string `json:"type"` Routes []*types.Route `json:"routes"` - Addresses []Address `json:"addresses"` + Addresses []Address `json:"addresses,omitempty"` DNS types.DNS `json:"dns"` } +type IPAMEnvArgs struct { + types.CommonArgs + IP net.IP `json:"ip,omitempty"` + SUBNET types.UnmarshallableString `json:"subnet,omitempty"` + GATEWAY net.IP `json:"gateway,omitempty"` +} + type Address struct { AddressStr string `json:"address"` Gateway net.IP `json:"gateway,omitempty"` @@ -77,9 +87,14 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { return nil, "", fmt.Errorf("IPAM config missing 'ipam' key") } + if len(n.RuntimeConfig.Addresses) != 0 { + n.IPAM.Addresses = append(n.RuntimeConfig.Addresses, n.IPAM.Addresses...) + } + // Validate all ranges numV4 := 0 numV6 := 0 + for i := range n.IPAM.Addresses { ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr) if err != nil { @@ -101,6 +116,34 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { } } + if envArgs != "" { + e := IPAMEnvArgs{} + err := types.LoadArgs(envArgs, &e) + if err != nil { + return nil, "", err + } + + if e.IP != nil && e.SUBNET != "" { + _, subnet, err := net.ParseCIDR(string(e.SUBNET)) + if err != nil { + return nil, "", fmt.Errorf("invalid CIDR %s: %s", e.SUBNET, err) + } + + addr := Address{Address: net.IPNet{IP: e.IP, Mask: subnet.Mask}} + if e.GATEWAY != nil { + addr.Gateway = e.GATEWAY + } + if addr.Address.IP.To4() != nil { + addr.Version = "4" + numV4++ + } else { + addr.Version = "6" + numV6++ + } + n.IPAM.Addresses = append(n.IPAM.Addresses, addr) + } + } + // CNI spec 0.2.0 and below supported only one v4 and v6 address if numV4 > 1 || numV6 > 1 { for _, v := range types020.SupportedVersions { diff --git a/plugins/ipam/static/static_test.go b/plugins/ipam/static/static_test.go index 31ac3c28..77d0ee6a 100644 --- a/plugins/ipam/static/static_test.go +++ b/plugins/ipam/static/static_test.go @@ -148,6 +148,68 @@ var _ = Describe("static Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("allocates and releases addresses with ADD/DEL, with ENV variables", func() { + const ifname string = "eth0" + const nspath string = "/some/where" + + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "static", + "routes": [ + { "dst": "0.0.0.0/0" }, + { "dst": "192.168.0.0/16", "gw": "10.10.5.1" }], + "dns": { + "nameservers" : ["8.8.8.8"], + "domain": "example.com", + "search": [ "example.com" ] + } + } + }` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: nspath, + IfName: ifname, + StdinData: []byte(conf), + Args: "IP=10.10.0.1;SUBNET=10.10.0.0/24;GATEWAY=10.10.0.254", + } + + // Allocate the IP + r, raw, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + // Gomega is cranky about slices with different caps + Expect(*result.IPs[0]).To(Equal( + current.IPConfig{ + Version: "4", + Address: mustCIDR("10.10.0.1/24"), + Gateway: net.ParseIP("10.10.0.254"), + })) + + Expect(len(result.IPs)).To(Equal(1)) + + Expect(result.Routes).To(Equal([]*types.Route{ + {Dst: mustCIDR("0.0.0.0/0")}, + {Dst: mustCIDR("192.168.0.0/16"), GW: net.ParseIP("10.10.5.1")}, + })) + + // Release the IP + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + }) }) func mustCIDR(s string) net.IPNet { From 3b3622db67614c2055e75187cc70aa197fda051a Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Fri, 6 Jul 2018 13:17:22 +0900 Subject: [PATCH 2/7] Incorporate jellonek's comments. --- plugins/ipam/static/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/ipam/static/README.md b/plugins/ipam/static/README.md index 37540603..329a955a 100644 --- a/plugins/ipam/static/README.md +++ b/plugins/ipam/static/README.md @@ -38,16 +38,17 @@ static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses stat ## Network configuration reference * `type` (string, required): "static" -* `addresses` (array, optional): an array of arrays of ip address objects: +* `addresses` (array, optional): an array of ip address objects: * `address` (string, required): CIDR notation IP address. * `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. * `routes` (string, optional): list of routes add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used. * `dns` (string, optional): the dictionary with "nameservers", "domain" and "search". ## Supported arguments + The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported: -* `IP`: request a specific IP address from a subnet +* `IP`: request a specific IP address * `SUBNET`: request a specific subnet * `GATEWAY`: request a specific gateway address From cc3ad266916e352d445fe54ab129f1fb70a51235 Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Thu, 26 Jul 2018 21:29:43 +0900 Subject: [PATCH 3/7] Incorporate Casey's comments --- plugins/ipam/static/main.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/plugins/ipam/static/main.go b/plugins/ipam/static/main.go index 74505644..e4267220 100644 --- a/plugins/ipam/static/main.go +++ b/plugins/ipam/static/main.go @@ -30,12 +30,9 @@ import ( // The top-level network config - IPAM plugins are passed the full configuration // of the calling plugin, not just the IPAM section. type Net struct { - Name string `json:"name"` - CNIVersion string `json:"cniVersion"` - IPAM *IPAMConfig `json:"ipam"` - RuntimeConfig struct { - Addresses []Address `json:"addresses,omitempty"` - } `json:"runtimeConfig,omitempty"` + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + IPAM *IPAMConfig `json:"ipam"` } type IPAMConfig struct { @@ -87,10 +84,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { return nil, "", fmt.Errorf("IPAM config missing 'ipam' key") } - if len(n.RuntimeConfig.Addresses) != 0 { - n.IPAM.Addresses = append(n.RuntimeConfig.Addresses, n.IPAM.Addresses...) - } - // Validate all ranges numV4 := 0 numV6 := 0 From 60a99ca331d9de2ac382e06338aa2c4b4ab4e5ed Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Wed, 5 Sep 2018 14:37:31 +0900 Subject: [PATCH 4/7] Incorporate Dan's comments. --- plugins/ipam/static/README.md | 5 ++--- plugins/ipam/static/main.go | 11 +++++------ plugins/ipam/static/static_test.go | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/ipam/static/README.md b/plugins/ipam/static/README.md index 329a955a..0c473d91 100644 --- a/plugins/ipam/static/README.md +++ b/plugins/ipam/static/README.md @@ -48,8 +48,7 @@ static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses stat The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported: -* `IP`: request a specific IP address -* `SUBNET`: request a specific subnet +* `IP`: request a specific CIDR notation IP address * `GATEWAY`: request a specific gateway address - (example: CNI_ARGS="IP=10.10.0.1;SUBNET=10.10.0.0/24;GATEWAY=10.10.0.254") + (example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254") diff --git a/plugins/ipam/static/main.go b/plugins/ipam/static/main.go index e4267220..e079c8dc 100644 --- a/plugins/ipam/static/main.go +++ b/plugins/ipam/static/main.go @@ -45,8 +45,7 @@ type IPAMConfig struct { type IPAMEnvArgs struct { types.CommonArgs - IP net.IP `json:"ip,omitempty"` - SUBNET types.UnmarshallableString `json:"subnet,omitempty"` + IP types.UnmarshallableString `json:"ip,omitempty"` GATEWAY net.IP `json:"gateway,omitempty"` } @@ -116,13 +115,13 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { return nil, "", err } - if e.IP != nil && e.SUBNET != "" { - _, subnet, err := net.ParseCIDR(string(e.SUBNET)) + if e.IP != "" { + ip, subnet, err := net.ParseCIDR(string(e.IP)) if err != nil { - return nil, "", fmt.Errorf("invalid CIDR %s: %s", e.SUBNET, err) + return nil, "", fmt.Errorf("invalid CIDR %s: %s", e.IP, err) } - addr := Address{Address: net.IPNet{IP: e.IP, Mask: subnet.Mask}} + addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}} if e.GATEWAY != nil { addr.Gateway = e.GATEWAY } diff --git a/plugins/ipam/static/static_test.go b/plugins/ipam/static/static_test.go index 77d0ee6a..afc631dd 100644 --- a/plugins/ipam/static/static_test.go +++ b/plugins/ipam/static/static_test.go @@ -176,7 +176,7 @@ var _ = Describe("static Operations", func() { Netns: nspath, IfName: ifname, StdinData: []byte(conf), - Args: "IP=10.10.0.1;SUBNET=10.10.0.0/24;GATEWAY=10.10.0.254", + Args: "IP=10.10.0.1/24;GATEWAY=10.10.0.254", } // Allocate the IP From 61c136126f378c680173b111110404f41197fc3a Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Thu, 6 Sep 2018 15:32:50 +0900 Subject: [PATCH 5/7] Support multiple IP addresses in CNI_ARGS --- plugins/ipam/static/README.md | 2 +- plugins/ipam/static/main.go | 47 +++++++++++++++++-------- plugins/ipam/static/static_test.go | 55 ++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/plugins/ipam/static/README.md b/plugins/ipam/static/README.md index 0c473d91..d9ca28c5 100644 --- a/plugins/ipam/static/README.md +++ b/plugins/ipam/static/README.md @@ -48,7 +48,7 @@ static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses stat The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported: -* `IP`: request a specific CIDR notation IP address +* `IP`: request a specific CIDR notation IP addresses, comma separated * `GATEWAY`: request a specific gateway address (example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254") diff --git a/plugins/ipam/static/main.go b/plugins/ipam/static/main.go index 3a196d6d..2a42d882 100644 --- a/plugins/ipam/static/main.go +++ b/plugins/ipam/static/main.go @@ -18,6 +18,7 @@ import ( "encoding/json" "fmt" "net" + "strings" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" @@ -46,7 +47,7 @@ type IPAMConfig struct { type IPAMEnvArgs struct { types.CommonArgs IP types.UnmarshallableString `json:"ip,omitempty"` - GATEWAY net.IP `json:"gateway,omitempty"` + GATEWAY types.UnmarshallableString `json:"gateway,omitempty"` } type Address struct { @@ -124,23 +125,39 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { } if e.IP != "" { - ip, subnet, err := net.ParseCIDR(string(e.IP)) - if err != nil { - return nil, "", fmt.Errorf("invalid CIDR %s: %s", e.IP, err) - } + for _, item := range strings.Split(string(e.IP), ",") { + ipstr := strings.TrimSpace(item) - addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}} - if e.GATEWAY != nil { - addr.Gateway = e.GATEWAY + ip, subnet, err := net.ParseCIDR(ipstr) + if err != nil { + return nil, "", fmt.Errorf("invalid CIDR %s: %s", e.IP, err) + } + + addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}} + if addr.Address.IP.To4() != nil { + addr.Version = "4" + numV4++ + } else { + addr.Version = "6" + numV6++ + } + n.IPAM.Addresses = append(n.IPAM.Addresses, addr) } - if addr.Address.IP.To4() != nil { - addr.Version = "4" - numV4++ - } else { - addr.Version = "6" - numV6++ + } + + if e.GATEWAY != "" { + for _, item := range strings.Split(string(e.GATEWAY), ",") { + gwip := net.ParseIP(strings.TrimSpace(item)) + if gwip == nil { + return nil, "", fmt.Errorf("invalid gateway address: %s", item) + } + + for i := range n.IPAM.Addresses { + if n.IPAM.Addresses[i].Address.Contains(gwip) { + n.IPAM.Addresses[i].Gateway = gwip + } + } } - n.IPAM.Addresses = append(n.IPAM.Addresses, addr) } } diff --git a/plugins/ipam/static/static_test.go b/plugins/ipam/static/static_test.go index afc631dd..c6d9aea6 100644 --- a/plugins/ipam/static/static_test.go +++ b/plugins/ipam/static/static_test.go @@ -210,6 +210,61 @@ var _ = Describe("static Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("allocates and releases multiple addresses with ADD/DEL, with ENV variables", func() { + const ifname string = "eth0" + const nspath string = "/some/where" + + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "static" + } + }` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: nspath, + IfName: ifname, + StdinData: []byte(conf), + Args: "IP=10.10.0.1/24,11.11.0.1/24;GATEWAY=10.10.0.254", + } + + // Allocate the IP + r, raw, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + // Gomega is cranky about slices with different caps + Expect(*result.IPs[0]).To(Equal( + current.IPConfig{ + Version: "4", + Address: mustCIDR("10.10.0.1/24"), + Gateway: net.ParseIP("10.10.0.254"), + })) + Expect(*result.IPs[1]).To(Equal( + current.IPConfig{ + Version: "4", + Address: mustCIDR("11.11.0.1/24"), + Gateway: nil, + })) + + Expect(len(result.IPs)).To(Equal(2)) + + // Release the IP + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + }) }) func mustCIDR(s string) net.IPNet { From 61a412ea773bbda0e073689739363defe58cdd15 Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Thu, 6 Sep 2018 15:56:40 +0900 Subject: [PATCH 6/7] Align test suite name with others --- plugins/ipam/static/static_suite_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ipam/static/static_suite_test.go b/plugins/ipam/static/static_suite_test.go index aae2fee4..c729ecf1 100644 --- a/plugins/ipam/static/static_suite_test.go +++ b/plugins/ipam/static/static_suite_test.go @@ -23,5 +23,5 @@ import ( func TestStatic(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Static Suite") + RunSpecs(t, "plugins/ipam/static") } From 094c9039328e1d2007d9e300804654ad0748a08c Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Thu, 13 Sep 2018 01:46:11 +0900 Subject: [PATCH 7/7] Incorporate Dan's comments. --- plugins/ipam/static/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ipam/static/main.go b/plugins/ipam/static/main.go index 2a42d882..1b4842ff 100644 --- a/plugins/ipam/static/main.go +++ b/plugins/ipam/static/main.go @@ -130,7 +130,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { ip, subnet, err := net.ParseCIDR(ipstr) if err != nil { - return nil, "", fmt.Errorf("invalid CIDR %s: %s", e.IP, err) + return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err) } addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}