spec/plugins: return interface details and multiple IP addresses to runtime
Updates the spec and plugins to return an array of interfaces and IP details to the runtime including: - interface names and MAC addresses configured by the plugin - whether the interfaces are sandboxed (container/VM) or host (bridge, veth, etc) - multiple IP addresses configured by IPAM and which interface they have been assigned to Returning interface details is useful for runtimes, as well as allowing more flexible chaining of CNI plugins themselves. For example, some meta plugins may need to know the host-side interface to be able to apply firewall or traffic shaping rules to the container.
This commit is contained in:
parent
b89b56dc97
commit
06b397912b
@ -40,7 +40,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
rawExec = &fakes.RawExec{}
|
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 = &fakes.VersionDecoder{}
|
||||||
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
|
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
|
||||||
@ -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.2.0" }`)
|
netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.0" }`)
|
||||||
cniargs = &fakes.CNIArgs{}
|
cniargs = &fakes.CNIArgs{}
|
||||||
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
|
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
|
||||||
})
|
})
|
||||||
@ -62,7 +62,8 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||||||
|
|
||||||
result, err := current.GetResult(r)
|
result, err := current.GetResult(r)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
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() {
|
It("passes its arguments through to the rawExec", func() {
|
||||||
|
@ -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.2.0"}`)
|
stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.0"}`)
|
||||||
execer = &invoke.RawExec{}
|
execer = &invoke.RawExec{}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
36
ipam/ipam.go
36
ipam/ipam.go
@ -16,6 +16,7 @@ package ipam
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/invoke"
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
@ -37,6 +38,10 @@ func ExecDel(plugin string, netconf []byte) error {
|
|||||||
// ConfigureIface takes the result of IPAM plugin and
|
// ConfigureIface takes the result of IPAM plugin and
|
||||||
// applies to the ifName interface
|
// applies to the ifName interface
|
||||||
func ConfigureIface(ifName string, res *current.Result) error {
|
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)
|
link, err := netlink.LinkByName(ifName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
@ -46,16 +51,35 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
|||||||
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
|
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(eyakubovich): IPv6
|
var v4gw, v6gw net.IP
|
||||||
addr := &netlink.Addr{IPNet: &res.IP4.IP, Label: ""}
|
for _, ipc := range res.IPs {
|
||||||
if err = netlink.AddrAdd(link, addr); err != nil {
|
if int(ipc.Interface) >= len(res.Interfaces) || res.Interfaces[ipc.Interface].Name != ifName {
|
||||||
return fmt.Errorf("failed to add IP addr to %q: %v", ifName, err)
|
// 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
|
gw := r.GW
|
||||||
if gw == nil {
|
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 {
|
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
|
||||||
// we skip over duplicate routes as we assume the first one wins
|
// we skip over duplicate routes as we assume the first one wins
|
||||||
|
@ -94,19 +94,35 @@ var _ = Describe("IPAM Operations", func() {
|
|||||||
Expect(ipgw6).NotTo(BeNil())
|
Expect(ipgw6).NotTo(BeNil())
|
||||||
|
|
||||||
result = ¤t.Result{
|
result = ¤t.Result{
|
||||||
IP4: ¤t.IPConfig{
|
Interfaces: []*current.Interface{
|
||||||
IP: *ipv4,
|
{
|
||||||
Gateway: ipgw4,
|
Name: "eth0",
|
||||||
Routes: []types.Route{
|
Mac: "00:11:22:33:44:55",
|
||||||
{Dst: *routev4, GW: routegwv4},
|
Sandbox: "/proc/3553/ns/net",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "fake0",
|
||||||
|
Mac: "00:33:44:55:66:77",
|
||||||
|
Sandbox: "/proc/1234/ns/net",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IP6: ¤t.IPConfig{
|
IPs: []*current.IPConfig{
|
||||||
IP: *ipv6,
|
{
|
||||||
Gateway: ipgw6,
|
Version: "4",
|
||||||
Routes: []types.Route{
|
Interface: 0,
|
||||||
{Dst: *routev6, GW: routegwv6},
|
Address: *ipv4,
|
||||||
|
Gateway: ipgw4,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Version: "6",
|
||||||
|
Interface: 0,
|
||||||
|
Address: *ipv6,
|
||||||
|
Gateway: ipgw6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Routes: []*types.Route{
|
||||||
|
{Dst: *routev4, GW: routegwv4},
|
||||||
|
{Dst: *routev6, GW: routegwv6},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -131,24 +147,39 @@ var _ = Describe("IPAM Operations", func() {
|
|||||||
Expect(len(v4addrs)).To(Equal(1))
|
Expect(len(v4addrs)).To(Equal(1))
|
||||||
Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(Equal(true))
|
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)
|
v6addrs, err := netlink.AddrList(link, syscall.AF_INET6)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(len(v6addrs)).To(Equal(1))
|
Expect(len(v6addrs)).To(Equal(2))
|
||||||
|
|
||||||
// Ensure the v4 route
|
var found bool
|
||||||
|
for _, a := range v6addrs {
|
||||||
|
if ipNetEqual(a.IPNet, ipv6) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(found).To(Equal(true))
|
||||||
|
|
||||||
|
// Ensure the v4 route, v6 route, and subnet route
|
||||||
routes, err := netlink.RouteList(link, 0)
|
routes, err := netlink.RouteList(link, 0)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
var v4found bool
|
var v4found, v6found bool
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
isv4 := route.Dst.IP.To4() != nil
|
isv4 := route.Dst.IP.To4() != nil
|
||||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
|
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
|
||||||
v4found = true
|
v4found = true
|
||||||
|
}
|
||||||
|
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(routegwv6) {
|
||||||
|
v6found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4found && v6found {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expect(v4found).To(Equal(true))
|
Expect(v4found).To(Equal(true))
|
||||||
|
Expect(v6found).To(Equal(true))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -156,8 +187,8 @@ var _ = Describe("IPAM Operations", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("configures a link with routes using address gateways", func() {
|
It("configures a link with routes using address gateways", func() {
|
||||||
result.IP4.Routes[0].GW = nil
|
result.Routes[0].GW = nil
|
||||||
result.IP6.Routes[0].GW = nil
|
result.Routes[1].GW = nil
|
||||||
err := originalNS.Do(func(ns.NetNS) error {
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
defer GinkgoRecover()
|
defer GinkgoRecover()
|
||||||
|
|
||||||
@ -168,25 +199,56 @@ var _ = Describe("IPAM Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(link.Attrs().Name).To(Equal(LINK_NAME))
|
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)
|
routes, err := netlink.RouteList(link, 0)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
var v4found bool
|
var v4found, v6found bool
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
isv4 := route.Dst.IP.To4() != nil
|
isv4 := route.Dst.IP.To4() != nil
|
||||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
|
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
|
||||||
v4found = true
|
v4found = true
|
||||||
|
}
|
||||||
|
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(ipgw6) {
|
||||||
|
v6found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4found && v6found {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expect(v4found).To(Equal(true))
|
Expect(v4found).To(Equal(true))
|
||||||
|
Expect(v6found).To(Equal(true))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
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() {
|
It("returns an error when configuring the wrong interface", func() {
|
||||||
err := originalNS.Do(func(ns.NetNS) error {
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
return ConfigureIface("asdfasdf", result)
|
return ConfigureIface("asdfasdf", result)
|
||||||
|
@ -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.2.0",
|
"cniVersion": "0.3.0",
|
||||||
"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.2.0",
|
"cniVersion": "0.3.0",
|
||||||
"supportedVersions": ["9.8.7"]
|
"supportedVersions": ["9.8.7"]
|
||||||
}`))
|
}`))
|
||||||
})
|
})
|
||||||
|
133
types/020/types.go
Normal file
133
types/020/types.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// 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
|
||||||
|
}
|
27
types/020/types_suite_test.go
Normal file
27
types/020/types_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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")
|
||||||
|
}
|
128
types/020/types_test.go
Normal file
128
types/020/types_test.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
})
|
||||||
|
})
|
@ -21,11 +21,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"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) {
|
func NewResult(data []byte) (types.Result, error) {
|
||||||
result := &Result{}
|
result := &Result{}
|
||||||
@ -36,11 +37,11 @@ func NewResult(data []byte) (types.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetResult(r types.Result) (*Result, error) {
|
func GetResult(r types.Result) (*Result, error) {
|
||||||
newResult, err := r.GetAsVersion(implementedSpecVersion)
|
resultCurrent, err := r.GetAsVersion(implementedSpecVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result, ok := newResult.(*Result)
|
result, ok := resultCurrent.(*Result)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to convert result")
|
return nil, fmt.Errorf("failed to convert result")
|
||||||
}
|
}
|
||||||
@ -51,10 +52,67 @@ var resultConverters = []struct {
|
|||||||
versions []string
|
versions []string
|
||||||
convert func(types.Result) (*Result, error)
|
convert func(types.Result) (*Result, error)
|
||||||
}{
|
}{
|
||||||
{SupportedVersions, convertFrom020},
|
{types020.SupportedVersions, convertFrom020},
|
||||||
|
{SupportedVersions, convertFrom030},
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertFrom020(result types.Result) (*Result, error) {
|
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)
|
newResult, ok := result.(*Result)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to convert result")
|
return nil, fmt.Errorf("failed to convert result")
|
||||||
@ -76,9 +134,58 @@ func NewResultFromResult(result types.Result) (*Result, error) {
|
|||||||
|
|
||||||
// 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
|
||||||
type Result struct {
|
type Result struct {
|
||||||
IP4 *IPConfig `json:"ip4,omitempty"`
|
Interfaces []*Interface `json:"interfaces,omitempty"`
|
||||||
IP6 *IPConfig `json:"ip6,omitempty"`
|
IPs []*IPConfig `json:"ips,omitempty"`
|
||||||
DNS types.DNS `json:"dns,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) Version() string {
|
||||||
@ -86,12 +193,13 @@ func (r *Result) Version() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||||
for _, supportedVersion := range SupportedVersions {
|
switch version {
|
||||||
if version == supportedVersion {
|
case implementedSpecVersion:
|
||||||
return r, nil
|
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 {
|
func (r *Result) Print() error {
|
||||||
@ -103,42 +211,67 @@ func (r *Result) Print() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
|
// String returns a formatted string in the form of "[Interfaces: $1,][ IP: $2,] DNS: $3" where
|
||||||
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
|
// $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.
|
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
|
||||||
func (r *Result) String() string {
|
func (r *Result) String() string {
|
||||||
var str string
|
var str string
|
||||||
if r.IP4 != nil {
|
if len(r.Interfaces) > 0 {
|
||||||
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
|
str += fmt.Sprintf("Interfaces:%+v, ", r.Interfaces)
|
||||||
}
|
}
|
||||||
if r.IP6 != nil {
|
if len(r.IPs) > 0 {
|
||||||
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
|
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)
|
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
|
||||||
type IPConfig struct {
|
func (r *Result) Convert() (*Result, error) {
|
||||||
IP net.IPNet
|
return r, nil
|
||||||
Gateway net.IP
|
|
||||||
Routes []types.Route
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
// Interface contains values about the created interfaces
|
||||||
// for our custom IPNet type
|
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 version, either "4" or "6"
|
||||||
|
Version string
|
||||||
|
// Index into Result structs Interfaces list
|
||||||
|
Interface int
|
||||||
|
Address net.IPNet
|
||||||
|
Gateway net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IPConfig) String() string {
|
||||||
|
return fmt.Sprintf("%+v", *i)
|
||||||
|
}
|
||||||
|
|
||||||
// JSON (un)marshallable types
|
// JSON (un)marshallable types
|
||||||
type ipConfig struct {
|
type ipConfig struct {
|
||||||
IP types.IPNet `json:"ip"`
|
Version string `json:"version"`
|
||||||
Gateway net.IP `json:"gateway,omitempty"`
|
Interface int `json:"interface,omitempty"`
|
||||||
Routes []types.Route `json:"routes,omitempty"`
|
Address types.IPNet `json:"address"`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||||
ipc := ipConfig{
|
ipc := ipConfig{
|
||||||
IP: types.IPNet(c.IP),
|
Version: c.Version,
|
||||||
Gateway: c.Gateway,
|
Interface: c.Interface,
|
||||||
Routes: c.Routes,
|
Address: types.IPNet(c.Address),
|
||||||
|
Gateway: c.Gateway,
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(ipc)
|
return json.Marshal(ipc)
|
||||||
@ -150,8 +283,9 @@ func (c *IPConfig) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
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.Gateway = ipc.Gateway
|
||||||
c.Routes = ipc.Routes
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -23,5 +23,5 @@ import (
|
|||||||
|
|
||||||
func TestTypes010(t *testing.T) {
|
func TestTypes010(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "0.1.0 Types Suite")
|
RunSpecs(t, "0.3.0 Types Suite")
|
||||||
}
|
}
|
||||||
|
@ -26,51 +26,66 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Ensures compatibility with the 0.1.0 spec", func() {
|
func testResult() *current.Result {
|
||||||
It("correctly encodes a 0.1.0 Result", func() {
|
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
||||||
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(ipv4).NotTo(BeNil())
|
||||||
Expect(ipv4).NotTo(BeNil())
|
|
||||||
|
|
||||||
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
|
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(routev4).NotTo(BeNil())
|
Expect(routev4).NotTo(BeNil())
|
||||||
Expect(routegwv4).NotTo(BeNil())
|
Expect(routegwv4).NotTo(BeNil())
|
||||||
|
|
||||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(ipv6).NotTo(BeNil())
|
Expect(ipv6).NotTo(BeNil())
|
||||||
|
|
||||||
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
|
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(routev6).NotTo(BeNil())
|
Expect(routev6).NotTo(BeNil())
|
||||||
Expect(routegwv6).NotTo(BeNil())
|
Expect(routegwv6).NotTo(BeNil())
|
||||||
|
|
||||||
// Set every field of the struct to ensure source compatibility
|
// Set every field of the struct to ensure source compatibility
|
||||||
res := current.Result{
|
return ¤t.Result{
|
||||||
IP4: ¤t.IPConfig{
|
Interfaces: []*current.Interface{
|
||||||
IP: *ipv4,
|
{
|
||||||
Gateway: net.ParseIP("1.2.3.1"),
|
Name: "eth0",
|
||||||
Routes: []types.Route{
|
Mac: "00:11:22:33:44:55",
|
||||||
{Dst: *routev4, GW: routegwv4},
|
Sandbox: "/proc/3553/ns/net",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
IP6: ¤t.IPConfig{
|
},
|
||||||
IP: *ipv6,
|
IPs: []*current.IPConfig{
|
||||||
Gateway: net.ParseIP("abcd:1234:ffff::1"),
|
{
|
||||||
Routes: []types.Route{
|
Version: "4",
|
||||||
{Dst: *routev6, GW: routegwv6},
|
Interface: 0,
|
||||||
},
|
Address: *ipv4,
|
||||||
|
Gateway: net.ParseIP("1.2.3.1"),
|
||||||
},
|
},
|
||||||
DNS: types.DNS{
|
{
|
||||||
Nameservers: []string{"1.2.3.4", "1::cafe"},
|
Version: "6",
|
||||||
Domain: "acompany.com",
|
Interface: 0,
|
||||||
Search: []string{"somedomain.com", "otherdomain.net"},
|
Address: *ipv6,
|
||||||
Options: []string{"foo", "bar"},
|
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"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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]}"))
|
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("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
|
// Redirect stdout to capture JSON result
|
||||||
oldStdout := os.Stdout
|
oldStdout := os.Stdout
|
||||||
@ -88,6 +103,76 @@ var _ = Describe("Ensures compatibility with the 0.1.0 spec", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
Expect(string(out)).To(Equal(`{
|
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": {
|
"ip4": {
|
||||||
"ip": "1.2.3.30/24",
|
"ip": "1.2.3.30/24",
|
||||||
"gateway": "1.2.3.1",
|
"gateway": "1.2.3.1",
|
||||||
@ -124,5 +209,6 @@ var _ = Describe("Ensures compatibility with the 0.1.0 spec", func() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`))
|
}`))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -16,6 +16,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@ -114,6 +115,10 @@ type Route struct {
|
|||||||
GW net.IP
|
GW net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Route) String() string {
|
||||||
|
return fmt.Sprintf("%+v", *r)
|
||||||
|
}
|
||||||
|
|
||||||
// Well known error codes
|
// Well known error codes
|
||||||
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||||
const (
|
const (
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"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"
|
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,8 +115,8 @@ func main() { skel.PluginMain(c, c) }
|
|||||||
//
|
//
|
||||||
// As we change the CNI spec, the Result type and this value may change.
|
// As we change the CNI spec, the Result type and this value may change.
|
||||||
// The text of the example plugins should not.
|
// The text of the example plugins should not.
|
||||||
var ExpectedResult = ¤t.Result{
|
var ExpectedResult = &types020.Result{
|
||||||
IP4: ¤t.IPConfig{
|
IP4: &types020.IPConfig{
|
||||||
IP: net.IPNet{
|
IP: net.IPNet{
|
||||||
IP: net.ParseIP("10.1.2.3"),
|
IP: net.ParseIP("10.1.2.3"),
|
||||||
Mask: net.CIDRMask(24, 32),
|
Mask: net.CIDRMask(24, 32),
|
||||||
|
@ -18,12 +18,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/020"
|
||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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.2.0"
|
return "0.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
||||||
@ -34,12 +35,14 @@ 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 resultFactories = []struct {
|
var resultFactories = []struct {
|
||||||
supportedVersions []string
|
supportedVersions []string
|
||||||
newResult types.ResultFactoryFunc
|
newResult types.ResultFactoryFunc
|
||||||
}{
|
}{
|
||||||
{current.SupportedVersions, current.NewResult},
|
{current.SupportedVersions, current.NewResult},
|
||||||
|
{types020.SupportedVersions, types020.NewResult},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds a Result object matching the requested version (if any) and asks
|
// Finds a Result object matching the requested version (if any) and asks
|
||||||
|
Loading…
x
Reference in New Issue
Block a user