BeforeEach(func() {
rawExec = &fakes.RawExec{}
- rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ip4": { "ip": "1.2.3.4/24" } }`)
+ rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
versionDecoder = &fakes.VersionDecoder{}
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
VersionDecoder: versionDecoder,
}
pluginPath = "/some/plugin/path"
- netconf = []byte(`{ "some": "stdin", "cniVersion": "0.2.0" }`)
+ netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.0" }`)
cniargs = &fakes.CNIArgs{}
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
})
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
- Expect(result.IP4.IP.IP.String()).To(Equal("1.2.3.4"))
+ Expect(len(result.IPs)).To(Equal(1))
+ Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
})
It("passes its arguments through to the rawExec", func() {
"CNI_PATH=/some/bin/path",
"CNI_IFNAME=some-eth0",
}
- stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.2.0"}`)
+ stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.0"}`)
execer = &invoke.RawExec{}
})
import (
"fmt"
+ "net"
"os"
"github.com/containernetworking/cni/pkg/invoke"
// ConfigureIface takes the result of IPAM plugin and
// applies to the ifName interface
func ConfigureIface(ifName string, res *current.Result) error {
+ if len(res.Interfaces) == 0 {
+ return fmt.Errorf("no interfaces to configure")
+ }
+
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
}
- // TODO(eyakubovich): IPv6
- addr := &netlink.Addr{IPNet: &res.IP4.IP, Label: ""}
- if err = netlink.AddrAdd(link, addr); err != nil {
- return fmt.Errorf("failed to add IP addr to %q: %v", ifName, err)
+ var v4gw, v6gw net.IP
+ for _, ipc := range res.IPs {
+ if int(ipc.Interface) >= len(res.Interfaces) || res.Interfaces[ipc.Interface].Name != ifName {
+ // IP address is for a different interface
+ return fmt.Errorf("failed to add IP addr %v to %q: invalid interface index", ipc, ifName)
+ }
+
+ addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}
+ if err = netlink.AddrAdd(link, addr); err != nil {
+ return fmt.Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err)
+ }
+
+ gwIsV4 := ipc.Gateway.To4() != nil
+ if gwIsV4 && v4gw == nil {
+ v4gw = ipc.Gateway
+ } else if !gwIsV4 && v6gw == nil {
+ v6gw = ipc.Gateway
+ }
}
- for _, r := range res.IP4.Routes {
+ for _, r := range res.Routes {
+ routeIsV4 := r.Dst.IP.To4() != nil
gw := r.GW
if gw == nil {
- gw = res.IP4.Gateway
+ if routeIsV4 && v4gw != nil {
+ gw = v4gw
+ } else if !routeIsV4 && v6gw != nil {
+ gw = v6gw
+ }
}
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
// we skip over duplicate routes as we assume the first one wins
Expect(ipgw6).NotTo(BeNil())
result = ¤t.Result{
- IP4: ¤t.IPConfig{
- IP: *ipv4,
- Gateway: ipgw4,
- Routes: []types.Route{
- {Dst: *routev4, GW: routegwv4},
+ Interfaces: []*current.Interface{
+ {
+ Name: "eth0",
+ Mac: "00:11:22:33:44:55",
+ Sandbox: "/proc/3553/ns/net",
+ },
+ {
+ Name: "fake0",
+ Mac: "00:33:44:55:66:77",
+ Sandbox: "/proc/1234/ns/net",
},
},
- IP6: ¤t.IPConfig{
- IP: *ipv6,
- Gateway: ipgw6,
- Routes: []types.Route{
- {Dst: *routev6, GW: routegwv6},
+ IPs: []*current.IPConfig{
+ {
+ Version: "4",
+ Interface: 0,
+ Address: *ipv4,
+ Gateway: ipgw4,
+ },
+ {
+ Version: "6",
+ Interface: 0,
+ Address: *ipv6,
+ Gateway: ipgw6,
},
},
+ Routes: []*types.Route{
+ {Dst: *routev4, GW: routegwv4},
+ {Dst: *routev6, GW: routegwv6},
+ },
}
})
Expect(len(v4addrs)).To(Equal(1))
Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(Equal(true))
- // Doesn't support IPv6 yet so only link-local address expected
v6addrs, err := netlink.AddrList(link, syscall.AF_INET6)
Expect(err).NotTo(HaveOccurred())
- Expect(len(v6addrs)).To(Equal(1))
+ Expect(len(v6addrs)).To(Equal(2))
+
+ var found bool
+ for _, a := range v6addrs {
+ if ipNetEqual(a.IPNet, ipv6) {
+ found = true
+ break
+ }
+ }
+ Expect(found).To(Equal(true))
- // Ensure the v4 route
+ // Ensure the v4 route, v6 route, and subnet route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
- var v4found bool
+ var v4found, v6found bool
for _, route := range routes {
isv4 := route.Dst.IP.To4() != nil
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
v4found = true
+ }
+ if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(routegwv6) {
+ v6found = true
+ }
+
+ if v4found && v6found {
break
}
}
Expect(v4found).To(Equal(true))
+ Expect(v6found).To(Equal(true))
return nil
})
})
It("configures a link with routes using address gateways", func() {
- result.IP4.Routes[0].GW = nil
- result.IP6.Routes[0].GW = nil
+ result.Routes[0].GW = nil
+ result.Routes[1].GW = nil
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(LINK_NAME))
- // Ensure the v4 route
+ // Ensure the v4 route, v6 route, and subnet route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
- var v4found bool
+ var v4found, v6found bool
for _, route := range routes {
isv4 := route.Dst.IP.To4() != nil
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
v4found = true
+ }
+ if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(ipgw6) {
+ v6found = true
+ }
+
+ if v4found && v6found {
break
}
}
Expect(v4found).To(Equal(true))
+ Expect(v6found).To(Equal(true))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
+ It("returns an error when the interface index doesn't match the link name", func() {
+ result.IPs[0].Interface = 1
+ err := originalNS.Do(func(ns.NetNS) error {
+ return ConfigureIface(LINK_NAME, result)
+ })
+ Expect(err).To(HaveOccurred())
+ })
+
+ It("returns an error when the interface index is too big", func() {
+ result.IPs[0].Interface = 2
+ err := originalNS.Do(func(ns.NetNS) error {
+ return ConfigureIface(LINK_NAME, result)
+ })
+ Expect(err).To(HaveOccurred())
+ })
+
+ It("returns an error when there are no interfaces to configure", func() {
+ result.Interfaces = []*current.Interface{}
+ err := originalNS.Do(func(ns.NetNS) error {
+ return ConfigureIface(LINK_NAME, result)
+ })
+ Expect(err).To(HaveOccurred())
+ })
+
It("returns an error when configuring the wrong interface", func() {
err := originalNS.Do(func(ns.NetNS) error {
return ConfigureIface("asdfasdf", result)
Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(`{
- "cniVersion": "0.2.0",
+ "cniVersion": "0.3.0",
"supportedVersions": ["9.8.7"]
}`))
})
Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(`{
- "cniVersion": "0.2.0",
+ "cniVersion": "0.3.0",
"supportedVersions": ["9.8.7"]
}`))
})
--- /dev/null
+// Copyright 2016 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 types020
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "os"
+
+ "github.com/containernetworking/cni/pkg/types"
+)
+
+const implementedSpecVersion string = "0.2.0"
+
+var SupportedVersions = []string{"", "0.1.0", implementedSpecVersion}
+
+// Compatibility types for CNI version 0.1.0 and 0.2.0
+
+func NewResult(data []byte) (types.Result, error) {
+ result := &Result{}
+ if err := json.Unmarshal(data, result); err != nil {
+ return nil, err
+ }
+ return result, nil
+}
+
+func GetResult(r types.Result) (*Result, error) {
+ // We expect version 0.1.0/0.2.0 results
+ result020, err := r.GetAsVersion(implementedSpecVersion)
+ if err != nil {
+ return nil, err
+ }
+ result, ok := result020.(*Result)
+ if !ok {
+ return nil, fmt.Errorf("failed to convert result")
+ }
+ return result, nil
+}
+
+// Result is what gets returned from the plugin (via stdout) to the caller
+type Result struct {
+ IP4 *IPConfig `json:"ip4,omitempty"`
+ IP6 *IPConfig `json:"ip6,omitempty"`
+ DNS types.DNS `json:"dns,omitempty"`
+}
+
+func (r *Result) Version() string {
+ return implementedSpecVersion
+}
+
+func (r *Result) GetAsVersion(version string) (types.Result, error) {
+ for _, supportedVersion := range SupportedVersions {
+ if version == supportedVersion {
+ return r, nil
+ }
+ }
+ return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
+}
+
+func (r *Result) Print() error {
+ data, err := json.MarshalIndent(r, "", " ")
+ if err != nil {
+ return err
+ }
+ _, err = os.Stdout.Write(data)
+ return err
+}
+
+// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
+// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
+// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
+func (r *Result) String() string {
+ var str string
+ if r.IP4 != nil {
+ str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
+ }
+ if r.IP6 != nil {
+ str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
+ }
+ return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
+}
+
+// IPConfig contains values necessary to configure an interface
+type IPConfig struct {
+ IP net.IPNet
+ Gateway net.IP
+ Routes []types.Route
+}
+
+// net.IPNet is not JSON (un)marshallable so this duality is needed
+// for our custom IPNet type
+
+// JSON (un)marshallable types
+type ipConfig struct {
+ IP types.IPNet `json:"ip"`
+ Gateway net.IP `json:"gateway,omitempty"`
+ Routes []types.Route `json:"routes,omitempty"`
+}
+
+func (c *IPConfig) MarshalJSON() ([]byte, error) {
+ ipc := ipConfig{
+ IP: types.IPNet(c.IP),
+ Gateway: c.Gateway,
+ Routes: c.Routes,
+ }
+
+ return json.Marshal(ipc)
+}
+
+func (c *IPConfig) UnmarshalJSON(data []byte) error {
+ ipc := ipConfig{}
+ if err := json.Unmarshal(data, &ipc); err != nil {
+ return err
+ }
+
+ c.IP = net.IPNet(ipc.IP)
+ c.Gateway = ipc.Gateway
+ c.Routes = ipc.Routes
+ return nil
+}
--- /dev/null
+// Copyright 2016 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 types020_test
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+
+ "testing"
+)
+
+func TestTypes010(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "0.1.0/0.2.0 Types Suite")
+}
--- /dev/null
+// Copyright 2016 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 types020_test
+
+import (
+ "io/ioutil"
+ "net"
+ "os"
+
+ "github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/cni/pkg/types/020"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
+ It("correctly encodes a 0.1.0/0.2.0 Result", func() {
+ ipv4, err := types.ParseCIDR("1.2.3.30/24")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(ipv4).NotTo(BeNil())
+
+ routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(routev4).NotTo(BeNil())
+ Expect(routegwv4).NotTo(BeNil())
+
+ ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(ipv6).NotTo(BeNil())
+
+ routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(routev6).NotTo(BeNil())
+ Expect(routegwv6).NotTo(BeNil())
+
+ // Set every field of the struct to ensure source compatibility
+ res := types020.Result{
+ IP4: &types020.IPConfig{
+ IP: *ipv4,
+ Gateway: net.ParseIP("1.2.3.1"),
+ Routes: []types.Route{
+ {Dst: *routev4, GW: routegwv4},
+ },
+ },
+ IP6: &types020.IPConfig{
+ IP: *ipv6,
+ Gateway: net.ParseIP("abcd:1234:ffff::1"),
+ Routes: []types.Route{
+ {Dst: *routev6, GW: routegwv6},
+ },
+ },
+ DNS: types.DNS{
+ Nameservers: []string{"1.2.3.4", "1::cafe"},
+ Domain: "acompany.com",
+ Search: []string{"somedomain.com", "otherdomain.net"},
+ Options: []string{"foo", "bar"},
+ },
+ }
+
+ Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{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]}"))
+
+ // Redirect stdout to capture JSON result
+ oldStdout := os.Stdout
+ r, w, err := os.Pipe()
+ Expect(err).NotTo(HaveOccurred())
+
+ os.Stdout = w
+ err = res.Print()
+ w.Close()
+ Expect(err).NotTo(HaveOccurred())
+
+ // parse the result
+ out, err := ioutil.ReadAll(r)
+ os.Stdout = oldStdout
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(string(out)).To(Equal(`{
+ "ip4": {
+ "ip": "1.2.3.30/24",
+ "gateway": "1.2.3.1",
+ "routes": [
+ {
+ "dst": "15.5.6.0/24",
+ "gw": "15.5.6.8"
+ }
+ ]
+ },
+ "ip6": {
+ "ip": "abcd:1234:ffff::cdde/64",
+ "gateway": "abcd:1234:ffff::1",
+ "routes": [
+ {
+ "dst": "1111:dddd::/80",
+ "gw": "1111:dddd::aaaa"
+ }
+ ]
+ },
+ "dns": {
+ "nameservers": [
+ "1.2.3.4",
+ "1::cafe"
+ ],
+ "domain": "acompany.com",
+ "search": [
+ "somedomain.com",
+ "otherdomain.net"
+ ],
+ "options": [
+ "foo",
+ "bar"
+ ]
+ }
+}`))
+ })
+})
"os"
"github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/cni/pkg/types/020"
)
-const implementedSpecVersion string = "0.2.0"
+const implementedSpecVersion string = "0.3.0"
-var SupportedVersions = []string{"", "0.1.0", implementedSpecVersion}
+var SupportedVersions = []string{implementedSpecVersion}
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
}
func GetResult(r types.Result) (*Result, error) {
- newResult, err := r.GetAsVersion(implementedSpecVersion)
+ resultCurrent, err := r.GetAsVersion(implementedSpecVersion)
if err != nil {
return nil, err
}
- result, ok := newResult.(*Result)
+ result, ok := resultCurrent.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
versions []string
convert func(types.Result) (*Result, error)
}{
- {SupportedVersions, convertFrom020},
+ {types020.SupportedVersions, convertFrom020},
+ {SupportedVersions, convertFrom030},
}
func convertFrom020(result types.Result) (*Result, error) {
+ oldResult, err := types020.GetResult(result)
+ if err != nil {
+ return nil, err
+ }
+
+ newResult := &Result{
+ DNS: oldResult.DNS,
+ Routes: []*types.Route{},
+ }
+
+ if oldResult.IP4 != nil {
+ newResult.IPs = append(newResult.IPs, &IPConfig{
+ Version: "4",
+ Interface: -1,
+ Address: oldResult.IP4.IP,
+ Gateway: oldResult.IP4.Gateway,
+ })
+ for _, route := range oldResult.IP4.Routes {
+ gw := route.GW
+ if gw == nil {
+ gw = oldResult.IP4.Gateway
+ }
+ newResult.Routes = append(newResult.Routes, &types.Route{
+ Dst: route.Dst,
+ GW: gw,
+ })
+ }
+ }
+
+ if oldResult.IP6 != nil {
+ newResult.IPs = append(newResult.IPs, &IPConfig{
+ Version: "6",
+ Interface: -1,
+ Address: oldResult.IP6.IP,
+ Gateway: oldResult.IP6.Gateway,
+ })
+ for _, route := range oldResult.IP6.Routes {
+ gw := route.GW
+ if gw == nil {
+ gw = oldResult.IP6.Gateway
+ }
+ newResult.Routes = append(newResult.Routes, &types.Route{
+ Dst: route.Dst,
+ GW: gw,
+ })
+ }
+ }
+
+ if len(newResult.IPs) == 0 {
+ return nil, fmt.Errorf("cannot convert: no valid IP addresses")
+ }
+
+ return newResult, nil
+}
+
+func convertFrom030(result types.Result) (*Result, error) {
newResult, ok := result.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
- IP4 *IPConfig `json:"ip4,omitempty"`
- IP6 *IPConfig `json:"ip6,omitempty"`
- DNS types.DNS `json:"dns,omitempty"`
+ Interfaces []*Interface `json:"interfaces,omitempty"`
+ IPs []*IPConfig `json:"ips,omitempty"`
+ Routes []*types.Route `json:"routes,omitempty"`
+ DNS types.DNS `json:"dns,omitempty"`
+}
+
+// Convert to the older 0.2.0 CNI spec Result type
+func (r *Result) convertTo020() (*types020.Result, error) {
+ oldResult := &types020.Result{
+ DNS: r.DNS,
+ }
+
+ for _, ip := range r.IPs {
+ // Only convert the first IP address of each version as 0.2.0
+ // and earlier cannot handle multiple IP addresses
+ if ip.Version == "4" && oldResult.IP4 == nil {
+ oldResult.IP4 = &types020.IPConfig{
+ IP: ip.Address,
+ Gateway: ip.Gateway,
+ }
+ } else if ip.Version == "6" && oldResult.IP6 == nil {
+ oldResult.IP6 = &types020.IPConfig{
+ IP: ip.Address,
+ Gateway: ip.Gateway,
+ }
+ }
+
+ if oldResult.IP4 != nil && oldResult.IP6 != nil {
+ break
+ }
+ }
+
+ for _, route := range r.Routes {
+ is4 := route.Dst.IP.To4() != nil
+ if is4 && oldResult.IP4 != nil {
+ oldResult.IP4.Routes = append(oldResult.IP4.Routes, types.Route{
+ Dst: route.Dst,
+ GW: route.GW,
+ })
+ } else if !is4 && oldResult.IP6 != nil {
+ oldResult.IP6.Routes = append(oldResult.IP6.Routes, types.Route{
+ Dst: route.Dst,
+ GW: route.GW,
+ })
+ }
+ }
+
+ if oldResult.IP4 == nil && oldResult.IP6 == nil {
+ return nil, fmt.Errorf("cannot convert: no valid IP addresses")
+ }
+
+ return oldResult, nil
}
func (r *Result) Version() string {
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
- for _, supportedVersion := range SupportedVersions {
- if version == supportedVersion {
- return r, nil
- }
+ switch version {
+ case implementedSpecVersion:
+ return r, nil
+ case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
+ return r.convertTo020()
}
- return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
+ return nil, fmt.Errorf("cannot convert version 0.3.0 to %q", version)
}
func (r *Result) Print() error {
return err
}
-// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
-// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
+// String returns a formatted string in the form of "[Interfaces: $1,][ IP: $2,] DNS: $3" where
+// $1 represents the receiver's Interfaces, $2 represents the receiver's IP addresses and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
- if r.IP4 != nil {
- str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
+ if len(r.Interfaces) > 0 {
+ str += fmt.Sprintf("Interfaces:%+v, ", r.Interfaces)
}
- if r.IP6 != nil {
- str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
+ if len(r.IPs) > 0 {
+ str += fmt.Sprintf("IP:%+v, ", r.IPs)
+ }
+ if len(r.Routes) > 0 {
+ str += fmt.Sprintf("Routes:%+v, ", r.Routes)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
-// IPConfig contains values necessary to configure an interface
+// Convert this old version result to the current CNI version result
+func (r *Result) Convert() (*Result, error) {
+ return r, nil
+}
+
+// Interface contains values about the created interfaces
+type Interface struct {
+ Name string `json:"name"`
+ Mac string `json:"mac,omitempty"`
+ Sandbox string `json:"sandbox,omitempty"`
+}
+
+func (i *Interface) String() string {
+ return fmt.Sprintf("%+v", *i)
+}
+
+// IPConfig contains values necessary to configure an IP address on an interface
type IPConfig struct {
- IP net.IPNet
- Gateway net.IP
- Routes []types.Route
+ // IP version, either "4" or "6"
+ Version string
+ // Index into Result structs Interfaces list
+ Interface int
+ Address net.IPNet
+ Gateway net.IP
}
-// net.IPNet is not JSON (un)marshallable so this duality is needed
-// for our custom IPNet type
+func (i *IPConfig) String() string {
+ return fmt.Sprintf("%+v", *i)
+}
// JSON (un)marshallable types
type ipConfig struct {
- IP types.IPNet `json:"ip"`
- Gateway net.IP `json:"gateway,omitempty"`
- Routes []types.Route `json:"routes,omitempty"`
+ Version string `json:"version"`
+ Interface int `json:"interface,omitempty"`
+ Address types.IPNet `json:"address"`
+ Gateway net.IP `json:"gateway,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
- IP: types.IPNet(c.IP),
- Gateway: c.Gateway,
- Routes: c.Routes,
+ Version: c.Version,
+ Interface: c.Interface,
+ Address: types.IPNet(c.Address),
+ Gateway: c.Gateway,
}
return json.Marshal(ipc)
return err
}
- c.IP = net.IPNet(ipc.IP)
+ c.Version = ipc.Version
+ c.Interface = ipc.Interface
+ c.Address = net.IPNet(ipc.Address)
c.Gateway = ipc.Gateway
- c.Routes = ipc.Routes
return nil
}
func TestTypes010(t *testing.T) {
RegisterFailHandler(Fail)
- RunSpecs(t, "0.1.0 Types Suite")
+ RunSpecs(t, "0.3.0 Types Suite")
}
. "github.com/onsi/gomega"
)
-var _ = Describe("Ensures compatibility with the 0.1.0 spec", func() {
- It("correctly encodes a 0.1.0 Result", func() {
- ipv4, err := types.ParseCIDR("1.2.3.30/24")
- Expect(err).NotTo(HaveOccurred())
- Expect(ipv4).NotTo(BeNil())
+func testResult() *current.Result {
+ ipv4, err := types.ParseCIDR("1.2.3.30/24")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(ipv4).NotTo(BeNil())
- routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
- Expect(err).NotTo(HaveOccurred())
- Expect(routev4).NotTo(BeNil())
- Expect(routegwv4).NotTo(BeNil())
+ routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(routev4).NotTo(BeNil())
+ Expect(routegwv4).NotTo(BeNil())
- ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
- Expect(err).NotTo(HaveOccurred())
- Expect(ipv6).NotTo(BeNil())
+ ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(ipv6).NotTo(BeNil())
- routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
- Expect(err).NotTo(HaveOccurred())
- Expect(routev6).NotTo(BeNil())
- Expect(routegwv6).NotTo(BeNil())
-
- // Set every field of the struct to ensure source compatibility
- res := current.Result{
- IP4: ¤t.IPConfig{
- IP: *ipv4,
- Gateway: net.ParseIP("1.2.3.1"),
- Routes: []types.Route{
- {Dst: *routev4, GW: routegwv4},
- },
+ routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(routev6).NotTo(BeNil())
+ Expect(routegwv6).NotTo(BeNil())
+
+ // Set every field of the struct to ensure source compatibility
+ return ¤t.Result{
+ Interfaces: []*current.Interface{
+ {
+ Name: "eth0",
+ Mac: "00:11:22:33:44:55",
+ Sandbox: "/proc/3553/ns/net",
},
- IP6: ¤t.IPConfig{
- IP: *ipv6,
- Gateway: net.ParseIP("abcd:1234:ffff::1"),
- Routes: []types.Route{
- {Dst: *routev6, GW: routegwv6},
- },
+ },
+ IPs: []*current.IPConfig{
+ {
+ Version: "4",
+ Interface: 0,
+ Address: *ipv4,
+ Gateway: net.ParseIP("1.2.3.1"),
},
- DNS: types.DNS{
- Nameservers: []string{"1.2.3.4", "1::cafe"},
- Domain: "acompany.com",
- Search: []string{"somedomain.com", "otherdomain.net"},
- Options: []string{"foo", "bar"},
+ {
+ Version: "6",
+ Interface: 0,
+ Address: *ipv6,
+ Gateway: net.ParseIP("abcd:1234:ffff::1"),
},
- }
+ },
+ Routes: []*types.Route{
+ {Dst: *routev4, GW: routegwv4},
+ {Dst: *routev6, GW: routegwv6},
+ },
+ DNS: types.DNS{
+ Nameservers: []string{"1.2.3.4", "1::cafe"},
+ Domain: "acompany.com",
+ Search: []string{"somedomain.com", "otherdomain.net"},
+ Options: []string{"foo", "bar"},
+ },
+ }
+}
+
+var _ = Describe("Ensures compatibility with the 0.3.0 spec", func() {
+ It("correctly encodes a 0.3.0 Result", func() {
+ res := testResult()
- Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{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]}"))
// Redirect stdout to capture JSON result
oldStdout := os.Stdout
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(Equal(`{
+ "interfaces": [
+ {
+ "name": "eth0",
+ "mac": "00:11:22:33:44:55",
+ "sandbox": "/proc/3553/ns/net"
+ }
+ ],
+ "ips": [
+ {
+ "version": "4",
+ "address": "1.2.3.30/24",
+ "gateway": "1.2.3.1"
+ },
+ {
+ "version": "6",
+ "address": "abcd:1234:ffff::cdde/64",
+ "gateway": "abcd:1234:ffff::1"
+ }
+ ],
+ "routes": [
+ {
+ "dst": "15.5.6.0/24",
+ "gw": "15.5.6.8"
+ },
+ {
+ "dst": "1111:dddd::/80",
+ "gw": "1111:dddd::aaaa"
+ }
+ ],
+ "dns": {
+ "nameservers": [
+ "1.2.3.4",
+ "1::cafe"
+ ],
+ "domain": "acompany.com",
+ "search": [
+ "somedomain.com",
+ "otherdomain.net"
+ ],
+ "options": [
+ "foo",
+ "bar"
+ ]
+ }
+}`))
+ })
+
+ var _ = Describe("Ensures compatibility with the 0.1.0 spec", func() {
+ It("correctly encodes a 0.1.0 Result", func() {
+ res, err := testResult().GetAsVersion("0.1.0")
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{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]}"))
+
+ // Redirect stdout to capture JSON result
+ oldStdout := os.Stdout
+ r, w, err := os.Pipe()
+ Expect(err).NotTo(HaveOccurred())
+
+ os.Stdout = w
+ err = res.Print()
+ w.Close()
+ Expect(err).NotTo(HaveOccurred())
+
+ // parse the result
+ out, err := ioutil.ReadAll(r)
+ os.Stdout = oldStdout
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(string(out)).To(Equal(`{
"ip4": {
"ip": "1.2.3.30/24",
"gateway": "1.2.3.1",
]
}
}`))
+ })
})
})
import (
"encoding/json"
+ "fmt"
"net"
"os"
)
GW net.IP
}
+func (r *Route) String() string {
+ return fmt.Sprintf("%+v", *r)
+}
+
// Well known error codes
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
const (
"sync"
"github.com/containernetworking/cni/pkg/types"
- "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/version/testhelpers"
)
//
// As we change the CNI spec, the Result type and this value may change.
// The text of the example plugins should not.
-var ExpectedResult = ¤t.Result{
- IP4: ¤t.IPConfig{
+var ExpectedResult = &types020.Result{
+ IP4: &types020.IPConfig{
IP: net.IPNet{
IP: net.ParseIP("10.1.2.3"),
Mask: net.CIDRMask(24, 32),
"fmt"
"github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
)
// Current reports the version of the CNI spec implemented by this library
func Current() string {
- return "0.2.0"
+ return "0.3.0"
}
// Legacy PluginInfo describes a plugin that is backwards compatible with the
// Any future CNI spec versions which meet this definition should be added to
// this list.
var Legacy = PluginSupports("0.1.0", "0.2.0")
+var All = PluginSupports("0.1.0", "0.2.0", "0.3.0")
var resultFactories = []struct {
supportedVersions []string
newResult types.ResultFactoryFunc
}{
{current.SupportedVersions, current.NewResult},
+ {types020.SupportedVersions, types020.NewResult},
}
// Finds a Result object matching the requested version (if any) and asks