Merge pull request #136 from s1061123/add_static
plugins: add static CNI plugin
This commit is contained in:
commit
0dba9daee0
45
plugins/ipam/static/README.md
Normal file
45
plugins/ipam/static/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
# static IP address management plugin
|
||||
|
||||
## Overview
|
||||
|
||||
static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses statically to container. This will be useful in debugging purpose and in case of assign same IP address in different vlan/vxlan to containers.
|
||||
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"addresses": [
|
||||
{
|
||||
"address": "10.10.0.1/24",
|
||||
"gateway": "10.10.0.254"
|
||||
},
|
||||
{
|
||||
"address": "3ffe:ffff:0:01ff::1/64",
|
||||
"gateway": "3ffe:ffff:0::1"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" },
|
||||
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
||||
],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `type` (string, required): "static"
|
||||
* `addresses` (array, required): 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".
|
142
plugins/ipam/static/main.go
Normal file
142
plugins/ipam/static/main.go
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright 2018 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type IPAMConfig struct {
|
||||
Name string
|
||||
Type string `json:"type"`
|
||||
Routes []*types.Route `json:"routes"`
|
||||
Addresses []Address `json:"addresses"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
AddressStr string `json:"address"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
Address net.IPNet
|
||||
Version string
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||
}
|
||||
|
||||
// canonicalizeIP makes sure a provided ip is in standard form
|
||||
func canonicalizeIP(ip *net.IP) error {
|
||||
if ip.To4() != nil {
|
||||
*ip = ip.To4()
|
||||
return nil
|
||||
} else if ip.To16() != nil {
|
||||
*ip = ip.To16()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("IP %s not v4 nor v6", *ip)
|
||||
}
|
||||
|
||||
// NewIPAMConfig creates a NetworkConfig from the given network name.
|
||||
func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
n := Net{}
|
||||
if err := json.Unmarshal(bytes, &n); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if n.IPAM == nil {
|
||||
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err)
|
||||
}
|
||||
n.IPAM.Addresses[i].Address = *addr
|
||||
n.IPAM.Addresses[i].Address.IP = ip
|
||||
|
||||
if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil {
|
||||
return nil, "", fmt.Errorf("invalid address %d: %s", i, err)
|
||||
}
|
||||
|
||||
if n.IPAM.Addresses[i].Address.IP.To4() != nil {
|
||||
n.IPAM.Addresses[i].Version = "4"
|
||||
numV4++
|
||||
} else {
|
||||
n.IPAM.Addresses[i].Version = "6"
|
||||
numV6++
|
||||
}
|
||||
}
|
||||
|
||||
// CNI spec 0.2.0 and below supported only one v4 and v6 address
|
||||
if numV4 > 1 || numV6 > 1 {
|
||||
for _, v := range types020.SupportedVersions {
|
||||
if n.CNIVersion == v {
|
||||
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy net name into IPAM so not to drag Net struct around
|
||||
n.IPAM.Name = n.Name
|
||||
|
||||
return n.IPAM, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
ipamConf, confVersion, err := LoadIPAMConfig(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := ¤t.Result{}
|
||||
result.DNS = ipamConf.DNS
|
||||
result.Routes = ipamConf.Routes
|
||||
for _, v := range ipamConf.Addresses {
|
||||
result.IPs = append(result.IPs, ¤t.IPConfig{
|
||||
Version: v.Version,
|
||||
Address: v.Address,
|
||||
Gateway: v.Gateway})
|
||||
}
|
||||
|
||||
result.Routes = ipamConf.Routes
|
||||
return types.PrintResult(result, confVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
// Nothing required because of no resource allocation in static plugin.
|
||||
return nil
|
||||
}
|
27
plugins/ipam/static/static_suite_test.go
Normal file
27
plugins/ipam/static/static_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2018 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestStatic(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Static Suite")
|
||||
}
|
161
plugins/ipam/static/static_test.go
Normal file
161
plugins/ipam/static/static_test.go
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2018 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("static Operations", func() {
|
||||
It("allocates and releases addresses with ADD/DEL", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"addresses": [ {
|
||||
"address": "10.10.0.1/24",
|
||||
"gateway": "10.10.0.254"
|
||||
},
|
||||
{
|
||||
"address": "3ffe:ffff:0:01ff::1/64",
|
||||
"gateway": "3ffe:ffff:0::1"
|
||||
}],
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" },
|
||||
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r, raw, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), 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: "6",
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
Gateway: net.ParseIP("3ffe:ffff:0::1"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
|
||||
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")},
|
||||
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64")},
|
||||
}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("doesn't error when passed an unknown ID on DEL", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.0",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"addresses": [ {
|
||||
"address": "10.10.0.1/24",
|
||||
"gateway": "10.10.0.254"
|
||||
},
|
||||
{
|
||||
"address": "3ffe:ffff:0:01ff::1/64",
|
||||
"gateway": "3ffe:ffff:0::1"
|
||||
}],
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" },
|
||||
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}}}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Release the IP
|
||||
err := testutils.CmdDelWithResult(nspath, ifname, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
func mustCIDR(s string) net.IPNet {
|
||||
ip, n, err := net.ParseCIDR(s)
|
||||
n.IP = ip
|
||||
if err != nil {
|
||||
Fail(err.Error())
|
||||
}
|
||||
|
||||
return *n
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user