From 703b8dfd116e5603b0122c88f629e4479bf24db6 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 8 Mar 2016 11:42:03 +0000 Subject: [PATCH 01/18] Expose 'ipam' type so we can refer to it elsewhere --- plugin/ipam/driver.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/plugin/ipam/driver.go b/plugin/ipam/driver.go index cd37e8d6..79ab1077 100644 --- a/plugin/ipam/driver.go +++ b/plugin/ipam/driver.go @@ -6,26 +6,25 @@ import ( "strings" "github.com/docker/libnetwork/discoverapi" - "github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/netlabel" "github.com/weaveworks/weave/api" . "github.com/weaveworks/weave/common" ) -type ipam struct { +type Ipam struct { weave *api.Client } -func NewIpam(weave *api.Client) ipamapi.Ipam { - return &ipam{weave: weave} +func NewIpam(weave *api.Client) *Ipam { + return &Ipam{weave: weave} } -func (i *ipam) GetDefaultAddressSpaces() (string, string, error) { +func (i *Ipam) GetDefaultAddressSpaces() (string, string, error) { Log.Debugln("GetDefaultAddressSpaces") return "weavelocal", "weaveglobal", nil } -func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (poolname string, subnet *net.IPNet, data map[string]string, err error) { +func (i *Ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (poolname string, subnet *net.IPNet, data map[string]string, err error) { Log.Debugln("RequestPool", addressSpace, pool, subPool, options) defer func() { Log.Debugln("RequestPool returning", poolname, subnet, data, err) }() if pool == "" { @@ -50,12 +49,12 @@ func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[strin return } -func (i *ipam) ReleasePool(poolID string) error { +func (i *Ipam) ReleasePool(poolID string) error { Log.Debugln("ReleasePool", poolID) return nil } -func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (ip *net.IPNet, _ map[string]string, err error) { +func (i *Ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (ip *net.IPNet, _ map[string]string, err error) { Log.Debugln("RequestAddress", poolID, address, options) defer func() { Log.Debugln("allocateIP returned", ip, err) }() // If we pass magic string "_" to weave IPAM it stores the address under its own string @@ -90,17 +89,17 @@ func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string] return } -func (i *ipam) ReleaseAddress(poolID string, address net.IP) error { +func (i *Ipam) ReleaseAddress(poolID string, address net.IP) error { Log.Debugln("ReleaseAddress", poolID, address) return i.weave.ReleaseIPsFor(address.String()) } // Functions required by ipamapi "contract" but not actually used. -func (i *ipam) DiscoverNew(discoverapi.DiscoveryType, interface{}) error { +func (i *Ipam) DiscoverNew(discoverapi.DiscoveryType, interface{}) error { return nil } -func (i *ipam) DiscoverDelete(discoverapi.DiscoveryType, interface{}) error { +func (i *Ipam) DiscoverDelete(discoverapi.DiscoveryType, interface{}) error { return nil } From f9615c34dc1a07cb1c45ff117c653422bc50ddb4 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 8 Mar 2016 11:43:09 +0000 Subject: [PATCH 02/18] CNI IPAM plugin --- .gitmodules | 3 ++ Makefile | 2 +- plugin/ipam/cni.go | 65 ++++++++++++++++++++++++++++++++++++++ prog/plugin/main.go | 11 +++++-- vendor/github.com/appc/cni | 1 + 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 plugin/ipam/cni.go create mode 160000 vendor/github.com/appc/cni diff --git a/.gitmodules b/.gitmodules index adec6564..f2a0e10d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -64,3 +64,6 @@ [submodule "vendor/github.com/hashicorp/go-cleanhttp"] path = vendor/github.com/hashicorp/go-cleanhttp url = http://github.com/hashicorp/go-cleanhttp +[submodule "vendor/github.com/appc/cni"] + path = vendor/github.com/appc/cni + url = https://github.com/appc/cni diff --git a/Makefile b/Makefile index 73072389..b181c05c 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ $(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/we $(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/*.go $(WEAVEUTIL_EXE): prog/weaveutil/*.go $(SIGPROXY_EXE): prog/sigproxy/*.go -$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/docker/*.go +$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/*.go common/docker/*.go $(TEST_TLS_EXE): test/tls/*.go $(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go diff --git a/plugin/ipam/cni.go b/plugin/ipam/cni.go new file mode 100644 index 00000000..8abcabcd --- /dev/null +++ b/plugin/ipam/cni.go @@ -0,0 +1,65 @@ +package ipamplugin + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/appc/cni/pkg/skel" + "github.com/appc/cni/pkg/types" +) + +func (i *Ipam) CmdAdd(args *skel.CmdArgs) error { + // extract the things we care about + conf, err := loadIPAMConf(args.StdinData) + if err != nil { + return err + } + if conf == nil { + conf = &ipamConf{} + } + containerID := args.ContainerID + var ipnet *net.IPNet + + if conf.Subnet == "" { + ipnet, err = i.weave.AllocateIP(containerID) + } else { + var subnet *net.IPNet + subnet, err = types.ParseCIDR(conf.Subnet) + if err != nil { + return fmt.Errorf("subnet given in config, but not parseable: %s", err) + } + ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet) + } + + if err != nil { + return err + } + result := types.Result{ + IP4: &types.IPConfig{ + IP: *ipnet, + Gateway: conf.Gateway, + Routes: conf.Routes, + }, + } + return result.Print() +} + +func (i *Ipam) CmdDel(args *skel.CmdArgs) error { + return i.weave.ReleaseIPsFor(args.ContainerID) +} + +type ipamConf struct { + Subnet string `json:"subnet,omitempty"` + Gateway net.IP `json:"gateway,omitempty"` + Routes []types.Route `json:"routes"` +} + +type netConf struct { + IPAM *ipamConf `json:"ipam"` +} + +func loadIPAMConf(stdinData []byte) (*ipamConf, error) { + var conf netConf + return conf.IPAM, json.Unmarshal(stdinData, &conf) +} diff --git a/prog/plugin/main.go b/prog/plugin/main.go index e0697e29..bbb818ec 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -9,6 +9,7 @@ import ( "os/signal" "syscall" + cni "github.com/appc/cni/pkg/skel" "github.com/docker/libnetwork/ipamapi" weaveapi "github.com/weaveworks/weave/api" . "github.com/weaveworks/weave/common" @@ -45,14 +46,20 @@ func main() { SetLogLevel(logLevel) + weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), Log) + + if os.Args[0] == "weave-ipam" { + i := ipamplugin.NewIpam(weave) + cni.PluginMain(i.CmdAdd, i.CmdDel) + os.Exit(0) + } + // API 1.21 is the first version that supports docker network commands dockerClient, err := docker.NewVersionedClientFromEnv("1.21") if err != nil { Log.Fatalf("unable to connect to docker: %s", err) } - weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), Log) - Log.Println("Weave plugin", version, "Command line options:", os.Args[1:]) Log.Info(dockerClient.Info()) diff --git a/vendor/github.com/appc/cni b/vendor/github.com/appc/cni new file mode 160000 index 00000000..cde7088c --- /dev/null +++ b/vendor/github.com/appc/cni @@ -0,0 +1 @@ +Subproject commit cde7088cbe539013c2efd994d07bdaac0f5a59b0 From 08059412b54f2897e0eedba9132058036e3f1d44 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 8 Mar 2016 15:21:37 +0000 Subject: [PATCH 03/18] Refactor so we can call parts individually --- plugin/ipam/cni.go | 22 ++++++++++---- plugin/net/driver.go | 69 +++++++++++++++++++++++++------------------- 2 files changed, 56 insertions(+), 35 deletions(-) diff --git a/plugin/ipam/cni.go b/plugin/ipam/cni.go index 8abcabcd..a5d407b8 100644 --- a/plugin/ipam/cni.go +++ b/plugin/ipam/cni.go @@ -10,10 +10,18 @@ import ( ) func (i *Ipam) CmdAdd(args *skel.CmdArgs) error { + result, err := i.Allocate(args) + if err != nil { + return err + } + return result.Print() +} + +func (i *Ipam) Allocate(args *skel.CmdArgs) (*types.Result, error) { // extract the things we care about conf, err := loadIPAMConf(args.StdinData) if err != nil { - return err + return nil, err } if conf == nil { conf = &ipamConf{} @@ -27,25 +35,29 @@ func (i *Ipam) CmdAdd(args *skel.CmdArgs) error { var subnet *net.IPNet subnet, err = types.ParseCIDR(conf.Subnet) if err != nil { - return fmt.Errorf("subnet given in config, but not parseable: %s", err) + return nil, fmt.Errorf("subnet given in config, but not parseable: %s", err) } ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet) } if err != nil { - return err + return nil, err } - result := types.Result{ + result := &types.Result{ IP4: &types.IPConfig{ IP: *ipnet, Gateway: conf.Gateway, Routes: conf.Routes, }, } - return result.Print() + return result, nil } func (i *Ipam) CmdDel(args *skel.CmdArgs) error { + return i.Release(args) +} + +func (i *Ipam) Release(args *skel.CmdArgs) error { return i.weave.ReleaseIPsFor(args.ContainerID) } diff --git a/plugin/net/driver.go b/plugin/net/driver.go index 3c0f76b0..034721c8 100644 --- a/plugin/net/driver.go +++ b/plugin/net/driver.go @@ -107,39 +107,11 @@ func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointI } func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) { - endID := j.EndpointID - - maybeBridge, err := netlink.LinkByName(WeaveBridge) + local, err := createAndAttach(j.EndpointID, WeaveBridge, 0) if err != nil { - return nil, errorf(`bridge "%s" not present; did you launch weave?`, WeaveBridge) + return nil, err } - // create and attach local name to the bridge - local := vethPair(endID[:5]) - local.Attrs().MTU = maybeBridge.Attrs().MTU - if err := netlink.LinkAdd(local); err != nil { - return nil, errorf("could not create veth pair: %s", err) - } - - switch maybeBridge.(type) { - case *netlink.Bridge: - if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil { - return nil, errorf(`unable to set master: %s`, err) - } - case *netlink.GenericLink: - if maybeBridge.Type() != "openvswitch" { - Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge) - return nil, errorf(`device "%s" is of type "%s"`, WeaveBridge, maybeBridge.Type()) - } - odp.AddDatapathInterface(WeaveBridge, local.Name) - case *netlink.Device: - Log.Warnf("kernel does not report what kind of device %s is, just %+v", WeaveBridge, maybeBridge) - // Assume it's our openvswitch device, and the kernel has not been updated to report the kind. - odp.AddDatapathInterface(WeaveBridge, local.Name) - default: - Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge) - return nil, errorf(`device "%s" not a bridge`, WeaveBridge) - } if err := netlink.LinkSetUp(local); err != nil { return nil, errorf(`unable to bring veth up: %s`, err) } @@ -163,6 +135,43 @@ func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error return response, nil } +// create and attach local name to the Weave bridge +func createAndAttach(id, bridgeName string, mtu int) (*netlink.Veth, error) { + maybeBridge, err := netlink.LinkByName(bridgeName) + if err != nil { + return nil, errorf(`bridge "%s" not present; did you launch weave?`, bridgeName) + } + + local := vethPair(id[:5]) + if mtu == 0 { + local.Attrs().MTU = maybeBridge.Attrs().MTU + } else { + local.Attrs().MTU = mtu + } + if err := netlink.LinkAdd(local); err != nil { + return nil, errorf("could not create veth pair: %s", err) + } + + switch maybeBridge.(type) { + case *netlink.Bridge: + if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil { + return nil, errorf(`unable to set master: %s`, err) + } + case *netlink.GenericLink: + if maybeBridge.Type() != "openvswitch" { + return nil, errorf(`device "%s" is of type "%s"`, bridgeName, maybeBridge.Type()) + } + odp.AddDatapathInterface(bridgeName, local.Name) + case *netlink.Device: + Log.Warnf("kernel does not report what kind of device %s is, just %+v", bridgeName, maybeBridge) + // Assume it's our openvswitch device, and the kernel has not been updated to report the kind. + odp.AddDatapathInterface(bridgeName, local.Name) + default: + return nil, errorf(`device "%s" not a bridge`, bridgeName) + } + return local, nil +} + func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error { Log.Debugf("Leave request: %+v", leave) From 42ff61f97c05cc5e9ac231ffa0ef9badfd09df1a Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 8 Mar 2016 16:50:39 +0000 Subject: [PATCH 04/18] CNI net plugin --- .gitmodules | 3 + plugin/net/cni.go | 158 +++++++++++++++++++++++++++ prog/plugin/main.go | 8 +- vendor/github.com/coreos/go-iptables | 1 + 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 plugin/net/cni.go create mode 160000 vendor/github.com/coreos/go-iptables diff --git a/.gitmodules b/.gitmodules index f2a0e10d..58ec72d5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -67,3 +67,6 @@ [submodule "vendor/github.com/appc/cni"] path = vendor/github.com/appc/cni url = https://github.com/appc/cni +[submodule "vendor/github.com/coreos/go-iptables"] + path = vendor/github.com/coreos/go-iptables + url = https://github.com/coreos/go-iptables diff --git a/plugin/net/cni.go b/plugin/net/cni.go new file mode 100644 index 00000000..4ab8bd9c --- /dev/null +++ b/plugin/net/cni.go @@ -0,0 +1,158 @@ +package plugin + +import ( + "crypto/rand" + "encoding/json" + "fmt" + + "github.com/appc/cni/pkg/ipam" + "github.com/appc/cni/pkg/skel" + "github.com/appc/cni/pkg/types" + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" + weaveapi "github.com/weaveworks/weave/api" + "github.com/weaveworks/weave/common" + ipamplugin "github.com/weaveworks/weave/plugin/ipam" +) + +type CNIPlugin struct { + weave *weaveapi.Client +} + +func NewCNIPlugin(weave *weaveapi.Client) *CNIPlugin { + return &CNIPlugin{weave: weave} +} + +func loadNetConf(bytes []byte) (*NetConf, error) { + n := &NetConf{ + BrName: "weave", + } + if err := json.Unmarshal(bytes, n); err != nil { + return nil, errorf("failed to load netconf: %v", err) + } + return n, nil +} + +func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error { + conf, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + + if conf.IsGW { + return errorf("Gateway functionality not supported") + } + if conf.IPMasq { + return errorf("IP Masquerading functionality not supported") + } + + ns, err := netns.GetFromPath(args.Netns) + if err != nil { + return err + } + defer ns.Close() + + id := args.ContainerID + if len(id) < 5 { + data := make([]byte, 5) + _, err := rand.Reader.Read(data) + if err != nil { + return err + } + id = fmt.Sprintf("%x", data) + } + + local, err := createAndAttach(id, conf.BrName, conf.MTU) + if err != nil { + return err + } + + cleanup := func(err error) error { + netlink.LinkDel(local) + return err + } + guest, err := netlink.LinkByName(local.PeerName) + if err != nil { + return cleanup(err) + } + if err = netlink.LinkSetNsFd(guest, int(ns)); err != nil { + return cleanup(errorf("failed to move veth to container netns: %s", err)) + } + + if err := netlink.LinkSetUp(local); err != nil { + return cleanup(errorf("unable to bring veth up: %s", err)) + } + + var result *types.Result + // Default IPAM is Weave's own + if conf.IPAM.Type == "" { + result, err = ipamplugin.NewIpam(c.weave).Allocate(args) + } else { + result, err = ipam.ExecAdd(conf.IPAM.Type, args.StdinData) + } + if err != nil { + return cleanup(errorf("unable to allocate IP address: %s", err)) + } + if result.IP4 == nil { + return cleanup(errorf("IPAM plugin failed to allocate IP address")) + } + + err = common.WithNetNS(ns, func() error { + guest, err := netlink.LinkByName(local.PeerName) + if err != nil { + return cleanup(err) + } + if err = netlink.LinkSetName(guest, args.IfName); err != nil { + return err + } + return ipam.ConfigureIface(args.IfName, result) + }) + if err != nil { + return cleanup(errorf("error setting up interface: %s", err)) + } + + result.DNS = conf.DNS + return result.Print() +} + +func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error { + conf, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + + ns, err := netns.GetFromPath(args.Netns) + if err != nil { + return err + } + defer ns.Close() + err = common.WithNetNS(ns, func() error { + link, err := netlink.LinkByName(args.IfName) + if err != nil { + return err + } + return netlink.LinkDel(link) + }) + if err != nil { + return errorf("error removing interface: %s", err) + } + + // Default IPAM is Weave's own + if conf.IPAM.Type == "" { + err = ipamplugin.NewIpam(c.weave).Release(args) + } else { + err = ipam.ExecDel(conf.IPAM.Type, args.StdinData) + } + if err != nil { + return errorf("unable to release IP address: %s", err) + } + return nil +} + +type NetConf struct { + types.NetConf + BrName string `json:"bridge"` + IsGW bool `json:"isGateway"` + IPMasq bool `json:"ipMasq"` + MTU int `json:"mtu"` +} diff --git a/prog/plugin/main.go b/prog/plugin/main.go index bbb818ec..38dc04ee 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" cni "github.com/appc/cni/pkg/skel" @@ -48,10 +49,15 @@ func main() { weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), Log) - if os.Args[0] == "weave-ipam" { + switch { + case strings.HasSuffix(os.Args[0], "weave-ipam"): i := ipamplugin.NewIpam(weave) cni.PluginMain(i.CmdAdd, i.CmdDel) os.Exit(0) + case strings.HasSuffix(os.Args[0], "weave"): + n := netplugin.NewCNIPlugin(weave) + cni.PluginMain(n.CmdAdd, n.CmdDel) + os.Exit(0) } // API 1.21 is the first version that supports docker network commands diff --git a/vendor/github.com/coreos/go-iptables b/vendor/github.com/coreos/go-iptables new file mode 160000 index 00000000..90456be5 --- /dev/null +++ b/vendor/github.com/coreos/go-iptables @@ -0,0 +1 @@ +Subproject commit 90456be57fcb8185b264b77ce42a9539df42df25 From ac4e87418ad8d19c759dce2121690dda7afda6a4 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 10 Mar 2016 16:52:53 +0000 Subject: [PATCH 05/18] Set a default route via bridge if config doesn't specify any --- plugin/net/cni.go | 91 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/plugin/net/cni.go b/plugin/net/cni.go index 4ab8bd9c..ce08ab20 100644 --- a/plugin/net/cni.go +++ b/plugin/net/cni.go @@ -4,6 +4,8 @@ import ( "crypto/rand" "encoding/json" "fmt" + "net" + "os" "github.com/appc/cni/pkg/ipam" "github.com/appc/cni/pkg/skel" @@ -15,6 +17,11 @@ import ( ipamplugin "github.com/weaveworks/weave/plugin/ipam" ) +var ( + zeroNetwork = net.IPNet{IP: net.IPv4zero, Mask: net.IPv4Mask(0, 0, 0, 0)} + mask32 = net.IPv4Mask(0xff, 0xff, 0xff, 0xff) +) + type CNIPlugin struct { weave *weaveapi.Client } @@ -97,15 +104,17 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error { return cleanup(errorf("IPAM plugin failed to allocate IP address")) } - err = common.WithNetNS(ns, func() error { - guest, err := netlink.LinkByName(local.PeerName) + // If config says nothing about routes or gateway, default one will be via the bridge + if result.IP4.Routes == nil && result.IP4.Gateway == nil { + bridgeIP, err := findBridgeIP(conf.BrName, result.IP4.IP) if err != nil { return cleanup(err) } - if err = netlink.LinkSetName(guest, args.IfName); err != nil { - return err - } - return ipam.ConfigureIface(args.IfName, result) + result.IP4.Gateway = bridgeIP + } + + err = common.WithNetNS(ns, func() error { + return setupGuestIP4(local.PeerName, args.IfName, result.IP4.IP, result.IP4.Gateway, result.IP4.Routes) }) if err != nil { return cleanup(errorf("error setting up interface: %s", err)) @@ -115,6 +124,76 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error { return result.Print() } +func setupGuestIP4(origName, name string, ipnet net.IPNet, gw net.IP, routes []types.Route) error { + guest, err := netlink.LinkByName(origName) + if err != nil { + return err + } + if err = netlink.LinkSetName(guest, name); err != nil { + return err + } + if err = netlink.LinkSetUp(guest); err != nil { + return err + } + if routes == nil { // If config says nothing about routes, add a default one + if !ipnet.Contains(gw) { + // The bridge IP is not on the same subnet; add a specific route to it + gw32 := &net.IPNet{IP: gw, Mask: mask32} + if err = addRoute(guest, netlink.SCOPE_LINK, gw32, nil); err != nil { + return err + } + } + routes = []types.Route{{Dst: zeroNetwork}} + } + if err = netlink.AddrAdd(guest, &netlink.Addr{IPNet: &ipnet}); err != nil { + return fmt.Errorf("failed to add IP address to %q: %v", name, err) + } + for _, r := range routes { + if r.GW != nil { + err = addRoute(guest, netlink.SCOPE_UNIVERSE, &r.Dst, r.GW) + } else { + err = addRoute(guest, netlink.SCOPE_UNIVERSE, &r.Dst, gw) + } + if err != nil { + return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, name, err) + } + } + return nil +} + +func addRoute(link netlink.Link, scope netlink.Scope, dst *net.IPNet, gw net.IP) error { + err := netlink.RouteAdd(&netlink.Route{ + LinkIndex: link.Attrs().Index, + Scope: scope, + Dst: dst, + Gw: gw, + }) + if os.IsExist(err) { // squash duplicate route errors + err = nil + } + return err +} + +func findBridgeIP(bridgeName string, subnet net.IPNet) (net.IP, error) { + netdevs, err := common.GetBridgeNetDev(bridgeName) + if err != nil { + return nil, fmt.Errorf("Failed to get netdev for %q bridge: %s", bridgeName, err) + } + if len(netdevs) == 0 { + return nil, fmt.Errorf("Could not find %q bridge", bridgeName) + } + if len(netdevs[0].CIDRs) == 0 { + return nil, fmt.Errorf("Bridge %q has no IP addresses", bridgeName) + } + for _, cidr := range netdevs[0].CIDRs { + if subnet.Contains(cidr.IP) { + return cidr.IP, nil + } + } + // None in the required subnet; just return the first one + return netdevs[0].CIDRs[0].IP, nil +} + func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error { conf, err := loadNetConf(args.StdinData) if err != nil { From 011fb55e5f7d80c1be73ab4b069a7f636c263c8b Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 11 Mar 2016 17:21:51 +0000 Subject: [PATCH 06/18] Refactor FindNetDevs so we can get the link, and re-use parts of the function --- common/utils.go | 57 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/common/utils.go b/common/utils.go index 63b7b5e2..92de13c6 100644 --- a/common/utils.go +++ b/common/utils.go @@ -48,7 +48,7 @@ type NetDev struct { } // Search the network namespace of a process for interfaces matching a predicate -func FindNetDevs(processID int, match func(string) bool) ([]NetDev, error) { +func FindNetDevs(processID int, match func(link netlink.Link) bool) ([]NetDev, error) { var netDevs []NetDev ns, err := netns.GetFromPid(processID) @@ -58,40 +58,57 @@ func FindNetDevs(processID int, match func(string) bool) ([]NetDev, error) { defer ns.Close() err = WithNetNS(ns, func() error { - links, err := netlink.LinkList() - if err != nil { - return err - } - for _, link := range links { - if match(link.Attrs().Name) { - addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + return forEachLink(func(link netlink.Link) error { + if match(link) { + netDev, err := linkToNetDev(link) if err != nil { return err } - - netDev := NetDev{MAC: link.Attrs().HardwareAddr} - for _, addr := range addrs { - netDev.CIDRs = append(netDev.CIDRs, addr.IPNet) - } - netDevs = append(netDevs, netDev) + netDevs = append(netDevs, *netDev) } - } - return nil + return nil + }) }) return netDevs, err } +func forEachLink(f func(netlink.Link) error) error { + links, err := netlink.LinkList() + if err != nil { + return err + } + for _, link := range links { + if err := f(link); err != nil { + return err + } + } + return nil +} + +func linkToNetDev(link netlink.Link) (*NetDev, error) { + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + if err != nil { + return nil, err + } + + netDev := &NetDev{MAC: link.Attrs().HardwareAddr} + for _, addr := range addrs { + netDev.CIDRs = append(netDev.CIDRs, addr.IPNet) + } + return netDev, nil +} + // Lookup the weave interface of a container func GetWeaveNetDevs(processID int) ([]NetDev, error) { - return FindNetDevs(processID, func(name string) bool { - return strings.HasPrefix(name, "ethwe") + return FindNetDevs(processID, func(link netlink.Link) bool { + return strings.HasPrefix(link.Attrs().Name, "ethwe") }) } // Get the weave bridge interface func GetBridgeNetDev(bridgeName string) ([]NetDev, error) { - return FindNetDevs(1, func(name string) bool { - return name == bridgeName + return FindNetDevs(1, func(link netlink.Link) bool { + return link.Attrs().Name == bridgeName }) } From 2d6a916b2d9243c903c7b5f37f325ed086cb4218 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 11 Mar 2016 20:08:53 +0000 Subject: [PATCH 07/18] Find weave veths by looking at what is connected to the bridge, not by name and don't even look at containers running in the root namespace --- common/utils.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/common/utils.go b/common/utils.go index 92de13c6..8f6615c3 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,6 +1,7 @@ package common import ( + "fmt" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" "net" @@ -101,8 +102,39 @@ func linkToNetDev(link netlink.Link) (*NetDev, error) { // Lookup the weave interface of a container func GetWeaveNetDevs(processID int) ([]NetDev, error) { + // Bail out if this container is running in the root namespace + nsToplevel, _ := netns.GetFromPid(1) + nsContainr, _ := netns.GetFromPid(processID) + if nsToplevel.Equal(nsContainr) { + return nil, nil + } + + var weaveBridge netlink.Link + forEachLink(func(link netlink.Link) error { + if link.Attrs().Name == "weave" { + weaveBridge = link + } + return nil + }) + if weaveBridge == nil { + return nil, fmt.Errorf("Cannot find weave bridge") + } + // Scan devices in root namespace to find those attached to weave bridge + indexes := make(map[int]struct{}) + err := forEachLink(func(link netlink.Link) error { + if link.Attrs().MasterIndex == weaveBridge.Attrs().Index { + indexes[link.Attrs().Index] = struct{}{} + } + return nil + }) + if err != nil { + return nil, err + } return FindNetDevs(processID, func(link netlink.Link) bool { - return strings.HasPrefix(link.Attrs().Name, "ethwe") + // For checking, rely on index number of veth end inside namespace being -1 of end outside + _, isveth := link.(*netlink.Veth) + _, found := indexes[link.Attrs().Index+1] + return isveth && found }) } From b09b5567961636836e9daf5350f4910a8ee2d793 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 5 Feb 2016 15:31:09 +0000 Subject: [PATCH 08/18] Utility function to configure ARP cache timers --- common/arp.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 common/arp.go diff --git a/common/arp.go b/common/arp.go new file mode 100644 index 00000000..094d6b16 --- /dev/null +++ b/common/arp.go @@ -0,0 +1,43 @@ +package common + +import "fmt" +import "io" +import "os" + +// Configure the ARP cache parameters for the given interface. This +// makes containers react more quickly to a change in the MAC address +// associated with an IP address. +func ConfigureARPCache(name string) error { + if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/base_reachable_time", name), "5"); err != nil { + return err + } + if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/delay_first_probe_time", name), "2"); err != nil { + return err + } + if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/ucast_solicit", name), "1"); err != nil { + return err + } + return nil +} + +func sysctl(variable, value string) error { + f, err := os.OpenFile(fmt.Sprintf("/proc/sys/%s", variable), os.O_WRONLY, 0) + if err != nil { + return err + } + + n, err := io.WriteString(f, value) + if err != nil { + return err + } + if n < len(value) { + return io.ErrShortWrite + } + + err = f.Close() + if err != nil { + return err + } + + return nil +} From ecdd7aa7ce386e8db16d410c44a6911f8cf0a0a9 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 15 Mar 2016 15:16:55 +0000 Subject: [PATCH 09/18] Configure arp cache on new interface --- plugin/net/cni.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/net/cni.go b/plugin/net/cni.go index ce08ab20..95977e10 100644 --- a/plugin/net/cni.go +++ b/plugin/net/cni.go @@ -135,6 +135,9 @@ func setupGuestIP4(origName, name string, ipnet net.IPNet, gw net.IP, routes []t if err = netlink.LinkSetUp(guest); err != nil { return err } + if err = common.ConfigureARPCache(name); err != nil { + return err + } if routes == nil { // If config says nothing about routes, add a default one if !ipnet.Contains(gw) { // The bridge IP is not on the same subnet; add a specific route to it From 024eb46c7d49ad783b2e0de3695769a9b0efb960 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 15 Mar 2016 16:51:31 +0000 Subject: [PATCH 10/18] CNI plugin implemented as script that `docker run`s plugin container, installed via `weave setup` --- prog/plugin/main.go | 7 +++++-- weave | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 38dc04ee..5e30e356 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -26,6 +26,7 @@ var version = "(unreleased version)" func main() { var ( justVersion bool + cniNet, cniIpam bool address string meshAddress string logLevel string @@ -33,6 +34,8 @@ func main() { ) flag.BoolVar(&justVersion, "version", false, "print version and exit") + flag.BoolVar(&cniNet, "cni-net", false, "act as a CNI network plugin") + flag.BoolVar(&cniIpam, "cni-ipam", false, "act as a CNI IPAM plugin") flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)") flag.StringVar(&address, "socket", "/run/docker/plugins/weave.sock", "socket on which to listen") flag.StringVar(&meshAddress, "meshsocket", "/run/docker/plugins/weavemesh.sock", "socket on which to listen in mesh mode") @@ -50,11 +53,11 @@ func main() { weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), Log) switch { - case strings.HasSuffix(os.Args[0], "weave-ipam"): + case cniIpam || strings.HasSuffix(os.Args[0], "weave-ipam"): i := ipamplugin.NewIpam(weave) cni.PluginMain(i.CmdAdd, i.CmdDel) os.Exit(0) - case strings.HasSuffix(os.Args[0], "weave"): + case cniNet || strings.HasSuffix(os.Args[0], "weave-net"): n := netplugin.NewCNIPlugin(weave) cni.PluginMain(n.CmdAdd, n.CmdDel) os.Exit(0) diff --git a/weave b/weave index 3b588876..f9a237c1 100755 --- a/weave +++ b/weave @@ -115,10 +115,35 @@ docker_run_options() { echo --privileged --net=host $(docker_sock_options) } +docker_exec_cni_options() { + [ "$1" = "setup" ] && echo -v /etc/cni:/etc/cni -v /opt/cni:/opt/cni +} + +create_cni_script() { + cat >"$1" <"$1" < Date: Fri, 11 Mar 2016 13:17:04 +0000 Subject: [PATCH 11/18] Docs for CNI plugins --- site/features.md | 8 ++++ site/plugin/cni-plugin-how-to.md | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 site/plugin/cni-plugin-how-to.md diff --git a/site/features.md b/site/features.md index 79bad582..57e55add 100644 --- a/site/features.md +++ b/site/features.md @@ -10,6 +10,7 @@ Net: * [Fast Data Path](#fast-data-path) * [Seamless Docker Integration](#docker) * [Docker Network Plugin](#plugin) + * [CNI Plugin](#cniplugin) * [Address Allocation (IPAM)](#addressing) * [Naming and Discovery](#naming-and-discovery) * [Application Isolation](#application-isolation) @@ -100,6 +101,13 @@ when there are network connectivity problems. See [Using the Weave Docker Network Plugin](/site/plugin/weave-plugin-how-to.md) for more details. +###Weave Network CNI Plugin + +Weave can be used as a plugin to systems that support the [Container Network Interface](https://github.com/appc/cni), such as Kubernetes. + +See [Using the Weave CNI Plugin](/site/plugin/cni-plugin-how-to.md) for more details. + + ###IP Address Management (IPAM) Containers are automatically allocated a unique IP address. To view the addresses allocated by Weave run, `weave ps`. diff --git a/site/plugin/cni-plugin-how-to.md b/site/plugin/cni-plugin-how-to.md new file mode 100644 index 00000000..e59ea070 --- /dev/null +++ b/site/plugin/cni-plugin-how-to.md @@ -0,0 +1,77 @@ +--- +title: Using the Weave Net CNI Plugin +layout: default +--- + +CNI, the [_Container Network Interface_](https://github.com/appc/cni#cni---the-container-network-interface), +is a proposed standard for configuring network interfaces for Linux +application containers. CNI is supported by +[Kubernetes](http://kubernetes.io/), an open source container cluster +manager built by Google. + +###Installing the Weave Net CNI plugin + +The Weave Net CNI plugin is installed when you run `weave setup`, if +it your machine has the directories normally used to host CNI plugins. +To create those directories, run (as root): + + mkdir -p /opt/cni/bin + mkdir -p /etc/cni/net.d + +Then run: + + weave setup + +###Launching Weave Net + +To create a network which can span multiple hosts, the Weave peers must be connected to each other, by specifying the other hosts during `weave launch` or via +[`weave connect`](/site/using-weave/finding-adding-hosts-dynamically.md). + +See [Deploying Applications to Weave Net](/site/using-weave/deploying-applications.md#peer-connections) for a discussion on peer connections. + +As well as launching Weave Net, you have to run an extra command to +configure the Weave bridge: + + weave launch + weave expose + +###Configuring Kubernetes to use the CNI Plugin + +After you've launched Weave and peered your hosts, you can configure +Kubernetes to use Weave, by adding the following options to the +`kubelet` command: + + --network-plugin=cni --network-plugin-dir=/etc/cni/net.d + +See the [`kubelet` documentation](http://kubernetes.io/v1.1/docs/admin/kubelet.html) +for more details. + +Now, whenever Kubernetes starts a pod, it will be attached to the Weave network. + +###The CNI network configuration file + +All CNI plugins are configured by a JSON file in the directory +`/etc/cni/net.d/`. `weave setup` installs a minimal configuration +file named `10-weave.conf`, which you can alter to suit your needs. + +See the [CNI Spec]](https://github.com/appc/cni/blob/master/SPEC.md#network-configuration) +for details on the format and contents of this file. + +By default, the Weave CNI plugin will add a default route out via the Weave bridge, so your containers can access resources on the internet. If you do not want this, add a section to the config file specifying no routes: + +``` + "ipam": { + "routes": [ ] + } +``` + +The following other fields in the spec are supported: + +- `ipam / type` - default is to use Weave's own IPAM +- `ipam / subnet` - default is to use Weave's IPAM default subnet +- `ipam / gateway` - default is to use the Weave bridge IP address (allocated by `weave expose`) + +###Caveats + +- The Weave router container must be running for CNI to allocate addresses +- The CNI plugin does not add entries to Weave DNS. From b153e6a0d2a5389875ffa2c9dbc67d458d4eac09 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 17 Mar 2016 14:23:48 +0000 Subject: [PATCH 12/18] Simplify, after review feedback --- common/utils.go | 14 ++++---------- prog/plugin/main.go | 3 ++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/common/utils.go b/common/utils.go index 8f6615c3..1f52a397 100644 --- a/common/utils.go +++ b/common/utils.go @@ -109,19 +109,13 @@ func GetWeaveNetDevs(processID int) ([]NetDev, error) { return nil, nil } - var weaveBridge netlink.Link - forEachLink(func(link netlink.Link) error { - if link.Attrs().Name == "weave" { - weaveBridge = link - } - return nil - }) - if weaveBridge == nil { - return nil, fmt.Errorf("Cannot find weave bridge") + weaveBridge, err := netlink.LinkByName("weave") + if err != nil { + return nil, fmt.Errorf("Cannot find weave bridge: %s", err) } // Scan devices in root namespace to find those attached to weave bridge indexes := make(map[int]struct{}) - err := forEachLink(func(link netlink.Link) error { + err = forEachLink(func(link netlink.Link) error { if link.Attrs().MasterIndex == weaveBridge.Attrs().Index { indexes[link.Attrs().Index] = struct{}{} } diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 5e30e356..4af2a4b3 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -26,7 +26,8 @@ var version = "(unreleased version)" func main() { var ( justVersion bool - cniNet, cniIpam bool + cniNet bool + cniIpam bool address string meshAddress string logLevel string From a66543f98e4064cfa6f1cd356ea8116a95b92b91 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 17 Mar 2016 17:35:41 +0000 Subject: [PATCH 13/18] Use link ParentIndex where available --- common/utils.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common/utils.go b/common/utils.go index 1f52a397..da553b24 100644 --- a/common/utils.go +++ b/common/utils.go @@ -117,7 +117,13 @@ func GetWeaveNetDevs(processID int) ([]NetDev, error) { indexes := make(map[int]struct{}) err = forEachLink(func(link netlink.Link) error { if link.Attrs().MasterIndex == weaveBridge.Attrs().Index { - indexes[link.Attrs().Index] = struct{}{} + peerIndex := link.Attrs().ParentIndex + if peerIndex == 0 { + // perhaps running on an older kernel where ParentIndex doesn't work. + // as fall-back, assume the indexes are consecutive + peerIndex = link.Attrs().Index - 1 + } + indexes[peerIndex] = struct{}{} } return nil }) @@ -125,9 +131,8 @@ func GetWeaveNetDevs(processID int) ([]NetDev, error) { return nil, err } return FindNetDevs(processID, func(link netlink.Link) bool { - // For checking, rely on index number of veth end inside namespace being -1 of end outside _, isveth := link.(*netlink.Veth) - _, found := indexes[link.Attrs().Index+1] + _, found := indexes[link.Attrs().Index] return isveth && found }) } From 4e09889cab720e57f63c3e42bca8ef7102ca38e6 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 17 Mar 2016 18:19:24 +0000 Subject: [PATCH 14/18] Make container_pid a common test function --- test/090_docker_restart_policy_2_test.sh | 8 ++------ test/config.sh | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/090_docker_restart_policy_2_test.sh b/test/090_docker_restart_policy_2_test.sh index b8b9a5a7..dba02595 100755 --- a/test/090_docker_restart_policy_2_test.sh +++ b/test/090_docker_restart_policy_2_test.sh @@ -4,17 +4,13 @@ start_suite "Test docker restart policy" -pidof() { - docker_on $1 inspect -f '{{.State.Pid}}' $2 -} - check_restart() { - OLD_PID=$(pidof $1 $2) + OLD_PID=$(container_pid $1 $2) run_on $1 sudo kill $OLD_PID for i in $(seq 1 30); do - NEW_PID=$(pidof $1 $2) + NEW_PID=$(container_pid $1 $2) if [ $NEW_PID != 0 -a $NEW_PID != $OLD_PID ] ; then return 0 diff --git a/test/config.sh b/test/config.sh index 3d23562f..aefdd6b7 100644 --- a/test/config.sh +++ b/test/config.sh @@ -183,6 +183,10 @@ container_ip() { weave_on $1 ps $2 | grep -o -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' } +container_pid() { + docker_on $1 inspect -f '{{.State.Pid}}' $2 +} + # assert_dns_record [ ...] assert_dns_record() { local host=$1 From c8ca295cda09723ccc431d4935bec6f1ed064b79 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 17 Mar 2016 18:07:34 +0000 Subject: [PATCH 15/18] CNI plugin smoke test --- test/830_cni_plugin_test.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 test/830_cni_plugin_test.sh diff --git a/test/830_cni_plugin_test.sh b/test/830_cni_plugin_test.sh new file mode 100755 index 00000000..63b07778 --- /dev/null +++ b/test/830_cni_plugin_test.sh @@ -0,0 +1,35 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Test CNI plugin" + +cni_connect() { + pid=$(container_pid $1 $2) + docker_on $1 run --rm --privileged --net=host --pid=host -i \ + -e CNI_VERSION=1 -e CNI_COMMAND=ADD -e CNI_CONTAINERID=c1 \ + -e CNI_NETNS=/proc/$pid/ns/net -e CNI_IFNAME=eth0 -e CNI_PATH=/opt/cni/bin \ + weaveworks/plugin:latest --cni-net </dev/null +cni_connect $HOST1 c2 >/dev/null + +C1IP=$(container_ip $HOST1 c1) +C2IP=$(container_ip $HOST1 c2) + +assert_raises "exec_on $HOST1 c1 $PING $C2IP" +assert_raises "exec_on $HOST1 c2 $PING $C1IP" + +end_suite From 88cb07203aeb7d1fbe2e7d97b33a68b41719b2a7 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 18 Mar 2016 19:02:28 +0000 Subject: [PATCH 16/18] Improve name of cni_volume_options and include them when running the CNI plugin inside a container --- weave | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/weave b/weave index f9a237c1..2dea13b2 100755 --- a/weave +++ b/weave @@ -115,7 +115,7 @@ docker_run_options() { echo --privileged --net=host $(docker_sock_options) } -docker_exec_cni_options() { +cni_volume_options() { [ "$1" = "setup" ] && echo -v /etc/cni:/etc/cni -v /opt/cni:/opt/cni } @@ -124,7 +124,7 @@ create_cni_script() { #!/bin/sh docker run --rm --privileged --net=host --pid=host -i \\ -e CNI_VERSION -e CNI_COMMAND -e CNI_CONTAINERID -e CNI_NETNS \\ - -e CNI_IFNAME -e CNI_ARGS -e CNI_PATH \\ + -e CNI_IFNAME -e CNI_ARGS -e CNI_PATH $(cni_volume_options setup) \\ $PLUGIN_IMAGE "$2" EOF chmod a+x "$1" @@ -143,7 +143,7 @@ exec_remote() { docker $DOCKER_CLIENT_ARGS run --rm \ $(docker_run_options) \ --pid=host \ - $(docker_exec_cni_options "$@") \ + $(cni_volume_options "$@") \ -e DOCKERHUB_USER="$DOCKERHUB_USER" \ -e WEAVE_VERSION \ -e WEAVE_DEBUG \ From b00f0aee65ab13e608aa43a049d29c861b9ed7f4 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 18 Mar 2016 19:39:52 +0000 Subject: [PATCH 17/18] Check errors on test for root namespace --- common/utils.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/utils.go b/common/utils.go index da553b24..43f9477c 100644 --- a/common/utils.go +++ b/common/utils.go @@ -103,8 +103,14 @@ func linkToNetDev(link netlink.Link) (*NetDev, error) { // Lookup the weave interface of a container func GetWeaveNetDevs(processID int) ([]NetDev, error) { // Bail out if this container is running in the root namespace - nsToplevel, _ := netns.GetFromPid(1) - nsContainr, _ := netns.GetFromPid(processID) + nsToplevel, err := netns.GetFromPid(1) + if err != nil { + return nil, fmt.Errorf("unable to open root namespace: %s", err) + } + nsContainr, err := netns.GetFromPid(processID) + if err != nil { + return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err) + } if nsToplevel.Equal(nsContainr) { return nil, nil } From e080f00354883e05c3a84cab86405a546add7134 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 18 Mar 2016 19:40:16 +0000 Subject: [PATCH 18/18] Review feedback --- site/plugin/cni-plugin-how-to.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/plugin/cni-plugin-how-to.md b/site/plugin/cni-plugin-how-to.md index e59ea070..8384358f 100644 --- a/site/plugin/cni-plugin-how-to.md +++ b/site/plugin/cni-plugin-how-to.md @@ -12,7 +12,7 @@ manager built by Google. ###Installing the Weave Net CNI plugin The Weave Net CNI plugin is installed when you run `weave setup`, if -it your machine has the directories normally used to host CNI plugins. +your machine has the directories normally used to host CNI plugins. To create those directories, run (as root): mkdir -p /opt/cni/bin @@ -30,7 +30,7 @@ To create a network which can span multiple hosts, the Weave peers must be conne See [Deploying Applications to Weave Net](/site/using-weave/deploying-applications.md#peer-connections) for a discussion on peer connections. As well as launching Weave Net, you have to run an extra command to -configure the Weave bridge: +perform some additional configuration of the Weave bridge: weave launch weave expose