diff --git a/.gitmodules b/.gitmodules
index adec6564..58ec72d5 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -64,3 +64,9 @@
[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
+[submodule "vendor/github.com/coreos/go-iptables"]
+ path = vendor/github.com/coreos/go-iptables
+ url = https://github.com/coreos/go-iptables
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/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
+}
diff --git a/common/utils.go b/common/utils.go
index 9c348902..19b736d2 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"
@@ -49,7 +50,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)
@@ -62,40 +63,93 @@ 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")
+ // Bail out if this container is running in the root namespace
+ 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
+ }
+
+ 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 {
+ if link.Attrs().MasterIndex == weaveBridge.Attrs().Index {
+ 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
+ })
+ if err != nil {
+ return nil, err
+ }
+ return FindNetDevs(processID, func(link netlink.Link) bool {
+ _, isveth := link.(*netlink.Veth)
+ _, found := indexes[link.Attrs().Index]
+ return isveth && found
})
}
// 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
})
}
diff --git a/plugin/ipam/cni.go b/plugin/ipam/cni.go
new file mode 100644
index 00000000..a5d407b8
--- /dev/null
+++ b/plugin/ipam/cni.go
@@ -0,0 +1,77 @@
+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 {
+ 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 nil, 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 nil, fmt.Errorf("subnet given in config, but not parseable: %s", err)
+ }
+ ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ result := &types.Result{
+ IP4: &types.IPConfig{
+ IP: *ipnet,
+ Gateway: conf.Gateway,
+ Routes: conf.Routes,
+ },
+ }
+ 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)
+}
+
+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/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
}
diff --git a/plugin/net/cni.go b/plugin/net/cni.go
new file mode 100644
index 00000000..95977e10
--- /dev/null
+++ b/plugin/net/cni.go
@@ -0,0 +1,240 @@
+package plugin
+
+import (
+ "crypto/rand"
+ "encoding/json"
+ "fmt"
+ "net"
+ "os"
+
+ "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"
+)
+
+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
+}
+
+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"))
+ }
+
+ // 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)
+ }
+ 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))
+ }
+
+ result.DNS = conf.DNS
+ 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 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
+ 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 {
+ 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/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)
diff --git a/prog/plugin/main.go b/prog/plugin/main.go
index 82a594e7..3eb12a55 100644
--- a/prog/plugin/main.go
+++ b/prog/plugin/main.go
@@ -7,8 +7,10 @@ import (
"net/http"
"os"
"os/signal"
+ "strings"
"syscall"
+ cni "github.com/appc/cni/pkg/skel"
"github.com/docker/libnetwork/ipamapi"
weaveapi "github.com/weaveworks/weave/api"
"github.com/weaveworks/weave/common"
@@ -26,6 +28,8 @@ var Log = common.Log
func main() {
var (
justVersion bool
+ cniNet bool
+ cniIpam bool
address string
meshAddress string
logLevel string
@@ -33,6 +37,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")
@@ -47,14 +53,25 @@ func main() {
common.SetLogLevel(logLevel)
+ weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), Log)
+
+ switch {
+ case cniIpam || strings.HasSuffix(os.Args[0], "weave-ipam"):
+ i := ipamplugin.NewIpam(weave)
+ cni.PluginMain(i.CmdAdd, i.CmdDel)
+ os.Exit(0)
+ case cniNet || strings.HasSuffix(os.Args[0], "weave-net"):
+ 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
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/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..8384358f
--- /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
+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
+perform some additional configuration of 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.
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/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
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
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
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
diff --git a/weave b/weave
index c3df0664..07f2375c 100755
--- a/weave
+++ b/weave
@@ -115,10 +115,35 @@ docker_run_options() {
echo --privileged --net=host $(docker_sock_options)
}
+cni_volume_options() {
+ [ "$1" = "setup" ] && echo -v /etc/cni:/etc/cni -v /opt/cni:/opt/cni
+}
+
+create_cni_script() {
+ cat >"$1" <"$1" <