Merge pull request #413 from dcbw/spec-fix-ips

spec/plugins: fix 'ip'->'ips' in the spec, bump to 0.3.1
This commit is contained in:
Tom Denham 2017-04-05 14:39:44 -07:00 committed by GitHub
commit 361ec957fb
17 changed files with 221 additions and 206 deletions

View File

@ -1,3 +1,9 @@
# How to upgrade to CNI Specification v0.3.1
The 0.3.0 specification contained a small error. The Result structure's `ip` field should have been renamed to `ips` to be consistent with the IPAM result structure definition; this rename was missed when updating the Result to accommodate multiple IP addresses and interfaces. All first-party CNI plugins (bridge, host-local, etc) were updated to use `ips` (and thus be inconsistent with the 0.3.0 specification) and most other plugins have not been updated to the 0.3.0 specification yet, so few (if any) users should be impacted by this change.
The 0.3.1 specification corrects the Result structure to use the `ips` field name as originally intended. This is the only change between 0.3.0 and 0.3.1.
# How to upgrade to CNI Specification v0.3.0 # How to upgrade to CNI Specification v0.3.0
Version 0.3.0 of the [CNI Specification](../SPEC.md) provides rich information Version 0.3.0 of the [CNI Specification](../SPEC.md) provides rich information

45
SPEC.md
View File

