Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support CNI_ARGS in static IPAM plugin #165

Merged
merged 8 commits into from
Sep 25, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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.
  • Loading branch information
s1061123 committed Jun 18, 2018
commit 6da1cb78768836e99a72ddf76cee2c173246117b
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 arrays of ip address objects:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's incorrect even in master - it's an array of ip addresses, not an array of arrays.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

* `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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing new line before this one. Please keep that in sync with rest of document.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


* `IP`: request a specific IP address from a subnet
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove from a subnet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

* `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")
51 changes: 47 additions & 4 deletions plugins/ipam/static/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
62 changes: 62 additions & 0 deletions plugins/ipam/static/static_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down