Skip to content

Commit

Permalink
Merge pull request containernetworking#165 from s1061123/dev/static-args
Browse files Browse the repository at this point in the history
Support CNI_ARGS in static IPAM plugin
  • Loading branch information
dcbw committed Sep 25, 2018
2 parents 9b86f52 + 094c903 commit 646dbba
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 3 deletions.
11 changes: 10 additions & 1 deletion plugins/ipam/static/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 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")
54 changes: 53 additions & 1 deletion plugins/ipam/static/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"net"
"strings"

"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
Expand All @@ -39,10 +40,16 @@ 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 types.UnmarshallableString `json:"ip,omitempty"`
GATEWAY types.UnmarshallableString `json:"gateway,omitempty"`
}

type Address struct {
AddressStr string `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
Expand Down Expand Up @@ -88,6 +95,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
// 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 {
Expand All @@ -109,6 +117,50 @@ 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 != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)

ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, 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 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
}
}
}
}
}

// CNI spec 0.2.0 and below supported only one v4 and v6 address
if numV4 > 1 || numV6 > 1 {
for _, v := range types020.SupportedVersions {
Expand Down
2 changes: 1 addition & 1 deletion plugins/ipam/static/static_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ import (

func TestStatic(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Static Suite")
RunSpecs(t, "plugins/ipam/static")
}
117 changes: 117 additions & 0 deletions plugins/ipam/static/static_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,123 @@ 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/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())
})

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 {
Expand Down

0 comments on commit 646dbba

Please sign in to comment.