@ -1,7 +1,7 @@
# Container Networking Interface Proposal # Container Networking Interface Proposal
## Version ## Version
This is CNI **spec** version **0.3.0**. This is CNI **spec** version **0.3.1**.
Note that this is **independent from the version of the CNI library and plugins** in this repository (e.g. the versions of [releases](https://github.com/containernetworking/cni/releases)). Note that this is **independent from the version of the CNI library and plugins** in this repository (e.g. the versions of [releases](https://github.com/containernetworking/cni/releases)).
@ -76,8 +76,8 @@ The operations that the CNI plugin needs to support are:
``` ```
{ {
"cniVersion": "0.3.0", // the version of the CNI spec in use for this output "cniVersion": "0.3.1", // the version of the CNI spec in use for this output
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0" ] // the list of CNI spec versions that this plugin supports "supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1" ] // the list of CNI spec versions that this plugin supports
} }
``` ```
@ -97,11 +97,11 @@ Network configuration in JSON format is streamed to the plugin through stdin. Th
Note that IPAM plugins return an abbreviated `Result` structure as described in [IP Allocation](#ip-allocation). Note that IPAM plugins return an abbreviated `Result` structure as described in [IP Allocation](#ip-allocation).
Success is indicated by a return code of zero and the following JSON printed to stdout in the case of the ADD command. The `ip` and `dns` items should be the same output as was returned by the IPAM plugin (see [IP Allocation](#ip-allocation) for details) except that the plugin should fill in the `interface` indexes appropriately, which are missing from IPAM plugin output since IPAM plugins should be unaware of interfaces. Success is indicated by a return code of zero and the following JSON printed to stdout in the case of the ADD command. The `ips` and `dns` items should be the same output as was returned by the IPAM plugin (see [IP Allocation](#ip-allocation) for details) except that the plugin should fill in the `interface` indexes appropriately, which are missing from IPAM plugin output since IPAM plugins should be unaware of interfaces.
``` ```
{ {
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"interfaces": [ (this key omitted by IPAM plugins) "interfaces": [ (this key omitted by IPAM plugins)
{ {
"name": "<name>", "name": "<name>",
@ -109,7 +109,7 @@ Success is indicated by a return code of zero and the following JSON printed to
"sandbox": "<netns path or hypervisor identifier>" (required for container/hypervisor interfaces, empty/omitted for host interfaces) "sandbox": "<netns path or hypervisor identifier>" (required for container/hypervisor interfaces, empty/omitted for host interfaces)
} }
], ],
"ip": [ "ips": [
{ {
"version": "<4-or-6>", "version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>", "address": "<ip-and-prefix-in-CIDR>",
@ -143,8 +143,8 @@ If the `CNI_IFNAME` variable exists the plugin must use that name for the sandbo
Hypervisor/VM-based plugins should return an ID unique to the virtualized sandbox the interface was created in. Hypervisor/VM-based plugins should return an ID unique to the virtualized sandbox the interface was created in.
This item must be provided for interfaces created or moved into a sandbox like a network namespace or a hypervisor/VM. This item must be provided for interfaces created or moved into a sandbox like a network namespace or a hypervisor/VM.
The `ip` field is a list of IP configuration information. The `ips` field is a list of IP configuration information.
See the [IP well-known structure](#ip) section for more information. See the [IP well-known structure](#ips) section for more information.
The `dns` field contains a dictionary consisting of common DNS information. The `dns` field contains a dictionary consisting of common DNS information.
See the [DNS well-known structure](#dns) section for more information. See the [DNS well-known structure](#dns) section for more information.
@ -155,7 +155,7 @@ Examples include generating an `/etc/resolv.conf` file to be injected into the c
Errors are indicated by a non-zero return code and the following JSON being printed to stdout: Errors are indicated by a non-zero return code and the following JSON being printed to stdout:
``` ```
{ {
"cniVersion": "0.2.0", "cniVersion": "0.3.1",
"code": <numeric-error-code>, "code": <numeric-error-code>,
"msg": <short-error-message>, "msg": <short-error-message>,
"details": <long-error-message> (optional) "details": <long-error-message> (optional)
@ -185,11 +185,12 @@ The network configuration is described in JSON form. The configuration can be st
- `options` (list of strings): list of options that can be passed to the resolver - `options` (list of strings): list of options that can be passed to the resolver
Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the `args` field may be used to pass arbitrary data which may be ignored by plugins. Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the `args` field may be used to pass arbitrary data which may be ignored by plugins.
### Example configurations ### Example configurations
```json ```json
{ {
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "dbnet", "name": "dbnet",
"type": "bridge", "type": "bridge",
// type (plugin) specific // type (plugin) specific
@ -208,7 +209,7 @@ Plugins may define additional fields that they accept and may generate an error
```json ```json
{ {
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "pci", "name": "pci",
"type": "ovs", "type": "ovs",
// type (plugin) specific // type (plugin) specific
@ -229,7 +230,7 @@ Plugins may define additional fields that they accept and may generate an error
```json ```json
{ {
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "wan", "name": "wan",
"type": "macvlan", "type": "macvlan",
// ipam specific // ipam specific
@ -278,7 +279,7 @@ Plugins should generally complete a DEL action without error even if some resour
```json ```json
{ {
"cniVersion": "0.2.0", "cniVersion": "0.3.1",
"name": "dbnet", "name": "dbnet",
"plugins": [ "plugins": [
{ {
@ -320,7 +321,7 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration
```json ```json
{ {
"cniVersion": "0.2.0", "cniVersion": "0.3.1",
"name": "dbnet", "name": "dbnet",
"type": "bridge", "type": "bridge",
"bridge": "cni0", "bridge": "cni0",
@ -345,7 +346,7 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration
```json ```json
{ {
"cniVersion": "0.2.0", "cniVersion": "0.3.1",
"name": "dbnet", "name": "dbnet",
"type": "tuning", "type": "tuning",
"sysctl": { "sysctl": {
@ -371,7 +372,7 @@ Also note that plugins are executed in reverse order from the ADD action.
```json ```json
{ {
"cniVersion": "0.2.0", "cniVersion": "0.3.1",
"name": "dbnet", "name": "dbnet",
"type": "tuning", "type": "tuning",
"sysctl": { "sysctl": {
@ -384,7 +385,7 @@ Also note that plugins are executed in reverse order from the ADD action.
```json ```json
{ {
"cniVersion": "0.2.0", "cniVersion": "0.3.1",
"name": "dbnet", "name": "dbnet",
"type": "bridge", "type": "bridge",
"bridge": "cni0", "bridge": "cni0",
@ -419,7 +420,7 @@ Success is indicated by a zero return code and the following JSON being printed
``` ```
{ {
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"ips": [ "ips": [
{ {
"version": "<4-or-6>", "version": "<4-or-6>",
@ -449,7 +450,7 @@ Note that unlike regular CNI plugins, IPAM plugins return an abbreviated `Result
`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin. `cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin.
The `ips` field is a list of IP configuration information. The `ips` field is a list of IP configuration information.
See the [IP well-known structure](#ip) section for more information. See the [IP well-known structure](#ips) section for more information.
The `dns` field contains a dictionary consisting of common DNS information. The `dns` field contains a dictionary consisting of common DNS information.
See the [DNS well-known structure](#dns) section for more information. See the [DNS well-known structure](#dns) section for more information.
@ -466,7 +467,7 @@ IPAM plugin examples:
### Well-known Structures ### Well-known Structures
#### IP #### IPs
``` ```
"ips": [ "ips": [
@ -480,8 +481,8 @@ IPAM plugin examples:
] ]
``` ```
The `ip` field is a list of IP configuration information determined by the plugin. Each item is a dictionary describing of IP configuration for a network interface. The `ips` field is a list of IP configuration information determined by the plugin. Each item is a dictionary describing of IP configuration for a network interface.
IP configuration for multiple network interfaces and multiple IP configurations for a single interface may be returned as separate items in the `ip` list. IP configuration for multiple network interfaces and multiple IP configurations for a single interface may be returned as separate items in the `ips` list.
All properties known to the plugin should be provided, even if not strictly required. All properties known to the plugin should be provided, even if not strictly required.
- `version` (string): either "4" or "6" and corresponds to the IP version of the addresses in the entry. - `version` (string): either "4" or "6" and corresponds to the IP version of the addresses in the entry.
All IP addresses and gateways provided must be valid for the given `version`. All IP addresses and gateways provided must be valid for the given `version`.

View File

@ -92,7 +92,7 @@ func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, res
err = json.Unmarshal([]byte(config), &newConfig) err = json.Unmarshal([]byte(config), &newConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
newConfig["name"] = "some-list" newConfig["name"] = "some-list"
newConfig["cniVersion"] = "0.3.0" newConfig["cniVersion"] = "0.3.1"
// Only include standard runtime config and capability args that this plugin advertises // Only include standard runtime config and capability args that this plugin advertises
newRuntimeConfig := make(map[string]interface{}) newRuntimeConfig := make(map[string]interface{})
@ -136,7 +136,7 @@ var _ = Describe("Invoking plugins", func() {
debug = &noop_debug.Debug{} debug = &noop_debug.Debug{}
Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
pluginConfig = []byte(`{ "type": "noop", "cniVersion": "0.3.0", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`) pluginConfig = []byte(`{ "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
netConfig, err = libcni.ConfFromBytes(pluginConfig) netConfig, err = libcni.ConfFromBytes(pluginConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -243,7 +243,7 @@ var _ = Describe("Invoking plugins", func() {
} }
cniBinPath = filepath.Dir(pluginPaths["noop"]) cniBinPath = filepath.Dir(pluginPaths["noop"])
pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.3.0", "capabilities": { "portMappings": true } }` pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.3.1", "capabilities": { "portMappings": true } }`
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}} cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
netConfig = &libcni.NetworkConfig{ netConfig = &libcni.NetworkConfig{
Network: &types.NetConf{ Network: &types.NetConf{
@ -381,7 +381,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(versionInfo).NotTo(BeNil()) Expect(versionInfo).NotTo(BeNil())
Expect(versionInfo.SupportedVersions()).To(Equal([]string{ Expect(versionInfo.SupportedVersions()).To(Equal([]string{
"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1",
})) }))
}) })
@ -452,7 +452,7 @@ var _ = Describe("Invoking plugins", func() {
configList := []byte(fmt.Sprintf(`{ configList := []byte(fmt.Sprintf(`{
"name": "some-list", "name": "some-list",
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"plugins": [ "plugins": [
%s, %s,
%s, %s,

View File

@ -123,7 +123,7 @@ var _ = Describe("Loading configuration from disk", func() {
configDir, err = ioutil.TempDir("", "plugin-conf") configDir, err = ioutil.TempDir("", "plugin-conf")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.0", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`) pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed()) Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
}) })
@ -397,7 +397,7 @@ var _ = Describe("ConfListFromConf", func() {
var testNetConfig *libcni.NetworkConfig var testNetConfig *libcni.NetworkConfig
BeforeEach(func() { BeforeEach(func() {
pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.0" }`) pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1" }`)
tc, err := libcni.ConfFromBytes(pb) tc, err := libcni.ConfFromBytes(pb)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
testNetConfig = tc testNetConfig = tc
@ -415,7 +415,7 @@ var _ = Describe("ConfListFromConf", func() {
Expect(ncl).To(Equal(&libcni.NetworkConfigList{ Expect(ncl).To(Equal(&libcni.NetworkConfigList{
Name: "some-plugin", Name: "some-plugin",
CNIVersion: "0.3.0", CNIVersion: "0.3.1",
Plugins: []*libcni.NetworkConfig{testNetConfig}, Plugins: []*libcni.NetworkConfig{testNetConfig},
})) }))

View File

@ -50,7 +50,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
VersionDecoder: versionDecoder, VersionDecoder: versionDecoder,
} }
pluginPath = "/some/plugin/path" pluginPath = "/some/plugin/path"
netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.0" }`) netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.1" }`)
cniargs = &fakes.CNIArgs{} cniargs = &fakes.CNIArgs{}
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"} cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
}) })

View File

@ -58,7 +58,7 @@ var _ = Describe("RawExec", func() {
"CNI_PATH=/some/bin/path", "CNI_PATH=/some/bin/path",
"CNI_IFNAME=some-eth0", "CNI_IFNAME=some-eth0",
} }
stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.0"}`) stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.1"}`)
execer = &invoke.RawExec{} execer = &invoke.RawExec{}
}) })

View File

@ -226,7 +226,7 @@ var _ = Describe("dispatching to the correct callback", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(`{ Expect(stdout).To(MatchJSON(`{
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"supportedVersions": ["9.8.7"] "supportedVersions": ["9.8.7"]
}`)) }`))
}) })
@ -258,7 +258,7 @@ var _ = Describe("dispatching to the correct callback", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(`{ Expect(stdout).To(MatchJSON(`{
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"supportedVersions": ["9.8.7"] "supportedVersions": ["9.8.7"]
}`)) }`))
}) })

View File

@ -24,9 +24,9 @@ import (
"github.com/containernetworking/cni/pkg/types/020" "github.com/containernetworking/cni/pkg/types/020"
) )
const implementedSpecVersion string = "0.3.0" const implementedSpecVersion string = "0.3.1"
var SupportedVersions = []string{implementedSpecVersion} var SupportedVersions = []string{"0.3.0", implementedSpecVersion}
func NewResult(data []byte) (types.Result, error) { func NewResult(data []byte) (types.Result, error) {
result := &Result{} result := &Result{}
@ -129,7 +129,7 @@ func NewResultFromResult(result types.Result) (*Result, error) {
} }
} }
} }
return nil, fmt.Errorf("unsupported CNI result version %q", version) return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
} }
// Result is what gets returned from the plugin (via stdout) to the caller // Result is what gets returned from the plugin (via stdout) to the caller
@ -194,12 +194,12 @@ func (r *Result) Version() string {
func (r *Result) GetAsVersion(version string) (types.Result, error) { func (r *Result) GetAsVersion(version string) (types.Result, error) {
switch version { switch version {
case implementedSpecVersion: case "0.3.0", implementedSpecVersion:
return r, nil return r, nil
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]: case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
return r.convertTo020() return r.convertTo020()
} }
return nil, fmt.Errorf("cannot convert version 0.3.0 to %q", version) return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
} }
func (r *Result) Print() error { func (r *Result) Print() error {

View File

@ -82,7 +82,7 @@ func testResult() *current.Result {
} }
var _ = Describe("Current types operations", func() { var _ = Describe("Current types operations", func() {
It("correctly encodes a 0.3.0 Result", func() { It("correctly encodes a 0.3.x Result", func() {
res := testResult() res := testResult()
Expect(res.String()).To(Equal("Interfaces:[{Name:eth0 Mac:00:11:22:33:44:55 Sandbox:/proc/3553/ns/net}], IP:[{Version:4 Interface:0 Address:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1} {Version:6 Interface:0 Address:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1}], Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8} {Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}], DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}")) Expect(res.String()).To(Equal("Interfaces:[{Name:eth0 Mac:00:11:22:33:44:55 Sandbox:/proc/3553/ns/net}], IP:[{Version:4 Interface:0 Address:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1} {Version:6 Interface:0 Address:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1}], Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8} {Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}], DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}"))

View File

@ -24,7 +24,7 @@ import (
// Current reports the version of the CNI spec implemented by this library // Current reports the version of the CNI spec implemented by this library
func Current() string { func Current() string {
return "0.3.0" return "0.3.1"
} }
// Legacy PluginInfo describes a plugin that is backwards compatible with the // Legacy PluginInfo describes a plugin that is backwards compatible with the
@ -35,7 +35,7 @@ func Current() string {
// Any future CNI spec versions which meet this definition should be added to // Any future CNI spec versions which meet this definition should be added to
// this list. // this list.
var Legacy = PluginSupports("0.1.0", "0.2.0") var Legacy = PluginSupports("0.1.0", "0.2.0")
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0") var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1")
var resultFactories = []struct { var resultFactories = []struct {
supportedVersions []string supportedVersions []string

View File

@ -45,7 +45,7 @@ var _ = Describe("host-local Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"master": "foo0", "master": "foo0",
@ -213,7 +213,7 @@ var _ = Describe("host-local Operations", func() {
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"master": "foo0", "master": "foo0",

View File

@ -35,6 +35,156 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
func checkBridgeConfig03x(version string, originalNS ns.NetNS) {
const BRNAME = "cni0"
const IFNAME = "eth0"
gwaddr, subnet, err := net.ParseCIDR("10.1.2.1/24")
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "mynet",
"type": "bridge",
"bridge": "%s",
"isDefaultGateway": true,
"ipMasq": false,
"ipam": {
"type": "host-local",
"subnet": "%s"
}
}`, version, BRNAME, subnet.String())
targetNs, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var result *current.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, raw, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(3))
Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
// Make sure bridge link exists
link, err := netlink.LinkByName(result.Interfaces[0].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
// Ensure bridge has gateway address
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(BeNumerically(">", 0))
found := false
subnetPrefix, subnetBits := subnet.Mask.Size()
for _, a := range addrs {
aPrefix, aBits := a.IPNet.Mask.Size()
if a.IPNet.IP.Equal(gwaddr) && aPrefix == subnetPrefix && aBits == subnetBits {
found = true
break
}
}
Expect(found).To(Equal(true))
// Check for the veth link in the main namespace
links, err := netlink.LinkList()
Expect(err).NotTo(HaveOccurred())
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
link, err = netlink.LinkByName(result.Interfaces[1].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(1))
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
// Ensure the default route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound bool
for _, route := range routes {
defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwaddr))
if defaultRouteFound {
break
}
}
Expect(defaultRouteFound).To(Equal(true))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the host veth has been deleted
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the container veth has been deleted
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(result.Interfaces[1].Name)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
}
var _ = Describe("bridge Operations", func() { var _ = Describe("bridge Operations", func() {
var originalNS ns.NetNS var originalNS ns.NetNS
@ -54,7 +204,7 @@ var _ = Describe("bridge Operations", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
CNIVersion: "0.3.0", CNIVersion: "0.3.1",
Name: "testConfig", Name: "testConfig",
Type: "bridge", Type: "bridge",
}, },
@ -99,7 +249,7 @@ var _ = Describe("bridge Operations", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
CNIVersion: "0.3.0", CNIVersion: "0.3.1",
Name: "testConfig", Name: "testConfig",
Type: "bridge", Type: "bridge",
}, },
@ -123,154 +273,12 @@ var _ = Describe("bridge Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("configures and deconfigures a bridge and veth with default route with ADD/DEL", func() { It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.0 config", func() {
const BRNAME = "cni0" checkBridgeConfig03x("0.3.0", originalNS)
const IFNAME = "eth0" })
gwaddr, subnet, err := net.ParseCIDR("10.1.2.1/24") It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.1 config", func() {
Expect(err).NotTo(HaveOccurred()) checkBridgeConfig03x("0.3.1", originalNS)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.0",
"name": "mynet",
"type": "bridge",
"bridge": "%s",
"isDefaultGateway": true,
"ipMasq": false,
"ipam": {
"type": "host-local",
"subnet": "%s"
}
}`, BRNAME, subnet.String())
targetNs, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var result *current.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, raw, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(3))
Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
// Make sure bridge link exists
link, err := netlink.LinkByName(result.Interfaces[0].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
// Ensure bridge has gateway address
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(BeNumerically(">", 0))
found := false
subnetPrefix, subnetBits := subnet.Mask.Size()
for _, a := range addrs {
aPrefix, aBits := a.IPNet.Mask.Size()
if a.IPNet.IP.Equal(gwaddr) && aPrefix == subnetPrefix && aBits == subnetBits {
found = true
break
}
}
Expect(found).To(Equal(true))
// Check for the veth link in the main namespace
links, err := netlink.LinkList()
Expect(err).NotTo(HaveOccurred())
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
link, err = netlink.LinkByName(result.Interfaces[1].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(1))
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
// Ensure the default route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound bool
for _, route := range routes {
defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwaddr))
if defaultRouteFound {
break
}
}
Expect(defaultRouteFound).To(Equal(true))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the host veth has been deleted
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the container veth has been deleted
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(result.Interfaces[1].Name)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
}) })
It("deconfigures an unconfigured bridge with DEL", func() { It("deconfigures an unconfigured bridge with DEL", func() {
@ -457,7 +465,7 @@ var _ = Describe("bridge Operations", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
CNIVersion: "0.3.0", CNIVersion: "0.3.1",
Name: "testConfig", Name: "testConfig",
Type: "bridge", Type: "bridge",
}, },

View File

@ -66,7 +66,7 @@ var _ = Describe("ipvlan Operations", func() {
It("creates an ipvlan link in a non-default namespace", func() { It("creates an ipvlan link in a non-default namespace", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
CNIVersion: "0.3.0", CNIVersion: "0.3.1",
Name: "testConfig", Name: "testConfig",
Type: "ipvlan", Type: "ipvlan",
}, },
@ -106,7 +106,7 @@ var _ = Describe("ipvlan Operations", func() {
const IFNAME = "ipvl0" const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"master": "%s", "master": "%s",

View File

@ -67,7 +67,7 @@ var _ = Describe("macvlan Operations", func() {
It("creates an macvlan link in a non-default namespace", func() { It("creates an macvlan link in a non-default namespace", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
CNIVersion: "0.3.0", CNIVersion: "0.3.1",
Name: "testConfig", Name: "testConfig",
Type: "macvlan", Type: "macvlan",
}, },
@ -105,7 +105,7 @@ var _ = Describe("macvlan Operations", func() {
const IFNAME = "macvl0" const IFNAME = "macvl0"
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "mynet", "name": "mynet",
"type": "macvlan", "type": "macvlan",
"master": "%s", "master": "%s",

View File

@ -43,7 +43,7 @@ var _ = Describe("ptp Operations", func() {
const IFNAME = "ptp0" const IFNAME = "ptp0"
conf := `{ conf := `{
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"name": "mynet", "name": "mynet",
"type": "ptp", "type": "ptp",
"ipMasq": true, "ipMasq": true,

View File

@ -142,7 +142,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
} }
func debugGetSupportedVersions(stdinData []byte) []string { func debugGetSupportedVersions(stdinData []byte) []string {
vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0"} vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1"}
cniArgs := os.Getenv("CNI_ARGS") cniArgs := os.Getenv("CNI_ARGS")
if cniArgs == "" { if cniArgs == "" {
return vers return vers

View File

@ -42,7 +42,7 @@ var _ = Describe("No-op plugin", func() {
BeforeEach(func() { BeforeEach(func() {
debug = &noop_debug.Debug{ debug = &noop_debug.Debug{
ReportResult: reportResult, ReportResult: reportResult,
ReportVersionSupport: []string{"0.1.0", "0.2.0", "0.3.0"}, ReportVersionSupport: []string{"0.1.0", "0.2.0", "0.3.0", "0.3.1"},
} }
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := ioutil.TempFile("", "cni_debug")
@ -64,14 +64,14 @@ var _ = Describe("No-op plugin", func() {
// Keep this last // Keep this last
"CNI_ARGS=" + args, "CNI_ARGS=" + args,
} }
cmd.Stdin = strings.NewReader(`{"some":"stdin-json", "cniVersion": "0.3.0"}`) cmd.Stdin = strings.NewReader(`{"some":"stdin-json", "cniVersion": "0.3.1"}`)
expectedCmdArgs = skel.CmdArgs{ expectedCmdArgs = skel.CmdArgs{
ContainerID: "some-container-id", ContainerID: "some-container-id",
Netns: "/some/netns/path", Netns: "/some/netns/path",
IfName: "some-eth0", IfName: "some-eth0",
Args: args, Args: args,
Path: "/some/bin/path", Path: "/some/bin/path",
StdinData: []byte(`{"some":"stdin-json", "cniVersion": "0.3.0"}`), StdinData: []byte(`{"some":"stdin-json", "cniVersion": "0.3.1"}`),
} }
}) })
@ -102,7 +102,7 @@ var _ = Describe("No-op plugin", func() {
cmd.Stdin = strings.NewReader(`{ cmd.Stdin = strings.NewReader(`{
"some":"stdin-json", "some":"stdin-json",
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"prevResult": { "prevResult": {
"ips": [{"version": "4", "address": "10.1.2.15/24"}] "ips": [{"version": "4", "address": "10.1.2.15/24"}]
} }
@ -119,7 +119,7 @@ var _ = Describe("No-op plugin", func() {
cmd.Stdin = strings.NewReader(`{ cmd.Stdin = strings.NewReader(`{
"some":"stdin-json", "some":"stdin-json",
"cniVersion": "0.3.0", "cniVersion": "0.3.1",
"prevResult": { "prevResult": {
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
@ -139,7 +139,7 @@ var _ = Describe("No-op plugin", func() {
// Remove the DEBUG option from CNI_ARGS and regular args // Remove the DEBUG option from CNI_ARGS and regular args
newArgs := "FOO=BAR" newArgs := "FOO=BAR"
cmd.Env[len(cmd.Env)-1] = "CNI_ARGS=" + newArgs cmd.Env[len(cmd.Env)-1] = "CNI_ARGS=" + newArgs
newStdin := fmt.Sprintf(`{"some":"stdin-json", "cniVersion": "0.3.0", "debugFile": "%s"}`, debugFileName) newStdin := fmt.Sprintf(`{"some":"stdin-json", "cniVersion": "0.3.1", "debugFile": "%s"}`, debugFileName)
cmd.Stdin = strings.NewReader(newStdin) cmd.Stdin = strings.NewReader(newStdin)
expectedCmdArgs.Args = newArgs expectedCmdArgs.Args = newArgs
expectedCmdArgs.StdinData = []byte(newStdin) expectedCmdArgs.StdinData = []byte(newStdin)