From e230eeb4860914add6b7607d1bb857c6c8feb5c4 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 12 Nov 2015 13:06:10 +0000 Subject: [PATCH 001/135] Use upstream netlink code --- net/if.go | 82 ++++++++++++++++++---------------------------------- net/route.go | 50 -------------------------------- 2 files changed, 28 insertions(+), 104 deletions(-) diff --git a/net/if.go b/net/if.go index b99993ec..780f4290 100644 --- a/net/if.go +++ b/net/if.go @@ -5,28 +5,33 @@ import ( "net" "syscall" - "github.com/vishvananda/netlink/nl" + "github.com/vishvananda/netlink" ) // Wait for an interface to come up. func EnsureInterface(ifaceName string) (*net.Interface, error) { - s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK) - if err != nil { - return nil, err - } - defer s.Close() - iface, err := ensureInterface(s, ifaceName) + iface, err := ensureInterface(ifaceName) if err != nil { return nil, err } return iface, err } -func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) { +func ensureInterface(ifaceName string) (*net.Interface, error) { if iface, err := findInterface(ifaceName); err == nil { return iface, nil } - netlinkReceiveUntil(s, matchIfName(ifaceName)) + ch := make(chan netlink.LinkUpdate) + done := make(chan struct{}) + defer close(done) + if err := netlink.LinkSubscribe(ch, done); err != nil { + return nil, err + } + for update := range ch { + if ifaceName == update.Link.Attrs().Name && update.IfInfomsg.Flags&syscall.IFF_UP != 0 { + break + } + } iface, err := findInterface(ifaceName) return iface, err } @@ -35,12 +40,7 @@ func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, err // This matches the behaviour in 'weave attach', which is the only context in which // we expect this to be called. If you change one, change the other to match. func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) { - s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE) - if err != nil { - return nil, err - } - defer s.Close() - iface, err := ensureInterface(s, ifaceName) + iface, err := ensureInterface(ifaceName) if err != nil { return nil, err } @@ -48,8 +48,19 @@ func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) { if CheckRouteExists(ifaceName, dest) { return iface, err } - netlinkReceiveUntil(s, matchRoute(ifaceName, dest)) - return iface, err + ch := make(chan netlink.RouteUpdate) + done := make(chan struct{}) + defer close(done) + if err := netlink.RouteSubscribe(ch, done); err != nil { + return nil, err + } + for update := range ch { + link, _ := netlink.LinkByIndex(update.Route.LinkIndex) + if link.Attrs().Name == ifaceName && update.Route.Dst.IP.Equal(dest) { + break + } + } + return iface, nil } func findInterface(ifaceName string) (iface *net.Interface, err error) { @@ -61,40 +72,3 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) { } return } - -func netlinkReceiveUntil(s *nl.NetlinkSocket, f func(syscall.NetlinkMessage) (bool, error)) error { - for { - msgs, err := s.Receive() - if err != nil { - return err - } - for _, m := range msgs { - if done, err := f(m); done { - return err - } - } - } -} - -func matchIfName(ifaceName string) func(m syscall.NetlinkMessage) (bool, error) { - return func(m syscall.NetlinkMessage) (bool, error) { - switch m.Header.Type { - case syscall.RTM_NEWLINK: // receive this type for link 'up' - ifmsg := nl.DeserializeIfInfomsg(m.Data) - attrs, err := syscall.ParseNetlinkRouteAttr(&m) - if err != nil { - return true, err - } - name := "" - for _, attr := range attrs { - if attr.Attr.Type == syscall.IFA_LABEL { - name = string(attr.Value[:len(attr.Value)-1]) - } - } - if ifaceName == name && ifmsg.Flags&syscall.IFF_UP != 0 { - return true, nil - } - } - return false, nil - } -} diff --git a/net/route.go b/net/route.go index 560ae22e..3424826c 100644 --- a/net/route.go +++ b/net/route.go @@ -3,10 +3,8 @@ package net import ( "fmt" "net" - "syscall" "github.com/vishvananda/netlink" - "github.com/vishvananda/netlink/nl" ) // A network is considered free if it does not overlap any existing @@ -65,51 +63,3 @@ func CheckRouteExists(ifaceName string, dest net.IP) bool { }) return found } - -func matchRoute(ifaceName string, dest net.IP) func(m syscall.NetlinkMessage) (bool, error) { - return func(m syscall.NetlinkMessage) (bool, error) { - switch m.Header.Type { - case syscall.RTM_NEWROUTE: - route, err := deserializeRoute(m.Data) - if err != nil { - return true, err - } - link, _ := netlink.LinkByIndex(route.LinkIndex) - if link.Attrs().Name == ifaceName && route.Dst.IP.Equal(dest) { - return true, nil - } - } - return false, nil - } -} - -// This code proposed for addition to the vishvananda/netlink library -// deserializeRoute decodes a binary netlink message into a Route struct -func deserializeRoute(m []byte) (netlink.Route, error) { - route := netlink.Route{} - msg := nl.DeserializeRtMsg(m) - attrs, err := nl.ParseRouteAttr(m[msg.Len():]) - if err != nil { - return route, err - } - route.Scope = netlink.Scope(msg.Scope) - - native := nl.NativeEndian() - for _, attr := range attrs { - switch attr.Attr.Type { - case syscall.RTA_GATEWAY: - route.Gw = net.IP(attr.Value) - case syscall.RTA_PREFSRC: - route.Src = net.IP(attr.Value) - case syscall.RTA_DST: - route.Dst = &net.IPNet{ - IP: attr.Value, - Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)), - } - case syscall.RTA_OIF: - routeIndex := int(native.Uint32(attr.Value[0:4])) - route.LinkIndex = routeIndex - } - } - return route, nil -} From 804e6ba5860552f41f513d2b674ee76f48bc8181 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 13 Nov 2015 11:08:57 +0000 Subject: [PATCH 002/135] Cope with changes in docker-py structure. Tests are now in a different directory, and get sharded by module rather than individually. --- test/600_proxy_docker_py.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/600_proxy_docker_py.sh b/test/600_proxy_docker_py.sh index ce9a0250..57ee606a 100755 --- a/test/600_proxy_docker_py.sh +++ b/test/600_proxy_docker_py.sh @@ -13,14 +13,14 @@ docker_py_test() { docker_on $HOST1 pull joffrey/docker-py >/dev/null CANDIDATES=$(docker_on $HOST1 run \ joffrey/docker-py \ - py.test --collect-only tests/integration_test.py \ - | sed -En "s/\s*/\1/p") + py.test --collect-only tests/integration/ \ + | sed -En "s/\s*/\1/p") i=0 TESTS= for test in $CANDIDATES; do if [ $(($i % $TOTAL_SHARDS)) -eq $SHARD ]; then - TESTS="$TESTS tests/integration_test.py::$test" + TESTS="$TESTS $test" fi i=$(($i + 1)) done From de802f9d760b63ad4bd99a87f37370da3a66d172 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 13 Nov 2015 11:44:08 +0000 Subject: [PATCH 003/135] Work round docker-py assuming busybox is present (https://github.com/docker/docker-py/issues/852) --- test/600_proxy_docker_py.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/600_proxy_docker_py.sh b/test/600_proxy_docker_py.sh index 57ee606a..724fbf4a 100755 --- a/test/600_proxy_docker_py.sh +++ b/test/600_proxy_docker_py.sh @@ -9,6 +9,8 @@ docker_py_test() { start_suite "Run docker-py test suite against the proxy" + # Work round https://github.com/docker/docker-py/issues/852 + docker_on $HOST1 pull busybox:buildroot-2014.02 >/dev/null # Get a list of the tests for use to shard docker_on $HOST1 pull joffrey/docker-py >/dev/null CANDIDATES=$(docker_on $HOST1 run \ From f458491d8817dd4da8d10839459d54e9dbf347eb Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 13 Nov 2015 15:06:16 +0000 Subject: [PATCH 004/135] [proxy] add --no-detect-tls flag, and proxy always tries to detect TLS. --- site/proxy.md | 10 +++++----- weave | 10 +++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/site/proxy.md b/site/proxy.md index acf3a379..78bbfd99 100644 --- a/site/proxy.md +++ b/site/proxy.md @@ -231,11 +231,11 @@ be able to launch a TLS-enabled proxy with: host1$ weave launch-proxy -You can also manually configure the proxy's TLS. This is accomplished -by launching the proxy with the same TLS-related command-line flags as -supplied to the docker daemon. For example, if you have generated your -certificates and keys into the docker host's `/tls` directory, we can -launch the proxy with: +To disable auto-detection of TLS configuration, you can either pass +the `--no-detect-tls` flag, or manually configure the proxy's TLS with +the same TLS-related command-line flags as supplied to the docker +daemon. For example, if you have generated your certificates and keys +into the docker host's `/tls` directory, we can launch the proxy with: host1$ weave launch-proxy --tls-verify --tls-cacert=/tls/ca.pem \ --tls-cert=/tls/server-cert.pem --tls-key=/tls/server-key.pem diff --git a/weave b/weave index 248d9df5..3be503b8 100755 --- a/weave +++ b/weave @@ -1278,6 +1278,9 @@ proxy_parse_args() { PROXY_HOST="${1#*=}" PROXY_ARGS="$PROXY_ARGS $1" ;; + -no-detect-tls|--no-detect-tls) + PROXY_TLS_DETECTION_DISABLED=1 + ;; -tls|--tls|-tlsverify|--tlsverify) PROXY_TLS_ENABLED=1 PROXY_ARGS="$PROXY_ARGS $1" @@ -1315,12 +1318,13 @@ proxy_args() { PROXY_VOLUMES="" PROXY_ARGS="" PROXY_TLS_ENABLED="" + PROXY_TLS_DETECTION_DISABLED="" PROXY_HOST="" proxy_parse_args "$@" - if [ -z "$PROXY_HOST" -a -n "$CLIENT_TLS_ENABLED" -a -z "$PROXY_TLS_ENABLED" ] ; then - if ! proxy_parse_args $(docker_tls_args) ; then - echo "When launching the proxy via TLS, -H and/or TLS options are required." >&2 + if [ -z "$PROXY_TLS_ENABLED" ] ; then + if [ -z "$PROXY_TLS_DETECTION_DISABLED" ] && ! proxy_parse_args $(docker_tls_args) ; then + echo "Unable to auto-detect TLS configuration, --no-detect-tls or TLS options are required." >&2 exit 1 fi fi From fb48acddb22f83c8d7609ecaad59dd4b5515ca89 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 16 Nov 2015 19:27:31 +0000 Subject: [PATCH 005/135] cosmetic: update comment --- mesh/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesh/connection.go b/mesh/connection.go index b19d2c2c..d6ddf3a8 100644 --- a/mesh/connection.go +++ b/mesh/connection.go @@ -134,7 +134,7 @@ func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) { // do not need locks for reading, and only need write locks for fields // read by other processes. -// Async +// Non-blocking. func (conn *LocalConnection) Shutdown(err error) { // err should always be a real error, even if only io.EOF if err == nil { From b48994b1b3a5c202eb2942734a84626a2b37e49b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 16 Nov 2015 23:01:20 +0000 Subject: [PATCH 006/135] cosmetic: move some functions --- mesh/gossip_channel.go | 134 ++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/mesh/gossip_channel.go b/mesh/gossip_channel.go index 098553e7..8e957812 100644 --- a/mesh/gossip_channel.go +++ b/mesh/gossip_channel.go @@ -97,6 +97,73 @@ func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) er return nil } +func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error { + return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg)) +} + +func (c *GossipChannel) GossipBroadcast(update GossipData) error { + return c.relayBroadcast(c.ourself.Name, update) +} + +func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) (err error) { + if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found { + err = fmt.Errorf("unknown relay destination: %s", dstPeerName) + } else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found { + err = fmt.Errorf("unable to find connection to relay peer %s", relayPeerName) + } else { + conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf}) + } + return err +} + +func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error { + names := c.routes.PeerNames() // do this outside the lock so they don't nest + c.Lock() + defer c.Unlock() + // GC - randomly (courtesy of go's map iterator) pick some + // existing broadcasters and stop&remove them if their source peer + // is unknown. We stop as soon as we encounter a valid entry; the + // idea being that when there is little or no garbage then this + // executes close to O(1)[1], whereas when there is lots of + // garbage we remove it quickly. + // + // [1] TODO Unfortunately, due to the desire to avoid nested + // locks, instead of simply invoking Peers.Fetch(name) below, we + // have that Peers.Names() invocation above. That is O(n_peers) at + // best. + for name, broadcaster := range c.broadcasters { + if _, found := names[name]; !found { + delete(c.broadcasters, name) + broadcaster.Stop() + } else { + break + } + } + broadcaster, found := c.broadcasters[srcName] + if !found { + broadcaster = NewGossipSender(func(pending GossipData) { c.sendBroadcast(srcName, pending) }) + c.broadcasters[srcName] = broadcaster + } + broadcaster.Send(update) + return nil +} + +func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) { + c.routes.EnsureRecalculated() + nextHops := c.routes.BroadcastAll(srcName) + if len(nextHops) == 0 { + return + } + connections := c.ourself.ConnectionsTo(nextHops) + for _, msg := range update.Encode() { + protocolMsg := ProtocolMsg{ProtocolGossipBroadcast, GobEncode(c.name, srcName, msg)} + // FIXME a single blocked connection can stall us + for _, conn := range connections { + conn.(ProtocolSender).SendProtocolMsg(protocolMsg) + } + } +} + func (c *GossipChannel) Send(srcName PeerName, data GossipData) { // do this outside the lock below so we avoid lock nesting c.routes.EnsureRecalculated() @@ -169,73 +236,6 @@ func (c *GossipChannel) sendDown(conn Connection, data GossipData) { sender.Send(data) } -func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error { - return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg)) -} - -func (c *GossipChannel) GossipBroadcast(update GossipData) error { - return c.relayBroadcast(c.ourself.Name, update) -} - -func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) (err error) { - if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found { - err = fmt.Errorf("unknown relay destination: %s", dstPeerName) - } else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found { - err = fmt.Errorf("unable to find connection to relay peer %s", relayPeerName) - } else { - conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf}) - } - return err -} - -func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error { - names := c.routes.PeerNames() // do this outside the lock so they don't nest - c.Lock() - defer c.Unlock() - // GC - randomly (courtesy of go's map iterator) pick some - // existing broadcasters and stop&remove them if their source peer - // is unknown. We stop as soon as we encounter a valid entry; the - // idea being that when there is little or no garbage then this - // executes close to O(1)[1], whereas when there is lots of - // garbage we remove it quickly. - // - // [1] TODO Unfortunately, due to the desire to avoid nested - // locks, instead of simply invoking Peers.Fetch(name) below, we - // have that Peers.Names() invocation above. That is O(n_peers) at - // best. - for name, broadcaster := range c.broadcasters { - if _, found := names[name]; !found { - delete(c.broadcasters, name) - broadcaster.Stop() - } else { - break - } - } - broadcaster, found := c.broadcasters[srcName] - if !found { - broadcaster = NewGossipSender(func(pending GossipData) { c.sendBroadcast(srcName, pending) }) - c.broadcasters[srcName] = broadcaster - } - broadcaster.Send(update) - return nil -} - -func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) { - c.routes.EnsureRecalculated() - nextHops := c.routes.BroadcastAll(srcName) - if len(nextHops) == 0 { - return - } - connections := c.ourself.ConnectionsTo(nextHops) - for _, msg := range update.Encode() { - protocolMsg := ProtocolMsg{ProtocolGossipBroadcast, GobEncode(c.name, srcName, msg)} - // FIXME a single blocked connection can stall us - for _, conn := range connections { - conn.(ProtocolSender).SendProtocolMsg(protocolMsg) - } - } -} - func (c *GossipChannel) log(args ...interface{}) { log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...) } From cab2e11ca97c24995b1072ae2ed6709500e902bf Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 16 Nov 2015 23:21:33 +0000 Subject: [PATCH 007/135] refactor: simplify per-connection GossipSender gc and creation by putting it in one place and inlining some helper functions --- mesh/gossip_channel.go | 77 ++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/mesh/gossip_channel.go b/mesh/gossip_channel.go index 8e957812..f641aa92 100644 --- a/mesh/gossip_channel.go +++ b/mesh/gossip_channel.go @@ -173,38 +173,31 @@ func (c *GossipChannel) Send(srcName PeerName, data GossipData) { selectedConnections[conn] = void } } + c.sendDown(selectedConnections, data) +} + +func (c *GossipChannel) SendDown(conn Connection, data GossipData) { + c.sendDown(ConnectionSet{conn: void}, data) +} + +func (c *GossipChannel) sendDown(selectedConnections ConnectionSet, data GossipData) { if len(selectedConnections) == 0 { return } connections := c.ourself.Connections() c.Lock() defer c.Unlock() - c.gcSenders(connections) - for conn := range selectedConnections { - c.sendDown(conn, data) - } -} - -func (c *GossipChannel) SendDown(conn Connection, data GossipData) { - connections := c.ourself.Connections() - c.Lock() - defer c.Unlock() - c.gcSenders(connections) - c.sendDown(conn, data) -} - -// GC - randomly (courtesy of go's map iterator) pick some existing -// senders and stop&remove them if the associated connection is no -// longer active. We stop as soon as we encounter a valid entry; the -// idea being that when there is little or no garbage then this -// executes close to O(1)[1], whereas when there is lots of garbage we -// remove it quickly. -// -// [1] TODO Unfortunately, due to the desire to avoid nested locks, -// instead of simply invoking LocalPeer.ConnectionTo(name), we pass in -// the result of LocalPeer.Connections(). That is O(n_our_connections) -// at best. -func (c *GossipChannel) gcSenders(connections ConnectionSet) { + // GC - randomly (courtesy of go's map iterator) pick some + // existing senders and stop&remove them if the associated + // connection is no longer active. We stop as soon as we + // encounter a valid entry; the idea being that when there is + // little or no garbage then this executes close to O(1)[1], + // whereas when there is lots of garbage we remove it quickly. + // + // [1] TODO Unfortunately, due to the desire to avoid nested + // locks, instead of simply invoking LocalPeer.ConnectionTo(name), + // we operate on LocalPeer.Connections(). That is + // O(n_our_connections) at best. for conn, sender := range c.senders { if _, found := connections[conn]; !found { delete(c.senders, conn) @@ -213,27 +206,31 @@ func (c *GossipChannel) gcSenders(connections ConnectionSet) { break } } + // start senders, if necessary, and send. + for conn := range selectedConnections { + sender, found := c.senders[conn] + if !found { + sender = c.makeSender(conn) + c.senders[conn] = sender + } + sender.Send(data) + } } // We have seen a couple of failures which suggest a >128GB slice was encountered. // 100MB should be enough for anyone. const maxFeasibleMessageLen = 100 * 1024 * 1024 -func (c *GossipChannel) sendDown(conn Connection, data GossipData) { - sender, found := c.senders[conn] - if !found { - sender = NewGossipSender(func(pending GossipData) { - for _, msg := range pending.Encode() { - if len(msg) > maxFeasibleMessageLen { - panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending)) - } - protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)} - conn.(ProtocolSender).SendProtocolMsg(protocolMsg) +func (c *GossipChannel) makeSender(conn Connection) *GossipSender { + return NewGossipSender(func(pending GossipData) { + for _, msg := range pending.Encode() { + if len(msg) > maxFeasibleMessageLen { + panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending)) } - }) - c.senders[conn] = sender - } - sender.Send(data) + protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)} + conn.(ProtocolSender).SendProtocolMsg(protocolMsg) + } + }) } func (c *GossipChannel) log(args ...interface{}) { From d01c26e618cf5afb8c725b243c27c693ea421ba7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 17 Nov 2015 09:27:49 +0000 Subject: [PATCH 008/135] cosmetic: remove outdated comment The deadlock described for SendProtocolMsg (if it was going via the actor) cannot occur since all sending of broadcasts and gossip by LocalPeer is mediated by GossipSenders, the interaction with which is non-blocking. --- mesh/connection.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mesh/connection.go b/mesh/connection.go index d6ddf3a8..7e9a671a 100644 --- a/mesh/connection.go +++ b/mesh/connection.go @@ -116,11 +116,6 @@ func (conn *LocalConnection) Established() bool { return conn.established } -// Send directly, not via the Actor. If it goes via the Actor we can -// get a deadlock where LocalConnection is blocked talking to -// LocalPeer and LocalPeer is blocked trying send a ProtocolMsg via -// LocalConnection, and the channels are full in both directions so -// nothing can proceed. func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) { if err := conn.sendProtocolMsg(m); err != nil { conn.Shutdown(err) From d80e72dfcded1473be7bee042ef5392c6bcd4350 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 23 Oct 2015 13:42:25 +0100 Subject: [PATCH 009/135] [proxy] dynamically mount -H dirs into the proxy when launching This lets you use -H unix://*.sock outside /var/run/weave. Using a socket directly in /var/run (i.e. /var/run/foo.sock), will reintroduce the bug with namespaces not being GCed, so that is a warning. --- ...onfig_test.sh => 690_proxy_config_test.sh} | 7 ++++- weave | 29 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) rename test/{690_proxy_autoconfig_test.sh => 690_proxy_config_test.sh} (81%) diff --git a/test/690_proxy_autoconfig_test.sh b/test/690_proxy_config_test.sh similarity index 81% rename from test/690_proxy_autoconfig_test.sh rename to test/690_proxy_config_test.sh index ce6e18b2..6293a3f5 100755 --- a/test/690_proxy_autoconfig_test.sh +++ b/test/690_proxy_config_test.sh @@ -2,7 +2,7 @@ . ./config.sh -start_suite "Boot the proxy should only listen on client's interface" +start_suite "Various launch-proxy configurations" # Booting it over unix socket listens on unix socket run_on $HOST1 COVERAGE=$COVERAGE sudo -E weave launch-proxy @@ -22,6 +22,11 @@ assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" assert_raises "proxy docker_on $HOST1 ps" weave_on $HOST1 stop-proxy +# Booting it with -H outside /var/run/weave, still works +socket="$(mktemp -d)/weave.sock" +weave_on $HOST1 launch-proxy -H unix://$socket +assert_raises "run_on $HOST1 sudo docker -H unix:///$socket ps" 0 +weave_on $HOST1 stop-proxy # Booting it over tls errors assert_raises "DOCKER_CLIENT_ARGS='--tls' weave_on $HOST1 launch-proxy" 1 diff --git a/weave b/weave index 248d9df5..02231801 100755 --- a/weave +++ b/weave @@ -1266,17 +1266,36 @@ tls_arg() { PROXY_ARGS="$PROXY_ARGS $1 /home/weave/tls/$3.pem" } +# TODO: Handle relative paths for args +# TODO: Handle args with spaces +host_arg() { + PROXY_HOST="$1" + if [ "$PROXY_HOST" != "${PROXY_HOST#unix://}" ]; then + host=$(dirname ${PROXY_HOST#unix://}) + if [ "$host" = "${host#/}" ]; then + echo "When launching the proxy, unix sockets must be specified as an absolute path." >&2 + exit 1 + fi + if [ "$host" = "/var/run" ]; then + echo "WARNING: When launching the proxy, unix sockets directly in /var/run can lead to a bug with net namespace cleanup." >&2 + echo "For more info, see https://github.com/weaveworks/weave/issues/1455" >&2 + fi + PROXY_VOLUMES="$PROXY_VOLUMES -v $host:/host$host" + PROXY_ARGS="$PROXY_ARGS -H /host$host/$(basename ${PROXY_HOST#unix://})" + else + PROXY_ARGS="$PROXY_ARGS -H $1" + fi +} + proxy_parse_args() { while [ $# -gt 0 ]; do case "$1" in -H) - PROXY_HOST="$2" - PROXY_ARGS="$PROXY_ARGS $1 $2" + host_arg "$2" shift ;; -H=*) - PROXY_HOST="${1#*=}" - PROXY_ARGS="$PROXY_ARGS $1" + host_arg "${1#*=}" ;; -tls|--tls|-tlsverify|--tlsverify) PROXY_TLS_ENABLED=1 @@ -1333,7 +1352,7 @@ proxy_args() { PROXY_HOST="tcp://0.0.0.0:12375" ;; esac - PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST" + host_arg "$PROXY_HOST" fi } From e80a83b30bd2ce03da3c4ed3f2d79bf8f2b2d3f6 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 16 Nov 2015 14:41:37 +0000 Subject: [PATCH 010/135] adding work around to the proxy /var/run warning --- weave | 1 + 1 file changed, 1 insertion(+) diff --git a/weave b/weave index 02231801..c62e9c1c 100755 --- a/weave +++ b/weave @@ -1278,6 +1278,7 @@ host_arg() { fi if [ "$host" = "/var/run" ]; then echo "WARNING: When launching the proxy, unix sockets directly in /var/run can lead to a bug with net namespace cleanup." >&2 + echo "To work around this, put the unix socket in a subdirectory of /var/run, and symlink it into place." >&2 echo "For more info, see https://github.com/weaveworks/weave/issues/1455" >&2 fi PROXY_VOLUMES="$PROXY_VOLUMES -v $host:/host$host" From 9ee3b1bfbd3eca6b8fdb592b3ad4e4ade16b14f8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 17 Nov 2015 10:28:28 +0000 Subject: [PATCH 011/135] add some timestamps to multiweave --- bin/multiweave | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/multiweave b/bin/multiweave index ad4441ac..560b6dd8 100755 --- a/bin/multiweave +++ b/bin/multiweave @@ -29,6 +29,7 @@ random_mac() { case "$1" in launch) shift 1 + echo $(date -R) launching for i in $(seq $START $FINISH); do WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch-router --iface '' --name $(random_mac) --nickname weave$i --port $PORT --no-dns "$@" done @@ -36,6 +37,7 @@ case "$1" in status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1))) PREV="${status#true }" fi + echo $(date -R) connecting for i in $(seq $START $FINISH); do if [ -n "$PREV" ] ; then echo connecting weave$i to $PREV @@ -44,6 +46,7 @@ case "$1" in status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i) PREV="${status#true }" done + echo $(date -R) done ;; stop) for i in $(seq $START $FINISH); do From a9611c782b08742b6d6fbbe7546e50aba18c0549 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 17 Nov 2015 13:56:18 +0000 Subject: [PATCH 012/135] cosmetic: move comment --- mesh/connection.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mesh/connection.go b/mesh/connection.go index 7e9a671a..b7b5d270 100644 --- a/mesh/connection.go +++ b/mesh/connection.go @@ -86,11 +86,10 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ro if connRemote.local != router.Ourself.Peer { log.Fatal("Attempt to create local connection from a peer which is not ourself") } - // NB, we're taking a copy of connRemote here. actionChan := make(chan ConnectionAction, ChannelSize) finished := make(chan struct{}) conn := &LocalConnection{ - RemoteConnection: *connRemote, + RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here. Router: router, TCPConn: tcpConn, uid: randUint64(), From e8c56a8e8e7d4e502f20e5b2b5788e6619b565a2 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 17 Nov 2015 14:18:04 +0000 Subject: [PATCH 013/135] Review Feedback --- weave | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/weave b/weave index c62e9c1c..462352c2 100755 --- a/weave +++ b/weave @@ -1277,12 +1277,11 @@ host_arg() { exit 1 fi if [ "$host" = "/var/run" ]; then - echo "WARNING: When launching the proxy, unix sockets directly in /var/run can lead to a bug with net namespace cleanup." >&2 + echo "WARNING: Proxying sockets whose immediate parent directory is /var/run is unreliable." >&2 echo "To work around this, put the unix socket in a subdirectory of /var/run, and symlink it into place." >&2 - echo "For more info, see https://github.com/weaveworks/weave/issues/1455" >&2 fi PROXY_VOLUMES="$PROXY_VOLUMES -v $host:/host$host" - PROXY_ARGS="$PROXY_ARGS -H /host$host/$(basename ${PROXY_HOST#unix://})" + PROXY_ARGS="$PROXY_ARGS -H unix:///host$host/$(basename ${PROXY_HOST#unix://})" else PROXY_ARGS="$PROXY_ARGS -H $1" fi From 7987d0943827cddaf366d711c6fa519a6a4a4961 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 17 Nov 2015 16:28:31 +0000 Subject: [PATCH 014/135] Revert "Merge pull request #1580 from weaveworks/1579-launch-proxy-mount-h" This reverts commit 262352f8bbf97189a61c1fe4f21d8146a8731ab4, reversing changes made to 312691376a560a9a9c932f6e23e68c8a9de3e084 due to a bug whereby `weave env` returns a unix domain socket with an incorrect path. This change will be superceeded by #1694. --- ...g_test.sh => 690_proxy_autoconfig_test.sh} | 7 +---- weave | 29 ++++--------------- 2 files changed, 6 insertions(+), 30 deletions(-) rename test/{690_proxy_config_test.sh => 690_proxy_autoconfig_test.sh} (81%) diff --git a/test/690_proxy_config_test.sh b/test/690_proxy_autoconfig_test.sh similarity index 81% rename from test/690_proxy_config_test.sh rename to test/690_proxy_autoconfig_test.sh index 6293a3f5..ce6e18b2 100755 --- a/test/690_proxy_config_test.sh +++ b/test/690_proxy_autoconfig_test.sh @@ -2,7 +2,7 @@ . ./config.sh -start_suite "Various launch-proxy configurations" +start_suite "Boot the proxy should only listen on client's interface" # Booting it over unix socket listens on unix socket run_on $HOST1 COVERAGE=$COVERAGE sudo -E weave launch-proxy @@ -22,11 +22,6 @@ assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" assert_raises "proxy docker_on $HOST1 ps" weave_on $HOST1 stop-proxy -# Booting it with -H outside /var/run/weave, still works -socket="$(mktemp -d)/weave.sock" -weave_on $HOST1 launch-proxy -H unix://$socket -assert_raises "run_on $HOST1 sudo docker -H unix:///$socket ps" 0 -weave_on $HOST1 stop-proxy # Booting it over tls errors assert_raises "DOCKER_CLIENT_ARGS='--tls' weave_on $HOST1 launch-proxy" 1 diff --git a/weave b/weave index 99684869..3be503b8 100755 --- a/weave +++ b/weave @@ -1266,36 +1266,17 @@ tls_arg() { PROXY_ARGS="$PROXY_ARGS $1 /home/weave/tls/$3.pem" } -# TODO: Handle relative paths for args -# TODO: Handle args with spaces -host_arg() { - PROXY_HOST="$1" - if [ "$PROXY_HOST" != "${PROXY_HOST#unix://}" ]; then - host=$(dirname ${PROXY_HOST#unix://}) - if [ "$host" = "${host#/}" ]; then - echo "When launching the proxy, unix sockets must be specified as an absolute path." >&2 - exit 1 - fi - if [ "$host" = "/var/run" ]; then - echo "WARNING: Proxying sockets whose immediate parent directory is /var/run is unreliable." >&2 - echo "To work around this, put the unix socket in a subdirectory of /var/run, and symlink it into place." >&2 - fi - PROXY_VOLUMES="$PROXY_VOLUMES -v $host:/host$host" - PROXY_ARGS="$PROXY_ARGS -H unix:///host$host/$(basename ${PROXY_HOST#unix://})" - else - PROXY_ARGS="$PROXY_ARGS -H $1" - fi -} - proxy_parse_args() { while [ $# -gt 0 ]; do case "$1" in -H) - host_arg "$2" + PROXY_HOST="$2" + PROXY_ARGS="$PROXY_ARGS $1 $2" shift ;; -H=*) - host_arg "${1#*=}" + PROXY_HOST="${1#*=}" + PROXY_ARGS="$PROXY_ARGS $1" ;; -no-detect-tls|--no-detect-tls) PROXY_TLS_DETECTION_DISABLED=1 @@ -1356,7 +1337,7 @@ proxy_args() { PROXY_HOST="tcp://0.0.0.0:12375" ;; esac - host_arg "$PROXY_HOST" + PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST" fi } From f57988761c60c6721e201fb0c855f59106fe41d5 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 17 Nov 2015 17:15:28 +0000 Subject: [PATCH 015/135] Revert "Revert "Merge pull request #1580 from weaveworks/1579-launch-proxy-mount-h"" This reverts commit 7987d0943827cddaf366d711c6fa519a6a4a4961. --- ...onfig_test.sh => 690_proxy_config_test.sh} | 7 ++++- weave | 29 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) rename test/{690_proxy_autoconfig_test.sh => 690_proxy_config_test.sh} (81%) diff --git a/test/690_proxy_autoconfig_test.sh b/test/690_proxy_config_test.sh similarity index 81% rename from test/690_proxy_autoconfig_test.sh rename to test/690_proxy_config_test.sh index ce6e18b2..6293a3f5 100755 --- a/test/690_proxy_autoconfig_test.sh +++ b/test/690_proxy_config_test.sh @@ -2,7 +2,7 @@ . ./config.sh -start_suite "Boot the proxy should only listen on client's interface" +start_suite "Various launch-proxy configurations" # Booting it over unix socket listens on unix socket run_on $HOST1 COVERAGE=$COVERAGE sudo -E weave launch-proxy @@ -22,6 +22,11 @@ assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" assert_raises "proxy docker_on $HOST1 ps" weave_on $HOST1 stop-proxy +# Booting it with -H outside /var/run/weave, still works +socket="$(mktemp -d)/weave.sock" +weave_on $HOST1 launch-proxy -H unix://$socket +assert_raises "run_on $HOST1 sudo docker -H unix:///$socket ps" 0 +weave_on $HOST1 stop-proxy # Booting it over tls errors assert_raises "DOCKER_CLIENT_ARGS='--tls' weave_on $HOST1 launch-proxy" 1 diff --git a/weave b/weave index 3be503b8..99684869 100755 --- a/weave +++ b/weave @@ -1266,17 +1266,36 @@ tls_arg() { PROXY_ARGS="$PROXY_ARGS $1 /home/weave/tls/$3.pem" } +# TODO: Handle relative paths for args +# TODO: Handle args with spaces +host_arg() { + PROXY_HOST="$1" + if [ "$PROXY_HOST" != "${PROXY_HOST#unix://}" ]; then + host=$(dirname ${PROXY_HOST#unix://}) + if [ "$host" = "${host#/}" ]; then + echo "When launching the proxy, unix sockets must be specified as an absolute path." >&2 + exit 1 + fi + if [ "$host" = "/var/run" ]; then + echo "WARNING: Proxying sockets whose immediate parent directory is /var/run is unreliable." >&2 + echo "To work around this, put the unix socket in a subdirectory of /var/run, and symlink it into place." >&2 + fi + PROXY_VOLUMES="$PROXY_VOLUMES -v $host:/host$host" + PROXY_ARGS="$PROXY_ARGS -H unix:///host$host/$(basename ${PROXY_HOST#unix://})" + else + PROXY_ARGS="$PROXY_ARGS -H $1" + fi +} + proxy_parse_args() { while [ $# -gt 0 ]; do case "$1" in -H) - PROXY_HOST="$2" - PROXY_ARGS="$PROXY_ARGS $1 $2" + host_arg "$2" shift ;; -H=*) - PROXY_HOST="${1#*=}" - PROXY_ARGS="$PROXY_ARGS $1" + host_arg "${1#*=}" ;; -no-detect-tls|--no-detect-tls) PROXY_TLS_DETECTION_DISABLED=1 @@ -1337,7 +1356,7 @@ proxy_args() { PROXY_HOST="tcp://0.0.0.0:12375" ;; esac - PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST" + host_arg "$PROXY_HOST" fi } From 77bc22baf5947f0567a45ad7d9bbec21cf7cf438 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 17 Nov 2015 17:14:00 +0000 Subject: [PATCH 016/135] [proxy] Auto-symlink /var/run/*.sock in from /var/run/weave/*.sock Avoids the obscure warning for users --- proxy/proxy.go | 8 +++++--- test/650_proxy_env_test.sh | 6 ++++++ weave | 11 +++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/proxy/proxy.go b/proxy/proxy.go index 778742d4..e5103e5c 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -8,6 +8,7 @@ import ( "net" "net/http" "os" + "path/filepath" "regexp" "strings" "sync" @@ -283,15 +284,16 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) { } case "unix": + localAddr := filepath.Join("/host", addr) // remove socket from last invocation - if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { + if err := os.Remove(localAddr); err != nil && !os.IsNotExist(err) { return nil, "", err } - listener, err = net.Listen(proto, addr) + listener, err = net.Listen(proto, localAddr) if err != nil { return nil, "", err } - if err = copyOwnerAndPermissions(dockerSock, addr); err != nil { + if err = copyOwnerAndPermissions(dockerSock, localAddr); err != nil { return nil, "", err } diff --git a/test/650_proxy_env_test.sh b/test/650_proxy_env_test.sh index 593f3d6d..345f661e 100755 --- a/test/650_proxy_env_test.sh +++ b/test/650_proxy_env_test.sh @@ -28,4 +28,10 @@ weave_on $HOST1 stop weave_on $HOST1 launch-proxy -H tcp://0.0.0.0:12375 -H unix:///var/run/weave/weave.sock check +# Check we can use weave env/config with unix -Hs specified +weave_on $HOST1 stop +weave_on $HOST1 launch-proxy -H unix:///var/run/weave/weave.sock +assert_raises "run_on $HOST1 'eval \$(weave env) ; docker $CMD'" +assert_raises "run_on $HOST1 'docker \$(weave config) $CMD'" + end_suite diff --git a/weave b/weave index 99684869..3eeb4de8 100755 --- a/weave +++ b/weave @@ -101,6 +101,7 @@ usage() { exec_remote() { docker $DOCKER_CLIENT_ARGS run --rm --privileged --net=host \ -v /var/run/docker.sock:/var/run/docker.sock \ + -v /var/run:/host/var/run \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e DOCKERHUB_USER="$DOCKERHUB_USER" \ @@ -1277,11 +1278,11 @@ host_arg() { exit 1 fi if [ "$host" = "/var/run" ]; then - echo "WARNING: Proxying sockets whose immediate parent directory is /var/run is unreliable." >&2 - echo "To work around this, put the unix socket in a subdirectory of /var/run, and symlink it into place." >&2 + host=/var/run/weave + PROXY_VAR_RUN_SYMLINKS="$PROXY_VAR_RUN_SYMLINKS $(basename ${PROXY_HOST#unix://})" fi PROXY_VOLUMES="$PROXY_VOLUMES -v $host:/host$host" - PROXY_ARGS="$PROXY_ARGS -H unix:///host$host/$(basename ${PROXY_HOST#unix://})" + PROXY_ARGS="$PROXY_ARGS -H unix://$host/$(basename ${PROXY_HOST#unix://})" else PROXY_ARGS="$PROXY_ARGS -H $1" fi @@ -1561,7 +1562,6 @@ launch_proxy() { PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \ $PROXY_VOLUMES \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v /var/run/weave:/var/run/weave \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e WEAVE_CIDR=none \ @@ -1571,6 +1571,9 @@ launch_proxy() { --entrypoint=/home/weave/weaveproxy \ $WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $COVERAGE_ARGS $PROXY_ARGS) wait_for_status $PROXY_CONTAINER_NAME http_call_unix $PROXY_CONTAINER_NAME status.sock + for sock in $PROXY_VAR_RUN_SYMLINKS; do + ln -sf -T "/var/run/weave/$sock" "/host/var/run/$sock" + done } stop_proxy() { From 3cd199e8523c19e306ce2474dfaa20b12db0045c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 17 Nov 2015 20:13:46 +0000 Subject: [PATCH 017/135] make GossipData immutable The same piece of GossipData can be shared and subject to different Merge operations. We either need to copy the data when we share it, or create copies on modification, i.e. copy-on-write. We choose the latter, since it is more efficient and there is just one mutating operation. Fixes #1697. --- ipam/allocator.go | 4 ++-- mesh/gossip.go | 5 ++--- mesh/router.go | 18 ++++++++++-------- mesh/surrogate_gossiper.go | 8 ++++++-- nameserver/entry.go | 14 ++++++++++---- nameserver/entry_test.go | 4 ++-- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/ipam/allocator.go b/ipam/allocator.go index 888b9433..a98697c8 100644 --- a/ipam/allocator.go +++ b/ipam/allocator.go @@ -478,8 +478,8 @@ type ipamGossipData struct { alloc *Allocator } -func (d *ipamGossipData) Merge(other mesh.GossipData) { - // no-op +func (d *ipamGossipData) Merge(other mesh.GossipData) mesh.GossipData { + return d // no-op } func (d *ipamGossipData) Encode() [][]byte { diff --git a/mesh/gossip.go b/mesh/gossip.go index abfab8fa..611639cb 100644 --- a/mesh/gossip.go +++ b/mesh/gossip.go @@ -6,7 +6,7 @@ import ( type GossipData interface { Encode() [][]byte - Merge(GossipData) + Merge(GossipData) GossipData } type Gossip interface { @@ -79,8 +79,7 @@ func (sender *GossipSender) Send(data GossipData) { // NB: this must not be invoked concurrently select { case pending := <-sender.cell: - pending.Merge(data) - sender.cell <- pending + sender.cell <- pending.Merge(data) default: sender.cell <- data } diff --git a/mesh/router.go b/mesh/router.go index 916ea5b7..c19556d7 100644 --- a/mesh/router.go +++ b/mesh/router.go @@ -127,17 +127,19 @@ func (router *Router) BroadcastTopologyUpdate(update []*Peer) { for _, p := range update { names[p.Name] = void } - - router.TopologyGossip.GossipBroadcast(&TopologyGossipData{ - peers: router.Peers, - update: names, - }) + router.TopologyGossip.GossipBroadcast( + &TopologyGossipData{peers: router.Peers, update: names}) } -func (d *TopologyGossipData) Merge(other GossipData) { - for name := range other.(*TopologyGossipData).update { - d.update[name] = void +func (d *TopologyGossipData) Merge(other GossipData) GossipData { + names := make(PeerNameSet) + for name := range d.update { + names[name] = void } + for name := range other.(*TopologyGossipData).update { + names[name] = void + } + return &TopologyGossipData{peers: d.peers, update: names} } func (d *TopologyGossipData) Encode() [][]byte { diff --git a/mesh/surrogate_gossiper.go b/mesh/surrogate_gossiper.go index aef844a8..7204d4aa 100644 --- a/mesh/surrogate_gossiper.go +++ b/mesh/surrogate_gossiper.go @@ -12,8 +12,12 @@ func (d *SurrogateGossipData) Encode() [][]byte { return d.messages } -func (d *SurrogateGossipData) Merge(other GossipData) { - d.messages = append(d.messages, other.(*SurrogateGossipData).messages...) +func (d *SurrogateGossipData) Merge(other GossipData) GossipData { + o := other.(*SurrogateGossipData) + messages := make([][]byte, 0, len(d.messages)+len(o.messages)) + messages = append(messages, d.messages...) + messages = append(messages, o.messages...) + return &SurrogateGossipData{messages: messages} } // SurrogateGossiper ignores unicasts and relays broadcasts and gossips. diff --git a/nameserver/entry.go b/nameserver/entry.go index 524845a3..cd18f6de 100644 --- a/nameserver/entry.go +++ b/nameserver/entry.go @@ -245,12 +245,18 @@ type GossipData struct { Entries } -func (g *GossipData) Merge(o mesh.GossipData) { +func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData { other := o.(*GossipData) - g.Entries.merge(other.Entries) - if g.Timestamp < other.Timestamp { - g.Timestamp = other.Timestamp + gossip := &GossipData{ + Entries: make(Entries, len(g.Entries)), + Timestamp: g.Timestamp, } + copy(gossip.Entries, g.Entries) + gossip.Entries.merge(other.Entries) + if gossip.Timestamp < other.Timestamp { + gossip.Timestamp = other.Timestamp + } + return gossip } func (g *GossipData) Decode(msg []byte) error { diff --git a/nameserver/entry_test.go b/nameserver/entry_test.go index 1e794660..57dfde02 100644 --- a/nameserver/entry_test.go +++ b/nameserver/entry_test.go @@ -117,7 +117,7 @@ func TestGossipDataMerge(t *testing.T) { g1 := GossipData{Entries: makeEntries("AcDf")} g2 := GossipData{Entries: makeEntries("BEf")} - g1.Merge(&g2) + g3 := g1.Merge(&g2).(*GossipData) - require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, g1) + require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, *g3) } From 1500b1dc00a7ff12f45b67ea3b1cd8c5c6df22f2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 18 Nov 2015 11:30:40 +0000 Subject: [PATCH 018/135] refactor: extract nameserver GossipData.copy and remove a self-evident comment --- nameserver/entry.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nameserver/entry.go b/nameserver/entry.go index cd18f6de..cb0830fa 100644 --- a/nameserver/entry.go +++ b/nameserver/entry.go @@ -247,11 +247,7 @@ type GossipData struct { func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData { other := o.(*GossipData) - gossip := &GossipData{ - Entries: make(Entries, len(g.Entries)), - Timestamp: g.Timestamp, - } - copy(gossip.Entries, g.Entries) + gossip := g.copy() gossip.Entries.merge(other.Entries) if gossip.Timestamp < other.Timestamp { gossip.Timestamp = other.Timestamp @@ -270,9 +266,7 @@ func (g *GossipData) Decode(msg []byte) error { } func (g *GossipData) Encode() [][]byte { - // Make a copy so we can sort: all outgoing data is sent in case-sensitive order - g2 := GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))} - copy(g2.Entries, g.Entries) + g2 := g.copy() sort.Sort(CaseSensitive(g2.Entries)) buf := &bytes.Buffer{} if err := gob.NewEncoder(buf).Encode(g2); err != nil { @@ -280,3 +274,9 @@ func (g *GossipData) Encode() [][]byte { } return [][]byte{buf.Bytes()} } + +func (g *GossipData) copy() *GossipData { + g2 := &GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))} + copy(g2.Entries, g.Entries) + return g2 +} From ddb390d5ecb2f338b773a77106924a0608944769 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Wed, 11 Nov 2015 15:43:09 +0100 Subject: [PATCH 019/135] Forbid traffic to the Weave port from other containers Make sure we have the Docker bridge IP before adding/removing iptables rules --- weave | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/weave b/weave index 3be503b8..de5197db 100755 --- a/weave +++ b/weave @@ -426,6 +426,13 @@ create_bridge() { run_iptables -t filter -I FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP fi + [ -n "$DOCKER_BRIDGE_IP" ] || docker_bridge_ip + + # forbid traffic to the Weave port from other containers + add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p tcp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP + add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP + add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $(($PORT + 1)) -j DROP + # let DNS traffic to weaveDNS, since otherwise it might get blocked by the likes of UFW add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dport 53 -j ACCEPT add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p tcp --dport 53 -j ACCEPT @@ -535,8 +542,15 @@ destroy_bridge() { run_iptables -t filter -D FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP 2>/dev/null || true fi + [ -n "$DOCKER_BRIDGE_IP" ] || docker_bridge_ip + run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dport 53 -j ACCEPT >/dev/null 2>&1 || true run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p tcp --dport 53 -j ACCEPT >/dev/null 2>&1 || true + + run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p tcp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP >/dev/null 2>&1 || true + run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $PORT -j DROP >/dev/null 2>&1 || true + run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $(($PORT + 1)) -j DROP >/dev/null 2>&1 || true + run_iptables -t filter -D FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT 2>/dev/null || true run_iptables -t nat -F WEAVE >/dev/null 2>&1 || true run_iptables -t nat -D POSTROUTING -j WEAVE >/dev/null 2>&1 || true From 7992bf41777c3af18cdcd32b66694b23e8a7c3c9 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Thu, 19 Nov 2015 14:54:23 +0100 Subject: [PATCH 020/135] DNS upstream servers report --- nameserver/status.go | 10 ++++++---- prog/weaver/http.go | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/nameserver/status.go b/nameserver/status.go index 6124f08d..2a280c57 100644 --- a/nameserver/status.go +++ b/nameserver/status.go @@ -1,10 +1,11 @@ package nameserver type Status struct { - Domain string - Address string - TTL uint32 - Entries []EntryStatus + Domain string + Upstream []string + Address string + TTL uint32 + Entries []EntryStatus } type EntryStatus struct { @@ -37,6 +38,7 @@ func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status { return &Status{ dnsServer.domain, + dnsServer.upstream.Servers, dnsServer.address, dnsServer.ttl, entryStatusSlice} diff --git a/prog/weaver/http.go b/prog/weaver/http.go index cb828f47..6794967e 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -25,6 +25,12 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } return count }, + "upstreamServers": func(servers []string) string { + if len(servers) == 0 { + return "none" + } + return strings.Join(servers, ", ") + }, "printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string { counts := make(map[string]int) for _, conn := range conns { @@ -108,6 +114,7 @@ var statusTemplate = defTemplate("status", `\ Service: dns Domain: {{.DNS.Domain}} + Upstream: {{upstreamServers .DNS.Upstream}} TTL: {{.DNS.TTL}} Entries: {{countDNSEntries .DNS.Entries}} {{end}}\ From 03ce6052cc506d90d9008ee99ae100b9a2484ce7 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Fri, 13 Nov 2015 12:20:28 +0100 Subject: [PATCH 021/135] Updated docs --- site/weavedns.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/weavedns.md b/site/weavedns.md index e3da2fcc..0cdf6a3e 100644 --- a/site/weavedns.md +++ b/site/weavedns.md @@ -260,6 +260,7 @@ DNS: Service: dns Domain: weave.local. + Upstream: 8.8.8.8, 8.8.4.4 TTL: 1 Entries: 9 @@ -272,6 +273,7 @@ guide](troubleshooting.html#weave-status) for more detail. The 'Service: dns' section is pertinent to weaveDNS, and includes: * The local domain suffix which is being served +* The list of upstream servers used for resolving names not in the local domain * The response ttl * The total number of entries From eb79711e0a1db390a66afe99b9226c1addf6be46 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Thu, 19 Nov 2015 15:05:16 +0100 Subject: [PATCH 022/135] Added flow misses count --- router/fastdp.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/router/fastdp.go b/router/fastdp.go index b4e7eeff..c1d30fa2 100644 --- a/router/fastdp.go +++ b/router/fastdp.go @@ -40,6 +40,7 @@ type FastDatapath struct { dpif *odp.Dpif dp odp.DatapathHandle deleteFlowsCount uint64 + missCount uint64 missHandlers map[odp.VportID]missHandler localPeer *mesh.Peer peers *mesh.Peers @@ -216,8 +217,10 @@ func (fastdp fastDatapathBridge) String() string { return fmt.Sprint(fastdp.dpname, " (via ODP)") } -func (fastDatapathBridge) Stats() map[string]int { - return nil +func (fastdp fastDatapathBridge) Stats() map[string]int { + return map[string]int{ + "FlowMisses": int(fastdp.missCount), + } } var routerBridgePortID = bridgePortID{router: true} @@ -937,6 +940,8 @@ func (fastdp *FastDatapath) Miss(packet []byte, fks odp.FlowKeys) error { lock := fastdp.startLock() defer lock.unlock() + fastdp.missCount++ + handler := fastdp.getMissHandler(ingress) if handler == nil { return nil From 33648b083c130d175c86fe8471bd8a75b03ff761 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Thu, 19 Nov 2015 14:10:37 +0000 Subject: [PATCH 023/135] SSH into test vms on gce as 'vagrant' user --- test/gce.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gce.sh b/test/gce.sh index b1cc7cca..ae22739a 100755 --- a/test/gce.sh +++ b/test/gce.sh @@ -140,7 +140,7 @@ function hosts { hosts="$hostname $hosts" args="--add-host=$hostname:$(internal_ip $json $name) $args" done - echo export SSH=ssh + echo export SSH=\"ssh -l vagrant\" echo export HOSTS=\"$hosts\" echo export ADD_HOST_ARGS=\"$args\" rm $json From b5a4aefe8bc044ba70a39a5a46bf90b08f807a96 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 19 Nov 2015 18:28:04 +0000 Subject: [PATCH 024/135] never broadcast empty nameserver gossip --- nameserver/nameserver.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index 0d33d184..201bda3b 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -74,13 +74,13 @@ func (n *Nameserver) Stop() { } func (n *Nameserver) broadcastEntries(es ...Entry) error { - if n.gossip != nil { - return n.gossip.GossipBroadcast(&GossipData{ - Entries: Entries(es), - Timestamp: now(), - }) + if n.gossip == nil || len(es) == 0 { + return nil } - return nil + return n.gossip.GossipBroadcast(&GossipData{ + Entries: Entries(es), + Timestamp: now(), + }) } func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName, addr address.Address) error { @@ -133,10 +133,8 @@ func (n *Nameserver) ContainerDied(ident string) { return false }) n.Unlock() - if len(entries) > 0 { - if err := n.broadcastEntries(entries...); err != nil { - n.errorf("failed to broadcast container %s death: %v", ident, err) - } + if err := n.broadcastEntries(entries...); err != nil { + n.errorf("failed to broadcast container %s death: %v", ident, err) } } From ee12b3433440ed3f816f5bbe53984713564d4a50 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 19 Nov 2015 20:57:27 +0000 Subject: [PATCH 025/135] eliminate a gossip inefficiency and dormant bug Fixes #1704 --- mesh/gossip.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesh/gossip.go b/mesh/gossip.go index 611639cb..2b3a4807 100644 --- a/mesh/gossip.go +++ b/mesh/gossip.go @@ -141,7 +141,7 @@ func (router *Router) SendAllGossip() { func (router *Router) SendAllGossipDown(conn Connection) { for channel := range router.gossipChannelSet() { if gossip := channel.gossiper.Gossip(); gossip != nil { - channel.SendDown(conn, channel.gossiper.Gossip()) + channel.SendDown(conn, gossip) } } } From 385b0aa07f93bce6f42a2982214346ae8213b531 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 20 Nov 2015 10:33:20 +0000 Subject: [PATCH 026/135] Fix typo in -tls info string --- prog/weaveproxy/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prog/weaveproxy/main.go b/prog/weaveproxy/main.go index a0a247df..b166d5b3 100644 --- a/prog/weaveproxy/main.go +++ b/prog/weaveproxy/main.go @@ -35,7 +35,7 @@ func main() { mflag.BoolVar(&c.NoRewriteHosts, []string{"-no-rewrite-hosts"}, false, "do not automatically rewrite /etc/hosts. Use if you need the docker IP to remain in /etc/hosts") mflag.StringVar(&c.TLSConfig.CACert, []string{"#tlscacert", "-tlscacert"}, "", "Trust certs signed only by this CA") mflag.StringVar(&c.TLSConfig.Cert, []string{"#tlscert", "-tlscert"}, "", "Path to TLS certificate file") - mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tls-verify") + mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tlsverify") mflag.StringVar(&c.TLSConfig.Key, []string{"#tlskey", "-tlskey"}, "", "Path to TLS key file") mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote") mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver") From bbf26fc3e8ac55e92ebe1deea9fffa7cc80dfe7f Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 18 Nov 2015 12:39:02 +0000 Subject: [PATCH 027/135] Raise minimum Docker version to 1.6 to address #1683 --- Makefile | 2 +- README.md | 2 +- build/Dockerfile | 2 +- proxy/proxy.go | 6 +++--- site/building.md | 2 +- test/README.md | 2 +- weave | 19 +++++++------------ 7 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index b13b3454..b4878f0a 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) WEAVE_EXPORT=weave.tar.gz -WEAVEEXEC_DOCKER_VERSION=1.3.1 +WEAVEEXEC_DOCKER_VERSION=1.6.2 DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \ diff --git a/README.md b/README.md index 0f024ee3..93d5fd66 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ capabilities, so these can continue to be used by containers. ## Installation Ensure you are running Linux (kernel 3.8 or later) and have Docker -(version 1.3.1 or later) installed. Then install weave with +(version 1.6.0 or later) installed. Then install weave with sudo curl -L git.io/weave -o /usr/local/bin/weave sudo chmod a+x /usr/local/bin/weave diff --git a/build/Dockerfile b/build/Dockerfile index 08df1d9d..aae48d06 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -3,7 +3,7 @@ FROM ubuntu RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list -RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.3.1 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests +RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.6.2 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests # When doing a build in a container, "apt-get update" happens twice, # which can be a very significant overhead for incremental builds. diff --git a/proxy/proxy.go b/proxy/proxy.go index 778742d4..d6fdaa13 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -81,12 +81,12 @@ func NewProxy(c Config) (*Proxy, error) { Log.Fatalf("Could not configure tls for proxy: %s", err) } - // We pin the protocol version to 1.15 (which corresponds to - // Docker 1.3.x; the earliest version supported by weave) in order + // We pin the protocol version to 1.18 (which corresponds to + // Docker 1.6.x; the earliest version supported by weave) in order // to insulate ourselves from breaking changes to the API, as // happened in 1.20 (Docker 1.8.0) when the presentation of // volumes changed in `inspect`. - client, err := weavedocker.NewVersionedClient(dockerSockUnix, "1.15") + client, err := weavedocker.NewVersionedClient(dockerSockUnix, "1.18") if err != nil { return nil, err } diff --git a/site/building.md b/site/building.md index 41b0bfe1..a94ace3b 100644 --- a/site/building.md +++ b/site/building.md @@ -11,7 +11,7 @@ You only need to build weave if you want to work on the weave codebase Apart from the `weave` shell script, weave is delivered as a set of container images. There is no distribution-specific packaging, so in principle it shouldn't matter which Linux distribution you build -under. But naturally, Docker is a prerequisite (version 1.3.1 or +under. But naturally, Docker is a prerequisite (version 1.6.0 or later). And it is difficult to build under Fedora because [Fedora does not include static libraries](http://fedoraproject.org/wiki/Packaging:Guidelines#Packaging_Static_Libraries). diff --git a/test/README.md b/test/README.md index d7358b86..804c226e 100644 --- a/test/README.md +++ b/test/README.md @@ -2,7 +2,7 @@ This directory contains integration tests for weave. ## Requirements -You need two VMs with docker >=1.3.1 installed and listening on TCP +You need two VMs with docker >=1.6.0 installed and listening on TCP port 2375 (see below). You also need to be able to ssh to these VMs, preferably without having to input anything. diff --git a/weave b/weave index 248d9df5..7e6506fe 100755 --- a/weave +++ b/weave @@ -11,19 +11,14 @@ else fi IMAGE_VERSION=${WEAVE_VERSION:-$IMAGE_VERSION} -# - Docker versions prior to 1.2.0 cannot parse `--dns-search=.` -# - Docker versions prior to 1.3.0 fail to include the domain when -# setting the hostname, so lookups for non-fq names fail. -# - Docker version 1.3.0 contains the severe bug -# https://github.com/docker/docker/issues/8632 -# - The weavexec image embeds a Docker 1.3.1 client. Misleading -# version mismatch errors are produced if the user is running with a -# client *and* server with a lower version. -# - Docker will complain if you run a client against a server older -# than that client. +# - Docker versions prior to 1.6 cannot pull images from DockerHub +# - The weavexec image embeds a Docker 1.6.2 client. Docker will give +# a "client is newer than server error" if the daemon has an older +# API version, which could be confusing if the user's Docker client +# is correctly matched with that older version. # -# We therefore check that the user's Docker *client* is >= 1.3.1 -MIN_DOCKER_VERSION=1.3.1 +# We therefore check that the user's Docker *client* is >= 1.6 +MIN_DOCKER_VERSION=1.6.0 # These are needed for remote execs, hence we introduce them here DOCKERHUB_USER=${DOCKERHUB_USER:-weaveworks} From 3bae4b0d935958a37c4810fdb8ef2f536c24313f Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 20 Nov 2015 16:23:40 +0000 Subject: [PATCH 028/135] Revert "Automatically symlink /var/run/*.sock sockets to /var/run/weave/*.sock" --- proxy/proxy.go | 8 ++--- test/650_proxy_env_test.sh | 6 ---- ...g_test.sh => 690_proxy_autoconfig_test.sh} | 7 +--- test/gce.sh | 2 +- weave | 34 ++++--------------- 5 files changed, 11 insertions(+), 46 deletions(-) rename test/{690_proxy_config_test.sh => 690_proxy_autoconfig_test.sh} (81%) diff --git a/proxy/proxy.go b/proxy/proxy.go index 63bf4fed..d6fdaa13 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -8,7 +8,6 @@ import ( "net" "net/http" "os" - "path/filepath" "regexp" "strings" "sync" @@ -284,16 +283,15 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) { } case "unix": - localAddr := filepath.Join("/host", addr) // remove socket from last invocation - if err := os.Remove(localAddr); err != nil && !os.IsNotExist(err) { + if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { return nil, "", err } - listener, err = net.Listen(proto, localAddr) + listener, err = net.Listen(proto, addr) if err != nil { return nil, "", err } - if err = copyOwnerAndPermissions(dockerSock, localAddr); err != nil { + if err = copyOwnerAndPermissions(dockerSock, addr); err != nil { return nil, "", err } diff --git a/test/650_proxy_env_test.sh b/test/650_proxy_env_test.sh index 345f661e..593f3d6d 100755 --- a/test/650_proxy_env_test.sh +++ b/test/650_proxy_env_test.sh @@ -28,10 +28,4 @@ weave_on $HOST1 stop weave_on $HOST1 launch-proxy -H tcp://0.0.0.0:12375 -H unix:///var/run/weave/weave.sock check -# Check we can use weave env/config with unix -Hs specified -weave_on $HOST1 stop -weave_on $HOST1 launch-proxy -H unix:///var/run/weave/weave.sock -assert_raises "run_on $HOST1 'eval \$(weave env) ; docker $CMD'" -assert_raises "run_on $HOST1 'docker \$(weave config) $CMD'" - end_suite diff --git a/test/690_proxy_config_test.sh b/test/690_proxy_autoconfig_test.sh similarity index 81% rename from test/690_proxy_config_test.sh rename to test/690_proxy_autoconfig_test.sh index 6293a3f5..ce6e18b2 100755 --- a/test/690_proxy_config_test.sh +++ b/test/690_proxy_autoconfig_test.sh @@ -2,7 +2,7 @@ . ./config.sh -start_suite "Various launch-proxy configurations" +start_suite "Boot the proxy should only listen on client's interface" # Booting it over unix socket listens on unix socket run_on $HOST1 COVERAGE=$COVERAGE sudo -E weave launch-proxy @@ -22,11 +22,6 @@ assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" assert_raises "proxy docker_on $HOST1 ps" weave_on $HOST1 stop-proxy -# Booting it with -H outside /var/run/weave, still works -socket="$(mktemp -d)/weave.sock" -weave_on $HOST1 launch-proxy -H unix://$socket -assert_raises "run_on $HOST1 sudo docker -H unix:///$socket ps" 0 -weave_on $HOST1 stop-proxy # Booting it over tls errors assert_raises "DOCKER_CLIENT_ARGS='--tls' weave_on $HOST1 launch-proxy" 1 diff --git a/test/gce.sh b/test/gce.sh index ae22739a..b1cc7cca 100755 --- a/test/gce.sh +++ b/test/gce.sh @@ -140,7 +140,7 @@ function hosts { hosts="$hostname $hosts" args="--add-host=$hostname:$(internal_ip $json $name) $args" done - echo export SSH=\"ssh -l vagrant\" + echo export SSH=ssh echo export HOSTS=\"$hosts\" echo export ADD_HOST_ARGS=\"$args\" rm $json diff --git a/weave b/weave index 987c9c2e..460936d6 100755 --- a/weave +++ b/weave @@ -96,7 +96,6 @@ usage() { exec_remote() { docker $DOCKER_CLIENT_ARGS run --rm --privileged --net=host \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v /var/run:/host/var/run \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e DOCKERHUB_USER="$DOCKERHUB_USER" \ @@ -1262,36 +1261,17 @@ tls_arg() { PROXY_ARGS="$PROXY_ARGS $1 /home/weave/tls/$3.pem" } -# TODO: Handle relative paths for args -# TODO: Handle args with spaces -host_arg() { - PROXY_HOST="$1" - if [ "$PROXY_HOST" != "${PROXY_HOST#unix://}" ]; then - host=$(dirname ${PROXY_HOST#unix://}) - if [ "$host" = "${host#/}" ]; then - echo "When launching the proxy, unix sockets must be specified as an absolute path." >&2 - exit 1 - fi - if [ "$host" = "/var/run" ]; then - host=/var/run/weave - PROXY_VAR_RUN_SYMLINKS="$PROXY_VAR_RUN_SYMLINKS $(basename ${PROXY_HOST#unix://})" - fi - PROXY_VOLUMES="$PROXY_VOLUMES -v $host:/host$host" - PROXY_ARGS="$PROXY_ARGS -H unix://$host/$(basename ${PROXY_HOST#unix://})" - else - PROXY_ARGS="$PROXY_ARGS -H $1" - fi -} - proxy_parse_args() { while [ $# -gt 0 ]; do case "$1" in -H) - host_arg "$2" + PROXY_HOST="$2" + PROXY_ARGS="$PROXY_ARGS $1 $2" shift ;; -H=*) - host_arg "${1#*=}" + PROXY_HOST="${1#*=}" + PROXY_ARGS="$PROXY_ARGS $1" ;; -no-detect-tls|--no-detect-tls) PROXY_TLS_DETECTION_DISABLED=1 @@ -1352,7 +1332,7 @@ proxy_args() { PROXY_HOST="tcp://0.0.0.0:12375" ;; esac - host_arg "$PROXY_HOST" + PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST" fi } @@ -1557,6 +1537,7 @@ launch_proxy() { PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \ $PROXY_VOLUMES \ -v /var/run/docker.sock:/var/run/docker.sock \ + -v /var/run/weave:/var/run/weave \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e WEAVE_CIDR=none \ @@ -1566,9 +1547,6 @@ launch_proxy() { --entrypoint=/home/weave/weaveproxy \ $WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $COVERAGE_ARGS $PROXY_ARGS) wait_for_status $PROXY_CONTAINER_NAME http_call_unix $PROXY_CONTAINER_NAME status.sock - for sock in $PROXY_VAR_RUN_SYMLINKS; do - ln -sf -T "/var/run/weave/$sock" "/host/var/run/$sock" - done } stop_proxy() { From 0f9b11774420faeaeafacfcc8cd6bdafc512c57b Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 20 Nov 2015 16:54:41 +0000 Subject: [PATCH 029/135] Connect to gce boxes as vagrant so we can access docker unix socket --- test/gce.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gce.sh b/test/gce.sh index b1cc7cca..ae22739a 100755 --- a/test/gce.sh +++ b/test/gce.sh @@ -140,7 +140,7 @@ function hosts { hosts="$hostname $hosts" args="--add-host=$hostname:$(internal_ip $json $name) $args" done - echo export SSH=ssh + echo export SSH=\"ssh -l vagrant\" echo export HOSTS=\"$hosts\" echo export ADD_HOST_ARGS=\"$args\" rm $json From 82cf4358ce7c1043142648a99f4c231ca6f878e0 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 23 Nov 2015 12:06:10 +0000 Subject: [PATCH 030/135] Add release v1.3.1 --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1136390a..993cf065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## Release 1.3.1 + +**Highlights** + +* The minimum Docker version has been increased to 1.6 due to the + [upcoming deprecation of Dockerhub access](https://blog.docker.com/2015/10/docker-hub-deprecation-1-5/) + for old clients. From December 7th onwards previous versions of the + `weave` script will fail to pull down images from the hub; if you + are unable to upgrade to 1.3.1 immediately you can work around this + by running `weave --local setup` in conjunction with a compatible + Docker client installation +* Docker networking plugin now works with older kernels and allows you + to configure the MTU + +More details in the +[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.1). + ## Release 1.3.0 **Highlights** From 89f27ec99a0ac0e6e7545f5da1bed8cd6ea81efd Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 24 Nov 2015 12:59:01 +0000 Subject: [PATCH 031/135] Set default port number on address before checking in InitiateConnections, to fix #1714 --- mesh/connection_maker.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mesh/connection_maker.go b/mesh/connection_maker.go index 78edaf58..d38f94c4 100644 --- a/mesh/connection_maker.go +++ b/mesh/connection_maker.go @@ -81,7 +81,7 @@ func (cm *ConnectionMaker) InitiateConnections(peers []string, replace bool) []e for peer, addr := range addrs { cm.directPeers[peer] = addr // curtail any existing reconnect interval - if target, found := cm.targets[addr.String()]; found { + if target, found := cm.targets[cm.completeAddr(*addr)]; found { target.nextTryNow() } } @@ -159,6 +159,13 @@ func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) { } } +func (cm *ConnectionMaker) completeAddr(addr net.TCPAddr) string { + if addr.Port == 0 { + addr.Port = cm.port + } + return addr.String() +} + func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration { var ( validTarget = make(map[string]struct{}) @@ -181,18 +188,16 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration { // Add direct targets that are not connected for _, addr := range cm.directPeers { - completeAddr := *addr attempt := true - if completeAddr.Port == 0 { - completeAddr.Port = cm.port + if addr.Port == 0 { // If a peer was specified w/o a port, then we do not // attempt to connect to it if we have any inbound // connections from that IP. - if _, connected := ourInboundIPs[completeAddr.IP.String()]; connected { + if _, connected := ourInboundIPs[addr.IP.String()]; connected { attempt = false } } - address := completeAddr.String() + address := cm.completeAddr(*addr) directTarget[address] = void if attempt { addTarget(address) From 3b4396077928b83c55b408229b76bb2c5c4ce559 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 25 Nov 2015 10:51:18 +0000 Subject: [PATCH 032/135] prevent unbounded number of goroutines for connection shutdown We need Connection.Shutdown to be non-blocked, and the way we achieved this is by spawning a goroutine. However, there can be a potentially unbounded number of Connection.Shutdown invocations. For example, if the TCP connection has gone bad then any Connection.SendProtocolMsg invocation will end up invoking Connection.Shutdown. There can be many such invocations until the Connection has been removed from the various data structures (e.g. LocalPeer.connections and GossipSender.senders). Hence we can end up with with an unbounded number of spawned goroutines. To fix this, we introduce a one-element error channel on the connection. Connection.Shutdown does a non-blocking send on that, and the Connection.actorLoop reads from it and terminates. This will throw away all but the first error encountered, but that is fine and not much different from what was happening before (where we'd throw away all but one of the errors encountered). --- mesh/connection.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/mesh/connection.go b/mesh/connection.go index b7b5d270..fdcdcf3d 100644 --- a/mesh/connection.go +++ b/mesh/connection.go @@ -48,6 +48,7 @@ type LocalConnection struct { Router *Router uid uint64 actionChan chan<- ConnectionAction + errorChan chan<- error finished <-chan struct{} // closed to signal that actorLoop has finished OverlayConn OverlayConnection } @@ -87,6 +88,7 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ro log.Fatal("Attempt to create local connection from a peer which is not ourself") } actionChan := make(chan ConnectionAction, ChannelSize) + errorChan := make(chan error, 1) finished := make(chan struct{}) conn := &LocalConnection{ RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here. @@ -94,8 +96,9 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ro TCPConn: tcpConn, uid: randUint64(), actionChan: actionChan, + errorChan: errorChan, finished: finished} - go conn.run(actionChan, finished, acceptNewPeer) + go conn.run(actionChan, errorChan, finished, acceptNewPeer) } func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak { @@ -135,8 +138,10 @@ func (conn *LocalConnection) Shutdown(err error) { panic("nil error") } - // Run on its own goroutine in case the channel is backed up - go func() { conn.sendAction(func() error { return err }) }() + select { + case conn.errorChan <- err: + default: + } } // Send an actor request to the actorLoop, but don't block if @@ -151,7 +156,7 @@ func (conn *LocalConnection) sendAction(action ConnectionAction) { // ACTOR server -func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished chan<- struct{}, acceptNewPeer bool) { +func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, errorChan <-chan error, finished chan<- struct{}, acceptNewPeer bool) { var err error // important to use this var and not create another one with 'err :=' defer func() { conn.shutdown(err) }() defer close(finished) @@ -237,7 +242,7 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch // running AddConnection in a separate goroutine, at least not // without some synchronisation. Which in turn requires the // launching of the receiveTCP goroutine to precede actorLoop. - err = conn.actorLoop(actionChan) + err = conn.actorLoop(actionChan, errorChan) } func (conn *LocalConnection) makeFeatures() map[string]string { @@ -329,7 +334,7 @@ func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) er return nil } -func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) { +func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction, errorChan <-chan error) (err error) { fwdErrorChan := conn.OverlayConn.ErrorChannel() fwdEstablishedChan := conn.OverlayConn.EstablishedChannel() @@ -338,6 +343,8 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err case action := <-actionChan: err = action() + case err = <-errorChan: + case <-conn.heartbeatTCP.C: err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat) From ea5bae711b3c76d7fbfa3e02ae0ba14dae0ff1c0 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 25 Nov 2015 13:56:09 +0000 Subject: [PATCH 033/135] Ensure /var/run/weave directory exists before telling Docker to use it --- weave | 1 + 1 file changed, 1 insertion(+) diff --git a/weave b/weave index 460936d6..662f0795 100755 --- a/weave +++ b/weave @@ -1534,6 +1534,7 @@ launch_proxy() { # when launching the weaveproxy container. docker_client_args $DOCKER_CLIENT_ARGS proxy_args "$@" + mkdir -p /var/run/weave PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \ $PROXY_VOLUMES \ -v /var/run/docker.sock:/var/run/docker.sock \ From 61e3bf4a204dd8ba8bfb6e7237da0aea084e00d5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 25 Nov 2015 14:32:34 +0000 Subject: [PATCH 034/135] cosmetic(ish) --- mesh/gossip_test.go | 6 +++--- mesh/router.go | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index 5f622826..21d2b858 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -238,10 +238,10 @@ func (g *testGossiper) OnGossip(update []byte) (GossipData, error) { g.state[v] = void } } - if len(delta) > 0 { - return NewSurrogateGossipData(delta), nil + if len(delta) == 0 { + return nil, nil } - return nil, nil + return NewSurrogateGossipData(delta), nil } func (g *testGossiper) checkHas(t *testing.T, vs ...byte) { diff --git a/mesh/router.go b/mesh/router.go index c19556d7..69b26831 100644 --- a/mesh/router.go +++ b/mesh/router.go @@ -122,15 +122,6 @@ type TopologyGossipData struct { update PeerNameSet } -func (router *Router) BroadcastTopologyUpdate(update []*Peer) { - names := make(PeerNameSet) - for _, p := range update { - names[p.Name] = void - } - router.TopologyGossip.GossipBroadcast( - &TopologyGossipData{peers: router.Peers, update: names}) -} - func (d *TopologyGossipData) Merge(other GossipData) GossipData { names := make(PeerNameSet) for name := range d.update { @@ -146,6 +137,15 @@ func (d *TopologyGossipData) Encode() [][]byte { return [][]byte{d.peers.EncodePeers(d.update)} } +func (router *Router) BroadcastTopologyUpdate(update []*Peer) { + names := make(PeerNameSet) + for _, p := range update { + names[p.Name] = void + } + router.TopologyGossip.GossipBroadcast( + &TopologyGossipData{peers: router.Peers, update: names}) +} + func (router *Router) OnGossipUnicast(sender PeerName, msg []byte) error { return fmt.Errorf("unexpected topology gossip unicast: %v", msg) } From 2058ce3f3ad5c33fd9551ceb2dff7b50ecf21546 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 25 Nov 2015 14:46:41 +0000 Subject: [PATCH 035/135] refactor: change Routes.RandomNeighbours return for consistency PeerNameSet -> []PeerName, which is what the other functions return. This is also easier to deal with at the call site. --- mesh/gossip_channel.go | 16 +++++++--------- mesh/routes.go | 12 ++++++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/mesh/gossip_channel.go b/mesh/gossip_channel.go index f641aa92..38555eb0 100644 --- a/mesh/gossip_channel.go +++ b/mesh/gossip_channel.go @@ -167,20 +167,18 @@ func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) { func (c *GossipChannel) Send(srcName PeerName, data GossipData) { // do this outside the lock below so we avoid lock nesting c.routes.EnsureRecalculated() - selectedConnections := make(ConnectionSet) - for name := range c.routes.RandomNeighbours(srcName) { - if conn, found := c.ourself.ConnectionTo(name); found { - selectedConnections[conn] = void - } + destinations := c.routes.RandomNeighbours(srcName) + if len(destinations) == 0 { + return } - c.sendDown(selectedConnections, data) + c.sendDown(c.ourself.ConnectionsTo(destinations), data) } func (c *GossipChannel) SendDown(conn Connection, data GossipData) { - c.sendDown(ConnectionSet{conn: void}, data) + c.sendDown([]Connection{conn}, data) } -func (c *GossipChannel) sendDown(selectedConnections ConnectionSet, data GossipData) { +func (c *GossipChannel) sendDown(selectedConnections []Connection, data GossipData) { if len(selectedConnections) == 0 { return } @@ -207,7 +205,7 @@ func (c *GossipChannel) sendDown(selectedConnections ConnectionSet, data GossipD } } // start senders, if necessary, and send. - for conn := range selectedConnections { + for _, conn := range selectedConnections { sender, found := c.senders[conn] if !found { sender = c.makeSender(conn) diff --git a/mesh/routes.go b/mesh/routes.go index d7787af9..675f1550 100644 --- a/mesh/routes.go +++ b/mesh/routes.go @@ -92,20 +92,24 @@ func (routes *Routes) BroadcastAll(name PeerName) []PeerName { // returns a higher proportion of neighbours than elsewhere. In // extremis, on peers with fewer than log2(n_peers) neighbours, all // neighbours are returned. -func (routes *Routes) RandomNeighbours(except PeerName) PeerNameSet { - res := make(PeerNameSet) +func (routes *Routes) RandomNeighbours(except PeerName) []PeerName { + destinations := make(PeerNameSet) routes.RLock() defer routes.RUnlock() count := int(math.Log2(float64(len(routes.unicastAll)))) // depends on go's random map iteration for _, dst := range routes.unicastAll { if dst != UnknownPeerName && dst != except { - res[dst] = void - if len(res) >= count { + destinations[dst] = void + if len(destinations) >= count { break } } } + res := make([]PeerName, 0, len(destinations)) + for dst := range destinations { + res = append(res, dst) + } return res } From a31e7118b4999e48a9ac39db09e04cd4c885ba14 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 18 Nov 2015 17:13:08 +0000 Subject: [PATCH 036/135] Optional intermediary bridge netdev --- weave | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/weave b/weave index 460936d6..a9994a05 100755 --- a/weave +++ b/weave @@ -293,6 +293,9 @@ PROCFS=${PROCFS:-/proc} DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0} CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave} BRIDGE=weave +FASTDP_BRIDGE=fdp-bridge +BRIDGE_IFNAME=${BRIDGE}-link +FASTDP_BRIDGE_IFNAME=${FASTDP_BRIDGE}-link CONTAINER_IFNAME=ethwe # ROUTER_HOSTNETNS_IFNAME is only used for fastdp with encryption ROUTER_HOSTNETNS_IFNAME=veth-weave @@ -404,7 +407,13 @@ create_bridge() { if [ -n "$WEAVE_NO_FASTDP" ] ; then BRIDGE_TYPE=bridge elif docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --create-datapath --datapath=$BRIDGE ; then - BRIDGE_TYPE=fastdp + # This is not meant to be exercised by users; to be + # replaced by autodetection as part of #1681. + if [ -n "$WEAVE_NO_BRIDGED_FASTDP" ]; then + BRIDGE_TYPE=fastdp + else + BRIDGE_TYPE=bridged_fastdp + fi elif [ $? = 17 ] ; then # Exit status of 17 means the kernel doesn't have ODP BRIDGE_TYPE=bridge @@ -449,7 +458,11 @@ EOF return 1 fi else - BRIDGE_TYPE=fastdp + if [ -d /sys/class/net/$FASTDP_BRIDGE ] ; then + BRIDGE_TYPE=bridged_fastdp + else + BRIDGE_TYPE=fastdp + fi if [ -n "$WEAVE_NO_FASTDP" ] ; then cat <&1 @@ -490,6 +503,39 @@ init_bridge_fastdp() { ip link set dev $BRIDGE mtu $MTU } +init_bridge_bridged_fastdp() { + # Initialise the datapath as normal. NB sets MTU for use below + init_bridge_fastdp + + # Create linking veth pair + ip link del $FASTDP_BRIDGE_IFNAME >/dev/null 2>&1 || true + ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true + ip link add name $FASTDP_BRIDGE_IFNAME mtu $MTU type veth peer name $BRIDGE_IFNAME mtu $MTU || return 1 + + # Create intermediary bridge netdev + ip link add name $FASTDP_BRIDGE type bridge + + # Set random MAC + ip link set dev $FASTDP_BRIDGE address $(random_mac) + + # Set MTU + ip link add name v${CONTAINER_IFNAME}du mtu $MTU type dummy + ip link set dev v${CONTAINER_IFNAME}du master $FASTDP_BRIDGE + ip link del dev v${CONTAINER_IFNAME}du + + # Link intermediary bridge and datapath + if ! ip link set $FASTDP_BRIDGE_IFNAME up || + ! ip link set $BRIDGE_IFNAME up || + ! add_iface_fastdp $BRIDGE_IFNAME || ! ip link set $FASTDP_BRIDGE_IFNAME master $FASTDP_BRIDGE ; then + ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true + ip link del $FASTDP_BRIDGE >/dev/null 2>&1 || true + docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$BRIDGE >/dev/null 2>&1 || true + return 1 + fi + + ip link set dev $FASTDP_BRIDGE up +} + init_bridge_bridge() { MTU=${WEAVE_MTU:-65535} @@ -513,6 +559,10 @@ ethtool_tx_off_fastdp() { true } +ethtool_tx_off_bridged_fastdp() { + true +} + ethtool_tx_off_bridge() { ethtool -K $1 tx off >/dev/null } @@ -526,6 +576,13 @@ destroy_bridge() { fi fi + if [ -d /sys/class/net/$FASTDP_BRIDGE ] ; then + ip link del dev $FASTDP_BRIDGE + fi + + ip link del $FASTDP_BRIDGE_IFNAME >/dev/null 2>&1 || true + ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true + if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then run_iptables -t filter -D FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP 2>/dev/null || true fi @@ -632,6 +689,10 @@ add_iface_fastdp() { docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --datapath=$BRIDGE --add-datapath-iface=$1 } +add_iface_bridged_fastdp() { + ip link set $1 master $FASTDP_BRIDGE +} + add_iface_bridge() { ip link set $1 master $BRIDGE } @@ -669,6 +730,10 @@ router_opts_fastdp() { fi } +router_opts_bridged_fastdp() { + router_opts_fastdp +} + router_opts_bridge() { echo "--iface $CONTAINER_IFNAME" } @@ -689,6 +754,10 @@ setup_router_iface_fastdp() { fi } +setup_router_iface_bridged_fastdp() { + setup_router_iface_fastdp +} + setup_router_iface_bridge() { if ! netnsenter ip link show eth0 >/dev/null ; then echo "Perhaps you are running the docker daemon with container networking disabled (-b=none)." >&2 @@ -1466,7 +1535,7 @@ launch_router() { fi fi - if [ "$BRIDGE_TYPE" = fastdp ] ; then + if [ "$BRIDGE_TYPE" = fastdp -o "$BRIDGE_TYPE" = bridged_fastdp ] ; then NETHOST_OPT="--net=host" HTTP_IP=127.0.0.1 # In case there is a lingering veth-weave netdev From 00c9164fdb85f74404de248fe13b788f4e54b0c0 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 24 Nov 2015 09:53:56 +0000 Subject: [PATCH 037/135] Pass `WEAVE_NO_BRIDGED_FASTDP` to remote for testing --- weave | 1 + 1 file changed, 1 insertion(+) diff --git a/weave b/weave index a9994a05..d3884cf3 100755 --- a/weave +++ b/weave @@ -108,6 +108,7 @@ exec_remote() { -e WEAVE_CONTAINER_NAME \ -e WEAVE_MTU \ -e WEAVE_NO_FASTDP \ + -e WEAVE_NO_BRIDGED_FASTDP \ -e DOCKER_BRIDGE \ -e DOCKER_CLIENT_HOST="$DOCKER_CLIENT_HOST" \ -e DOCKER_CLIENT_TLS_VERIFY="$DOCKER_CLIENT_TLS_VERIFY" \ From 9830c146d051bd7d0ff41a4607aac9ab63b0c5ea Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 25 Nov 2015 12:42:57 +0000 Subject: [PATCH 038/135] The bridge netdev is always named `weave` --- weave | 228 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 125 insertions(+), 103 deletions(-) diff --git a/weave b/weave index d3884cf3..1f96ffb0 100755 --- a/weave +++ b/weave @@ -294,9 +294,10 @@ PROCFS=${PROCFS:-/proc} DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0} CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave} BRIDGE=weave -FASTDP_BRIDGE=fdp-bridge -BRIDGE_IFNAME=${BRIDGE}-link -FASTDP_BRIDGE_IFNAME=${FASTDP_BRIDGE}-link +# This value is overridden when the datapath is used unbridged +DATAPATH=datapath +BRIDGE_IFNAME=link-${BRIDGE} +DATAPATH_IFNAME=${DATAPATH}-link CONTAINER_IFNAME=ethwe # ROUTER_HOSTNETNS_IFNAME is only used for fastdp with encryption ROUTER_HOSTNETNS_IFNAME=veth-weave @@ -401,25 +402,55 @@ random_mac() { # weave and docker specific helpers ###################################################################### -create_bridge() { +# Detect the current bridge/datapath state. When invoked, the values of +# $BRIDGE and $DATAPATH are expected to be distinct. $BRIDGE_TYPE and +# $DATAPATH are set correctly on success; failure indicates that the +# bridge/datapath devices have yet to be configured. If netdevs do exist +# but are in an inconsistent state the script aborts with an error. +detect_bridge_type() { BRIDGE_TYPE= - - if [ ! -d /sys/class/net/$BRIDGE ] ; then - if [ -n "$WEAVE_NO_FASTDP" ] ; then - BRIDGE_TYPE=bridge - elif docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --create-datapath --datapath=$BRIDGE ; then - # This is not meant to be exercised by users; to be - # replaced by autodetection as part of #1681. - if [ -n "$WEAVE_NO_BRIDGED_FASTDP" ]; then - BRIDGE_TYPE=fastdp - else - BRIDGE_TYPE=bridged_fastdp - fi - elif [ $? = 17 ] ; then - # Exit status of 17 means the kernel doesn't have ODP + if [ -d /sys/class/net/$DATAPATH ] ; then + # Unfortunately there's no simple way to positively check whether + # $DATAPATH is an ODP netdev so we have to make sure it isn't + # a bridge instead (and that $BRIDGE is). + if [ ! -d /sys/class/net/$DATAPATH/bridge -a -d /sys/class/net/$BRIDGE/bridge ] ; then + BRIDGE_TYPE=bridged_fastdp + else + echo "Inconsistent bridge state detected. Please do 'weave reset' and try again" >&2 + exit 1 + fi + elif [ -d /sys/class/net/$BRIDGE ] ; then + if [ -d /sys/class/net/$BRIDGE/bridge ] ; then BRIDGE_TYPE=bridge else - return 1 + BRIDGE_TYPE=fastdp + # The datapath is the bridge when there is no intermediary + DATAPATH="$BRIDGE" + fi + else + # No bridge/datapath devices configured + return 1 + fi +} + +create_bridge() { + if ! detect_bridge_type ; then + BRIDGE_TYPE=bridge + if [ -z "$WEAVE_NO_FASTDP" ] ; then + BRIDGE_TYPE=bridged_fastdp + if [ -n "$WEAVE_NO_BRIDGED_FASTDP" ] ; then + BRIDGE_TYPE=fastdp + # The datapath is the bridge when there is no intermediary + DATAPATH="$BRIDGE" + fi + if docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --create-datapath --datapath=$DATAPATH ; then + : # ODP datapath created successfully + elif [ $? = 17 ] ; then + # Exit status of 17 means the kernel doesn't have ODP + BRIDGE_TYPE=bridge + else + return 1 + fi fi init_bridge_$BRIDGE_TYPE @@ -443,14 +474,8 @@ create_bridge() { run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true add_iptables_rule nat POSTROUTING -j WEAVE else - # Detect whether fast datapath is in use on - # $BRIDGE. Unfortunately there's no simple way to positively - # check whether $BRIDGE is a ODP netdev, so we have to check - # whether it is a bridge instead. - if [ -d /sys/class/net/$BRIDGE/bridge ] ; then - BRIDGE_TYPE=bridge - - if [ -n "$LAUNCHING_ROUTER" -a -z "$WEAVE_NO_FASTDP" ] ; then + if [ -n "$LAUNCHING_ROUTER" ] ; then + if [ "$BRIDGE_TYPE" = bridge -a -z "$WEAVE_NO_FASTDP" ] ; then cat <&1 WEAVE_NO_FASTDP is not set, but there is already a bridge present of the wrong type for fast datapath. Please do 'weave reset' to remove @@ -458,14 +483,7 @@ the bridge first. EOF return 1 fi - else - if [ -d /sys/class/net/$FASTDP_BRIDGE ] ; then - BRIDGE_TYPE=bridged_fastdp - else - BRIDGE_TYPE=fastdp - fi - - if [ -n "$WEAVE_NO_FASTDP" ] ; then + if [ "$BRIDGE_TYPE" != bridge -a -n "$WEAVE_NO_FASTDP" ] ; then cat <&1 WEAVE_NO_FASTDP is set, but there is already a weave fast datapath bridge present. Please do 'weave reset' to remove the bridge first. @@ -501,44 +519,14 @@ init_bridge_fastdp() { MTU=${WEAVE_MTU:-1410} # create_bridge already created the datapath netdev - ip link set dev $BRIDGE mtu $MTU -} - -init_bridge_bridged_fastdp() { - # Initialise the datapath as normal. NB sets MTU for use below - init_bridge_fastdp - - # Create linking veth pair - ip link del $FASTDP_BRIDGE_IFNAME >/dev/null 2>&1 || true - ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true - ip link add name $FASTDP_BRIDGE_IFNAME mtu $MTU type veth peer name $BRIDGE_IFNAME mtu $MTU || return 1 - - # Create intermediary bridge netdev - ip link add name $FASTDP_BRIDGE type bridge - - # Set random MAC - ip link set dev $FASTDP_BRIDGE address $(random_mac) - - # Set MTU - ip link add name v${CONTAINER_IFNAME}du mtu $MTU type dummy - ip link set dev v${CONTAINER_IFNAME}du master $FASTDP_BRIDGE - ip link del dev v${CONTAINER_IFNAME}du - - # Link intermediary bridge and datapath - if ! ip link set $FASTDP_BRIDGE_IFNAME up || - ! ip link set $BRIDGE_IFNAME up || - ! add_iface_fastdp $BRIDGE_IFNAME || ! ip link set $FASTDP_BRIDGE_IFNAME master $FASTDP_BRIDGE ; then - ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true - ip link del $FASTDP_BRIDGE >/dev/null 2>&1 || true - docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$BRIDGE >/dev/null 2>&1 || true - return 1 - fi - - ip link set dev $FASTDP_BRIDGE up + ip link set dev $DATAPATH mtu $MTU } init_bridge_bridge() { - MTU=${WEAVE_MTU:-65535} + # Observe any MTU that is already set + if [ -z "$MTU" ] ; then + MTU=${WEAVE_MTU:-65535} + fi ip link add name $BRIDGE type bridge @@ -556,11 +544,37 @@ init_bridge_bridge() { ip link del dev v${CONTAINER_IFNAME}du } -ethtool_tx_off_fastdp() { - true +init_bridge_bridged_fastdp() { + # Initialise the datapath as normal. NB sets MTU for use below + init_bridge_fastdp + + # Create linking veth pair. We do this before initialising the bridge + # so that `ip link show` displays the datapath, linking veths and + # the bridge in natural order + ip link del $DATAPATH_IFNAME >/dev/null 2>&1 || true + ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true + ip link add name $BRIDGE_IFNAME mtu $MTU type veth peer name $DATAPATH_IFNAME mtu $MTU || return 1 + + # Initialise the bridge using fast datapath MTU + init_bridge_bridge + + # Link intermediary bridge and datapath + if ! ip link set $DATAPATH_IFNAME up || + ! ip link set $BRIDGE_IFNAME up || + ! add_iface_fastdp $DATAPATH_IFNAME || ! ip link set $BRIDGE_IFNAME master $BRIDGE ; then + # Failed to link bridge and datapath - clean up + ip link del $BRIDGE >/dev/null 2>&1 || true + ip link del $DATAPATH_IFNAME >/dev/null 2>&1 || true + ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true + docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$DATAPATH >/dev/null 2>&1 || true + return 1 + fi + + # Finally, bring the datapath up + ip link set dev $DATAPATH up } -ethtool_tx_off_bridged_fastdp() { +ethtool_tx_off_fastdp() { true } @@ -568,20 +582,27 @@ ethtool_tx_off_bridge() { ethtool -K $1 tx off >/dev/null } +ethtool_tx_off_bridged_fastdp() { + true +} + destroy_bridge() { - if [ -d /sys/class/net/$BRIDGE ] ; then - if [ -d /sys/class/net/$BRIDGE/bridge ] ; then - ip link del dev $BRIDGE - else - docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$BRIDGE + # It's important that detect_bridge_type has not been called so + # we have distinct values for $BRIDGE and $DATAPATH. Make best efforts + # to remove netdevs of any type with those names so `weave reset` can + # recover from inconsistent states. + for NETDEV in $BRIDGE $DATAPATH ; do + if [ -d /sys/class/net/$NETDEV ] ; then + if [ -d /sys/class/net/$NETDEV/bridge ] ; then + ip link del $NETDEV + else + docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --delete-datapath --datapath=$NETDEV + fi fi - fi + done - if [ -d /sys/class/net/$FASTDP_BRIDGE ] ; then - ip link del dev $FASTDP_BRIDGE - fi - - ip link del $FASTDP_BRIDGE_IFNAME >/dev/null 2>&1 || true + # Remove any lingering bridged fastdp veth + ip link del $DATAPATH_IFNAME >/dev/null 2>&1 || true ip link del $BRIDGE_IFNAME >/dev/null 2>&1 || true if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then @@ -687,17 +708,17 @@ connect_container_to_bridge() { } add_iface_fastdp() { - docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --datapath=$BRIDGE --add-datapath-iface=$1 -} - -add_iface_bridged_fastdp() { - ip link set $1 master $FASTDP_BRIDGE + docker run --rm --privileged --net=host $IMAGE $COVERAGE_ARGS --datapath=$DATAPATH --add-datapath-iface=$1 } add_iface_bridge() { ip link set $1 master $BRIDGE } +add_iface_bridged_fastdp() { + add_iface_bridge "$@" +} + ask_version() { if ! DOCKERIMAGE=$(docker inspect --format='{{.Image}}' $1 2>/dev/null) ; then if ! DOCKERIMAGE=$(docker inspect --format='{{.Id}}' $2 2>/dev/null) ; then @@ -709,7 +730,7 @@ ask_version() { router_opts_fastdp() { if [ -z "$WEAVE_PASSWORD" ] ; then - echo "--datapath $BRIDGE" + echo "--datapath $DATAPATH" else # When using encryption, we still do bridging on the ODP # datapath, because you can 'weave launch' without encryption @@ -727,18 +748,18 @@ router_opts_fastdp() { # Having a netdev in the host netns called "ethwe" might # surprise people, so it is called $ROUTER_HOSTNETNS_IFNAME # instead. - echo "--datapath $BRIDGE --iface $ROUTER_HOSTNETNS_IFNAME" + echo "--datapath $DATAPATH --iface $ROUTER_HOSTNETNS_IFNAME" fi } -router_opts_bridged_fastdp() { - router_opts_fastdp -} - router_opts_bridge() { echo "--iface $CONTAINER_IFNAME" } +router_opts_bridged_fastdp() { + router_opts_fastdp "$@" +} + ###################################################################### # functions invoked through with_container_netns ###################################################################### @@ -755,10 +776,6 @@ setup_router_iface_fastdp() { fi } -setup_router_iface_bridged_fastdp() { - setup_router_iface_fastdp -} - setup_router_iface_bridge() { if ! netnsenter ip link show eth0 >/dev/null ; then echo "Perhaps you are running the docker daemon with container networking disabled (-b=none)." >&2 @@ -773,6 +790,10 @@ setup_router_iface_bridge() { netnsenter ip link set $CONTAINER_IFNAME up } +setup_router_iface_bridged_fastdp() { + setup_router_iface_fastdp "$@" +} + attach() { if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "/proc/self/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink /proc/self/ns/net)" ] ; then echo "Container is running in the host network namespace, and therefore cannot be" >&2 @@ -1536,7 +1557,7 @@ launch_router() { fi fi - if [ "$BRIDGE_TYPE" = fastdp -o "$BRIDGE_TYPE" = bridged_fastdp ] ; then + if [ "$BRIDGE_TYPE" != bridge ] ; then NETHOST_OPT="--net=host" HTTP_IP=127.0.0.1 # In case there is a lingering veth-weave netdev @@ -1593,9 +1614,10 @@ attach_router() { stop_router() { stop $CONTAINER_NAME "Weave" conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true - # remove the veth-weave netdev in a fastdp context - [ -d /sys/class/net/$BRIDGE -a ! -d /sys/class/net/$BRIDGE/bridge ] \ - && ip link del $ROUTER_HOSTNETNS_IFNAME >/dev/null 2>&1 || true + # Remove the veth-weave netdev in a fastdp context + if detect_bridge_type && [ "$BRIDGE_TYPE" != bridge ] ; then + ip link del $ROUTER_HOSTNETNS_IFNAME >/dev/null 2>&1 || true + fi } launch_proxy() { From c8aa0320517d685106c429d578f1ae7742cb8edb Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 25 Nov 2015 14:51:57 +0000 Subject: [PATCH 039/135] Eliminate function name stutter --- weave | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/weave b/weave index 1f96ffb0..3294fad0 100755 --- a/weave +++ b/weave @@ -453,7 +453,7 @@ create_bridge() { fi fi - init_bridge_$BRIDGE_TYPE + init_$BRIDGE_TYPE # Drop traffic from Docker bridge to Weave; it can break # subnet isolation @@ -510,7 +510,7 @@ EOF configure_arp_cache $BRIDGE } -init_bridge_fastdp() { +init_fastdp() { # GCE has the lowest underlay network MTU we're likely to encounter on # a local network, at 1460 bytes. To get the overlay MTU from that we # subtract 20 bytes for the outer IPv4 header, 8 bytes for the outer @@ -522,7 +522,7 @@ init_bridge_fastdp() { ip link set dev $DATAPATH mtu $MTU } -init_bridge_bridge() { +init_bridge() { # Observe any MTU that is already set if [ -z "$MTU" ] ; then MTU=${WEAVE_MTU:-65535} @@ -544,9 +544,9 @@ init_bridge_bridge() { ip link del dev v${CONTAINER_IFNAME}du } -init_bridge_bridged_fastdp() { +init_bridged_fastdp() { # Initialise the datapath as normal. NB sets MTU for use below - init_bridge_fastdp + init_fastdp # Create linking veth pair. We do this before initialising the bridge # so that `ip link show` displays the datapath, linking veths and @@ -556,7 +556,7 @@ init_bridge_bridged_fastdp() { ip link add name $BRIDGE_IFNAME mtu $MTU type veth peer name $DATAPATH_IFNAME mtu $MTU || return 1 # Initialise the bridge using fast datapath MTU - init_bridge_bridge + init_bridge # Link intermediary bridge and datapath if ! ip link set $DATAPATH_IFNAME up || From 890522f6f4b5c156f004e84e1fe06548ae24397e Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 25 Nov 2015 15:45:09 +0000 Subject: [PATCH 040/135] Add hidden `weave bridge-type` command --- weave | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weave b/weave index 3294fad0..2548de34 100755 --- a/weave +++ b/weave @@ -1781,6 +1781,9 @@ EOF fi create_bridge --without-ethtool ;; + bridge-type) + detect_bridge_type && echo $BRIDGE_TYPE + ;; launch) deprecation_warnings "$@" check_not_running $CONTAINER_NAME $BASE_IMAGE From 8ac745b4e5d3d45bd024a0522cd5aea4682168fa Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 25 Nov 2015 17:05:19 +0000 Subject: [PATCH 041/135] Lock read of miss counter --- router/fastdp.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/router/fastdp.go b/router/fastdp.go index c1d30fa2..571fb10c 100644 --- a/router/fastdp.go +++ b/router/fastdp.go @@ -218,6 +218,9 @@ func (fastdp fastDatapathBridge) String() string { } func (fastdp fastDatapathBridge) Stats() map[string]int { + lock := fastdp.startLock() + defer lock.unlock() + return map[string]int{ "FlowMisses": int(fastdp.missCount), } From 57a15a4e963da51cc51f8ab002aa30794e055c82 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 15 Oct 2015 17:30:45 +0100 Subject: [PATCH 042/135] Add fast datapath high level overview --- docs/fastdp.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 docs/fastdp.md diff --git a/docs/fastdp.md b/docs/fastdp.md new file mode 100644 index 00000000..231e9414 --- /dev/null +++ b/docs/fastdp.md @@ -0,0 +1,168 @@ +# Overlay Method Selection + +Weave automatically selects the best working overlay implementation +when connecting to a new peer. It does this by initiating both +forwarders in parallel, eventually settling on the most preferred +established forwarder at which point the other forwarder is shut down, +assuming it had not failed already. In the event that the remaining +forwarder fails at a later time (e.g. because of a heartbeat timeout) +the control plane TCP connection will recycle allowing the selection +algorithm to run again. By default fast datapath is preferred over +Sleeve. + +# Local Bridging + +Unlike the weave bridge netdev used by Sleeve, the OVS datapath has no +inherent bridging capability. Consequently fastdp implements an +ethernet bridge in addition to the vxlan overlay, maintaining its own +forwarding database by learning MAC addresses and dispatching +broadcast, unicast & multicast traffic accordingly. + +# Short Peer IDs + +The router relies on having access to the source and destination peer +when deciding how to forward packets. When the Sleeve overlay is in +use this information is conveyed directly within the encapsulation by +including the names of the peers in question, a solution not +accomodated directly by the vxlan wire format. Fortunately vxlan does +have a twenty four bit segment ID field in the header we can use to +encode this data - the challenge is to identify peers uniquely with a +twelve bit identifier instead of the seventeen bytes used by Sleeve. +In practice this is achieved by peers adopting a random 'short ID' and +resolving ownership collisions of same via the existing gossip +mechanism. + +# Heartbeats via vxlan + +Heartbeats are implemented by sending an encapsulated ethernet frame +with source and destination MAC addresses of `00:00:00:00:00:00` and a +payload consisting of the connection UID and total ethernet frame +length. The vxlan vport miss handler on the receiving side detects the +all-zero addresses and acknowledges the heartbeat via the TCP control +channel after validating the connection UID and frame length against +their expected values. + +# PMTU Discovery + +The length of the heartbeat ethernet frame is set to the MTU of the +datapath interface. In the event the vxlan packet is dropped or +truncated, the heartbeat will not be acknowledged; this lack of +acknowledgement will cause the peers to fall back to the Sleeve +overlay, which has a more sophisticated dynamic mechanism for coping +with low path MTUs. + +To avoid triggering this fallback in typical deployments, the datapath +interface is statically configured with an MTU of 1410 bytes allowing +it to work with most underlay network provider MTUs, including GCE at +1460 bytes (the fifty byte difference accomodates the vxlan overhead). + +# Virtual Ports + +There are three kinds of virtual port associated with the weave +datapath: + +* `internal` - one of these is created automatically, named after the + datapath. It corresponds to the network interface of the same name + that appears on the host when the datapath is created; it is the + ingress/egress port for `weave expose` +* `netdev` - one for each application container, corresponding to the + host end of the veth pair +* `vxlan` - one for each UDP port on which we are listening for vxlan + encapsulated packets. Typically there is only one of these, but + there may be more in a network in which peers do not all use the same + port configuration + +# Misses and Flow Creation + +As mentioned above, the datapath has no inherent behaviour - any +ingressing packet which does not match a flow is passed to a userspace +miss handler. The miss handler is then responsible for a) instructing +the datapath to take actions for the packet that caused the miss (for +example by copying it to one or more ports) and optionally b) +installing flow rules which will allow the datapath to act on similar +packets in future without invoking the miss handler. + +At a high level the fast datapath implementation can be viewed as a +set of miss handlers that determine what actions the OVS datapath +should take based on router topology information, together with some +additional machinery that manages the expiry of resulting flows when +that state changes. + +When a miss handler is invoked, it has two pieces of context: a byte +array containing the packet that triggered the miss, and a set of +'flow keys' that have been extracted from the packet by the kernel +module. The following flow keys are of interest to weave: + +* `InPort` - the identifier of the ingress virtual port +* `Ethernet` - the source and destination MAC addresses of the + ethernet frame +* `Tunnel` - tunnel identifier (see section on short peer IDs above), + and source/dest IPv4 addresses. Only present for ingress via a vxlan + vport + +Crucially, the router must use this information alone to determine +which actions to take - this allows the specification of a flow which +matches these keys and instructs the kernel to take action +automatically in future without further userspace involvement. + +Two actions are of interest: + +* `Output` - output the packet to the specified vport +* `SetTunnel` - update the effective vxlan tunnel parameters. Only + required prior to output from a vxlan vport + +It is possible to have multiple actions in a flow, so the router can +for example create a single rule that copies broadcast traffic to all +'local' vports (save the ingress vport obviously) as well as to vxlan +vports for onward peers as dictated by the routing topology. + +Every flow specified by the router has the following characteristics: + +* An `InPort` key matching the ingress vport +* A `Tunnel` key if the ingress vport is of type vxlan +* An `Ethernet` key matching the source and destination MAC +* A list of `Output` (for internal and netdev vports) and + `SetTunnel`+`Output` (for vxlan vports) actions + +Under certain circumstances the miss handler will instruct the +datapath to execute a set of actions for a packet without creating a +corresponding flow: + +* When broadcasting packets to discover the path to unknown + destination MACs. This condition is transient in the overwhelming + majority of cases and so the benefit of creating a flow is + outweighed by the need to invalidate it once the MAC is learned +* When the packet also needs to be forwarded to one or more peers via + Sleeve. In this case the router needs to handle all subsequent + matching packets as there is no OVS flow action to do it without + userspace involvement + +# Flow Invalidation + +Once a flow has been created for a particular combination of keys, the +miss handler will never again be invoked for matching packets. It is +therefore extremely important that we detect events which invalidate +existing flow actions: + +* Addition of netdev vports to the datapath +* Route invalidation (topology change) +* Short peer ID collision + +The response in each case is the same - delete all flows from the +datapath, allowing them to be recreated taking into account the +updated state. + +In addition to these event based invalidations there is an expiry +process that executes every five minutes. This process enumerates all +flows in the datapath, removing any which have not been used since the +last check; this cleans up: + +* Flows referring to netdev vports which have been removed +* Flows created by forwarders which have been stopped +* Flows related to MAC addresses which have not communicated recently + +Flows keyed on tunnel IPv4 address do not need to be cleared when a +peer appears to change IP address due to NAT; this will cause a miss +resulting in a new flow, and the old flow will expire naturally via +the timer mechanism. + From 2f69b4d190dca9dd512c684be09f7aa877b2f0b7 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 26 Nov 2015 10:39:41 +0000 Subject: [PATCH 043/135] Mention WEAVE_MTU --- docs/fastdp.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/fastdp.md b/docs/fastdp.md index 231e9414..51aa211e 100644 --- a/docs/fastdp.md +++ b/docs/fastdp.md @@ -55,6 +55,8 @@ To avoid triggering this fallback in typical deployments, the datapath interface is statically configured with an MTU of 1410 bytes allowing it to work with most underlay network provider MTUs, including GCE at 1460 bytes (the fifty byte difference accomodates the vxlan overhead). +This value can be overriden by setting `WEAVE_MTU` at launch if +necessary. # Virtual Ports From 350b8efb0b374ad3a53babceb9300f3f983f7dda Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 13:20:11 +0000 Subject: [PATCH 044/135] Move Weave docker plugin into this repo --- .gitignore | 2 + Makefile | 18 +++- plugin/dns.go | 55 +++++++++++ plugin/docker.go | 23 +++++ plugin/driver.go | 183 +++++++++++++++++++++++++++++++++++ plugin/ipam.go | 89 +++++++++++++++++ plugin/skel/listener.go | 206 ++++++++++++++++++++++++++++++++++++++++ plugin/watcher.go | 89 +++++++++++++++++ prog/plugin/Dockerfile | 3 + prog/plugin/main.go | 78 +++++++++++++++ 10 files changed, 741 insertions(+), 5 deletions(-) create mode 100644 plugin/dns.go create mode 100644 plugin/docker.go create mode 100644 plugin/driver.go create mode 100644 plugin/ipam.go create mode 100644 plugin/skel/listener.go create mode 100644 plugin/watcher.go create mode 100644 prog/plugin/Dockerfile create mode 100644 prog/plugin/main.go diff --git a/.gitignore b/.gitignore index 4506fcc5..1934d8f6 100644 --- a/.gitignore +++ b/.gitignore @@ -42,12 +42,14 @@ prog/weavewait/weavewait prog/weavewait/weavewait_noop prog/netcheck/netcheck prog/docker_tls_args/docker_tls_args +prog/plugin/plugin testing/cover/cover testing/runner/runner releases .weaver.uptodate .weavedns.uptodate .weaveexec.uptodate +.dockerplugin.uptodate weave.tar.gz prog/weaveexec/weave prog/weaveexec/weavehosts diff --git a/Makefile b/Makefile index b4878f0a..d802efc0 100644 --- a/Makefile +++ b/Makefile @@ -16,19 +16,22 @@ WEAVEWAIT_EXE=prog/weavewait/weavewait WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop NETCHECK_EXE=prog/netcheck/netcheck DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args +DOCKERPLUGIN_EXE=prog/plugin/plugin RUNNER_EXE=tools/runner/runner -EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) +EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) WEAVER_UPTODATE=.weaver.uptodate WEAVEEXEC_UPTODATE=.weaveexec.uptodate +DOCKERPLUGIN_UPTODATE=.dockerplugin.uptodate -IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) +IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(DOCKERPLUGIN_UPTODATE) WEAVER_IMAGE=$(DOCKERHUB_USER)/weave WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec +DOCKERPLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin -IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) +IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(DOCKERPLUGIN_IMAGE) WEAVE_EXPORT=weave.tar.gz @@ -74,13 +77,14 @@ $(WEAVER_EXE): router/*.go mesh/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/ $(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go $(NETCHECK_EXE): prog/netcheck/netcheck.go -# Sigproxy and weavewait need separate rules as they fail the netgo check in +# These next programs need separate rules as they fail the netgo check in # the main build stanza due to not importing net package $(SIGPROXY_EXE): prog/sigproxy/main.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go $(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go +$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*.go plugin/skel/*.go -$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE): +$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE): go get -tags netgo ./$(@D) go build $(BUILD_FLAGS) -o $@ ./$(@D) @@ -104,6 +108,10 @@ $(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPR $(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec touch $@ +$(DOCKERPLUGIN_UPTODATE): prog/plugin/Dockerfile $(DOCKERPLUGIN_EXE) + $(SUDO) docker build -t $(DOCKERPLUGIN_IMAGE) prog/plugin + touch $@ + $(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(SUDO) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@ diff --git a/plugin/dns.go b/plugin/dns.go new file mode 100644 index 00000000..27652cfe --- /dev/null +++ b/plugin/dns.go @@ -0,0 +1,55 @@ +package plugin + +import ( + "fmt" + "net/http" + "net/url" + "strings" +) + +func (d *dockerer) registerWithDNS(ID string, fqdn string, ip string) error { + dnsip, err := d.getContainerBridgeIP(WeaveDNSContainer) + if err != nil { + return fmt.Errorf("nameserver not available: %s", err) + } + data := url.Values{} + data.Add("fqdn", fqdn) + + req, err := http.NewRequest("PUT", fmt.Sprintf("http://%s:6785/name/%s/%s", dnsip, ID, ip), strings.NewReader(data.Encode())) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + cl := &http.Client{} + res, err := cl.Do(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return fmt.Errorf("non-OK status from nameserver: %d", res.StatusCode) + } + return nil +} + +func (d *dockerer) deregisterWithDNS(ID string, ip string) error { + dnsip, err := d.getContainerBridgeIP(WeaveDNSContainer) + if err != nil { + return fmt.Errorf("nameserver not available: %s", err) + } + + req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s:6785/name/%s/%s", dnsip, ID, ip), nil) + if err != nil { + return err + } + + cl := &http.Client{} + res, err := cl.Do(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return fmt.Errorf("non-OK status from nameserver: %d", res.StatusCode) + } + return nil +} diff --git a/plugin/docker.go b/plugin/docker.go new file mode 100644 index 00000000..d7b4b337 --- /dev/null +++ b/plugin/docker.go @@ -0,0 +1,23 @@ +package plugin + +import ( + "github.com/fsouza/go-dockerclient" + . "github.com/weaveworks/weave/common" +) + +type dockerer struct { + client *docker.Client +} + +func (d *dockerer) getContainerBridgeIP(nameOrID string) (string, error) { + Log.Debugf("Getting IP for container %s", nameOrID) + info, err := d.InspectContainer(nameOrID) + if err != nil { + return "", err + } + return info.NetworkSettings.IPAddress, nil +} + +func (d *dockerer) InspectContainer(nameOrId string) (*docker.Container, error) { + return d.client.InspectContainer(nameOrId) +} diff --git a/plugin/driver.go b/plugin/driver.go new file mode 100644 index 00000000..8467c58f --- /dev/null +++ b/plugin/driver.go @@ -0,0 +1,183 @@ +package plugin + +import ( + "fmt" + + "github.com/docker/libnetwork/drivers/remote/api" + "github.com/docker/libnetwork/types" + + . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/common/odp" + "github.com/weaveworks/weave/plugin/skel" + + docker "github.com/fsouza/go-dockerclient" + "github.com/vishvananda/netlink" +) + +const ( + WeaveContainer = "weave" + WeaveBridge = "weave" +) + +type driver struct { + version string + nameserver string +} + +func New(version string, nameserver string) (skel.Driver, error) { + client, err := docker.NewClient("unix:///var/run/docker.sock") + if err != nil { + return nil, errorf("could not connect to docker: %s", err) + } + + _, err = NewWatcher(client) + if err != nil { + return nil, err + } + + return &driver{ + nameserver: nameserver, + version: version, + }, nil +} + +func errorf(format string, a ...interface{}) error { + Log.Errorf(format, a...) + return fmt.Errorf(format, a...) +} + +// === protocol handlers + +var caps = &api.GetCapabilityResponse{ + Scope: "global", +} + +func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) { + Log.Debugf("Get capabilities: responded with %+v", caps) + return caps, nil +} + +func (driver *driver) CreateNetwork(create *api.CreateNetworkRequest) error { + Log.Debugf("Create network request %+v", create) + Log.Infof("Create network %s", create.NetworkID) + return nil +} + +func (driver *driver) DeleteNetwork(delete *api.DeleteNetworkRequest) error { + Log.Debugf("Delete network request: %+v", delete) + Log.Infof("Destroy network %s", delete.NetworkID) + return nil +} + +func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error) { + Log.Debugf("Create endpoint request %+v", create) + endID := create.EndpointID + + if create.Interface == nil { + return nil, fmt.Errorf("Not supported: creating an interface from within CreateEndpoint") + } + resp := &api.CreateEndpointResponse{} + + Log.Infof("Create endpoint %s %+v", endID, resp) + return resp, nil +} + +func (driver *driver) DeleteEndpoint(delete *api.DeleteEndpointRequest) error { + Log.Debugf("Delete endpoint request: %+v", delete) + Log.Infof("Delete endpoint %s", delete.EndpointID) + return nil +} + +func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error) { + Log.Debugf("Endpoint info request: %+v", req) + Log.Infof("Endpoint info %s", req.EndpointID) + return &api.EndpointInfoResponse{Value: map[string]interface{}{}}, nil +} + +func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) { + endID := j.EndpointID + + maybeBridge, err := netlink.LinkByName(WeaveBridge) + if err != nil { + return nil, errorf(`bridge "%s" not present; did you launch weave?`, WeaveBridge) + } + + // 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) + } + + ifname := &api.InterfaceName{ + SrcName: local.PeerName, + DstPrefix: "ethwe", + } + + response := &api.JoinResponse{ + InterfaceName: ifname, + } + if driver.nameserver != "" { + routeToDNS := api.StaticRoute{ + Destination: driver.nameserver + "/32", + RouteType: types.CONNECTED, + NextHop: "", + } + response.StaticRoutes = []api.StaticRoute{routeToDNS} + } + Log.Infof("Join endpoint %s:%s to %s", j.NetworkID, j.EndpointID, j.SandboxKey) + return response, nil +} + +func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error { + Log.Debugf("Leave request: %+v", leave) + + local := vethPair(leave.EndpointID[:5]) + if err := netlink.LinkDel(local); err != nil { + Log.Warningf("unable to delete veth on leave: %s", err) + } + Log.Infof("Leave %s:%s", leave.NetworkID, leave.EndpointID) + return nil +} + +func (driver *driver) DiscoverNew(disco *api.DiscoveryNotification) error { + Log.Debugf("Dicovery new notification: %+v", disco) + return nil +} + +func (driver *driver) DiscoverDelete(disco *api.DiscoveryNotification) error { + Log.Debugf("Dicovery delete notification: %+v", disco) + return nil +} + +// === + +func vethPair(suffix string) *netlink.Veth { + return &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{Name: "vethwl" + suffix}, + PeerName: "vethwg" + suffix, + } +} diff --git a/plugin/ipam.go b/plugin/ipam.go new file mode 100644 index 00000000..927f7fff --- /dev/null +++ b/plugin/ipam.go @@ -0,0 +1,89 @@ +package plugin + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + + . "github.com/weaveworks/weave/common" +) + +func (d *dockerer) ipamOp(ID string, op string) (*net.IPNet, error) { + weaveip, err := d.getContainerBridgeIP(WeaveContainer) + Log.Debugf("IPAM operation %s for %s", op, ID) + if err != nil { + return nil, err + } + + var res *http.Response + + url := fmt.Sprintf("http://%s:6784/ip/%s", weaveip, ipamID(ID)) + Log.Debugf("Attempting to %s to %s", op, url) + if op == "POST" { + res, err = http.Post(url, "", nil) + } else if op == "GET" { + res, err = http.Get(url) + } + + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("received status %d from IPAM", res.StatusCode) + } + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return parseIP(string(body)) +} + +// returns an IP for the ID given, allocating a fresh one if necessary +func (d *dockerer) allocateIP(ID string) (*net.IPNet, error) { + return d.ipamOp(ID, "POST") +} + +// returns an IP for the ID given, or nil if one has not been +// allocated +func (d *dockerer) lookupIP(ID string) (*net.IPNet, error) { + return d.ipamOp(ID, "GET") +} + +// release an IP which is no longer needed +func (d *dockerer) releaseIP(ID string) error { + weaveip, err := d.getContainerBridgeIP(WeaveContainer) + if err != nil { + return err + } + req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s:6784/ip/%s", weaveip, ipamID(ID)), nil) + if err != nil { + return err + } + cl := &http.Client{} + res, err := cl.Do(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent { + return fmt.Errorf("unexpected HTTP status code from IP release: %d", res.StatusCode) + } + return nil +} + +func parseIP(body string) (*net.IPNet, error) { + ip, ipnet, err := net.ParseCIDR(string(body)) + if err != nil { + return nil, err + } + ipnet.IP = ip + return ipnet, nil +} + +// If something looking like a container ID is supplied to IPAM, it +// will try to check that it's running, and return nothing (and empty +// string) if it's not. So I make sure it doesn't look like a +// container ID by including non-hex characters. +func ipamID(ID string) string { + return "endpoint:" + ID +} diff --git a/plugin/skel/listener.go b/plugin/skel/listener.go new file mode 100644 index 00000000..f86af91e --- /dev/null +++ b/plugin/skel/listener.go @@ -0,0 +1,206 @@ +package skel + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + + "github.com/gorilla/mux" + + "github.com/docker/libnetwork/drivers/remote/api" +) + +const ( + MethodReceiver = "NetworkDriver" +) + +type Driver interface { + GetCapabilities() (*api.GetCapabilityResponse, error) + CreateNetwork(create *api.CreateNetworkRequest) error + DeleteNetwork(delete *api.DeleteNetworkRequest) error + CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error) + DeleteEndpoint(delete *api.DeleteEndpointRequest) error + EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error) + JoinEndpoint(j *api.JoinRequest) (response *api.JoinResponse, error error) + LeaveEndpoint(leave *api.LeaveRequest) error + DiscoverNew(discover *api.DiscoveryNotification) error + DiscoverDelete(delete *api.DiscoveryNotification) error +} + +type listener struct { + d Driver +} + +func Listen(socket net.Listener, driver Driver) error { + router := mux.NewRouter() + router.NotFoundHandler = http.HandlerFunc(notFound) + + router.Methods("POST").Path("/Plugin.Activate").HandlerFunc(handshake) + + handleMethod := func(method string, h http.HandlerFunc) { + router.Methods("POST").Path(fmt.Sprintf("/%s.%s", MethodReceiver, method)).HandlerFunc(h) + } + + listener := &listener{driver} + + handleMethod("GetCapabilities", listener.getCapabilities) + handleMethod("CreateNetwork", listener.createNetwork) + handleMethod("DeleteNetwork", listener.deleteNetwork) + handleMethod("CreateEndpoint", listener.createEndpoint) + handleMethod("DeleteEndpoint", listener.deleteEndpoint) + handleMethod("EndpointOperInfo", listener.infoEndpoint) + handleMethod("Join", listener.joinEndpoint) + handleMethod("Leave", listener.leaveEndpoint) + + return http.Serve(socket, router) +} + +// === protocol handlers + +type handshakeResp struct { + Implements []string +} + +func handshake(w http.ResponseWriter, r *http.Request) { + err := json.NewEncoder(w).Encode(&handshakeResp{ + []string{"NetworkDriver"}, + }) + if err != nil { + sendError(w, "encode error", http.StatusInternalServerError) + return + } +} + +func (listener *listener) getCapabilities(w http.ResponseWriter, r *http.Request) { + caps, err := listener.d.GetCapabilities() + objectOrErrorResponse(w, caps, err) +} + +func (listener *listener) createNetwork(w http.ResponseWriter, r *http.Request) { + var create api.CreateNetworkRequest + err := json.NewDecoder(r.Body).Decode(&create) + if err != nil { + sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } + emptyOrErrorResponse(w, listener.d.CreateNetwork(&create)) +} + +func (listener *listener) deleteNetwork(w http.ResponseWriter, r *http.Request) { + var delete api.DeleteNetworkRequest + if err := json.NewDecoder(r.Body).Decode(&delete); err != nil { + sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } + emptyOrErrorResponse(w, listener.d.DeleteNetwork(&delete)) +} + +func (listener *listener) createEndpoint(w http.ResponseWriter, r *http.Request) { + var create api.CreateEndpointRequest + if err := json.NewDecoder(r.Body).Decode(&create); err != nil { + sendError(w, "unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } + res, err := listener.d.CreateEndpoint(&create) + objectOrErrorResponse(w, res, err) +} + +func (listener *listener) deleteEndpoint(w http.ResponseWriter, r *http.Request) { + var delete api.DeleteEndpointRequest + if err := json.NewDecoder(r.Body).Decode(&delete); err != nil { + sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest) + return + } + emptyOrErrorResponse(w, listener.d.DeleteEndpoint(&delete)) +} + +func (listener *listener) infoEndpoint(w http.ResponseWriter, r *http.Request) { + var req api.EndpointInfoRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest) + return + } + info, err := listener.d.EndpointInfo(&req) + objectOrErrorResponse(w, info, err) +} + +func (listener *listener) joinEndpoint(w http.ResponseWriter, r *http.Request) { + var join api.JoinRequest + if err := json.NewDecoder(r.Body).Decode(&join); err != nil { + sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest) + return + } + res, err := listener.d.JoinEndpoint(&join) + objectOrErrorResponse(w, res, err) +} + +func (listener *listener) leaveEndpoint(w http.ResponseWriter, r *http.Request) { + var l api.LeaveRequest + if err := json.NewDecoder(r.Body).Decode(&l); err != nil { + sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest) + return + } + emptyOrErrorResponse(w, listener.d.LeaveEndpoint(&l)) +} + +func (listener *listener) discoverNew(w http.ResponseWriter, r *http.Request) { + var disco api.DiscoveryNotification + if err := json.NewDecoder(r.Body).Decode(&disco); err != nil { + sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest) + return + } + emptyOrErrorResponse(w, listener.d.DiscoverNew(&disco)) +} + +func (listener *listener) discoverDelete(w http.ResponseWriter, r *http.Request) { + var disco api.DiscoveryNotification + if err := json.NewDecoder(r.Body).Decode(&disco); err != nil { + sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest) + return + } + emptyOrErrorResponse(w, listener.d.DiscoverDelete(&disco)) +} + +// === + +func notFound(w http.ResponseWriter, r *http.Request) { + http.NotFound(w, r) +} + +func sendError(w http.ResponseWriter, msg string, code int) { + http.Error(w, msg, code) +} + +func errorResponse(w http.ResponseWriter, fmtString string, item ...interface{}) { + json.NewEncoder(w).Encode(map[string]string{ + "Err": fmt.Sprintf(fmtString, item...), + }) +} + +func objectResponse(w http.ResponseWriter, obj interface{}) { + if err := json.NewEncoder(w).Encode(obj); err != nil { + sendError(w, "Could not JSON encode response", http.StatusInternalServerError) + return + } +} + +func emptyResponse(w http.ResponseWriter) { + json.NewEncoder(w).Encode(map[string]string{}) +} + +func objectOrErrorResponse(w http.ResponseWriter, obj interface{}, err error) { + if err != nil { + errorResponse(w, err.Error()) + return + } + objectResponse(w, obj) +} + +func emptyOrErrorResponse(w http.ResponseWriter, err error) { + if err != nil { + errorResponse(w, err.Error()) + return + } + emptyResponse(w) +} diff --git a/plugin/watcher.go b/plugin/watcher.go new file mode 100644 index 00000000..50f87dd5 --- /dev/null +++ b/plugin/watcher.go @@ -0,0 +1,89 @@ +package plugin + +import ( + "fmt" + "strings" + + "github.com/fsouza/go-dockerclient" + . "github.com/weaveworks/weave/common" +) + +const ( + WeaveDNSContainer = "weavedns" + WeaveDomain = "weave.local" +) + +type watcher struct { + dockerer + networks map[string]bool + events chan *docker.APIEvents +} + +type Watcher interface { +} + +func NewWatcher(client *docker.Client) (Watcher, error) { + w := &watcher{ + dockerer: dockerer{ + client: client, + }, + networks: make(map[string]bool), + events: make(chan *docker.APIEvents), + } + err := client.AddEventListener(w.events) + if err != nil { + return nil, err + } + + go func() { + for event := range w.events { + switch event.Status { + case "start": + w.ContainerStart(event.ID) + case "die": + w.ContainerDied(event.ID) + } + } + }() + + return w, nil +} + +func (w *watcher) ContainerStart(id string) { + Log.Debugf("Container started %s", id) + info, err := w.InspectContainer(id) + if err != nil { + Log.Warningf("error inspecting container: %s", err) + return + } + // FIXME: check that it's on our network; but, the docker client lib doesn't know about .NetworkID + if isSubdomain(info.Config.Domainname, WeaveDomain) { + // one of ours + ip := info.NetworkSettings.IPAddress + fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname) + if err := w.registerWithDNS(id, fqdn, ip); err != nil { + Log.Warningf("unable to register with weaveDNS: %s", err) + } + } +} + +func (w *watcher) ContainerDied(id string) { + Log.Debugf("Container died %s", id) + info, err := w.InspectContainer(id) + if err != nil { + Log.Warningf("error inspecting container: %s", err) + return + } + if isSubdomain(info.Config.Domainname, WeaveDomain) { + ip := info.NetworkSettings.IPAddress + if err := w.deregisterWithDNS(id, ip); err != nil { + Log.Warningf("unable to deregister with weaveDNS: %s", err) + } + } +} + +// Cheap and cheerful way to check x is, or is a subdomain, of +// y. Neither are expected to start with a '.'. +func isSubdomain(x string, y string) bool { + return x == y || strings.HasSuffix(x, "."+y) +} diff --git a/prog/plugin/Dockerfile b/prog/plugin/Dockerfile new file mode 100644 index 00000000..946f0b28 --- /dev/null +++ b/prog/plugin/Dockerfile @@ -0,0 +1,3 @@ +FROM weaveworks/weaveexec +ADD ./plugin /home/weave/ +ENTRYPOINT ["/home/weave/plugin"] diff --git a/prog/plugin/main.go b/prog/plugin/main.go new file mode 100644 index 00000000..77438cd4 --- /dev/null +++ b/prog/plugin/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "flag" + "fmt" + "net" + "os" + "os/signal" + "syscall" + + . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/plugin" + "github.com/weaveworks/weave/plugin/skel" +) + +var version = "(unreleased version)" + +func main() { + var ( + justVersion bool + address string + nameserver string + logLevel string + ) + + flag.BoolVar(&justVersion, "version", false, "print version and exit") + 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(&nameserver, "nameserver", "", "nameserver to provide to containers") + + flag.Parse() + + if justVersion { + fmt.Printf("weave plugin %s\n", version) + os.Exit(0) + } + + SetLogLevel(logLevel) + + Log.Println("Weave plugin", version, "Command line options:", os.Args) + + var d skel.Driver + d, err := plugin.New(version, nameserver) + if err != nil { + Log.Fatalf("unable to create driver: %s", err) + } + + var listener net.Listener + + // remove socket from last invocation + if err := os.Remove(address); err != nil && !os.IsNotExist(err) { + Log.Fatal(err) + } + listener, err = net.Listen("unix", address) + if err != nil { + Log.Fatal(err) + } + defer listener.Close() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM) + + endChan := make(chan error, 1) + go func() { + endChan <- skel.Listen(listener, d) + }() + + select { + case sig := <-sigChan: + Log.Debugf("Caught signal %s; shutting down", sig) + case err := <-endChan: + if err != nil { + Log.Errorf("Error from listener: ", err) + listener.Close() + os.Exit(1) + } + } +} From 901bf63cb8f8e251860b4d1b1b4231ca35dd707a Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 15:19:57 +0000 Subject: [PATCH 045/135] Fix printf error caught by lint --- prog/plugin/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 77438cd4..659d5a8a 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -70,7 +70,7 @@ func main() { Log.Debugf("Caught signal %s; shutting down", sig) case err := <-endChan: if err != nil { - Log.Errorf("Error from listener: ", err) + Log.Errorf("Error from listener: %s", err) listener.Close() os.Exit(1) } From f2d7a282588377488d901168d854ed3d7b37bf95 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 17:18:59 +0000 Subject: [PATCH 046/135] Add docker plugin README --- plugin/README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 plugin/README.md diff --git a/plugin/README.md b/plugin/README.md new file mode 100644 index 00000000..800480ad --- /dev/null +++ b/plugin/README.md @@ -0,0 +1,60 @@ +# Weave network driver extension for Docker + +A [plugin](http://docs.docker.com/engine/extend/plugin_api/) to +integrate [weave Net](http://weave.works/net/) with Docker. + +## Setup: + +Before you can use this plugin, you must [install weave](https://github.com/weaveworks/weave#installation) and [configure docker with a "cluster store"](https://docs.docker.com/engine/userguide/networking/get-started-overlay/#step-1-set-up-a-key-value-store). + + $ weave launch + $ weave launch-plugin + $ docker network create --driver=weave weave + +## Starting a container: + + $ docker run --net=weave -ti ubuntu + +## Plugin command-line arguments + + * `--socket=`, which is the socket on which to listen for the + [plugin protocol](http://docs.docker.com/engine/extend/plugin_api/). + This defaults to what Docker expects, that is + `"/run/docker/plugins/weave.sock"`, but you may wish to + change it, for instance if you're running more than one instance of + the driver for some reason. + + * `--log-level=debug|info|warning|error`, which tells the plugin + how much information to emit for debugging. + +## Points to note + +Network plugins require Docker version 1.9 or later. + +### Docker needs a "cluster store" + +Weave operates as a "globally scoped" libnetwork driver, which means +libnetwork will assume all networks and endpoints are commonly known +to all hosts. It does this by using a shared database which you must +provide. + +As a consequence, you need to supply Docker with the address of a "cluster +store" when you start it; for example, an etcd installation. + +There's no specific documentation for using a cluster store, but the +first part of [this guide](https://github.com/docker/docker/blob/master/docs/userguide/networking/get-started-overlay.md) may help. + +### WeaveDNS not supported + +Weave's built-in service discovery mechanism is not currently +supported by the plugin. However, Docker provides a rudimentary +discovery mechanism by writing all user-provided container names and +hostnames into every container's `/etc/hosts`. + +### Restarting + +We start the plugin with a restart policy of 'always', because +Docker attempts to recreate networks on startup which means the plugin +container has to be started. If it isn't started automatically by Docker +with a restart policy, it's not possible to start it manually because +Docker is blocked. From 34e7996f2f70c7d0ad14744a83ccb021155d86c6 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Mon, 16 Nov 2015 11:26:48 +0100 Subject: [PATCH 047/135] Show some IPAM status at /status/ipam --- ipam/ring/ring.go | 20 +++++++++ ipam/status.go | 17 ++++++++ prog/weaver/http.go | 98 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/ipam/ring/ring.go b/ipam/ring/ring.go index 30f32b8d..e992dd43 100644 --- a/ipam/ring/ring.go +++ b/ipam/ring/ring.go @@ -316,6 +316,26 @@ func (r *Ring) OwnedRanges() (result []address.Range) { return r.splitRangesOverZero(result) } +func (r *Ring) OwnedRangesByPeer() map[mesh.PeerName][]address.Range { + r.assertInvariants() + + result := make(map[mesh.PeerName][]address.Range) + for i, entry := range r.Entries { + nextEntry := r.Entries.entry(i + 1) + resultPeer, found := result[entry.Peer] + r := address.Range{Start: entry.Token, End: nextEntry.Token} + if !found { + result[entry.Peer] = []address.Range{r} + } else { + result[entry.Peer] = append(resultPeer, r) + } + } + for peer, resultPeer := range result { + result[peer] = r.splitRangesOverZero(resultPeer) + } + return result +} + // ClaimForPeers claims the entire ring for the array of peers passed // in. Only works for empty rings. func (r *Ring) ClaimForPeers(peers []mesh.PeerName) { diff --git a/ipam/status.go b/ipam/status.go index 72df8d3e..39bc4588 100644 --- a/ipam/status.go +++ b/ipam/status.go @@ -2,13 +2,23 @@ package ipam import ( "github.com/weaveworks/weave/ipam/paxos" + "github.com/weaveworks/weave/ipam/ring" + "github.com/weaveworks/weave/ipam/space" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" ) type Status struct { Paxos *paxos.Status Range string + RangeNumIPs int DefaultSubnet string + Ring *ring.Ring // information on ranges owned by all peers + Space space.Space // more detail on ranges owned by us + Owned map[string][]address.Address // addresses by container-ID + OurName mesh.PeerName + Nicknames map[mesh.PeerName]string `json:"-"` + IsKnownPeer func(mesh.PeerName) bool `json:"-"` Entries []EntryStatus PendingClaims []ClaimStatus PendingAllocates []string @@ -40,7 +50,14 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status { resultChan <- &Status{ paxosStatus, allocator.universe.String(), + int(allocator.universe.Size()), defaultSubnet.String(), + allocator.ring, + allocator.space, + allocator.owned, + allocator.ourName, + allocator.nicknames, + allocator.isKnownPeer, newEntryStatusSlice(allocator), newClaimStatusSlice(allocator), newAllocateIdentSlice(allocator)} diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 6794967e..848afbc5 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -1,8 +1,13 @@ package main import ( + "bytes" "encoding/json" "fmt" + "net/http" + "strings" + "text/template" + "github.com/gorilla/mux" . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/ipam" @@ -10,9 +15,6 @@ import ( "github.com/weaveworks/weave/nameserver" "github.com/weaveworks/weave/net/address" weave "github.com/weaveworks/weave/router" - "net/http" - "strings" - "text/template" ) var rootTemplate = template.New("root").Funcs(map[string]interface{}{ @@ -31,6 +33,76 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } return strings.Join(servers, ", ") }, + "printIPAMRanges": func(status ipam.Status) string { + var buffer bytes.Buffer + + type NicknameStats struct { + Ranges []address.Range + Addresses []address.Address + Reachable bool + } + + nicknameStats := make(map[string]*NicknameStats) + getNickName := func(peer mesh.PeerName) string { + name, found := status.Nicknames[peer] + if !found { + return fmt.Sprintf("%s", peer) + } + return name + } + getOrCreateNicknameStats := func(peer mesh.PeerName) *NicknameStats { + peerName := getNickName(peer) + stats, found := nicknameStats[peerName] + if !found { + stats = &NicknameStats{ + Ranges: []address.Range{}, + Addresses: []address.Address{}, + Reachable: status.IsKnownPeer(peer), + } + nicknameStats[peerName] = stats + } + return stats + } + + for _, entry := range status.Ring.Entries { + stats := getOrCreateNicknameStats(entry.Peer) + stats.Addresses = append(stats.Addresses, entry.Token) + } + + for peer, ranges := range status.Ring.OwnedRangesByPeer() { + stats := getOrCreateNicknameStats(peer) + stats.Ranges = append(stats.Ranges, ranges...) + } + + appendAddresses := func(displayName string, reachable bool, addresses []address.Address, ranges []address.Range) { + reachableStr := "" + if !reachable { + reachableStr = "- unreachable!" + } + ipsInRange := 0 + for _, chunk := range ranges { + ipsInRange += int(chunk.Size()) + } + percentageRanges := float32(ipsInRange) * 100.0 / float32(status.RangeNumIPs) + + fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of universe, in %3d ranges) - used: %8d IPs %s\n", + displayName, ipsInRange, percentageRanges, len(ranges), len(addresses), reachableStr) + } + + // print the local addresses + ourNickname := getNickName(status.OurName) + ourStats := nicknameStats[ourNickname] + appendAddresses("(local)", true, ourStats.Addresses, ourStats.Ranges) + + // and then the rest + for nickname, stats := range nicknameStats { + if nickname != ourNickname { + appendAddresses(nickname, stats.Reachable, stats.Addresses, stats.Ranges) + } + } + + return buffer.String() + }, "printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string { counts := make(map[string]int) for _, conn := range conns { @@ -152,6 +224,25 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\ {{end}}\ `) +var ipamTemplate = defTemplate("ipamTemplate", `\ + Universe: {{.IPAM.Range}} ({{.IPAM.RangeNumIPs}} IPs) + DefaultSubnet: {{.IPAM.DefaultSubnet}} +{{if .IPAM.Owned}}\ + Containers: +{{range $id, $addresses := .IPAM.Owned}}\ + {{printf "%-12s" $id}}: {{$addresses}} +{{end}}\ +{{end}}\ + Ownerships: +{{printIPAMRanges .IPAM}} +{{if .IPAM.PendingClaims}}\ + Claims: +{{range .IPAM.PendingClaims}} + {{printf "%-15v" .Ident}} {{.Address}} +{{end}}\ +{{end}}\ +`) + type WeaveStatus struct { Version string Router *weave.NetworkRouterStatus `json:"Router,omitempty"` @@ -207,5 +298,6 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRout defHandler("/status/connections", connectionsTemplate) defHandler("/status/peers", peersTemplate) defHandler("/status/dns", dnsEntriesTemplate) + defHandler("/status/ipam", ipamTemplate) } From 6d3fd6da07241a955e70cd8ca18177afa5b310ab Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 20 Nov 2015 18:04:12 +0000 Subject: [PATCH 048/135] Simplify ipam status format Don't use the word "universe" in user-visible output; it is an internal term. Removed everything but owned ranges from 'weave status ipam' results, for consistency with similar functions --- prog/weaver/http.go | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 848afbc5..52bd5d3f 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -46,7 +46,7 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ getNickName := func(peer mesh.PeerName) string { name, found := status.Nicknames[peer] if !found { - return fmt.Sprintf("%s", peer) + return peer.String() } return name } @@ -54,11 +54,7 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ peerName := getNickName(peer) stats, found := nicknameStats[peerName] if !found { - stats = &NicknameStats{ - Ranges: []address.Range{}, - Addresses: []address.Address{}, - Reachable: status.IsKnownPeer(peer), - } + stats = &NicknameStats{Reachable: status.IsKnownPeer(peer)} nicknameStats[peerName] = stats } return stats @@ -85,8 +81,8 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } percentageRanges := float32(ipsInRange) * 100.0 / float32(status.RangeNumIPs) - fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of universe, in %3d ranges) - used: %8d IPs %s\n", - displayName, ipsInRange, percentageRanges, len(ranges), len(addresses), reachableStr) + fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of total) - used: %d %s\n", + displayName, ipsInRange, percentageRanges, len(addresses), reachableStr) } // print the local addresses @@ -224,24 +220,7 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\ {{end}}\ `) -var ipamTemplate = defTemplate("ipamTemplate", `\ - Universe: {{.IPAM.Range}} ({{.IPAM.RangeNumIPs}} IPs) - DefaultSubnet: {{.IPAM.DefaultSubnet}} -{{if .IPAM.Owned}}\ - Containers: -{{range $id, $addresses := .IPAM.Owned}}\ - {{printf "%-12s" $id}}: {{$addresses}} -{{end}}\ -{{end}}\ - Ownerships: -{{printIPAMRanges .IPAM}} -{{if .IPAM.PendingClaims}}\ - Claims: -{{range .IPAM.PendingClaims}} - {{printf "%-15v" .Ident}} {{.Address}} -{{end}}\ -{{end}}\ -`) +var ipamTemplate = defTemplate("ipamTemplate", `{{printIPAMRanges .IPAM}}`) type WeaveStatus struct { Version string From 5f42a4d95edf431bbdf7979f43d3a00e7a48a0c8 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 23 Nov 2015 15:11:56 +0000 Subject: [PATCH 049/135] Hold stats by peer identity not nickname so we can print the peer name for each ownership. Also avoid crashing if we don't own anything --- prog/weaver/http.go | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 52bd5d3f..c1474901 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -36,41 +36,33 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ "printIPAMRanges": func(status ipam.Status) string { var buffer bytes.Buffer - type NicknameStats struct { + type stats struct { Ranges []address.Range Addresses []address.Address Reachable bool } - nicknameStats := make(map[string]*NicknameStats) - getNickName := func(peer mesh.PeerName) string { - name, found := status.Nicknames[peer] + peerStats := make(map[mesh.PeerName]*stats) + getOrCreateStats := func(peer mesh.PeerName) *stats { + s, found := peerStats[peer] if !found { - return peer.String() + s = &stats{Reachable: status.IsKnownPeer(peer)} + peerStats[peer] = s } - return name - } - getOrCreateNicknameStats := func(peer mesh.PeerName) *NicknameStats { - peerName := getNickName(peer) - stats, found := nicknameStats[peerName] - if !found { - stats = &NicknameStats{Reachable: status.IsKnownPeer(peer)} - nicknameStats[peerName] = stats - } - return stats + return s } for _, entry := range status.Ring.Entries { - stats := getOrCreateNicknameStats(entry.Peer) + stats := getOrCreateStats(entry.Peer) stats.Addresses = append(stats.Addresses, entry.Token) } for peer, ranges := range status.Ring.OwnedRangesByPeer() { - stats := getOrCreateNicknameStats(peer) + stats := getOrCreateStats(peer) stats.Ranges = append(stats.Ranges, ranges...) } - appendAddresses := func(displayName string, reachable bool, addresses []address.Address, ranges []address.Range) { + appendAddresses := func(peer mesh.PeerName, nickName string, reachable bool, addresses []address.Address, ranges []address.Range) { reachableStr := "" if !reachable { reachableStr = "- unreachable!" @@ -81,19 +73,24 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } percentageRanges := float32(ipsInRange) * 100.0 / float32(status.RangeNumIPs) + displayName := peer.String() + "(" + nickName + ")" fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of total) - used: %d %s\n", displayName, ipsInRange, percentageRanges, len(addresses), reachableStr) } // print the local addresses - ourNickname := getNickName(status.OurName) - ourStats := nicknameStats[ourNickname] - appendAddresses("(local)", true, ourStats.Addresses, ourStats.Ranges) + if ourStats := peerStats[status.OurName]; ourStats != nil { + appendAddresses(status.OurName, "local", true, ourStats.Addresses, ourStats.Ranges) + } // and then the rest - for nickname, stats := range nicknameStats { - if nickname != ourNickname { - appendAddresses(nickname, stats.Reachable, stats.Addresses, stats.Ranges) + for peer, stats := range peerStats { + if peer != status.OurName { + nickname, found := status.Nicknames[peer] + if !found { + nickname = peer.String() + } + appendAddresses(peer, nickname, stats.Reachable, stats.Addresses, stats.Ranges) } } From 044c6474b21bf4fc94b22daeecae6c84af979dc0 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 23 Nov 2015 17:23:49 +0000 Subject: [PATCH 050/135] Remove counting of ring tokens, since it isn't useful to end-user. Previously the count of tokens was claimed to be a count of addresses in use, which is incorrect. Also rename `appendAddresses` to `appendOwned` since it doesn't involve addresses. --- prog/weaver/http.go | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index c1474901..dbf64c75 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -38,31 +38,21 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ type stats struct { Ranges []address.Range - Addresses []address.Address Reachable bool } peerStats := make(map[mesh.PeerName]*stats) - getOrCreateStats := func(peer mesh.PeerName) *stats { + + for peer, ranges := range status.Ring.OwnedRangesByPeer() { s, found := peerStats[peer] if !found { s = &stats{Reachable: status.IsKnownPeer(peer)} peerStats[peer] = s } - return s + s.Ranges = append(s.Ranges, ranges...) } - for _, entry := range status.Ring.Entries { - stats := getOrCreateStats(entry.Peer) - stats.Addresses = append(stats.Addresses, entry.Token) - } - - for peer, ranges := range status.Ring.OwnedRangesByPeer() { - stats := getOrCreateStats(peer) - stats.Ranges = append(stats.Ranges, ranges...) - } - - appendAddresses := func(peer mesh.PeerName, nickName string, reachable bool, addresses []address.Address, ranges []address.Range) { + appendOwned := func(peer mesh.PeerName, nickName string, reachable bool, ranges []address.Range) { reachableStr := "" if !reachable { reachableStr = "- unreachable!" @@ -74,13 +64,13 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ percentageRanges := float32(ipsInRange) * 100.0 / float32(status.RangeNumIPs) displayName := peer.String() + "(" + nickName + ")" - fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of total) - used: %d %s\n", - displayName, ipsInRange, percentageRanges, len(addresses), reachableStr) + fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of total) %s\n", + displayName, ipsInRange, percentageRanges, reachableStr) } // print the local addresses if ourStats := peerStats[status.OurName]; ourStats != nil { - appendAddresses(status.OurName, "local", true, ourStats.Addresses, ourStats.Ranges) + appendOwned(status.OurName, "local", true, ourStats.Ranges) } // and then the rest @@ -90,7 +80,7 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ if !found { nickname = peer.String() } - appendAddresses(peer, nickname, stats.Reachable, stats.Addresses, stats.Ranges) + appendOwned(peer, nickname, stats.Reachable, stats.Ranges) } } From e38018ab1ec8d2ab89200aab22871fd98f0f48a2 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 23 Nov 2015 18:06:18 +0000 Subject: [PATCH 051/135] Add warning to main ipam status if all IPs owned by unreachable peers --- prog/weaver/http.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index dbf64c75..8db1d425 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -86,6 +86,20 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ return buffer.String() }, + "printIPAMWarning": func(status ipam.Status) string { + ipsReachable := 0 + for peer, ranges := range status.Ring.OwnedRangesByPeer() { + if status.IsKnownPeer(peer) { + for _, chunk := range ranges { + ipsReachable += int(chunk.Size()) + } + } + } + if ipsReachable == 0 { + return "- all IPs owned by unreachable peers!" + } + return "" + }, "printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string { counts := make(map[string]int) for _, conn := range conns { @@ -156,7 +170,7 @@ var statusTemplate = defTemplate("status", `\ Service: ipam {{if .IPAM.Entries}}\ - Consensus: achieved + Consensus: achieved {{printIPAMWarning .IPAM}} {{else if .IPAM.Paxos}}\ Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}}) {{else}}\ From 3a9bcbcf6b7a20403a55de44928cf778a2e634a2 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 23 Nov 2015 17:54:27 +0000 Subject: [PATCH 052/135] Remove specialised knowledge of IPAM from weave status formatter and unnecessary info from `weave report` output --- ipam/ring/ring.go | 25 ++++++++-------- ipam/status.go | 35 ++++++++++------------ prog/weaver/http.go | 71 ++++++++++++++++++++------------------------- 3 files changed, 59 insertions(+), 72 deletions(-) diff --git a/ipam/ring/ring.go b/ipam/ring/ring.go index e992dd43..b7b948c3 100644 --- a/ipam/ring/ring.go +++ b/ipam/ring/ring.go @@ -316,24 +316,25 @@ func (r *Ring) OwnedRanges() (result []address.Range) { return r.splitRangesOverZero(result) } -func (r *Ring) OwnedRangesByPeer() map[mesh.PeerName][]address.Range { +// For printing status +type RangeInfo struct { + Peer mesh.PeerName + address.Range + Version uint32 +} + +func (r *Ring) AllRangeInfo() (result []RangeInfo) { r.assertInvariants() - result := make(map[mesh.PeerName][]address.Range) for i, entry := range r.Entries { nextEntry := r.Entries.entry(i + 1) - resultPeer, found := result[entry.Peer] - r := address.Range{Start: entry.Token, End: nextEntry.Token} - if !found { - result[entry.Peer] = []address.Range{r} - } else { - result[entry.Peer] = append(resultPeer, r) + ranges := []address.Range{{Start: entry.Token, End: nextEntry.Token}} + ranges = r.splitRangesOverZero(ranges) + for _, r := range ranges { + result = append(result, RangeInfo{entry.Peer, r, entry.Version}) } } - for peer, resultPeer := range result { - result[peer] = r.splitRangesOverZero(resultPeer) - } - return result + return } // ClaimForPeers claims the entire ring for the array of peers passed diff --git a/ipam/status.go b/ipam/status.go index 39bc4588..57af14a9 100644 --- a/ipam/status.go +++ b/ipam/status.go @@ -2,9 +2,6 @@ package ipam import ( "github.com/weaveworks/weave/ipam/paxos" - "github.com/weaveworks/weave/ipam/ring" - "github.com/weaveworks/weave/ipam/space" - "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" ) @@ -13,21 +10,18 @@ type Status struct { Range string RangeNumIPs int DefaultSubnet string - Ring *ring.Ring // information on ranges owned by all peers - Space space.Space // more detail on ranges owned by us - Owned map[string][]address.Address // addresses by container-ID - OurName mesh.PeerName - Nicknames map[mesh.PeerName]string `json:"-"` - IsKnownPeer func(mesh.PeerName) bool `json:"-"` Entries []EntryStatus PendingClaims []ClaimStatus PendingAllocates []string } type EntryStatus struct { - Token string - Peer string - Version uint32 + Token string + Size uint32 + Peer string + Nickname string + IsKnownPeer bool + Version uint32 } type ClaimStatus struct { @@ -52,12 +46,6 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status { allocator.universe.String(), int(allocator.universe.Size()), defaultSubnet.String(), - allocator.ring, - allocator.space, - allocator.owned, - allocator.ourName, - allocator.nicknames, - allocator.isKnownPeer, newEntryStatusSlice(allocator), newClaimStatusSlice(allocator), newAllocateIdentSlice(allocator)} @@ -73,8 +61,15 @@ func newEntryStatusSlice(allocator *Allocator) []EntryStatus { return slice } - for _, entry := range allocator.ring.Entries { - slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version}) + for _, r := range allocator.ring.AllRangeInfo() { + slice = append(slice, EntryStatus{ + Token: r.Start.String(), + Size: uint32(r.Size()), + Peer: r.Peer.String(), + Nickname: allocator.nicknames[r.Peer], + IsKnownPeer: allocator.isKnownPeer(r.Peer), + Version: r.Version, + }) } return slice diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 8db1d425..10b542d4 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -33,72 +33,59 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } return strings.Join(servers, ", ") }, - "printIPAMRanges": func(status ipam.Status) string { + "printIPAMRanges": func(router weave.NetworkRouterStatus, status ipam.Status) string { var buffer bytes.Buffer type stats struct { - Ranges []address.Range - Reachable bool + ips uint32 + nickname string + reachable bool } - peerStats := make(map[mesh.PeerName]*stats) + peerStats := make(map[string]*stats) - for peer, ranges := range status.Ring.OwnedRangesByPeer() { - s, found := peerStats[peer] + for _, entry := range status.Entries { + s, found := peerStats[entry.Peer] if !found { - s = &stats{Reachable: status.IsKnownPeer(peer)} - peerStats[peer] = s + s = &stats{nickname: entry.Nickname, reachable: entry.IsKnownPeer} + peerStats[entry.Peer] = s } - s.Ranges = append(s.Ranges, ranges...) + s.ips += entry.Size } - appendOwned := func(peer mesh.PeerName, nickName string, reachable bool, ranges []address.Range) { + printOwned := func(name string, nickName string, reachable bool, ips uint32) { reachableStr := "" if !reachable { reachableStr = "- unreachable!" } - ipsInRange := 0 - for _, chunk := range ranges { - ipsInRange += int(chunk.Size()) - } - percentageRanges := float32(ipsInRange) * 100.0 / float32(status.RangeNumIPs) + percentageRanges := float32(ips) * 100.0 / float32(status.RangeNumIPs) - displayName := peer.String() + "(" + nickName + ")" - fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of total) %s\n", - displayName, ipsInRange, percentageRanges, reachableStr) + displayName := name + " (" + nickName + ")" + fmt.Fprintf(&buffer, "%30s: %8d IPs (%04.1f%% of total) %s\n", + displayName, ips, percentageRanges, reachableStr) } - // print the local addresses - if ourStats := peerStats[status.OurName]; ourStats != nil { - appendOwned(status.OurName, "local", true, ourStats.Ranges) + // print the local info first + if ourStats := peerStats[router.Name]; ourStats != nil { + printOwned(router.Name, "local", true, ourStats.ips) } // and then the rest for peer, stats := range peerStats { - if peer != status.OurName { - nickname, found := status.Nicknames[peer] - if !found { - nickname = peer.String() - } - appendOwned(peer, nickname, stats.Reachable, stats.Ranges) + if peer != router.Name { + printOwned(peer, stats.nickname, stats.reachable, stats.ips) } } return buffer.String() }, - "printIPAMWarning": func(status ipam.Status) string { - ipsReachable := 0 - for peer, ranges := range status.Ring.OwnedRangesByPeer() { - if status.IsKnownPeer(peer) { - for _, chunk := range ranges { - ipsReachable += int(chunk.Size()) - } + "allIPAMOwnersUnreachable": func(status ipam.Status) bool { + for _, entry := range status.Entries { + if entry.Size > 0 && entry.IsKnownPeer { + return false } } - if ipsReachable == 0 { - return "- all IPs owned by unreachable peers!" - } - return "" + return true }, "printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string { counts := make(map[string]int) @@ -170,7 +157,11 @@ var statusTemplate = defTemplate("status", `\ Service: ipam {{if .IPAM.Entries}}\ - Consensus: achieved {{printIPAMWarning .IPAM}} +{{if allIPAMOwnersUnreachable .IPAM}}\ + Halted: all IPs owned by unreachable peers +{{else}}\ + Consensus: achieved +{{end}}\ {{else if .IPAM.Paxos}}\ Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}}) {{else}}\ @@ -221,7 +212,7 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\ {{end}}\ `) -var ipamTemplate = defTemplate("ipamTemplate", `{{printIPAMRanges .IPAM}}`) +var ipamTemplate = defTemplate("ipamTemplate", `{{printIPAMRanges .Router .IPAM}}`) type WeaveStatus struct { Version string From cb65a80dce23a197f56d75c695950a9668fff761 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 24 Nov 2015 10:48:30 +0000 Subject: [PATCH 053/135] Reworded IPAM service description in `weave status` following review in #1702 --- prog/weaver/http.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 10b542d4..1e7c0480 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -60,14 +60,14 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } percentageRanges := float32(ips) * 100.0 / float32(status.RangeNumIPs) - displayName := name + " (" + nickName + ")" - fmt.Fprintf(&buffer, "%30s: %8d IPs (%04.1f%% of total) %s\n", + displayName := name + " (" + nickName + "):" + fmt.Fprintf(&buffer, "%-32s %8d IPs (%04.1f%% of total) %s\n", displayName, ips, percentageRanges, reachableStr) } // print the local info first if ourStats := peerStats[router.Name]; ourStats != nil { - printOwned(router.Name, "local", true, ourStats.ips) + printOwned(router.Name, ourStats.nickname, true, ourStats.ips) } // and then the rest @@ -158,14 +158,16 @@ var statusTemplate = defTemplate("status", `\ Service: ipam {{if .IPAM.Entries}}\ {{if allIPAMOwnersUnreachable .IPAM}}\ - Halted: all IPs owned by unreachable peers + Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead +{{else if len .IPAM.PendingAllocates}}\ + Status: waiting for IP range grant from peers {{else}}\ - Consensus: achieved + Status: ready {{end}}\ {{else if .IPAM.Paxos}}\ - Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}}) + Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}}) {{else}}\ - Consensus: deferred + Status: idle {{end}}\ Range: {{.IPAM.Range}} DefaultSubnet: {{.IPAM.DefaultSubnet}} From 2fbbb802bd96a159e8a32644650c8bf6c35e756f Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 24 Nov 2015 10:36:21 +0000 Subject: [PATCH 054/135] Don't assert invariants when printing status - if it's broken you might appreciate being able to print it out --- ipam/ring/ring.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/ipam/ring/ring.go b/ipam/ring/ring.go index b7b948c3..2e384eca 100644 --- a/ipam/ring/ring.go +++ b/ipam/ring/ring.go @@ -324,8 +324,6 @@ type RangeInfo struct { } func (r *Ring) AllRangeInfo() (result []RangeInfo) { - r.assertInvariants() - for i, entry := range r.Entries { nextEntry := r.Entries.entry(i + 1) ranges := []address.Range{{Start: entry.Token, End: nextEntry.Token}} From 8bbf8231d20e4b2d009c9d5d407c286332afecba Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 24 Nov 2015 11:15:46 +0000 Subject: [PATCH 055/135] Show which range is being allocated in `weave report` --- ipam/status.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipam/status.go b/ipam/status.go index 57af14a9..e7a252ba 100644 --- a/ipam/status.go +++ b/ipam/status.go @@ -1,6 +1,7 @@ package ipam import ( + "fmt" "github.com/weaveworks/weave/ipam/paxos" "github.com/weaveworks/weave/net/address" ) @@ -88,7 +89,7 @@ func newAllocateIdentSlice(allocator *Allocator) []string { var slice []string for _, op := range allocator.pendingAllocates { allocate := op.(*allocate) - slice = append(slice, allocate.ident) + slice = append(slice, fmt.Sprintf("%s %s", allocate.ident, allocate.r.String())) } return slice } From 175f38414e00bccb385281d1d49fe55ba08ff2ec Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 28 Nov 2015 20:15:57 +0000 Subject: [PATCH 056/135] cosmetic --- mesh/connection.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mesh/connection.go b/mesh/connection.go index fdcdcf3d..7edb9253 100644 --- a/mesh/connection.go +++ b/mesh/connection.go @@ -294,8 +294,7 @@ func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) { var hasShortID bool if shortIDStr, present := features["ShortID"]; present { hasShortID = true - shortID, err = strconv.ParseUint(shortIDStr, 10, - PeerShortIDBits) + shortID, err = strconv.ParseUint(shortIDStr, 10, PeerShortIDBits) if err != nil { return nil, err } From 9dee2f223053601c30669b973cb22bceb5c556dd Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 29 Nov 2015 16:32:50 +0000 Subject: [PATCH 057/135] update Peer.HasShortID as part of topology merge This was missing. There are two parts to this change: 1. updating the Peers.byName mapping when HasShortID changed (in addition to the existing update on ShortID change). We need to be careful about the sequencing here - both ShortID and HasShortID must be set between the calls to deleteByShortID and addByShortID, since the former is meant to delete the existing entry (hence use the old values) and the latter add the new entry (hence use the new values). 2. update a peer when the received info has the same version and UID but the current record has HasShortID=false, and the received info has HasShortID=true. This caters for the case where we receive an update about a weave 1.2+ peer first via a pre-1.2 peer (which does not transmit ShortIDs, and always sets HasShortID=false) and subsequently via a 1.2+ peer. The former update will force us to communicate with that peer over the 'sleeve' overlay. We want to accept the latter update, even though the version is the same, in order to update the ShortID and HasShortID, so we can switch to fastdp. Fixes #1731. --- mesh/peers.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mesh/peers.go b/mesh/peers.go index 70ec4c8d..486c27fd 100644 --- a/mesh/peers.go +++ b/mesh/peers.go @@ -476,16 +476,20 @@ func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]Connecti newUpdate[name] = void default: // existing peer if newPeer.Version < peer.Version || - (newPeer.Version == peer.Version && newPeer.UID <= peer.UID) { + (newPeer.Version == peer.Version && + (newPeer.UID < peer.UID || + (newPeer.UID == peer.UID && + (!newPeer.HasShortID || peer.HasShortID)))) { continue } peer.Version = newPeer.Version peer.UID = newPeer.UID peer.connections = makeConnsMap(peer, connSummaries, peers.byName) - if newPeer.ShortID != peer.ShortID { + if newPeer.ShortID != peer.ShortID || newPeer.HasShortID != peer.HasShortID { peers.deleteByShortID(peer, pending) peer.ShortID = newPeer.ShortID + peer.HasShortID = newPeer.HasShortID peers.addByShortID(peer, pending) } newUpdate[name] = void From 7e0e1fbaad92952f674e6b8ad64cd3232331069c Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 30 Nov 2015 12:07:59 +0000 Subject: [PATCH 058/135] Adjust formatting after review feedback; hostnames up to 14 chars will now align --- prog/weaver/http.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 1e7c0480..1dc84925 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -60,8 +60,8 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } percentageRanges := float32(ips) * 100.0 / float32(status.RangeNumIPs) - displayName := name + " (" + nickName + "):" - fmt.Fprintf(&buffer, "%-32s %8d IPs (%04.1f%% of total) %s\n", + displayName := name + "(" + nickName + ")" + fmt.Fprintf(&buffer, "%-37v %8d IPs (%04.1f%% of total) %s\n", displayName, ips, percentageRanges, reachableStr) } @@ -198,7 +198,7 @@ var peersTemplate = defTemplate("peers", `\ {{.Name}}({{.NickName}}) {{range .Connections}}\ {{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} \ -{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-32v" $nameNickName}} \ +{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-37v" $nameNickName}} \ {{if .Established}}established{{else}}pending{{end}} {{end}}\ {{end}}\ From bd2b8df7c56ea963672b1fba0e44849b9cb198c1 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 15:17:50 +0000 Subject: [PATCH 059/135] Refactor to use Weave's docker client and watcher --- common/docker/client.go | 13 +++++++++++++ plugin/dns.go | 4 ++-- plugin/docker.go | 16 +--------------- plugin/driver.go | 2 +- plugin/ipam.go | 4 ++-- plugin/watcher.go | 28 ++++++---------------------- 6 files changed, 25 insertions(+), 42 deletions(-) diff --git a/common/docker/client.go b/common/docker/client.go index 6a4eaff1..ded610b3 100644 --- a/common/docker/client.go +++ b/common/docker/client.go @@ -80,3 +80,16 @@ func (c *Client) IsContainerNotRunning(idStr string) bool { Log.Errorf("[docker] Could not check container status: %s", err) return false } + +func (c *Client) GetContainerBridgeIP(nameOrID string) (string, error) { + Log.Debugf("Getting IP for container %s", nameOrID) + info, err := c.InspectContainer(nameOrID) + if err != nil { + return "", err + } + return info.NetworkSettings.IPAddress, nil +} + +func (c *Client) InspectContainer(nameOrId string) (*docker.Container, error) { + return c.Client.InspectContainer(nameOrId) +} diff --git a/plugin/dns.go b/plugin/dns.go index 27652cfe..526d46bd 100644 --- a/plugin/dns.go +++ b/plugin/dns.go @@ -8,7 +8,7 @@ import ( ) func (d *dockerer) registerWithDNS(ID string, fqdn string, ip string) error { - dnsip, err := d.getContainerBridgeIP(WeaveDNSContainer) + dnsip, err := d.client.GetContainerBridgeIP(WeaveContainer) if err != nil { return fmt.Errorf("nameserver not available: %s", err) } @@ -33,7 +33,7 @@ func (d *dockerer) registerWithDNS(ID string, fqdn string, ip string) error { } func (d *dockerer) deregisterWithDNS(ID string, ip string) error { - dnsip, err := d.getContainerBridgeIP(WeaveDNSContainer) + dnsip, err := d.client.GetContainerBridgeIP(WeaveContainer) if err != nil { return fmt.Errorf("nameserver not available: %s", err) } diff --git a/plugin/docker.go b/plugin/docker.go index d7b4b337..4934d737 100644 --- a/plugin/docker.go +++ b/plugin/docker.go @@ -1,23 +1,9 @@ package plugin import ( - "github.com/fsouza/go-dockerclient" - . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/common/docker" ) type dockerer struct { client *docker.Client } - -func (d *dockerer) getContainerBridgeIP(nameOrID string) (string, error) { - Log.Debugf("Getting IP for container %s", nameOrID) - info, err := d.InspectContainer(nameOrID) - if err != nil { - return "", err - } - return info.NetworkSettings.IPAddress, nil -} - -func (d *dockerer) InspectContainer(nameOrId string) (*docker.Container, error) { - return d.client.InspectContainer(nameOrId) -} diff --git a/plugin/driver.go b/plugin/driver.go index 8467c58f..85cded7c 100644 --- a/plugin/driver.go +++ b/plugin/driver.go @@ -7,10 +7,10 @@ import ( "github.com/docker/libnetwork/types" . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/common/docker" "github.com/weaveworks/weave/common/odp" "github.com/weaveworks/weave/plugin/skel" - docker "github.com/fsouza/go-dockerclient" "github.com/vishvananda/netlink" ) diff --git a/plugin/ipam.go b/plugin/ipam.go index 927f7fff..bbc28aaf 100644 --- a/plugin/ipam.go +++ b/plugin/ipam.go @@ -10,7 +10,7 @@ import ( ) func (d *dockerer) ipamOp(ID string, op string) (*net.IPNet, error) { - weaveip, err := d.getContainerBridgeIP(WeaveContainer) + weaveip, err := d.client.GetContainerBridgeIP(WeaveContainer) Log.Debugf("IPAM operation %s for %s", op, ID) if err != nil { return nil, err @@ -52,7 +52,7 @@ func (d *dockerer) lookupIP(ID string) (*net.IPNet, error) { // release an IP which is no longer needed func (d *dockerer) releaseIP(ID string) error { - weaveip, err := d.getContainerBridgeIP(WeaveContainer) + weaveip, err := d.client.GetContainerBridgeIP(WeaveContainer) if err != nil { return err } diff --git a/plugin/watcher.go b/plugin/watcher.go index 50f87dd5..1bfafd03 100644 --- a/plugin/watcher.go +++ b/plugin/watcher.go @@ -4,19 +4,16 @@ import ( "fmt" "strings" - "github.com/fsouza/go-dockerclient" . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/common/docker" ) const ( - WeaveDNSContainer = "weavedns" - WeaveDomain = "weave.local" + WeaveDomain = "weave.local" ) type watcher struct { dockerer - networks map[string]bool - events chan *docker.APIEvents } type Watcher interface { @@ -27,31 +24,18 @@ func NewWatcher(client *docker.Client) (Watcher, error) { dockerer: dockerer{ client: client, }, - networks: make(map[string]bool), - events: make(chan *docker.APIEvents), } - err := client.AddEventListener(w.events) + err := client.AddObserver(w) if err != nil { return nil, err } - go func() { - for event := range w.events { - switch event.Status { - case "start": - w.ContainerStart(event.ID) - case "die": - w.ContainerDied(event.ID) - } - } - }() - return w, nil } -func (w *watcher) ContainerStart(id string) { +func (w *watcher) ContainerStarted(id string) { Log.Debugf("Container started %s", id) - info, err := w.InspectContainer(id) + info, err := w.client.InspectContainer(id) if err != nil { Log.Warningf("error inspecting container: %s", err) return @@ -69,7 +53,7 @@ func (w *watcher) ContainerStart(id string) { func (w *watcher) ContainerDied(id string) { Log.Debugf("Container died %s", id) - info, err := w.InspectContainer(id) + info, err := w.client.InspectContainer(id) if err != nil { Log.Warningf("error inspecting container: %s", err) return From e101dde62e1373f523b1194f8f6a812669170be7 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 16:36:19 +0000 Subject: [PATCH 060/135] Extract 'api' package --- Makefile | 2 +- api/dns.go | 18 ++++++++++ api/ipam.go | 43 +++++++++++++++++++++++ api/weaveapi.go | 59 +++++++++++++++++++++++++++++++ plugin/dns.go | 55 ----------------------------- plugin/ipam.go | 89 ----------------------------------------------- plugin/watcher.go | 22 +++++++++--- 7 files changed, 139 insertions(+), 149 deletions(-) create mode 100644 api/dns.go create mode 100644 api/ipam.go create mode 100644 api/weaveapi.go delete mode 100644 plugin/dns.go delete mode 100644 plugin/ipam.go diff --git a/Makefile b/Makefile index d802efc0..d2adf329 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ $(NETCHECK_EXE): prog/netcheck/netcheck.go $(SIGPROXY_EXE): prog/sigproxy/main.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go $(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go -$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*.go plugin/skel/*.go +$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*.go plugin/skel/*.go api/*.go common/docker/*.go $(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE): go get -tags netgo ./$(@D) diff --git a/api/dns.go b/api/dns.go new file mode 100644 index 00000000..750c34f2 --- /dev/null +++ b/api/dns.go @@ -0,0 +1,18 @@ +package api + +import ( + "fmt" + "net/url" +) + +func (client *Client) RegisterWithDNS(ID string, fqdn string, ip string) error { + data := url.Values{} + data.Add("fqdn", fqdn) + _, err := httpVerb("PUT", fmt.Sprintf("%s/name/%s/%s", client.baseUrl, ID, ip), data) + return err +} + +func (client *Client) DeregisterWithDNS(ID string, ip string) error { + _, err := httpVerb("DELETE", fmt.Sprintf("%s/name/%s/%s", client.baseUrl, ID, ip), nil) + return err +} diff --git a/api/ipam.go b/api/ipam.go new file mode 100644 index 00000000..91a3053a --- /dev/null +++ b/api/ipam.go @@ -0,0 +1,43 @@ +package api + +import ( + "fmt" + "net" + + . "github.com/weaveworks/weave/common" +) + +func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) { + Log.Debugf("IPAM operation %s for %s", op, ID) + ip, err := httpVerb(op, fmt.Sprintf("%s/ip/%s", client.baseUrl, ID), nil) + if err != nil { + return nil, err + } + return parseIP(ip) +} + +// returns an IP for the ID given, allocating a fresh one if necessary +func (client *Client) AllocateIP(ID string) (*net.IPNet, error) { + return client.ipamOp(ID, "POST") +} + +// returns an IP for the ID given, or nil if one has not been +// allocated +func (client *Client) LookupIP(ID string) (*net.IPNet, error) { + return client.ipamOp(ID, "GET") +} + +// release an IP which is no longer needed +func (client *Client) ReleaseIP(ID string) error { + _, err := client.ipamOp(ID, "DELETE") + return err +} + +func parseIP(body string) (*net.IPNet, error) { + ip, ipnet, err := net.ParseCIDR(string(body)) + if err != nil { + return nil, err + } + ipnet.IP = ip + return ipnet, nil +} diff --git a/api/weaveapi.go b/api/weaveapi.go new file mode 100644 index 00000000..d75394bd --- /dev/null +++ b/api/weaveapi.go @@ -0,0 +1,59 @@ +package api + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + + . "github.com/weaveworks/weave/common" +) + +const ( + WeaveHttpPort = 6784 +) + +type Client struct { + baseUrl string +} + +func httpVerb(verb string, url string, values url.Values) (string, error) { + Log.Debugf("weave %s to %s with %v", verb, url, values) + var body io.Reader + if values != nil { + body = strings.NewReader(values.Encode()) + } + req, err := http.NewRequest(verb, url, body) + if err != nil { + return "", err + } + if values != nil { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + if resp, err := http.DefaultClient.Do(req); err != nil { + return "", err + } else { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent { + return string(body), nil + } else { + return "", errors.New(resp.Status + ": " + string(body)) + } + } +} + +func NewClient(addr string) *Client { + return &Client{baseUrl: fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)} +} + +func (client *Client) Connect(remote string) error { + _, err := httpVerb("POST", client.baseUrl+"/connect", url.Values{"peer": {remote}}) + return err +} diff --git a/plugin/dns.go b/plugin/dns.go deleted file mode 100644 index 526d46bd..00000000 --- a/plugin/dns.go +++ /dev/null @@ -1,55 +0,0 @@ -package plugin - -import ( - "fmt" - "net/http" - "net/url" - "strings" -) - -func (d *dockerer) registerWithDNS(ID string, fqdn string, ip string) error { - dnsip, err := d.client.GetContainerBridgeIP(WeaveContainer) - if err != nil { - return fmt.Errorf("nameserver not available: %s", err) - } - data := url.Values{} - data.Add("fqdn", fqdn) - - req, err := http.NewRequest("PUT", fmt.Sprintf("http://%s:6785/name/%s/%s", dnsip, ID, ip), strings.NewReader(data.Encode())) - if err != nil { - return err - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - cl := &http.Client{} - res, err := cl.Do(req) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return fmt.Errorf("non-OK status from nameserver: %d", res.StatusCode) - } - return nil -} - -func (d *dockerer) deregisterWithDNS(ID string, ip string) error { - dnsip, err := d.client.GetContainerBridgeIP(WeaveContainer) - if err != nil { - return fmt.Errorf("nameserver not available: %s", err) - } - - req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s:6785/name/%s/%s", dnsip, ID, ip), nil) - if err != nil { - return err - } - - cl := &http.Client{} - res, err := cl.Do(req) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return fmt.Errorf("non-OK status from nameserver: %d", res.StatusCode) - } - return nil -} diff --git a/plugin/ipam.go b/plugin/ipam.go deleted file mode 100644 index bbc28aaf..00000000 --- a/plugin/ipam.go +++ /dev/null @@ -1,89 +0,0 @@ -package plugin - -import ( - "fmt" - "io/ioutil" - "net" - "net/http" - - . "github.com/weaveworks/weave/common" -) - -func (d *dockerer) ipamOp(ID string, op string) (*net.IPNet, error) { - weaveip, err := d.client.GetContainerBridgeIP(WeaveContainer) - Log.Debugf("IPAM operation %s for %s", op, ID) - if err != nil { - return nil, err - } - - var res *http.Response - - url := fmt.Sprintf("http://%s:6784/ip/%s", weaveip, ipamID(ID)) - Log.Debugf("Attempting to %s to %s", op, url) - if op == "POST" { - res, err = http.Post(url, "", nil) - } else if op == "GET" { - res, err = http.Get(url) - } - - if err != nil { - return nil, err - } - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("received status %d from IPAM", res.StatusCode) - } - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - return parseIP(string(body)) -} - -// returns an IP for the ID given, allocating a fresh one if necessary -func (d *dockerer) allocateIP(ID string) (*net.IPNet, error) { - return d.ipamOp(ID, "POST") -} - -// returns an IP for the ID given, or nil if one has not been -// allocated -func (d *dockerer) lookupIP(ID string) (*net.IPNet, error) { - return d.ipamOp(ID, "GET") -} - -// release an IP which is no longer needed -func (d *dockerer) releaseIP(ID string) error { - weaveip, err := d.client.GetContainerBridgeIP(WeaveContainer) - if err != nil { - return err - } - req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s:6784/ip/%s", weaveip, ipamID(ID)), nil) - if err != nil { - return err - } - cl := &http.Client{} - res, err := cl.Do(req) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent { - return fmt.Errorf("unexpected HTTP status code from IP release: %d", res.StatusCode) - } - return nil -} - -func parseIP(body string) (*net.IPNet, error) { - ip, ipnet, err := net.ParseCIDR(string(body)) - if err != nil { - return nil, err - } - ipnet.IP = ip - return ipnet, nil -} - -// If something looking like a container ID is supplied to IPAM, it -// will try to check that it's running, and return nothing (and empty -// string) if it's not. So I make sure it doesn't look like a -// container ID by including non-hex characters. -func ipamID(ID string) string { - return "endpoint:" + ID -} diff --git a/plugin/watcher.go b/plugin/watcher.go index 1bfafd03..3dbbe504 100644 --- a/plugin/watcher.go +++ b/plugin/watcher.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + weaveapi "github.com/weaveworks/weave/api" . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/common/docker" ) @@ -14,6 +15,7 @@ const ( type watcher struct { dockerer + weave *weaveapi.Client } type Watcher interface { @@ -33,6 +35,18 @@ func NewWatcher(client *docker.Client) (Watcher, error) { return w, nil } +func (w *watcher) haveWeaveClient() bool { + if w.weave == nil { + dnsip, err := w.client.GetContainerBridgeIP(WeaveContainer) + if err != nil { + Log.Warningf("nameserver not available: %s", err) + return false + } + w.weave = weaveapi.NewClient(dnsip) + } + return true +} + func (w *watcher) ContainerStarted(id string) { Log.Debugf("Container started %s", id) info, err := w.client.InspectContainer(id) @@ -41,11 +55,11 @@ func (w *watcher) ContainerStarted(id string) { return } // FIXME: check that it's on our network; but, the docker client lib doesn't know about .NetworkID - if isSubdomain(info.Config.Domainname, WeaveDomain) { + if isSubdomain(info.Config.Domainname, WeaveDomain) && w.haveWeaveClient() { // one of ours ip := info.NetworkSettings.IPAddress fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname) - if err := w.registerWithDNS(id, fqdn, ip); err != nil { + if err := w.weave.RegisterWithDNS(id, fqdn, ip); err != nil { Log.Warningf("unable to register with weaveDNS: %s", err) } } @@ -58,9 +72,9 @@ func (w *watcher) ContainerDied(id string) { Log.Warningf("error inspecting container: %s", err) return } - if isSubdomain(info.Config.Domainname, WeaveDomain) { + if isSubdomain(info.Config.Domainname, WeaveDomain) && w.haveWeaveClient() { ip := info.NetworkSettings.IPAddress - if err := w.deregisterWithDNS(id, ip); err != nil { + if err := w.weave.DeregisterWithDNS(id, ip); err != nil { Log.Warningf("unable to deregister with weaveDNS: %s", err) } } From 85e465cfa41d055665b1daf52025836875eea65b Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 17:16:43 +0000 Subject: [PATCH 061/135] Eliminate 'dockerer' --- plugin/docker.go | 9 --------- plugin/watcher.go | 10 +++------- 2 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 plugin/docker.go diff --git a/plugin/docker.go b/plugin/docker.go deleted file mode 100644 index 4934d737..00000000 --- a/plugin/docker.go +++ /dev/null @@ -1,9 +0,0 @@ -package plugin - -import ( - "github.com/weaveworks/weave/common/docker" -) - -type dockerer struct { - client *docker.Client -} diff --git a/plugin/watcher.go b/plugin/watcher.go index 3dbbe504..701c53b9 100644 --- a/plugin/watcher.go +++ b/plugin/watcher.go @@ -14,19 +14,15 @@ const ( ) type watcher struct { - dockerer - weave *weaveapi.Client + client *docker.Client + weave *weaveapi.Client } type Watcher interface { } func NewWatcher(client *docker.Client) (Watcher, error) { - w := &watcher{ - dockerer: dockerer{ - client: client, - }, - } + w := &watcher{client: client} err := client.AddObserver(w) if err != nil { return nil, err From 5eb99fe5efd90530a9bfd3e4606332bb83a6cfd2 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 18:11:37 +0000 Subject: [PATCH 062/135] Get container IP for various networks --- common/docker/client.go | 15 ++++++++++++++- plugin/watcher.go | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/common/docker/client.go b/common/docker/client.go index ded610b3..f6de8981 100644 --- a/common/docker/client.go +++ b/common/docker/client.go @@ -81,12 +81,25 @@ func (c *Client) IsContainerNotRunning(idStr string) bool { return false } -func (c *Client) GetContainerBridgeIP(nameOrID string) (string, error) { +// This is intended to find an IP address that we can reach the container on; +// if it is on the Docker bridge network then that address; if on the host network +// then localhost +func (c *Client) GetContainerIP(nameOrID string) (string, error) { Log.Debugf("Getting IP for container %s", nameOrID) info, err := c.InspectContainer(nameOrID) if err != nil { return "", err } + if info.NetworkSettings.Networks != nil { + Log.Debugln("Networks: ", info.NetworkSettings.Networks) + if bridgeNetwork, ok := info.NetworkSettings.Networks["bridge"]; ok { + return bridgeNetwork.IPAddress, nil + } else if _, ok := info.NetworkSettings.Networks["host"]; ok { + return "127.0.0.1", nil + } + } else if info.HostConfig.NetworkMode == "host" { + return "127.0.0.1", nil + } return info.NetworkSettings.IPAddress, nil } diff --git a/plugin/watcher.go b/plugin/watcher.go index 701c53b9..c37b41e0 100644 --- a/plugin/watcher.go +++ b/plugin/watcher.go @@ -33,7 +33,7 @@ func NewWatcher(client *docker.Client) (Watcher, error) { func (w *watcher) haveWeaveClient() bool { if w.weave == nil { - dnsip, err := w.client.GetContainerBridgeIP(WeaveContainer) + dnsip, err := w.client.GetContainerIP(WeaveContainer) if err != nil { Log.Warningf("nameserver not available: %s", err) return false From a22b88ed89110125893ddcb76fa3d0eb437b8f9e Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 27 Nov 2015 16:14:23 +0000 Subject: [PATCH 063/135] Track endpoints so we can register the right address with DNS --- plugin/driver.go | 36 +++++++++++++++++++++++++++--------- plugin/watcher.go | 43 +++++++++++++++---------------------------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/plugin/driver.go b/plugin/driver.go index 85cded7c..e5b1e413 100644 --- a/plugin/driver.go +++ b/plugin/driver.go @@ -2,6 +2,7 @@ package plugin import ( "fmt" + "sync" "github.com/docker/libnetwork/drivers/remote/api" "github.com/docker/libnetwork/types" @@ -22,6 +23,8 @@ const ( type driver struct { version string nameserver string + sync.RWMutex + endpoints map[string]struct{} } func New(version string, nameserver string) (skel.Driver, error) { @@ -30,15 +33,17 @@ func New(version string, nameserver string) (skel.Driver, error) { return nil, errorf("could not connect to docker: %s", err) } - _, err = NewWatcher(client) + driver := &driver{ + nameserver: nameserver, + version: version, + endpoints: make(map[string]struct{}), + } + + _, err = NewWatcher(client, driver) if err != nil { return nil, err } - - return &driver{ - nameserver: nameserver, - version: version, - }, nil + return driver, nil } func errorf(format string, a ...interface{}) error { @@ -76,18 +81,31 @@ func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.Cr if create.Interface == nil { return nil, fmt.Errorf("Not supported: creating an interface from within CreateEndpoint") } + driver.Lock() + driver.endpoints[endID] = struct{}{} + driver.Unlock() resp := &api.CreateEndpointResponse{} Log.Infof("Create endpoint %s %+v", endID, resp) return resp, nil } -func (driver *driver) DeleteEndpoint(delete *api.DeleteEndpointRequest) error { - Log.Debugf("Delete endpoint request: %+v", delete) - Log.Infof("Delete endpoint %s", delete.EndpointID) +func (driver *driver) DeleteEndpoint(deleteReq *api.DeleteEndpointRequest) error { + Log.Debugf("Delete endpoint request: %+v", deleteReq) + Log.Infof("Delete endpoint %s", deleteReq.EndpointID) + driver.Lock() + delete(driver.endpoints, deleteReq.EndpointID) + driver.Unlock() return nil } +func (driver *driver) HasEndpoint(endpointID string) bool { + driver.Lock() + _, found := driver.endpoints[endpointID] + driver.Unlock() + return found +} + func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error) { Log.Debugf("Endpoint info request: %+v", req) Log.Infof("Endpoint info %s", req.EndpointID) diff --git a/plugin/watcher.go b/plugin/watcher.go index c37b41e0..fbf43014 100644 --- a/plugin/watcher.go +++ b/plugin/watcher.go @@ -2,7 +2,6 @@ package plugin import ( "fmt" - "strings" weaveapi "github.com/weaveworks/weave/api" . "github.com/weaveworks/weave/common" @@ -16,13 +15,14 @@ const ( type watcher struct { client *docker.Client weave *weaveapi.Client + driver *driver } type Watcher interface { } -func NewWatcher(client *docker.Client) (Watcher, error) { - w := &watcher{client: client} +func NewWatcher(client *docker.Client, driver *driver) (Watcher, error) { + w := &watcher{client: client, driver: driver} err := client.AddObserver(w) if err != nil { return nil, err @@ -50,34 +50,21 @@ func (w *watcher) ContainerStarted(id string) { Log.Warningf("error inspecting container: %s", err) return } - // FIXME: check that it's on our network; but, the docker client lib doesn't know about .NetworkID - if isSubdomain(info.Config.Domainname, WeaveDomain) && w.haveWeaveClient() { - // one of ours - ip := info.NetworkSettings.IPAddress - fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname) - if err := w.weave.RegisterWithDNS(id, fqdn, ip); err != nil { - Log.Warningf("unable to register with weaveDNS: %s", err) + if !w.haveWeaveClient() { + return + } + // check that it's on our network, via the endpointID + for _, net := range info.NetworkSettings.Networks { + if w.driver.HasEndpoint(net.EndpointID) { + fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname) + if err := w.weave.RegisterWithDNS(id, fqdn, net.IPAddress); err != nil { + Log.Warningf("unable to register with weaveDNS: %s", err) + } } } } func (w *watcher) ContainerDied(id string) { - Log.Debugf("Container died %s", id) - info, err := w.client.InspectContainer(id) - if err != nil { - Log.Warningf("error inspecting container: %s", err) - return - } - if isSubdomain(info.Config.Domainname, WeaveDomain) && w.haveWeaveClient() { - ip := info.NetworkSettings.IPAddress - if err := w.weave.DeregisterWithDNS(id, ip); err != nil { - Log.Warningf("unable to deregister with weaveDNS: %s", err) - } - } -} - -// Cheap and cheerful way to check x is, or is a subdomain, of -// y. Neither are expected to start with a '.'. -func isSubdomain(x string, y string) bool { - return x == y || strings.HasSuffix(x, "."+y) + // don't need to do this as WeaveDNS removes names on container died anyway + // (note by the time we get this event we can't see the EndpointID) } From cd7c88e5a51696022e096bc1afe52fdf354609d9 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 20 Nov 2015 16:55:17 +0000 Subject: [PATCH 064/135] [proxy] Symlink sockets to /var/run/weave/weave.sock from inside the proxy We copy docker.sock owner to symlinks. Permissions are not copied, because the permissions will inherit from the target. use versioned exec image to create symlinks --- Makefile | 2 +- prog/weaveexec/Dockerfile | 2 +- prog/weaveexec/symlink | 18 ++++ prog/weaveproxy/main.go | 6 +- proxy/proxy.go | 96 +++++++++++++++++++ test/650_proxy_env_test.sh | 6 ++ ...onfig_test.sh => 690_proxy_config_test.sh} | 7 +- weave | 24 ++++- 8 files changed, 152 insertions(+), 9 deletions(-) create mode 100755 prog/weaveexec/symlink rename test/{690_proxy_autoconfig_test.sh => 690_proxy_config_test.sh} (81%) diff --git a/Makefile b/Makefile index d802efc0..c27dbc3d 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ $(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE) $(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver touch $@ -$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) +$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) cp weave prog/weaveexec/weave cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy diff --git a/prog/weaveexec/Dockerfile b/prog/weaveexec/Dockerfile index 3003d03e..2b44f344 100644 --- a/prog/weaveexec/Dockerfile +++ b/prog/weaveexec/Dockerfile @@ -18,7 +18,7 @@ RUN apk add --update \ bind-tools \ && rm -rf /var/cache/apk/* -ADD ./weave ./sigproxy ./weaveproxy /home/weave/ +ADD ./weave ./sigproxy ./weaveproxy ./symlink /home/weave/ ADD ./netcheck ./docker_tls_args /usr/bin/ ADD ./weavewait /w/w ADD ./weavewait_noop /w-noop/w diff --git a/prog/weaveexec/symlink b/prog/weaveexec/symlink new file mode 100755 index 00000000..56078149 --- /dev/null +++ b/prog/weaveexec/symlink @@ -0,0 +1,18 @@ +#!/bin/sh +# This lets us force-symlink a bunch of stuff all at once, so we can minimize +# running containers. + +set -e + +[ -z "$DEBUG" ] || set -x + +target="$1" +user="$(stat -c '%u:%g' "$target")" +mode="$(stat -c '%a' "$target")" +shift 1 + +for link in "$@" ; do + ln -sfT "$target" "$link" +done +chown -h "$user" "$@" +chmod "$mode" "$@" diff --git a/prog/weaveproxy/main.go b/prog/weaveproxy/main.go index b166d5b3..9db07cd5 100644 --- a/prog/weaveproxy/main.go +++ b/prog/weaveproxy/main.go @@ -19,7 +19,7 @@ func main() { var ( justVersion bool logLevel = "info" - c = proxy.Config{ListenAddrs: []string{}} + c = proxy.Config{Image: "weaveworks/weaveexec"} ) c.Version = version @@ -53,6 +53,10 @@ func main() { SetLogLevel(logLevel) + if image := os.Getenv("EXEC_IMAGE"); image != "" { + c.Image = image + } + Log.Infoln("weave proxy", version) Log.Infoln("Command line arguments:", strings.Join(os.Args[1:], " ")) diff --git a/proxy/proxy.go b/proxy/proxy.go index d6fdaa13..b43a288f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -1,6 +1,7 @@ package proxy import ( + "bytes" "crypto/tls" "errors" "fmt" @@ -8,6 +9,7 @@ import ( "net" "net/http" "os" + "path/filepath" "regexp" "strings" "sync" @@ -25,6 +27,9 @@ const ( defaultCertFile = "cert.pem" dockerSock = "/var/run/docker.sock" dockerSockUnix = "unix://" + dockerSock + + weaveSock = "/var/run/weave/weave.sock" + weaveSockUnix = "unix://" + weaveSock ) var ( @@ -46,6 +51,7 @@ type Config struct { HostnameFromLabel string HostnameMatch string HostnameReplacement string + Image string ListenAddrs []string RewriteInspect bool NoDefaultIPAM bool @@ -186,7 +192,12 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (proxy *Proxy) Listen() []net.Listener { listeners := []net.Listener{} proxy.normalisedAddrs = []string{} + unixAddrs := []string{} for _, addr := range proxy.ListenAddrs { + if strings.HasPrefix(addr, "unix://") { + unixAddrs = append(unixAddrs, addr) + continue + } listener, normalisedAddr, err := proxy.listen(addr) if err != nil { Log.Fatalf("Cannot listen on %s: %s", addr, err) @@ -195,6 +206,20 @@ func (proxy *Proxy) Listen() []net.Listener { proxy.normalisedAddrs = append(proxy.normalisedAddrs, normalisedAddr) } + if len(unixAddrs) > 0 { + listener, _, err := proxy.listen(weaveSockUnix) + if err != nil { + Log.Fatalf("Cannot listen on %s: %s", weaveSockUnix, err) + } + listeners = append(listeners, listener) + + if err := proxy.symlink(unixAddrs); err != nil { + Log.Fatalf("Cannot listen on unix sockets: %s", err) + } + + proxy.normalisedAddrs = append(proxy.normalisedAddrs, weaveSockUnix) + } + for _, addr := range proxy.normalisedAddrs { Log.Infoln("proxy listening on", addr) } @@ -527,3 +552,74 @@ func (proxy *Proxy) updateContainerNetworkSettings(container jsonObject) error { networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size() return nil } + +func (proxy *Proxy) symlink(unixAddrs []string) (err error) { + var container *docker.Container + binds := []string{"/var/run/weave:/var/run/weave"} + froms := []string{} + for _, addr := range unixAddrs { + if addr == weaveSockUnix { + continue + } + from := strings.TrimPrefix(addr, "unix://") + dir := filepath.Dir(from) + binds = append(binds, dir+":"+filepath.Join("/host", dir)) + froms = append(froms, filepath.Join("/host", from)) + proxy.normalisedAddrs = append(proxy.normalisedAddrs, addr) + } + if len(froms) == 0 { + return nil + } + + env := []string{ + "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + } + + if val := os.Getenv("DEBUG"); val != "" { + env = append(env, fmt.Sprintf("%s=%s", "DEBUG", val)) + } + + container, err = proxy.client.CreateContainer(docker.CreateContainerOptions{ + Config: &docker.Config{ + Image: proxy.Image, + Entrypoint: []string{"/home/weave/symlink", weaveSock}, + Cmd: froms, + Env: env, + }, + HostConfig: &docker.HostConfig{Binds: binds}, + }) + if err != nil { + return err + } + + defer func() { + err2 := proxy.client.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID}) + if err == nil { + err = err2 + } + }() + + if err := proxy.client.StartContainer(container.ID, nil); err != nil { + return err + } + + var buf bytes.Buffer + err = proxy.client.AttachToContainer(docker.AttachToContainerOptions{ + Container: container.ID, + ErrorStream: &buf, + Logs: true, + Stderr: true, + }) + if err != nil { + return err + } + + rc, err := proxy.client.WaitContainer(container.ID) + if err != nil { + return err + } + if rc != 0 { + return errors.New(buf.String()) + } + return nil +} diff --git a/test/650_proxy_env_test.sh b/test/650_proxy_env_test.sh index 593f3d6d..345f661e 100755 --- a/test/650_proxy_env_test.sh +++ b/test/650_proxy_env_test.sh @@ -28,4 +28,10 @@ weave_on $HOST1 stop weave_on $HOST1 launch-proxy -H tcp://0.0.0.0:12375 -H unix:///var/run/weave/weave.sock check +# Check we can use weave env/config with unix -Hs specified +weave_on $HOST1 stop +weave_on $HOST1 launch-proxy -H unix:///var/run/weave/weave.sock +assert_raises "run_on $HOST1 'eval \$(weave env) ; docker $CMD'" +assert_raises "run_on $HOST1 'docker \$(weave config) $CMD'" + end_suite diff --git a/test/690_proxy_autoconfig_test.sh b/test/690_proxy_config_test.sh similarity index 81% rename from test/690_proxy_autoconfig_test.sh rename to test/690_proxy_config_test.sh index ce6e18b2..6293a3f5 100755 --- a/test/690_proxy_autoconfig_test.sh +++ b/test/690_proxy_config_test.sh @@ -2,7 +2,7 @@ . ./config.sh -start_suite "Boot the proxy should only listen on client's interface" +start_suite "Various launch-proxy configurations" # Booting it over unix socket listens on unix socket run_on $HOST1 COVERAGE=$COVERAGE sudo -E weave launch-proxy @@ -22,6 +22,11 @@ assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" assert_raises "proxy docker_on $HOST1 ps" weave_on $HOST1 stop-proxy +# Booting it with -H outside /var/run/weave, still works +socket="$(mktemp -d)/weave.sock" +weave_on $HOST1 launch-proxy -H unix://$socket +assert_raises "run_on $HOST1 sudo docker -H unix:///$socket ps" 0 +weave_on $HOST1 stop-proxy # Booting it over tls errors assert_raises "DOCKER_CLIENT_ARGS='--tls' weave_on $HOST1 launch-proxy" 1 diff --git a/weave b/weave index 30acc00e..55300e55 100755 --- a/weave +++ b/weave @@ -1275,17 +1275,30 @@ tls_arg() { PROXY_ARGS="$PROXY_ARGS $1 /home/weave/tls/$3.pem" } +# TODO: Handle relative paths for args +# TODO: Handle args with spaces +host_arg() { + PROXY_HOST="$1" + if [ "$PROXY_HOST" != "${PROXY_HOST#unix://}" ]; then + host=$(dirname ${PROXY_HOST#unix://}) + if [ "$host" = "${host#/}" ]; then + echo "When launching the proxy, unix sockets must be specified as an absolute path." >&2 + exit 1 + fi + PROXY_VOLUMES="$PROXY_VOLUMES -v /var/run/weave:/var/run/weave" + fi + PROXY_ARGS="$PROXY_ARGS -H $1" +} + proxy_parse_args() { while [ $# -gt 0 ]; do case "$1" in -H) - PROXY_HOST="$2" - PROXY_ARGS="$PROXY_ARGS $1 $2" + host_arg "$2" shift ;; -H=*) - PROXY_HOST="${1#*=}" - PROXY_ARGS="$PROXY_ARGS $1" + host_arg "${1#*=}" ;; -no-detect-tls|--no-detect-tls) PROXY_TLS_DETECTION_DISABLED=1 @@ -1346,7 +1359,7 @@ proxy_args() { PROXY_HOST="tcp://0.0.0.0:12375" ;; esac - PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST" + host_arg "$PROXY_HOST" fi } @@ -1559,6 +1572,7 @@ launch_proxy() { -e DOCKER_BRIDGE \ -e WEAVE_DEBUG \ -e COVERAGE \ + -e EXEC_IMAGE=$EXEC_IMAGE \ --entrypoint=/home/weave/weaveproxy \ $WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $COVERAGE_ARGS $PROXY_ARGS) wait_for_status $PROXY_CONTAINER_NAME http_call_unix $PROXY_CONTAINER_NAME status.sock From 7e15eabe65539c30cd0c7393b29992d1f97db4c1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 30 Nov 2015 13:34:38 +0000 Subject: [PATCH 065/135] remove UnknownPeerError when encountering an unknown peer in received gossip, rather than ignoring the entire gossip we create a placeholder Peer. This has version 0 and uid 0, which means it will be replaced by any complete peer information subsequently received via gossip. --- mesh/peer.go | 11 ++++++++++- mesh/peers.go | 20 ++++---------------- mesh/router.go | 8 -------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/mesh/peer.go b/mesh/peer.go index 1846f352..44d14893 100644 --- a/mesh/peer.go +++ b/mesh/peer.go @@ -26,7 +26,12 @@ func randUint16() (r uint16) { type PeerUID uint64 func randomPeerUID() PeerUID { - return PeerUID(randUint64()) + for { + uid := randUint64() + if uid != 0 { // uid 0 is reserved for peer placeholder + return PeerUID(uid) + } + } } func ParsePeerUID(s string) (PeerUID, error) { @@ -95,6 +100,10 @@ func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64, shortI }) } +func NewPeerPlaceholder(name PeerName) *Peer { + return NewPeerFromSummary(PeerSummary{NameByte: name.Bin()}) +} + func NewPeerFrom(peer *Peer) *Peer { return NewPeerFromSummary(peer.PeerSummary) } diff --git a/mesh/peers.go b/mesh/peers.go index 486c27fd..8fc0761b 100644 --- a/mesh/peers.go +++ b/mesh/peers.go @@ -3,7 +3,6 @@ package mesh import ( "bytes" "encoding/gob" - "fmt" "io" "math/rand" "sync" @@ -32,14 +31,6 @@ type ShortIDPeers struct { others []*Peer } -type UnknownPeerError struct { - Name PeerName -} - -func (upe UnknownPeerError) Error() string { - return fmt.Sprint("Reference to unknown peer ", upe.Name) -} - type PeerNameSet map[PeerName]struct{} type ConnectionSummary struct { @@ -334,9 +325,7 @@ func (peers *Peers) ApplyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) return nil, nil, err } - // By this point, we know the update doesn't refer to any peers we - // have no knowledge of. We can now apply the update. Start by - // adding in any new peers. + // Add new peers for name, newPeer := range newPeers { peers.byName[name] = newPeer peers.addByShortID(newPeer, &pending) @@ -446,10 +435,8 @@ func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, de if _, found := peers.byName[remoteName]; found { continue } - // Update refers to a peer which we have no knowledge - // of. Thus we can't apply the update. Abort. - err = UnknownPeerError{remoteName} - return + // Update refers to a peer which we have no knowledge of. + newPeers[remoteName] = NewPeerPlaceholder(remoteName) } } return @@ -484,6 +471,7 @@ func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]Connecti } peer.Version = newPeer.Version peer.UID = newPeer.UID + peer.NickName = newPeer.NickName peer.connections = makeConnsMap(peer, connSummaries, peers.byName) if newPeer.ShortID != peer.ShortID || newPeer.HasShortID != peer.HasShortID { diff --git a/mesh/router.go b/mesh/router.go index 69b26831..5f51cc87 100644 --- a/mesh/router.go +++ b/mesh/router.go @@ -172,14 +172,6 @@ func (router *Router) OnGossip(update []byte) (GossipData, error) { func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) { origUpdate, newUpdate, err := router.Peers.ApplyUpdate(update) - if _, ok := err.(UnknownPeerError); err != nil && ok { - // That update contained a reference to a peer which wasn't - // itself included in the update, and we didn't know about - // already. We ignore this; eventually we should receive an - // update containing a complete topology. - log.Println("Topology gossip:", err) - return nil, nil, nil - } if err != nil { return nil, nil, err } From cbb2ad018da70c52578cc694f65379c83db01114 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Dec 2015 13:10:06 +0000 Subject: [PATCH 066/135] don't broadcast-gossip other peer when adding a connection This is more efficient since broadcasting about other peers is strictly redundant. Any change to the peers will be broadcast by those peers anyway. And SendAllGossipDown takes care of informing new peers about others on first connect, and periodic gossip take care of missed broadcasts. The only reason we did broadcast about the remote peer is to reduce the liklihood of UnknownPeerError and the associated throwing away of gossip. This was always a bit of kludge, and is no longer necessary since UnknownPeerError is gone. Now peers only ever broadcast information about themselves. Which, unlike information about other peers, is always authorative and most up-to-date. Which is nice. --- mesh/local_peer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mesh/local_peer.go b/mesh/local_peer.go index 72262e59..d26ad5a3 100644 --- a/mesh/local_peer.go +++ b/mesh/local_peer.go @@ -157,7 +157,7 @@ func (peer *LocalPeer) handleAddConnection(conn Connection) error { } peer.router.Routes.Recalculate() - peer.broadcastPeerUpdate(conn.Remote()) + peer.broadcastPeerUpdate() return nil } @@ -199,13 +199,13 @@ func (peer *LocalPeer) handleDeleteConnection(conn Connection) { // helpers -func (peer *LocalPeer) broadcastPeerUpdate(peers ...*Peer) { +func (peer *LocalPeer) broadcastPeerUpdate() { // Some tests run without a router. This should be fixed so // that the relevant part of Router can be easily run in the // context of a test, but that will involve significant // reworking of tests. if peer.router != nil { - peer.router.BroadcastTopologyUpdate(append(peers, peer.Peer)) + peer.router.BroadcastTopologyUpdate([]*Peer{peer.Peer}) } } From 295a2ced320849dc0fbf62af6927f454c63938d1 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 1 Dec 2015 15:45:32 +0000 Subject: [PATCH 067/135] [proxy] treat addrs prefix with / as unix addrs --- proxy/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/proxy.go b/proxy/proxy.go index b43a288f..b23aebd1 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -194,7 +194,7 @@ func (proxy *Proxy) Listen() []net.Listener { proxy.normalisedAddrs = []string{} unixAddrs := []string{} for _, addr := range proxy.ListenAddrs { - if strings.HasPrefix(addr, "unix://") { + if strings.HasPrefix(addr, "unix://") || strings.HasPrefix(addr, "/") { unixAddrs = append(unixAddrs, addr) continue } From 6f25d2170d150eb353080b05450a93b8d78a3981 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 1 Dec 2015 15:51:16 +0000 Subject: [PATCH 068/135] [proxy] have symlinking respond to WEAVE_DEBUG not DEBUG --- prog/weaveexec/symlink | 2 +- proxy/proxy.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/prog/weaveexec/symlink b/prog/weaveexec/symlink index 56078149..2c06e901 100755 --- a/prog/weaveexec/symlink +++ b/prog/weaveexec/symlink @@ -4,7 +4,7 @@ set -e -[ -z "$DEBUG" ] || set -x +[ -z "$WEAVE_DEBUG" ] || set -x target="$1" user="$(stat -c '%u:%g' "$target")" diff --git a/proxy/proxy.go b/proxy/proxy.go index b23aebd1..d267a610 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -575,8 +575,8 @@ func (proxy *Proxy) symlink(unixAddrs []string) (err error) { "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", } - if val := os.Getenv("DEBUG"); val != "" { - env = append(env, fmt.Sprintf("%s=%s", "DEBUG", val)) + if val := os.Getenv("WEAVE_DEBUG"); val != "" { + env = append(env, fmt.Sprintf("%s=%s", "WEAVE_DEBUG", val)) } container, err = proxy.client.CreateContainer(docker.CreateContainerOptions{ From ac1cf633c72d14a319b1ff707ba5665661287667 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 1 Dec 2015 16:09:41 +0000 Subject: [PATCH 069/135] clarify some variable shadowing --- proxy/proxy.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/proxy/proxy.go b/proxy/proxy.go index d267a610..bf14cb76 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -568,7 +568,7 @@ func (proxy *Proxy) symlink(unixAddrs []string) (err error) { proxy.normalisedAddrs = append(proxy.normalisedAddrs, addr) } if len(froms) == 0 { - return nil + return } env := []string{ @@ -589,7 +589,7 @@ func (proxy *Proxy) symlink(unixAddrs []string) (err error) { HostConfig: &docker.HostConfig{Binds: binds}, }) if err != nil { - return err + return } defer func() { @@ -599,8 +599,9 @@ func (proxy *Proxy) symlink(unixAddrs []string) (err error) { } }() - if err := proxy.client.StartContainer(container.ID, nil); err != nil { - return err + err = proxy.client.StartContainer(container.ID, nil) + if err != nil { + return } var buf bytes.Buffer @@ -611,15 +612,16 @@ func (proxy *Proxy) symlink(unixAddrs []string) (err error) { Stderr: true, }) if err != nil { - return err + return } - rc, err := proxy.client.WaitContainer(container.ID) + var rc int + rc, err = proxy.client.WaitContainer(container.ID) if err != nil { - return err + return } if rc != 0 { - return errors.New(buf.String()) + err = errors.New(buf.String()) } - return nil + return } From ec420c66d487e4136e2cfcdcfff1c8a7d811fd75 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 1 Dec 2015 16:40:19 +0000 Subject: [PATCH 070/135] Review feedback --- common/docker/client.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common/docker/client.go b/common/docker/client.go index f6de8981..bd4b78f6 100644 --- a/common/docker/client.go +++ b/common/docker/client.go @@ -1,7 +1,9 @@ package docker import ( + "errors" "github.com/fsouza/go-dockerclient" + . "github.com/weaveworks/weave/common" ) @@ -100,9 +102,8 @@ func (c *Client) GetContainerIP(nameOrID string) (string, error) { } else if info.HostConfig.NetworkMode == "host" { return "127.0.0.1", nil } + if info.NetworkSettings.IPAddress == "" { + return "", errors.New("No IP address found for container " + nameOrID) + } return info.NetworkSettings.IPAddress, nil } - -func (c *Client) InspectContainer(nameOrId string) (*docker.Container, error) { - return c.Client.InspectContainer(nameOrId) -} From 4e70fb7f9dc195b9294c2aa74f0a246a9ca1f595 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Dec 2015 14:20:29 +0000 Subject: [PATCH 071/135] '==' is not POSIX --- weave | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/weave b/weave index 7e6506fe..e4e96d85 100755 --- a/weave +++ b/weave @@ -1611,14 +1611,14 @@ deprecation_warnings() { case "$1" in -password|-password=*) deprecation_warning $1 "--password" - [ "$1" == "-password" ] && shift + [ "$1" = "-password" ] && shift ;; --password) shift ;; -nickname|-nickname=*) deprecation_warning $1 "--nickname" - [ "$1" == "-nickname" ] && shift + [ "$1" = "-nickname" ] && shift ;; --nickname) shift @@ -1628,21 +1628,21 @@ deprecation_warnings() { ;; -iprange|--iprange|-iprange=*|--iprange=*) deprecation_warning $1 "--ipalloc-range" - [ ${1#--} == "iprange" ] && shift + [ ${1#--} = "iprange" ] && shift ;; --ipalloc-range) shift ;; -ipsubnet|--ipsubnet|-ipsubnet=*|--ipsubnet=*) deprecation_warning $1 "--ipalloc-default-subnet" - [ ${1#--} == "ipsubnet" ] && shift + [ ${1#--} = "ipsubnet" ] && shift ;; --ipalloc-default-subnet) shift ;; -initpeercount|--initpeercount|-initpeercount=*|--initpeercount=*) deprecation_warning $1 "--init-peer-count" - [ ${1#--} == "initpeercount" ] && shift + [ ${1#--} = "initpeercount" ] && shift ;; --init-peer-count) shift @@ -1732,7 +1732,7 @@ EOF ;; connect) [ $# -gt 0 ] || usage - if [ "$1" == "--replace" ] ; then + if [ "$1" = "--replace" ] ; then replace="-d replace=true" shift fi From 20a48f05b32ddfc7150a1bd651054884075d4e23 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Dec 2015 14:22:23 +0000 Subject: [PATCH 072/135] fix (harmless) typo --- weave | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weave b/weave index 7e6506fe..de4e98e8 100755 --- a/weave +++ b/weave @@ -1179,7 +1179,7 @@ ipam_cidrs() { CONTAINER_ID="$2" shift 2 ALL_CIDRS="" - IPAM_CIDRs="" + IPAM_CIDRS="" # If no addresses passed in, select the default subnet [ $# -gt 0 ] || set -- net:default for arg in "$@" ; do From 0602c91c54385e91ee545aac9f221a8067c31927 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Dec 2015 15:28:41 +0000 Subject: [PATCH 073/135] quote $@ --- weave | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weave b/weave index de4e98e8..385d1dc2 100755 --- a/weave +++ b/weave @@ -1037,7 +1037,7 @@ rewrite_etc_hosts() { CONTAINERS_PATH=$(dirname $HOSTS) MNT=/container MNT_HOSTS=$MNT/$(basename $HOSTS) - CONTENTS="$(etc_hosts_contents $FQDN $@)" + CONTENTS="$(etc_hosts_contents $FQDN "$@")" # rewrite /etc/hosts, unlinking the file (so Docker does not modify it again) but # leaving it with valid contents... docker run --rm --net=none --privileged \ From 3909936cc8314266a8a72fe9ff32caaf4cff1e2b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Dec 2015 15:33:38 +0000 Subject: [PATCH 074/135] cosmetic(ish) use PROXY_PORT --- weave | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weave b/weave index de4e98e8..138e53eb 100755 --- a/weave +++ b/weave @@ -1325,7 +1325,7 @@ proxy_args() { PROXY_HOST="unix:///var/run/weave/weave.sock" ;; *) - PROXY_HOST="tcp://0.0.0.0:12375" + PROXY_HOST="tcp://0.0.0.0:$PROXY_PORT" ;; esac PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST" From 245ae5e987c51ca4be8137a3b37aff0fa80ce29b Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 2 Dec 2015 17:17:04 +0000 Subject: [PATCH 075/135] remove CLIENT_TLS_ENABLED and DOCKER_CLIENT_TLS_VERIFY, as they're unused --- weave | 7 ------- 1 file changed, 7 deletions(-) diff --git a/weave b/weave index bdf69914..c032ee67 100755 --- a/weave +++ b/weave @@ -27,7 +27,6 @@ EXEC_IMAGE=$BASE_EXEC_IMAGE:$IMAGE_VERSION PROXY_HOST=${PROXY_HOST:-$(echo "${DOCKER_HOST#tcp://}" | cut -s -d: -f1)} PROXY_HOST=${PROXY_HOST:-127.0.0.1} DOCKER_CLIENT_HOST=${DOCKER_CLIENT_HOST:-$DOCKER_HOST} -DOCKER_CLIENT_TLS_VERIFY=${DOCKER_CLIENT_TLS_VERIFY:-$DOCKER_TLS_VERIFY} # Define some regular expressions for matching addresses. # The regexp here is far from precise, but good enough. @@ -111,7 +110,6 @@ exec_remote() { -e WEAVE_NO_BRIDGED_FASTDP \ -e DOCKER_BRIDGE \ -e DOCKER_CLIENT_HOST="$DOCKER_CLIENT_HOST" \ - -e DOCKER_CLIENT_TLS_VERIFY="$DOCKER_CLIENT_TLS_VERIFY" \ -e DOCKER_CLIENT_ARGS \ -e PROXY_HOST="$PROXY_HOST" \ -e WEAVE_CIDR=none \ @@ -1340,8 +1338,6 @@ show_addrs() { ###################################################################### docker_client_args() { - CLIENT_TLS_ENABLED="" - [ -z "$DOCKER_CLIENT_TLS_VERIFY" ] || CLIENT_TLS_ENABLED=1 while [ $# -gt 0 ]; do case "$1" in -H|--host) @@ -1351,9 +1347,6 @@ docker_client_args() { -H=*|--host=*) DOCKER_CLIENT_HOST="${1#*=}" ;; - -tls|--tls|-tlsverify|--tlsverify) - CLIENT_TLS_ENABLED=1 - ;; esac shift done From c981cff450937eb51d1db8a7e734c04650d354dc Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 8 Dec 2015 14:39:55 +0000 Subject: [PATCH 076/135] Move build of test/tls program into Makefile so it builds in the build container --- Makefile | 8 +++++--- test/setup.sh | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b4878f0a..08293ea6 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,9 @@ WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop NETCHECK_EXE=prog/netcheck/netcheck DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args RUNNER_EXE=tools/runner/runner +TEST_TLS_EXE=test/tls/tls -EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) +EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(TEST_TLS_EXE) WEAVER_UPTODATE=.weaver.uptodate WEAVEEXEC_UPTODATE=.weaveexec.uptodate @@ -47,7 +48,7 @@ BUILD_FLAGS=-ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" PACKAGE_BASE=$(shell go list -e ./) -all: $(WEAVE_EXPORT) $(RUNNER_EXE) +all: $(WEAVE_EXPORT) $(RUNNER_EXE) $(TEST_TLS_EXE) travis: $(EXES) @@ -79,8 +80,9 @@ $(NETCHECK_EXE): prog/netcheck/netcheck.go $(SIGPROXY_EXE): prog/sigproxy/main.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go $(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go +$(TEST_TLS_EXE): test/tls/*.go -$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE): +$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(TEST_TLS_EXE): go get -tags netgo ./$(@D) go build $(BUILD_FLAGS) -o $@ ./$(@D) diff --git a/test/setup.sh b/test/setup.sh index 9312fc20..1e8ce207 100755 --- a/test/setup.sh +++ b/test/setup.sh @@ -6,7 +6,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")" . ./config.sh -(cd ./tls && go get -tags netgo ./... && go run generate_certs.go $HOSTS) +(cd ./tls && ./tls $HOSTS) echo "Copying weave images, scripts, and certificates to hosts, and" echo " prefetch test images" @@ -19,7 +19,7 @@ setup_host() { run_on $HOST mkdir -p bin upload_executable $HOST ../bin/docker-ns upload_executable $HOST ../weave - rsync -az -e "$SSH" ./tls/ $HOST:~/tls + rsync -az -e "$SSH" --exclude=tls ./tls/ $HOST:~/tls for IMG in $TEST_IMAGES ; do docker_on $HOST inspect --format=" " $IMG >/dev/null 2>&1 || docker_on $HOST pull $IMG done From 9f86e2ba43cfa406e438d08452d5edd1a73f2fd8 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 16:45:53 +0000 Subject: [PATCH 077/135] Moved net plugin to its own directory --- Makefile | 2 +- plugin/{ => net}/driver.go | 0 plugin/{ => net}/watcher.go | 0 prog/plugin/main.go | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename plugin/{ => net}/driver.go (100%) rename plugin/{ => net}/watcher.go (100%) diff --git a/Makefile b/Makefile index b04ed091..5e52cb37 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ $(NETCHECK_EXE): prog/netcheck/netcheck.go $(SIGPROXY_EXE): prog/sigproxy/main.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go $(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go -$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*.go plugin/skel/*.go api/*.go common/docker/*.go +$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/net/*.go plugin/skel/*.go api/*.go common/docker/*.go $(TEST_TLS_EXE): test/tls/*.go $(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE): diff --git a/plugin/driver.go b/plugin/net/driver.go similarity index 100% rename from plugin/driver.go rename to plugin/net/driver.go diff --git a/plugin/watcher.go b/plugin/net/watcher.go similarity index 100% rename from plugin/watcher.go rename to plugin/net/watcher.go diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 659d5a8a..397d706d 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -9,7 +9,7 @@ import ( "syscall" . "github.com/weaveworks/weave/common" - "github.com/weaveworks/weave/plugin" + "github.com/weaveworks/weave/plugin/net" "github.com/weaveworks/weave/plugin/skel" ) From c1d2c159f0b016a05b17183e79f5a2e41189f208 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 16:41:02 +0000 Subject: [PATCH 078/135] Expand listener skeleton to support IPAM driver --- api/ipam.go | 3 - plugin/skel/listener.go | 128 ++++++++++++++++++++++++++++++++++------ prog/plugin/main.go | 2 +- 3 files changed, 111 insertions(+), 22 deletions(-) diff --git a/api/ipam.go b/api/ipam.go index 91a3053a..39da04a4 100644 --- a/api/ipam.go +++ b/api/ipam.go @@ -3,12 +3,9 @@ package api import ( "fmt" "net" - - . "github.com/weaveworks/weave/common" ) func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) { - Log.Debugf("IPAM operation %s for %s", op, ID) ip, err := httpVerb(op, fmt.Sprintf("%s/ip/%s", client.baseUrl, ID), nil) if err != nil { return nil, err diff --git a/plugin/skel/listener.go b/plugin/skel/listener.go index f86af91e..2b4deb71 100644 --- a/plugin/skel/listener.go +++ b/plugin/skel/listener.go @@ -9,10 +9,13 @@ import ( "github.com/gorilla/mux" "github.com/docker/libnetwork/drivers/remote/api" + "github.com/docker/libnetwork/ipamapi" + iapi "github.com/docker/libnetwork/ipams/remote/api" ) const ( - MethodReceiver = "NetworkDriver" + networkReceiver = "NetworkDriver" + ipamReceiver = ipamapi.PluginEndpointType ) type Driver interface { @@ -30,42 +33,67 @@ type Driver interface { type listener struct { d Driver + i ipamapi.Ipam } -func Listen(socket net.Listener, driver Driver) error { +func Listen(socket net.Listener, driver Driver, ipamDriver ipamapi.Ipam) error { router := mux.NewRouter() router.NotFoundHandler = http.HandlerFunc(notFound) - router.Methods("POST").Path("/Plugin.Activate").HandlerFunc(handshake) + listener := &listener{driver, ipamDriver} - handleMethod := func(method string, h http.HandlerFunc) { - router.Methods("POST").Path(fmt.Sprintf("/%s.%s", MethodReceiver, method)).HandlerFunc(h) + router.Methods("POST").Path("/Plugin.Activate").HandlerFunc(listener.handshake) + + handleMethod := func(receiver, method string, h http.HandlerFunc) { + router.Methods("POST").Path(fmt.Sprintf("/%s.%s", receiver, method)).HandlerFunc(h) } - listener := &listener{driver} + handleMethod(networkReceiver, "GetCapabilities", listener.getCapabilities) - handleMethod("GetCapabilities", listener.getCapabilities) - handleMethod("CreateNetwork", listener.createNetwork) - handleMethod("DeleteNetwork", listener.deleteNetwork) - handleMethod("CreateEndpoint", listener.createEndpoint) - handleMethod("DeleteEndpoint", listener.deleteEndpoint) - handleMethod("EndpointOperInfo", listener.infoEndpoint) - handleMethod("Join", listener.joinEndpoint) - handleMethod("Leave", listener.leaveEndpoint) + if driver != nil { + handleMethod(networkReceiver, "CreateNetwork", listener.createNetwork) + handleMethod(networkReceiver, "DeleteNetwork", listener.deleteNetwork) + handleMethod(networkReceiver, "CreateEndpoint", listener.createEndpoint) + handleMethod(networkReceiver, "DeleteEndpoint", listener.deleteEndpoint) + handleMethod(networkReceiver, "EndpointOperInfo", listener.infoEndpoint) + handleMethod(networkReceiver, "Join", listener.joinEndpoint) + handleMethod(networkReceiver, "Leave", listener.leaveEndpoint) + } + + if ipamDriver != nil { + handleMethod(ipamReceiver, "GetDefaultAddressSpaces", listener.getDefaultAddressSpaces) + handleMethod(ipamReceiver, "RequestPool", listener.requestPool) + handleMethod(ipamReceiver, "ReleasePool", listener.releasePool) + handleMethod(ipamReceiver, "RequestAddress", listener.requestAddress) + handleMethod(ipamReceiver, "ReleaseAddress", listener.releaseAddress) + } return http.Serve(socket, router) } +func decode(w http.ResponseWriter, r *http.Request, v interface{}) error { + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + } + return err +} + // === protocol handlers type handshakeResp struct { Implements []string } -func handshake(w http.ResponseWriter, r *http.Request) { - err := json.NewEncoder(w).Encode(&handshakeResp{ - []string{"NetworkDriver"}, - }) +func (listener *listener) handshake(w http.ResponseWriter, r *http.Request) { + var resp handshakeResp + if listener.d != nil { + resp.Implements = append(resp.Implements, "NetworkDriver") + } + if listener.i != nil { + resp.Implements = append(resp.Implements, "IpamDriver") + } + err := json.NewEncoder(w).Encode(&resp) if err != nil { sendError(w, "encode error", http.StatusInternalServerError) return @@ -164,6 +192,70 @@ func (listener *listener) discoverDelete(w http.ResponseWriter, r *http.Request) // === +func (listener *listener) getDefaultAddressSpaces(w http.ResponseWriter, r *http.Request) { + local, global, err := listener.i.GetDefaultAddressSpaces() + response := &iapi.GetAddressSpacesResponse{ + LocalDefaultAddressSpace: local, + GlobalDefaultAddressSpace: global, + } + objectOrErrorResponse(w, response, err) +} + +func (listener *listener) requestPool(w http.ResponseWriter, r *http.Request) { + var rq iapi.RequestPoolRequest + if err := decode(w, r, &rq); err != nil { + return + } + poolID, pool, data, err := listener.i.RequestPool(rq.AddressSpace, rq.Pool, rq.SubPool, rq.Options, rq.V6) + if err != nil { + errorResponse(w, err.Error()) + return + } + response := &iapi.RequestPoolResponse{ + PoolID: poolID, + Pool: pool.String(), + Data: data, + } + objectResponse(w, response) +} + +func (listener *listener) releasePool(w http.ResponseWriter, r *http.Request) { + var rq iapi.ReleasePoolRequest + if err := decode(w, r, &rq); err != nil { + return + } + err := listener.i.ReleasePool(rq.PoolID) + emptyOrErrorResponse(w, err) +} + +func (listener *listener) requestAddress(w http.ResponseWriter, r *http.Request) { + var rq iapi.RequestAddressRequest + if err := decode(w, r, &rq); err != nil { + return + } + address, data, err := listener.i.RequestAddress(rq.PoolID, net.ParseIP(rq.Address), rq.Options) + if err != nil { + errorResponse(w, err.Error()) + return + } + response := &iapi.RequestAddressResponse{ + Address: address.String(), + Data: data, + } + objectResponse(w, response) +} + +func (listener *listener) releaseAddress(w http.ResponseWriter, r *http.Request) { + var rq iapi.ReleaseAddressRequest + if err := decode(w, r, &rq); err != nil { + return + } + err := listener.i.ReleaseAddress(rq.PoolID, net.ParseIP(rq.Address)) + emptyOrErrorResponse(w, err) +} + +// === + func notFound(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) } diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 397d706d..09315cd6 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -62,7 +62,7 @@ func main() { endChan := make(chan error, 1) go func() { - endChan <- skel.Listen(listener, d) + endChan <- skel.Listen(listener, d, nil) }() select { From ec3de1a07294cc1ea1c1b72196903cedde85ee69 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 17:17:51 +0000 Subject: [PATCH 079/135] Introduce ipam plugin --- Makefile | 2 +- ipam/allocate.go | 5 +++ plugin/ipam/driver.go | 74 +++++++++++++++++++++++++++++++++++++++++++ plugin/net/driver.go | 9 ++---- prog/plugin/main.go | 19 ++++++++--- 5 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 plugin/ipam/driver.go diff --git a/Makefile b/Makefile index 5e52cb37..7680e1a7 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ $(NETCHECK_EXE): prog/netcheck/netcheck.go $(SIGPROXY_EXE): prog/sigproxy/main.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go $(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go -$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/net/*.go plugin/skel/*.go api/*.go common/docker/*.go +$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/net/*.go plugin/ipam/*.go plugin/skel/*.go api/*.go common/docker/*.go $(TEST_TLS_EXE): test/tls/*.go $(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE): diff --git a/ipam/allocate.go b/ipam/allocate.go index e8950e37..d6dd2f10 100644 --- a/ipam/allocate.go +++ b/ipam/allocate.go @@ -38,6 +38,11 @@ func (g *allocate) Try(alloc *Allocator) bool { alloc.establishRing() if ok, addr := alloc.space.Allocate(g.r); ok { + // If caller hasn't supplied a unique ID, file it under the IP address + // which lets the caller then release the address using DELETE /ip/address + if g.ident == "_" { + g.ident = addr.String() + } alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r) alloc.addOwned(g.ident, addr) g.resultChan <- allocateResult{addr, nil} diff --git a/plugin/ipam/driver.go b/plugin/ipam/driver.go new file mode 100644 index 00000000..a81de6f5 --- /dev/null +++ b/plugin/ipam/driver.go @@ -0,0 +1,74 @@ +package ipamplugin + +import ( + "fmt" + "net" + + "github.com/docker/libnetwork/ipamapi" + "github.com/weaveworks/weave/api" + . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/common/docker" +) + +const ( + WeaveContainer = "weave" +) + +type ipam struct { + client *docker.Client + weave *api.Client +} + +func NewIpam(client *docker.Client, version string) (ipamapi.Ipam, error) { + return &ipam{client: client}, nil +} + +func (i *ipam) configureWeaveClient() error { + if i.weave != nil { + return nil + } + ip, err := i.client.GetContainerIP(WeaveContainer) + if err != nil { + Log.Warningf("weave ipam not available: %s", err) + return fmt.Errorf("weave ipam not available: %s", err) + } + i.weave = api.NewClient(ip) + return nil +} + +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) (string, *net.IPNet, map[string]string, error) { + Log.Debugln("RequestPool", addressSpace, pool, subPool, options) + _, cidr, _ := net.ParseCIDR("10.32.0.0/12") + return "weavepool", cidr, nil, nil +} + +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) (*net.IPNet, map[string]string, error) { + Log.Debugln("RequestAddress", poolID, address, options) + // Pass magic string to weave IPAM, which then stores the address under its own string + if err := i.configureWeaveClient(); err == nil { + ip, err := i.weave.AllocateIP("_") + Log.Debugln("allocateIP returned", ip, err) + return ip, nil, err + } else { + return nil, nil, err + } +} + +func (i *ipam) ReleaseAddress(poolID string, address net.IP) error { + Log.Debugln("ReleaseAddress", poolID, address) + if err := i.configureWeaveClient(); err == nil { + return i.weave.ReleaseIP(address.String()) + } else { + return err + } +} diff --git a/plugin/net/driver.go b/plugin/net/driver.go index e5b1e413..5eb5c984 100644 --- a/plugin/net/driver.go +++ b/plugin/net/driver.go @@ -27,19 +27,14 @@ type driver struct { endpoints map[string]struct{} } -func New(version string, nameserver string) (skel.Driver, error) { - client, err := docker.NewClient("unix:///var/run/docker.sock") - if err != nil { - return nil, errorf("could not connect to docker: %s", err) - } - +func New(client *docker.Client, version string, nameserver string) (skel.Driver, error) { driver := &driver{ nameserver: nameserver, version: version, endpoints: make(map[string]struct{}), } - _, err = NewWatcher(client, driver) + _, err := NewWatcher(client, driver) if err != nil { return nil, err } diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 09315cd6..4ae75164 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -9,7 +9,9 @@ import ( "syscall" . "github.com/weaveworks/weave/common" - "github.com/weaveworks/weave/plugin/net" + "github.com/weaveworks/weave/common/docker" + ipamplugin "github.com/weaveworks/weave/plugin/ipam" + netplugin "github.com/weaveworks/weave/plugin/net" "github.com/weaveworks/weave/plugin/skel" ) @@ -39,8 +41,17 @@ func main() { Log.Println("Weave plugin", version, "Command line options:", os.Args) - var d skel.Driver - d, err := plugin.New(version, nameserver) + dockerClient, err := docker.NewClient("unix:///var/run/docker.sock") + if err != nil { + Log.Fatalf("unable to connect to docker: %s", err) + } + + d, err := netplugin.New(dockerClient, version, nameserver) + if err != nil { + Log.Fatalf("unable to create driver: %s", err) + } + + i, err := ipamplugin.NewIpam(dockerClient, version) if err != nil { Log.Fatalf("unable to create driver: %s", err) } @@ -62,7 +73,7 @@ func main() { endChan := make(chan error, 1) go func() { - endChan <- skel.Listen(listener, d, nil) + endChan <- skel.Listen(listener, d, i) }() select { From f87876c1c3a146409ed8f7e5f8e88b69bfbbd531 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 29 Nov 2015 17:58:28 +0000 Subject: [PATCH 080/135] Fetch the default subnet from weave to use as the ipam driver 'pool' --- api/ipam.go | 9 +++++++++ ipam/http.go | 4 ++++ plugin/ipam/driver.go | 9 +++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/api/ipam.go b/api/ipam.go index 39da04a4..00467dc9 100644 --- a/api/ipam.go +++ b/api/ipam.go @@ -30,6 +30,15 @@ func (client *Client) ReleaseIP(ID string) error { return err } +func (client *Client) DefaultSubnet() (*net.IPNet, error) { + cidr, err := httpVerb("GET", fmt.Sprintf("%s/ipinfo/defaultsubnet", client.baseUrl), nil) + if err != nil { + return nil, err + } + _, ipnet, err := net.ParseCIDR(cidr) + return ipnet, err +} + func parseIP(body string) (*net.IPNet, error) { ip, ipnet, err := net.ParseCIDR(string(body)) if err != nil { diff --git a/ipam/http.go b/ipam/http.go index a86fbc50..eb51b87a 100644 --- a/ipam/http.go +++ b/ipam/http.go @@ -57,6 +57,10 @@ func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.Resp // HandleHTTP wires up ipams HTTP endpoints to the provided mux. func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) { + router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "%s", defaultSubnet) + }) + router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) ident := vars["id"] diff --git a/plugin/ipam/driver.go b/plugin/ipam/driver.go index a81de6f5..b94e302b 100644 --- a/plugin/ipam/driver.go +++ b/plugin/ipam/driver.go @@ -43,8 +43,13 @@ func (i *ipam) GetDefaultAddressSpaces() (string, string, error) { func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) { Log.Debugln("RequestPool", addressSpace, pool, subPool, options) - _, cidr, _ := net.ParseCIDR("10.32.0.0/12") - return "weavepool", cidr, nil, nil + if err := i.configureWeaveClient(); err == nil { + cidr, err := i.weave.DefaultSubnet() + Log.Debugln("RequestPool returning ", cidr, err) + return "weavepool", cidr, nil, err + } else { + return "", nil, nil, err + } } func (i *ipam) ReleasePool(poolID string) error { From a6f07fbaac6ab79bca47f70dfdb1cfbddb21cd8b Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 4 Dec 2015 13:13:29 +0000 Subject: [PATCH 081/135] Move baseUrl usage up to httpVerb --- api/dns.go | 4 ++-- api/ipam.go | 4 ++-- api/weaveapi.go | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/api/dns.go b/api/dns.go index 750c34f2..36ec8630 100644 --- a/api/dns.go +++ b/api/dns.go @@ -8,11 +8,11 @@ import ( func (client *Client) RegisterWithDNS(ID string, fqdn string, ip string) error { data := url.Values{} data.Add("fqdn", fqdn) - _, err := httpVerb("PUT", fmt.Sprintf("%s/name/%s/%s", client.baseUrl, ID, ip), data) + _, err := client.httpVerb("PUT", fmt.Sprintf("/name/%s/%s", ID, ip), data) return err } func (client *Client) DeregisterWithDNS(ID string, ip string) error { - _, err := httpVerb("DELETE", fmt.Sprintf("%s/name/%s/%s", client.baseUrl, ID, ip), nil) + _, err := client.httpVerb("DELETE", fmt.Sprintf("/name/%s/%s", ID, ip), nil) return err } diff --git a/api/ipam.go b/api/ipam.go index 00467dc9..8be57b27 100644 --- a/api/ipam.go +++ b/api/ipam.go @@ -6,7 +6,7 @@ import ( ) func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) { - ip, err := httpVerb(op, fmt.Sprintf("%s/ip/%s", client.baseUrl, ID), nil) + ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), nil) if err != nil { return nil, err } @@ -31,7 +31,7 @@ func (client *Client) ReleaseIP(ID string) error { } func (client *Client) DefaultSubnet() (*net.IPNet, error) { - cidr, err := httpVerb("GET", fmt.Sprintf("%s/ipinfo/defaultsubnet", client.baseUrl), nil) + cidr, err := client.httpVerb("GET", fmt.Sprintf("/ipinfo/defaultsubnet"), nil) if err != nil { return nil, err } diff --git a/api/weaveapi.go b/api/weaveapi.go index d75394bd..fd4c0320 100644 --- a/api/weaveapi.go +++ b/api/weaveapi.go @@ -20,7 +20,8 @@ type Client struct { baseUrl string } -func httpVerb(verb string, url string, values url.Values) (string, error) { +func (client *Client) httpVerb(verb string, url string, values url.Values) (string, error) { + url = client.baseUrl + url Log.Debugf("weave %s to %s with %v", verb, url, values) var body io.Reader if values != nil { @@ -54,6 +55,6 @@ func NewClient(addr string) *Client { } func (client *Client) Connect(remote string) error { - _, err := httpVerb("POST", client.baseUrl+"/connect", url.Values{"peer": {remote}}) + _, err := client.httpVerb("POST", "/connect", url.Values{"peer": {remote}}) return err } From ecd93bbfe4605ea4a997ddcfe97753ebc7804803 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 4 Dec 2015 14:01:05 +0000 Subject: [PATCH 082/135] Resolve weave IP every time we need it, in case it changed --- api/weaveapi.go | 15 ++++++++++++++- plugin/ipam/driver.go | 43 +++++++++---------------------------------- plugin/net/watcher.go | 19 +++---------------- 3 files changed, 26 insertions(+), 51 deletions(-) diff --git a/api/weaveapi.go b/api/weaveapi.go index fd4c0320..8be6b334 100644 --- a/api/weaveapi.go +++ b/api/weaveapi.go @@ -18,10 +18,19 @@ const ( type Client struct { baseUrl string + resolve func() (string, error) } func (client *Client) httpVerb(verb string, url string, values url.Values) (string, error) { - url = client.baseUrl + url + baseUrl := client.baseUrl + if client.resolve != nil { + addr, err := client.resolve() + if err != nil { + return "", err + } + baseUrl = fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort) + } + url = baseUrl + url Log.Debugf("weave %s to %s with %v", verb, url, values) var body io.Reader if values != nil { @@ -54,6 +63,10 @@ func NewClient(addr string) *Client { return &Client{baseUrl: fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)} } +func NewClientWithResolver(resolver func() (string, error)) *Client { + return &Client{resolve: resolver} +} + func (client *Client) Connect(remote string) error { _, err := client.httpVerb("POST", "/connect", url.Values{"peer": {remote}}) return err diff --git a/plugin/ipam/driver.go b/plugin/ipam/driver.go index b94e302b..1fbdf91b 100644 --- a/plugin/ipam/driver.go +++ b/plugin/ipam/driver.go @@ -1,7 +1,6 @@ package ipamplugin import ( - "fmt" "net" "github.com/docker/libnetwork/ipamapi" @@ -20,20 +19,8 @@ type ipam struct { } func NewIpam(client *docker.Client, version string) (ipamapi.Ipam, error) { - return &ipam{client: client}, nil -} - -func (i *ipam) configureWeaveClient() error { - if i.weave != nil { - return nil - } - ip, err := i.client.GetContainerIP(WeaveContainer) - if err != nil { - Log.Warningf("weave ipam not available: %s", err) - return fmt.Errorf("weave ipam not available: %s", err) - } - i.weave = api.NewClient(ip) - return nil + resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) } + return &ipam{client: client, weave: api.NewClientWithResolver(resolver)}, nil } func (i *ipam) GetDefaultAddressSpaces() (string, string, error) { @@ -43,13 +30,9 @@ func (i *ipam) GetDefaultAddressSpaces() (string, string, error) { func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) { Log.Debugln("RequestPool", addressSpace, pool, subPool, options) - if err := i.configureWeaveClient(); err == nil { - cidr, err := i.weave.DefaultSubnet() - Log.Debugln("RequestPool returning ", cidr, err) - return "weavepool", cidr, nil, err - } else { - return "", nil, nil, err - } + cidr, err := i.weave.DefaultSubnet() + Log.Debugln("RequestPool returning ", cidr, err) + return "weavepool", cidr, nil, err } func (i *ipam) ReleasePool(poolID string) error { @@ -60,20 +43,12 @@ func (i *ipam) ReleasePool(poolID string) error { func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) { Log.Debugln("RequestAddress", poolID, address, options) // Pass magic string to weave IPAM, which then stores the address under its own string - if err := i.configureWeaveClient(); err == nil { - ip, err := i.weave.AllocateIP("_") - Log.Debugln("allocateIP returned", ip, err) - return ip, nil, err - } else { - return nil, nil, err - } + ip, err := i.weave.AllocateIP("_") + Log.Debugln("allocateIP returned", ip, err) + return ip, nil, err } func (i *ipam) ReleaseAddress(poolID string, address net.IP) error { Log.Debugln("ReleaseAddress", poolID, address) - if err := i.configureWeaveClient(); err == nil { - return i.weave.ReleaseIP(address.String()) - } else { - return err - } + return i.weave.ReleaseIP(address.String()) } diff --git a/plugin/net/watcher.go b/plugin/net/watcher.go index fbf43014..05241d4f 100644 --- a/plugin/net/watcher.go +++ b/plugin/net/watcher.go @@ -22,7 +22,9 @@ type Watcher interface { } func NewWatcher(client *docker.Client, driver *driver) (Watcher, error) { - w := &watcher{client: client, driver: driver} + resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) } + w := &watcher{client: client, driver: driver, + weave: weaveapi.NewClientWithResolver(resolver)} err := client.AddObserver(w) if err != nil { return nil, err @@ -31,18 +33,6 @@ func NewWatcher(client *docker.Client, driver *driver) (Watcher, error) { return w, nil } -func (w *watcher) haveWeaveClient() bool { - if w.weave == nil { - dnsip, err := w.client.GetContainerIP(WeaveContainer) - if err != nil { - Log.Warningf("nameserver not available: %s", err) - return false - } - w.weave = weaveapi.NewClient(dnsip) - } - return true -} - func (w *watcher) ContainerStarted(id string) { Log.Debugf("Container started %s", id) info, err := w.client.InspectContainer(id) @@ -50,9 +40,6 @@ func (w *watcher) ContainerStarted(id string) { Log.Warningf("error inspecting container: %s", err) return } - if !w.haveWeaveClient() { - return - } // check that it's on our network, via the endpointID for _, net := range info.NetworkSettings.Networks { if w.driver.HasEndpoint(net.EndpointID) { From 0b521272ae5352a0b64b86c406c6184d2cc9e0c7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 8 Dec 2015 23:06:02 +0000 Subject: [PATCH 083/135] prevent peer duplication in tests and correct the test assertions accordingly - they make more sense now. Fixes #1766. --- mesh/gossip_test.go | 9 +++++---- mesh/mocks_test.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index 5f622826..e8346f6e 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -45,8 +45,8 @@ func (router *Router) AddTestChannelConnection(r *Router) { fromPeer := NewPeerFrom(router.Ourself.Peer) toPeer := NewPeerFrom(r.Ourself.Peer) - r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount - router.Peers.FetchWithDefault(toPeer) // + r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount + toPeer = router.Peers.FetchWithDefault(toPeer) // conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r} router.Ourself.handleAddConnection(conn) @@ -69,8 +69,9 @@ func (router *Router) DeleteTestChannelConnection(r *Router) { func TestGossipTopology(t *testing.T) { wt.RunWithTimeout(t, 5*time.Second, func() { - implTestGossipTopology(t) + //implTestGossipTopology(t) }) + implTestGossipTopology(t) } // Create a Peer representing the receiver router, with connections to @@ -141,7 +142,7 @@ func implTestGossipTopology(t *testing.T) { r2.DeleteTestChannelConnection(r3) sendPendingGossip(r1, r2, r3) checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1)) - checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1)) + checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1)) checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1)) // Drop the connection from 1 to 3 diff --git a/mesh/mocks_test.go b/mesh/mocks_test.go index 08ae90aa..12ae0d74 100644 --- a/mesh/mocks_test.go +++ b/mesh/mocks_test.go @@ -17,7 +17,7 @@ func (peers *Peers) AddTestConnection(p *Peer) { summary := p.PeerSummary summary.Version = 0 toPeer := NewPeerFromSummary(summary) - peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount + toPeer = peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount conn := newMockConnection(peers.ourself.Peer, toPeer) peers.ourself.addConnection(conn) peers.ourself.connectionEstablished(conn) From eddcf939420382002cf3d299a0cbf85beb62382f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 8 Dec 2015 23:08:19 +0000 Subject: [PATCH 084/135] oops --- mesh/gossip_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index e8346f6e..17e576dc 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -69,9 +69,8 @@ func (router *Router) DeleteTestChannelConnection(r *Router) { func TestGossipTopology(t *testing.T) { wt.RunWithTimeout(t, 5*time.Second, func() { - //implTestGossipTopology(t) + implTestGossipTopology(t) }) - implTestGossipTopology(t) } // Create a Peer representing the receiver router, with connections to From 18fc6ccc03046b68d7fc67dfd0b840cdeab4ac1b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 9 Dec 2015 10:32:14 +0000 Subject: [PATCH 085/135] remove redundant code in test --- mesh/gossip_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index 17e576dc..49764a68 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -147,8 +147,6 @@ func implTestGossipTopology(t *testing.T) { // Drop the connection from 1 to 3 r1.DeleteTestChannelConnection(r3) sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1)) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1)) checkTopology(t, r2, r1.tp(r2), r2.tp(r1)) // r3 still thinks r1 has a connection to it From 0ef57a102fc4572cbe12860a23e08402d7b9912c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 9 Dec 2015 12:37:53 +0000 Subject: [PATCH 086/135] cosmetic: remove unnecessary comment --- mesh/gossip_channel.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mesh/gossip_channel.go b/mesh/gossip_channel.go index 38555eb0..569ca48b 100644 --- a/mesh/gossip_channel.go +++ b/mesh/gossip_channel.go @@ -165,7 +165,6 @@ func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) { } func (c *GossipChannel) Send(srcName PeerName, data GossipData) { - // do this outside the lock below so we avoid lock nesting c.routes.EnsureRecalculated() destinations := c.routes.RandomNeighbours(srcName) if len(destinations) == 0 { From 1374e4c4deef226f82f4eaf41192ed9054a7fd4e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 9 Dec 2015 13:20:57 +0000 Subject: [PATCH 087/135] ditch RunWithTimeout test helper It breaks stack traces on failure. We can just rely on running `go test -timeout ...` instead, which we already do in tools/test. --- ipam/http_test.go | 7 ------- mesh/gossip_test.go | 10 +--------- nameserver/nameserver_test.go | 7 ------- testing/util.go | 17 ----------------- 4 files changed, 1 insertion(+), 40 deletions(-) diff --git a/ipam/http_test.go b/ipam/http_test.go index 36183b56..77777b1b 100644 --- a/ipam/http_test.go +++ b/ipam/http_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/net/address" - wt "github.com/weaveworks/weave/testing" ) func HTTPPost(t *testing.T, url string) string { @@ -145,12 +144,6 @@ func TestBadHttp(t *testing.T) { } func TestHTTPCancel(t *testing.T) { - wt.RunWithTimeout(t, 2*time.Second, func() { - impTestHTTPCancel(t) - }) -} - -func impTestHTTPCancel(t *testing.T) { var ( containerID = "deadbeef" testCIDR1 = "10.0.3.0/29" diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index 5fb4926b..31166a90 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -4,10 +4,8 @@ import ( "fmt" "sync" "testing" - "time" "github.com/stretchr/testify/require" - wt "github.com/weaveworks/weave/testing" ) // TODO test gossip unicast; atm we only test topology gossip and @@ -67,12 +65,6 @@ func (router *Router) DeleteTestChannelConnection(r *Router) { router.Ourself.handleDeleteConnection(conn) } -func TestGossipTopology(t *testing.T) { - wt.RunWithTimeout(t, 5*time.Second, func() { - implTestGossipTopology(t) - }) -} - // Create a Peer representing the receiver router, with connections to // the routers supplied as arguments, carrying across all UID and // version information. @@ -95,7 +87,7 @@ func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) { router.Peers.RUnlock() } -func implTestGossipTopology(t *testing.T) { +func TestGossipTopology(t *testing.T) { // Create some peers that will talk to each other r1 := NewTestRouter("01:00:00:01:00:00") r2 := NewTestRouter("02:00:00:02:00:00") diff --git a/nameserver/nameserver_test.go b/nameserver/nameserver_test.go index f7b603b5..f30e53a4 100644 --- a/nameserver/nameserver_test.go +++ b/nameserver/nameserver_test.go @@ -13,7 +13,6 @@ import ( "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" - wt "github.com/weaveworks/weave/testing" "github.com/weaveworks/weave/testing/gossip" ) @@ -76,12 +75,6 @@ func (m mapping) Addrs() []address.Address { } func TestNameservers(t *testing.T) { - wt.RunWithTimeout(t, 2*time.Minute, func() { - testNameservers(t) - }) -} - -func testNameservers(t *testing.T) { //common.SetLogLevel("debug") lookupTimeout := 10 // ms diff --git a/testing/util.go b/testing/util.go index 42f39cee..aed7ae31 100644 --- a/testing/util.go +++ b/testing/util.go @@ -6,7 +6,6 @@ import ( "reflect" "runtime" "testing" - "time" "github.com/stretchr/testify/require" ) @@ -36,22 +35,6 @@ func stackTrace(all bool) string { return string(buf[:stacklen]) } -// Borrowed from net/http tests: -// goTimeout runs f, failing t if f takes more than d to complete. -func RunWithTimeout(t *testing.T, d time.Duration, f func()) { - ch := make(chan bool, 2) - timer := time.AfterFunc(d, func() { - t.Errorf("Timeout expired after %v: stacks:\n%s", d, stackTrace(true)) - ch <- true - }) - defer timer.Stop() - go func() { - defer func() { ch <- true }() - f() - }() - <-ch -} - // TrimTestArgs finds the first -- in os.Args and trim all args before that func TrimTestArgs() { i, l := 0, len(os.Args) From c3e1f5a6c6799fca3577b6d1c6d73b7f22e42ebf Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 10 Dec 2015 10:08:04 +0000 Subject: [PATCH 088/135] .gitignore tls binary --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1934d8f6..438bc599 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ prog/weaveexec/netcheck prog/weaveexec/docker_tls_args prog/weaveexec/docker*.tgz Vagrantfile.local +test/tls/tls test/tls/*.pem test/coverage test/coverage.* From 700851cdd5c6d4f5ca051077b4d46108b4245422 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 10 Dec 2015 10:40:48 +0000 Subject: [PATCH 089/135] Add a note about connecting up peers --- plugin/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/README.md b/plugin/README.md index 800480ad..4af2efc7 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -11,6 +11,11 @@ Before you can use this plugin, you must [install weave](https://github.com/weav $ weave launch-plugin $ docker network create --driver=weave weave +When you start subsequent weave peers you have to tell them about other peers, e.g. + + $ weave launch + $ weave launch-plugin + ## Starting a container: $ docker run --net=weave -ti ubuntu From 86d2db5cc2cc6e5419f70064ea73481ae99351f1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 10 Dec 2015 10:54:10 +0000 Subject: [PATCH 090/135] better emulation of connections in gossip tests real Connections do not process gossip until after AddConnection. mockChannelConnection wasn't behaving in that way, which resulted in some sporadic test failures since it would cause some gossip to get processed too early, which in turn would trigger gc of some of the just-received peers, which we would then find missing in the test assertions. To fix this we introduce a latch on mockChannelConnection which closes when the remote end is ready to receive gossip. Effectively this emulates the entering of LocalConnection.receiveTCP after the call to AddConnection completes. Unfortunately an open latch will cause sendPendingGossip to block. We get around that by abandoning checks in "half-connected" connection states. Fixes #1765. --- mesh/gossip_test.go | 82 ++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index 31166a90..1993a05b 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -13,7 +13,9 @@ import ( type mockChannelConnection struct { RemoteConnection - dest *Router + router *Router + dest *Router + start chan struct{} } func NewTestRouter(name string) *Router { @@ -24,11 +26,21 @@ func NewTestRouter(name string) *Router { } func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) { + <-conn.start if err := conn.dest.handleGossip(protocolMsg.tag, protocolMsg.msg); err != nil { panic(err) } } +func (conn *mockChannelConnection) Connect() { + conn.router.Ourself.handleAddConnection(conn) + conn.router.Ourself.handleConnectionEstablished(conn) +} + +func (conn *mockChannelConnection) Start() { + close(conn.start) +} + func sendPendingGossip(routers ...*Router) { // Loop until all routers report they didn't send anything for sentSomething := true; sentSomething; { @@ -39,16 +51,24 @@ func sendPendingGossip(routers ...*Router) { } } -func (router *Router) AddTestChannelConnection(r *Router) { +func AddTestGossipConnection(r1, r2 *Router) { + c1 := r1.NewTestChannelConnection(r2) + c2 := r2.NewTestChannelConnection(r1) + c1.Connect() + c2.Start() + c2.Connect() + c1.Start() +} + +func (router *Router) NewTestChannelConnection(r *Router) *mockChannelConnection { fromPeer := NewPeerFrom(router.Ourself.Peer) toPeer := NewPeerFrom(r.Ourself.Peer) r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount toPeer = router.Peers.FetchWithDefault(toPeer) // - conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r} - router.Ourself.handleAddConnection(conn) - router.Ourself.handleConnectionEstablished(conn) + return &mockChannelConnection{ + RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, router, r, make(chan struct{})} } func (router *Router) DeleteTestChannelConnection(r *Router) { @@ -98,56 +118,42 @@ func TestGossipTopology(t *testing.T) { checkTopology(t, r2, r2.tp()) // Now try adding some connections - r1.AddTestChannelConnection(r2) - sendPendingGossip(r1, r2) - checkTopology(t, r1, r1.tp(r2), r2.tp()) - checkTopology(t, r2, r1.tp(r2), r2.tp()) - r2.AddTestChannelConnection(r1) + AddTestGossipConnection(r1, r2) sendPendingGossip(r1, r2) checkTopology(t, r1, r1.tp(r2), r2.tp(r1)) checkTopology(t, r2, r1.tp(r2), r2.tp(r1)) - // Currently, the connection from 2 to 3 is one-way only - r2.AddTestChannelConnection(r3) + AddTestGossipConnection(r2, r3) sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp()) - checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp()) - // When r2 gossiped to r3, 1 was unreachable from r3 so it got removed from the - // list of peers, but remains referenced in the connection from 1 to 3. - checkTopology(t, r3, r2.tp(r1, r3), r3.tp()) + checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) + checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) + checkTopology(t, r3, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) - // Add a connection from 3 to 1 and now r1 is reachable. - r3.AddTestChannelConnection(r1) + AddTestGossipConnection(r3, r1) sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1)) - checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1)) - checkTopology(t, r3, r1.tp(), r2.tp(r1, r3), r3.tp(r1)) - - r1.AddTestChannelConnection(r3) - sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1)) - checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1)) - checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1)) + checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) + checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) + checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) // Drop the connection from 2 to 3 r2.DeleteTestChannelConnection(r3) sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1)) - checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1)) - checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1)) + checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) + checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) + checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) // Drop the connection from 1 to 3 r1.DeleteTestChannelConnection(r3) sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1)) - checkTopology(t, r2, r1.tp(r2), r2.tp(r1)) + checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1, r2)) + checkTopology(t, r2, r1.tp(r2), r2.tp(r1), r3.tp(r1, r2)) // r3 still thinks r1 has a connection to it - checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1)) + checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) // On a timer, r3 will gossip to r1 r3.SendAllGossip() sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1)) + checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1, r2)) } func TestGossipSurrogate(t *testing.T) { @@ -155,10 +161,8 @@ func TestGossipSurrogate(t *testing.T) { r1 := NewTestRouter("01:00:00:01:00:00") r2 := NewTestRouter("02:00:00:02:00:00") r3 := NewTestRouter("03:00:00:03:00:00") - r1.AddTestChannelConnection(r2) - r2.AddTestChannelConnection(r1) - r3.AddTestChannelConnection(r2) - r2.AddTestChannelConnection(r3) + AddTestGossipConnection(r1, r2) + AddTestGossipConnection(r3, r2) sendPendingGossip(r1, r2, r3) checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) From ed14561afdad0e3fb871f5bf86f6c10fcd356c17 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 8 Dec 2015 23:21:31 +0000 Subject: [PATCH 091/135] refactor: rename --- mesh/gossip_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index 1993a05b..f1782f11 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -11,7 +11,7 @@ import ( // TODO test gossip unicast; atm we only test topology gossip and // surrogates, neither of which employ unicast. -type mockChannelConnection struct { +type mockGossipConnection struct { RemoteConnection router *Router dest *Router @@ -25,19 +25,19 @@ func NewTestRouter(name string) *Router { return router } -func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) { +func (conn *mockGossipConnection) SendProtocolMsg(protocolMsg ProtocolMsg) { <-conn.start if err := conn.dest.handleGossip(protocolMsg.tag, protocolMsg.msg); err != nil { panic(err) } } -func (conn *mockChannelConnection) Connect() { +func (conn *mockGossipConnection) Connect() { conn.router.Ourself.handleAddConnection(conn) conn.router.Ourself.handleConnectionEstablished(conn) } -func (conn *mockChannelConnection) Start() { +func (conn *mockGossipConnection) Start() { close(conn.start) } @@ -52,26 +52,26 @@ func sendPendingGossip(routers ...*Router) { } func AddTestGossipConnection(r1, r2 *Router) { - c1 := r1.NewTestChannelConnection(r2) - c2 := r2.NewTestChannelConnection(r1) + c1 := r1.NewTestGossipConnection(r2) + c2 := r2.NewTestGossipConnection(r1) c1.Connect() c2.Start() c2.Connect() c1.Start() } -func (router *Router) NewTestChannelConnection(r *Router) *mockChannelConnection { +func (router *Router) NewTestGossipConnection(r *Router) *mockGossipConnection { fromPeer := NewPeerFrom(router.Ourself.Peer) toPeer := NewPeerFrom(r.Ourself.Peer) r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount toPeer = router.Peers.FetchWithDefault(toPeer) // - return &mockChannelConnection{ + return &mockGossipConnection{ RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, router, r, make(chan struct{})} } -func (router *Router) DeleteTestChannelConnection(r *Router) { +func (router *Router) DeleteTestGossipConnection(r *Router) { fromName := router.Ourself.Peer.Name toName := r.Ourself.Peer.Name @@ -136,14 +136,14 @@ func TestGossipTopology(t *testing.T) { checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) // Drop the connection from 2 to 3 - r2.DeleteTestChannelConnection(r3) + r2.DeleteTestGossipConnection(r3) sendPendingGossip(r1, r2, r3) checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) // Drop the connection from 1 to 3 - r1.DeleteTestChannelConnection(r3) + r1.DeleteTestGossipConnection(r3) sendPendingGossip(r1, r2, r3) checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1, r2)) checkTopology(t, r2, r1.tp(r2), r2.tp(r1), r3.tp(r1, r2)) From ab902b45aa6176f95fe747a6c3fa006946f00826 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 10 Dec 2015 10:56:18 +0000 Subject: [PATCH 092/135] refactor: introduce a helper to reduce duplication --- mesh/gossip_test.go | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/mesh/gossip_test.go b/mesh/gossip_test.go index f1782f11..32ab6472 100644 --- a/mesh/gossip_test.go +++ b/mesh/gossip_test.go @@ -107,12 +107,19 @@ func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) { router.Peers.RUnlock() } +func flushAndCheckTopology(t *testing.T, routers []*Router, wantedPeers ...*Peer) { + sendPendingGossip(routers...) + for _, r := range routers { + checkTopology(t, r, wantedPeers...) + } +} + func TestGossipTopology(t *testing.T) { // Create some peers that will talk to each other r1 := NewTestRouter("01:00:00:01:00:00") r2 := NewTestRouter("02:00:00:02:00:00") r3 := NewTestRouter("03:00:00:03:00:00") - + routers := []*Router{r1, r2, r3} // Check state when they have no connections checkTopology(t, r1, r1.tp()) checkTopology(t, r2, r2.tp()) @@ -124,23 +131,14 @@ func TestGossipTopology(t *testing.T) { checkTopology(t, r2, r1.tp(r2), r2.tp(r1)) AddTestGossipConnection(r2, r3) - sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) - checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) - checkTopology(t, r3, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) + flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) AddTestGossipConnection(r3, r1) - sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) - checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) - checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) + flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2)) // Drop the connection from 2 to 3 r2.DeleteTestGossipConnection(r3) - sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) - checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) - checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) + flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2)) // Drop the connection from 1 to 3 r1.DeleteTestGossipConnection(r3) @@ -161,12 +159,10 @@ func TestGossipSurrogate(t *testing.T) { r1 := NewTestRouter("01:00:00:01:00:00") r2 := NewTestRouter("02:00:00:02:00:00") r3 := NewTestRouter("03:00:00:03:00:00") + routers := []*Router{r1, r2, r3} AddTestGossipConnection(r1, r2) AddTestGossipConnection(r3, r2) - sendPendingGossip(r1, r2, r3) - checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) - checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) - checkTopology(t, r3, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) + flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2)) // create a gossiper at either end, but not the middle g1 := newTestGossiper() From 68d6c262d1c39419f6713eead43dd7f3e6fe7505 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sat, 12 Dec 2015 14:12:39 +0000 Subject: [PATCH 093/135] Remove --with-dns option from proxy --- prog/weaveproxy/main.go | 5 ----- proxy/proxy.go | 6 ------ test/630_proxy_dns_test.sh | 5 ----- weave | 2 +- 4 files changed, 1 insertion(+), 17 deletions(-) diff --git a/prog/weaveproxy/main.go b/prog/weaveproxy/main.go index 9db07cd5..4ffa9515 100644 --- a/prog/weaveproxy/main.go +++ b/prog/weaveproxy/main.go @@ -38,7 +38,6 @@ func main() { mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tlsverify") mflag.StringVar(&c.TLSConfig.Key, []string{"#tlskey", "-tlskey"}, "", "Path to TLS key file") mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote") - mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver") mflag.BoolVar(&c.WithoutDNS, []string{"-without-dns"}, false, "instruct created containers to never use weaveDNS as their nameserver") mflag.Parse() @@ -47,10 +46,6 @@ func main() { os.Exit(0) } - if c.WithDNS && c.WithoutDNS { - Log.Fatalf("Cannot use both '--with-dns' and '--without-dns' flags") - } - SetLogLevel(logLevel) if image := os.Getenv("EXEC_IMAGE"); image != "" { diff --git a/proxy/proxy.go b/proxy/proxy.go index bf14cb76..d43b64ca 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -18,7 +18,6 @@ import ( docker "github.com/fsouza/go-dockerclient" . "github.com/weaveworks/weave/common" weavedocker "github.com/weaveworks/weave/common/docker" - "github.com/weaveworks/weave/nameserver" ) const ( @@ -58,7 +57,6 @@ type Config struct { NoRewriteHosts bool TLSConfig TLSConfig Version string - WithDNS bool WithoutDNS bool } @@ -501,10 +499,6 @@ func (proxy *Proxy) getDNSDomain() (domain string) { return "" } - if proxy.WithDNS { - domain = nameserver.DefaultDomain - } - weaveContainer, err := proxy.client.InspectContainer("weave") var weaveIP string if err == nil && weaveContainer.NetworkSettings != nil { diff --git a/test/630_proxy_dns_test.sh b/test/630_proxy_dns_test.sh index 63b9f694..0c6d6e43 100755 --- a/test/630_proxy_dns_test.sh +++ b/test/630_proxy_dns_test.sh @@ -23,11 +23,6 @@ start_suite "Proxy registers containers with dns" bridge_ip=$(weave_on $HOST1 docker-bridge-ip) -# Assert behaviour without weaveDNS running, but dns forced -weave_on $HOST1 launch-proxy --with-dns -do_assert_resolution assert_no_dns_record -weave_on $HOST1 stop-proxy - # Assert behaviour without weaveDNS weave_on $HOST1 launch-proxy do_assert_resolution assert_no_dns_record diff --git a/weave b/weave index bd3c49dc..c306b267 100755 --- a/weave +++ b/weave @@ -46,7 +46,7 @@ weave launch [--password ] [--nickname ] weave launch-router [--password ] [--nickname ] [--ipalloc-range [--ipalloc-default-subnet ]] [--no-discovery] [--init-peer-count ] ... -weave launch-proxy [-H ] [--with-dns | --without-dns] +weave launch-proxy [-H ] [--without-dns] [--no-default-ipalloc] [--no-rewrite-hosts] [--hostname-from-label ] [--hostname-match ] From 6fd0f121a053c24c9da7041afb9a66804471b764 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sat, 12 Dec 2015 14:44:54 +0000 Subject: [PATCH 094/135] Add a 'json' function for 'weave report -f' --- prog/weaver/http.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 1dc84925..68e72500 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -245,7 +245,13 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRout muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - formatTemplate, err := template.New("format").Parse(mux.Vars(r)["format"]) + funcs := template.FuncMap{ + "json": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + } + formatTemplate, err := template.New("format").Funcs(funcs).Parse(mux.Vars(r)["format"]) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return From 98a7d3fc99314b804b31ea4d62d349b1afe49194 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 3 Dec 2015 15:08:53 +0000 Subject: [PATCH 095/135] `weave run` and `weave attach` accept --no-multicast-route --- weave | 58 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/weave b/weave index bd3c49dc..cc4beb64 100755 --- a/weave +++ b/weave @@ -59,7 +59,8 @@ weave connect [--replace] [ ...] weave forget ... weave status [targets | connections | peers | dns] weave report [-f ] -weave run [--with-dns | --without-dns] [--no-rewrite-hosts] +weave run [--with-dns | --without-dns] + [--no-rewrite-hosts] [--no-multicast-route] [ ...] ... weave start [ ...] weave attach [ ...] @@ -258,20 +259,42 @@ elif [ "$1" = "run" -a -z "$IS_LOCAL" ] ; then # because we are passing arbitrary arguments on to docker run, and # we can't rely on our baked-in docker to support those arguments. shift 1 + if [ "$1" != "--without-dns" ] ; then DNS_ARGS=$(exec_remote dns-args "$@" || true) fi shift $(dns_arg_count "$@") - if [ "$1" = "--no-rewrite-hosts" ] ; then + + REWRITE_HOSTS=1 + NO_MULTICAST_ROUTE= + while [ $# -gt 0 ]; do + case "$1" in + --no-rewrite-hosts) + REWRITE_HOSTS= + ;; + --no-multicast-route) + NO_MULTICAST_ROUTE=1 + ;; + *) + break + ;; + esac shift - else + done + + if [ -n "$REWRITE_HOSTS" ] ; then extra_hosts_args "$@" DNS_EXTRA_HOSTS_ARGS="--rewrite-hosts $DNS_EXTRA_HOSTS_ARGS" fi + + if [ -n "$NO_MULTICAST_ROUTE" ] ; then + ATTACH_ARGS="--no-multicast-route" + fi + collect_cidr_args "$@" shift $CIDR_ARG_COUNT CONTAINER=$(docker $DOCKER_CLIENT_ARGS run -e WEAVE_CIDR=none $DNS_ARGS -d "$@") - exec_remote attach $CIDR_ARGS --or-die $DNS_EXTRA_HOSTS_ARGS $CONTAINER >/dev/null + exec_remote attach $CIDR_ARGS --or-die $DNS_EXTRA_HOSTS_ARGS $ATTACH_ARGS $CONTAINER >/dev/null echo $CONTAINER exit 0 fi @@ -841,8 +864,10 @@ attach() { # multicast packets. Since RFC1122 prohibits sending of ICMP # errors for packets with multicast destinations, that causes # packets larger than the PMTU to be dropped silently. - if ! netnsenter ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then - netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME mtu lock $MTU + if [ -z "$NO_MULTICAST_ROUTE" ] ; then + if ! netnsenter ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then + netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME mtu lock $MTU + fi fi } @@ -1914,10 +1939,21 @@ EOF dns_args "$@" shift $(dns_arg_count "$@") REWRITE_HOSTS=1 - if [ "$1" = "--no-rewrite-hosts" ] ; then - REWRITE_HOSTS= + NO_MULTICAST_ROUTE= + while [ $# -gt 0 ]; do + case "$1" in + --no-rewrite-hosts) + REWRITE_HOSTS= + ;; + --no-multicast-route) + NO_MULTICAST_ROUTE=1 + ;; + *) + break + ;; + esac shift - fi + done collect_cidr_args "$@" shift $CIDR_ARG_COUNT CONTAINER=$(docker run -e WEAVE_CIDR=none $DNS_ARGS -d "$@") @@ -1954,6 +1990,7 @@ EOF attach) DNS_EXTRA_HOSTS= REWRITE_HOSTS= + NO_MULTICAST_ROUTE= collect_cidr_args "$@" shift $CIDR_ARG_COUNT while [ $# -gt 0 ]; do @@ -1971,6 +2008,9 @@ EOF --add-host=*) DNS_EXTRA_HOSTS="${1#*=} $DNS_EXTRA_HOSTS" ;; + --no-multicast-route) + NO_MULTICAST_ROUTE=1 + ;; *) break ;; From 367b73e6e39e63f1c0fb53daf4065882413955fe Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 11 Dec 2015 17:52:58 +0000 Subject: [PATCH 096/135] Introduce nomcast weavewait --- .gitignore | 2 ++ Makefile | 19 ++++++++++++++----- prog/weavewait/check_network.go | 9 ++------- prog/weavewait/check_network_iface.go | 12 ++++++++++++ prog/weavewait/check_network_iface_mcast.go | 12 ++++++++++++ prog/weavewait/noop.go | 7 ------- 6 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 prog/weavewait/check_network_iface.go create mode 100644 prog/weavewait/check_network_iface_mcast.go delete mode 100644 prog/weavewait/noop.go diff --git a/.gitignore b/.gitignore index 438bc599..4d5f7002 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ prog/weaveproxy/weaveproxy prog/sigproxy/sigproxy prog/weavewait/weavewait prog/weavewait/weavewait_noop +prog/weavewait/weavewait_nomcast prog/netcheck/netcheck prog/docker_tls_args/docker_tls_args prog/plugin/plugin @@ -56,6 +57,7 @@ prog/weaveexec/weavehosts prog/weaveexec/weaveproxy prog/weaveexec/weavewait prog/weaveexec/weavewait_noop +prog/weaveexec/weavewait_nomcast prog/weaveexec/sigproxy prog/weaveexec/netcheck prog/weaveexec/docker_tls_args diff --git a/Makefile b/Makefile index 7680e1a7..cb468e93 100644 --- a/Makefile +++ b/Makefile @@ -14,13 +14,14 @@ WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy SIGPROXY_EXE=prog/sigproxy/sigproxy WEAVEWAIT_EXE=prog/weavewait/weavewait WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop +WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast NETCHECK_EXE=prog/netcheck/netcheck DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args DOCKERPLUGIN_EXE=prog/plugin/plugin RUNNER_EXE=tools/runner/runner TEST_TLS_EXE=test/tls/tls -EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE) +EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE) WEAVER_UPTODATE=.weaver.uptodate WEAVEEXEC_UPTODATE=.weaveexec.uptodate @@ -81,29 +82,37 @@ $(NETCHECK_EXE): prog/netcheck/netcheck.go # These next programs need separate rules as they fail the netgo check in # the main build stanza due to not importing net package $(SIGPROXY_EXE): prog/sigproxy/main.go -$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go $(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go $(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/net/*.go plugin/ipam/*.go plugin/skel/*.go api/*.go common/docker/*.go $(TEST_TLS_EXE): test/tls/*.go -$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE): +$(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE): go get -tags netgo ./$(@D) go build $(BUILD_FLAGS) -o $@ ./$(@D) +$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go + go get -tags netgo ./$(@D) + go build $(BUILD_FLAGS) -tags "iface mcast" -o $@ ./$(@D) + +$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go + go get -tags netgo ./$(@D) + go build $(BUILD_FLAGS) -tags iface -o $@ ./$(@D) + $(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go go get -tags netgo ./$(@D) - go build $(BUILD_FLAGS) -tags noop -o $@ ./$(@D) + go build $(BUILD_FLAGS) -o $@ ./$(@D) $(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE) $(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver touch $@ -$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) +$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) cp weave prog/weaveexec/weave cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop + cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast cp $(NETCHECK_EXE) prog/weaveexec/netcheck cp $(DOCKERTLSARGS_EXE) prog/weaveexec/docker_tls_args cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz diff --git a/prog/weavewait/check_network.go b/prog/weavewait/check_network.go index 9c17bf8e..53b5385d 100644 --- a/prog/weavewait/check_network.go +++ b/prog/weavewait/check_network.go @@ -1,12 +1,7 @@ -// +build !noop +// +build !iface,!mcast package main -import ( - weavenet "github.com/weaveworks/weave/net" -) - func checkNetwork() error { - _, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe") - return err + return nil } diff --git a/prog/weavewait/check_network_iface.go b/prog/weavewait/check_network_iface.go new file mode 100644 index 00000000..9a627af8 --- /dev/null +++ b/prog/weavewait/check_network_iface.go @@ -0,0 +1,12 @@ +// +build iface,!mcast + +package main + +import ( + weavenet "github.com/weaveworks/weave/net" +) + +func checkNetwork() error { + _, err := weavenet.EnsureInterface("ethwe") + return err +} diff --git a/prog/weavewait/check_network_iface_mcast.go b/prog/weavewait/check_network_iface_mcast.go new file mode 100644 index 00000000..898c3b3d --- /dev/null +++ b/prog/weavewait/check_network_iface_mcast.go @@ -0,0 +1,12 @@ +// +build iface,mcast + +package main + +import ( + weavenet "github.com/weaveworks/weave/net" +) + +func checkNetwork() error { + _, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe") + return err +} diff --git a/prog/weavewait/noop.go b/prog/weavewait/noop.go deleted file mode 100644 index 2342311d..00000000 --- a/prog/weavewait/noop.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build noop - -package main - -func checkNetwork() error { - return nil -} From ce25f58639490840ba2f6f1557a98004e9fa3bde Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 3 Dec 2015 15:09:23 +0000 Subject: [PATCH 097/135] `weave launch-proxy` accepts --no-multicast-route --- prog/weaveexec/Dockerfile | 2 ++ prog/weaveproxy/main.go | 1 + proxy/create_container_interceptor.go | 10 ++++++++-- proxy/proxy.go | 24 ++++++++++++++++-------- proxy/start_container_interceptor.go | 10 ++++++++-- weave | 1 + 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/prog/weaveexec/Dockerfile b/prog/weaveexec/Dockerfile index 2b44f344..f700f20a 100644 --- a/prog/weaveexec/Dockerfile +++ b/prog/weaveexec/Dockerfile @@ -6,6 +6,7 @@ LABEL works.weave.role=system WORKDIR /home/weave VOLUME /w VOLUME /w-noop +VOLUME /w-nomcast ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"] RUN apk add --update \ @@ -22,4 +23,5 @@ ADD ./weave ./sigproxy ./weaveproxy ./symlink /home/weave/ ADD ./netcheck ./docker_tls_args /usr/bin/ ADD ./weavewait /w/w ADD ./weavewait_noop /w-noop/w +ADD ./weavewait_nomcast /w-nomcast/w ADD ./docker.tgz / diff --git a/prog/weaveproxy/main.go b/prog/weaveproxy/main.go index 9db07cd5..6de9a8b4 100644 --- a/prog/weaveproxy/main.go +++ b/prog/weaveproxy/main.go @@ -40,6 +40,7 @@ func main() { mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote") mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver") mflag.BoolVar(&c.WithoutDNS, []string{"-without-dns"}, false, "instruct created containers to never use weaveDNS as their nameserver") + mflag.BoolVar(&c.NoMulticastRoute, []string{"-no-multicast-route"}, false, "do not add a multicast route via the weave interface when attaching containers") mflag.Parse() if justVersion { diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go index 7f4bc84e..b13d16f3 100644 --- a/proxy/create_container_interceptor.go +++ b/proxy/create_container_interceptor.go @@ -53,8 +53,14 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error { Log.Infof("Leaving container alone because %s", err) } else { Log.Infof("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " ")) - if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil { - return err + if i.proxy.NoMulticastRoute { + if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil { + return err + } + } else { + if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil { + return err + } } if err := i.setWeaveWaitEntrypoint(container); err != nil { return err diff --git a/proxy/proxy.go b/proxy/proxy.go index bf14cb76..9c8b15f7 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -60,6 +60,7 @@ type Config struct { Version string WithDNS bool WithoutDNS bool + NoMulticastRoute bool } type wait struct { @@ -71,13 +72,14 @@ type wait struct { type Proxy struct { sync.Mutex Config - client *docker.Client - dockerBridgeIP string - hostnameMatchRegexp *regexp.Regexp - weaveWaitVolume string - weaveWaitNoopVolume string - normalisedAddrs []string - waiters map[*http.Request]*wait + client *docker.Client + dockerBridgeIP string + hostnameMatchRegexp *regexp.Regexp + weaveWaitVolume string + weaveWaitNoopVolume string + weaveWaitNomcastVolume string + normalisedAddrs []string + waiters map[*http.Request]*wait } func NewProxy(c Config) (*Proxy, error) { @@ -146,7 +148,10 @@ func (proxy *Proxy) findWeaveWaitVolumes() error { if proxy.weaveWaitVolume, err = proxy.findVolume("/w"); err != nil { return err } - proxy.weaveWaitNoopVolume, err = proxy.findVolume("/w-noop") + if proxy.weaveWaitNoopVolume, err = proxy.findVolume("/w-noop"); err != nil { + return err + } + proxy.weaveWaitNomcastVolume, err = proxy.findVolume("/w-nomcast") return err } @@ -430,6 +435,9 @@ func (proxy *Proxy) attach(container *docker.Container, orDie bool) error { } } } + if proxy.NoMulticastRoute { + args = append(args, "--no-multicast-route") + } if orDie { args = append(args, "--or-die") } diff --git a/proxy/start_container_interceptor.go b/proxy/start_container_interceptor.go index 6e68059e..678fbacf 100644 --- a/proxy/start_container_interceptor.go +++ b/proxy/start_container_interceptor.go @@ -41,8 +41,14 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error { return err } } else { - if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil { - return err + if i.proxy.NoMulticastRoute { + if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil { + return err + } + } else { + if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil { + return err + } } if dnsDomain := i.proxy.getDNSDomain(); dnsDomain != "" { if err := i.proxy.setWeaveDNS(hostConfig, container.Config.Hostname, dnsDomain); err != nil { diff --git a/weave b/weave index cc4beb64..c9e7399d 100755 --- a/weave +++ b/weave @@ -48,6 +48,7 @@ weave launch-router [--password ] [--nickname ] [--no-discovery] [--init-peer-count ] ... weave launch-proxy [-H ] [--with-dns | --without-dns] [--no-default-ipalloc] [--no-rewrite-hosts] + [--no-multicast-route] [--hostname-from-label ] [--hostname-match ] [--hostname-replacement ] From 96f69583e7f6056136945589c37c98565a9a7539 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 3 Dec 2015 15:32:03 +0000 Subject: [PATCH 098/135] Smoke test --no-multicast-route --- test/195_no_multicast_route_1_test.sh | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100755 test/195_no_multicast_route_1_test.sh diff --git a/test/195_no_multicast_route_1_test.sh b/test/195_no_multicast_route_1_test.sh new file mode 100755 index 00000000..1aedd257 --- /dev/null +++ b/test/195_no_multicast_route_1_test.sh @@ -0,0 +1,30 @@ +#! /bin/bash + +. ./config.sh + +show_multicast_route_on() { + exec_on $1 $2 ip route show 224.0.0.0/4 +} + +start_suite "--no-multicast-route operation" + +# Ensure containers run either way have no multicast route +weave_on $HOST1 launch-router +weave_on $HOST1 launch-proxy --no-multicast-route + +start_container $HOST1 --no-multicast-route --name c1 +proxy_start_container $HOST1 --name c2 + +assert "show_multicast_route_on $HOST1 c1" +assert "show_multicast_route_on $HOST1 c2" + +# Ensure current proxy options are obeyed on container start +docker_on $HOST1 stop c2 +weave_on $HOST1 stop-proxy +weave_on $HOST1 launch-proxy + +proxy docker_on $HOST1 start c2 + +assert "show_multicast_route_on $HOST1 c2" "224.0.0.0/4 dev ethwe " + +end_suite From d434b50dfcdd1da79a5316450499a8918a68da1d Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 4 Dec 2015 15:49:39 +0000 Subject: [PATCH 099/135] Include trust level in features exchange Add a 'Trusted' feature to the protocol exchange which indicates whether the remote considers us to be reachable via a secure network; overlay encryption will be disabled in the event that both peers agree. For backwards compatibility purposes a default value of untrusted is assumed if the feature is missing. --- mesh/connection.go | 47 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/mesh/connection.go b/mesh/connection.go index 7edb9253..b2c09139 100644 --- a/mesh/connection.go +++ b/mesh/connection.go @@ -40,17 +40,19 @@ type RemoteConnection struct { type LocalConnection struct { sync.RWMutex RemoteConnection - TCPConn *net.TCPConn - version byte - tcpSender TCPSender - SessionKey *[32]byte - heartbeatTCP *time.Ticker - Router *Router - uid uint64 - actionChan chan<- ConnectionAction - errorChan chan<- error - finished <-chan struct{} // closed to signal that actorLoop has finished - OverlayConn OverlayConnection + TCPConn *net.TCPConn + TrustRemote bool // is remote on a trusted subnet? + TrustedByRemote bool // does remote trust us? + version byte + tcpSender TCPSender + SessionKey *[32]byte + heartbeatTCP *time.Ticker + Router *Router + uid uint64 + actionChan chan<- ConnectionAction + errorChan chan<- error + finished <-chan struct{} // closed to signal that actorLoop has finished + OverlayConn OverlayConnection } type ConnectionAction func() error @@ -94,6 +96,7 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ro RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here. Router: router, TCPConn: tcpConn, + TrustRemote: false, uid: randUint64(), actionChan: actionChan, errorChan: errorChan, @@ -189,13 +192,19 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, errorChan < conn.Log("connection ready; using protocol version", conn.version) + // only use negotiated session key for untrusted connections + var sessionKey *[32]byte + if conn.Untrusted() { + sessionKey = conn.SessionKey + } + params := OverlayConnectionParams{ RemotePeer: conn.remote, LocalAddr: conn.TCPConn.LocalAddr().(*net.TCPAddr), RemoteAddr: conn.TCPConn.RemoteAddr().(*net.TCPAddr), Outbound: conn.outbound, ConnUID: conn.uid, - SessionKey: conn.SessionKey, + SessionKey: sessionKey, SendControlMessage: conn.sendOverlayControlMessage, Features: intro.Features, } @@ -253,6 +262,7 @@ func (conn *LocalConnection) makeFeatures() map[string]string { "ShortID": fmt.Sprint(conn.local.ShortID), "UID": fmt.Sprint(conn.local.UID), "ConnID": fmt.Sprint(conn.uid), + "Trusted": fmt.Sprint(conn.TrustRemote), } conn.Router.Overlay.AddFeaturesTo(features) return features @@ -300,6 +310,15 @@ func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) { } } + var trusted bool + if trustedStr, present := features["Trusted"]; present { + trusted, err = strconv.ParseBool(trustedStr) + if err != nil { + return nil, err + } + } + conn.TrustedByRemote = trusted + uid, err := ParsePeerUID(features.Get("UID")) if err != nil { return nil, err @@ -435,3 +454,7 @@ func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) func (conn *LocalConnection) extendReadDeadline() { conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2)) } + +func (conn *LocalConnection) Untrusted() bool { + return !conn.TrustRemote || !conn.TrustedByRemote +} From 059cc65b559f70d797897c3a1bfe2f5266ee1070 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 4 Dec 2015 16:52:02 +0000 Subject: [PATCH 100/135] Fallback to encrypted sleeve on a per-connection basis If overlay encryption is required between peers, the fast datapath forwarder returns an error rather than aborting the process, allowing the overlay switch to fall back gracefully to an encrypted sleeve overlay connection. --- mesh/connection.go | 2 +- mesh/router.go | 15 +++++++++++++++ prog/weaver/main.go | 30 ++++++++++++++++++++++++++---- router/fastdp.go | 5 ++--- router/overlay_switch.go | 12 ++++++++++-- weave | 3 ++- 6 files changed, 56 insertions(+), 11 deletions(-) diff --git a/mesh/connection.go b/mesh/connection.go index b2c09139..ae03017d 100644 --- a/mesh/connection.go +++ b/mesh/connection.go @@ -96,7 +96,7 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ro RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here. Router: router, TCPConn: tcpConn, - TrustRemote: false, + TrustRemote: router.Trusts(connRemote), uid: randUint64(), actionChan: actionChan, errorChan: errorChan, diff --git a/mesh/router.go b/mesh/router.go index 5f51cc87..598e485d 100644 --- a/mesh/router.go +++ b/mesh/router.go @@ -37,6 +37,7 @@ type Config struct { Password []byte ConnLimit int PeerDiscovery bool + TrustedSubnets []*net.IPNet } type Router struct { @@ -181,3 +182,17 @@ func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameS } return origUpdate, newUpdate, nil } + +func (router *Router) Trusts(remote *RemoteConnection) bool { + if tcpAddr, err := net.ResolveTCPAddr("tcp4", remote.remoteTCPAddr); err == nil { + for _, trustedSubnet := range router.TrustedSubnets { + if trustedSubnet.Contains(tcpAddr.IP) { + return true + } + } + } else { + // Should not happen as remoteTCPAddr was obtained from TCPConn + log.Errorf("Unable to parse remote TCP addr: %s", err) + } + return false +} diff --git a/prog/weaver/main.go b/prog/weaver/main.go index d5fb2553..51acd0c3 100644 --- a/prog/weaver/main.go +++ b/prog/weaver/main.go @@ -69,6 +69,7 @@ func main() { dnsEffectiveListenAddress string iface *net.Interface datapathName string + trustedSubnetStr string ) mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit") @@ -101,6 +102,8 @@ func main() { mflag.StringVar(&dnsEffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping") mflag.StringVar(&datapathName, []string{"-datapath"}, "", "ODP datapath name") + mflag.StringVar(&trustedSubnetStr, []string{"-trusted-subnets"}, "", "Command separated list of trusted subnets in CIDR notation") + // crude way of detecting that we probably have been started in a // container, with `weave launch` --> suppress misleading paths in // mflags error messages. @@ -193,10 +196,7 @@ func main() { Log.Println("Communication between peers is unencrypted.") } else { config.Password = []byte(password) - Log.Println("Communication between peers is encrypted.") - - // fastdp doesn't support encryption - fastDPOverlay = nil + Log.Println("Communication between peers via untrusted networks is encrypted.") } overlays := weave.NewOverlaySwitch() @@ -237,6 +237,10 @@ func main() { networkConfig.PacketLogging = nopPacketLogging{} } + if config.TrustedSubnets, err = parseTrustedSubnets(trustedSubnetStr); err != nil { + Log.Fatal("Unable to parse trusted subnets: ", err) + } + router := weave.NewNetworkRouter(config, networkConfig, name, nickName, overlays) Log.Println("Our name is", router.Ourself) @@ -405,6 +409,24 @@ func determineQuorum(initPeerCountFlag int, peers []string) uint { return quorum } +func parseTrustedSubnets(trustedSubnetStr string) ([]*net.IPNet, error) { + trustedSubnets := []*net.IPNet{} + + if trustedSubnetStr == "" { + return trustedSubnets, nil + } + + for _, subnetStr := range strings.Split(trustedSubnetStr, ",") { + _, subnet, err := net.ParseCIDR(subnetStr) + if err != nil { + return nil, err + } + trustedSubnets = append(trustedSubnets, subnet) + } + + return trustedSubnets, nil +} + func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) { protocol := "tcp" if strings.HasPrefix(httpAddr, "/") { diff --git a/router/fastdp.go b/router/fastdp.go index 571fb10c..0fee5205 100644 --- a/router/fastdp.go +++ b/router/fastdp.go @@ -515,9 +515,8 @@ type fastDatapathForwarder struct { func (fastdp fastDatapathOverlay) PrepareConnection(params mesh.OverlayConnectionParams) (mesh.OverlayConnection, error) { if params.SessionKey != nil { - // No encryption suport in fastdp. The weaver main.go - // is responsible for ensuring this doesn't happen. - log.Fatal("Attempt to use FastDatapath with encryption") + // No encryption support in fastdp + return nil, fmt.Errorf("encryption not supported") } vxlanVportID := fastdp.mainVxlanVportID diff --git a/router/overlay_switch.go b/router/overlay_switch.go index 9e6efd00..da4bab78 100644 --- a/router/overlay_switch.go +++ b/router/overlay_switch.go @@ -204,8 +204,16 @@ func (osw *OverlaySwitch) PrepareConnection(params mesh.OverlayConnectionParams) subConn, err := overlay.PrepareConnection(params) if err != nil { - fwd.stopFrom(0) - return nil, err + log.Infof("Unable to use %s for connection to %s(%s): %s", + overlay.name, + params.RemotePeer.Name, + params.RemotePeer.NickName, + err) + // failed to start subforwarder - record overlay name and continue + fwd.forwarders[i] = subForwarder{ + overlayName: overlay.name, + } + continue } subFwd := subConn.(OverlayForwarder) diff --git a/weave b/weave index bd3c49dc..feca703d 100755 --- a/weave +++ b/weave @@ -42,7 +42,8 @@ weave setup weave version weave launch [--password ] [--nickname ] [--ipalloc-range [--ipalloc-default-subnet ]] - [--no-discovery] [--init-peer-count ] ... + [--no-discovery] [--init-peer-count ] + [--trusted-subnets ,...] ... weave launch-router [--password ] [--nickname ] [--ipalloc-range [--ipalloc-default-subnet ]] [--no-discovery] [--init-peer-count ] ... From 4e41d4d45125e13d923088b0e124639ac5d4423c Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 7 Dec 2015 14:54:42 +0000 Subject: [PATCH 101/135] Add trusted subnets to `weave status` `weave status` now displays the list of specified trusted subnets, and `weave staus connections` shows the encryption state of individual connections. --- mesh/status.go | 20 +++++++++++++++++++- prog/weaver/http.go | 9 +++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/mesh/status.go b/mesh/status.go index 0dce08bb..559dae66 100644 --- a/mesh/status.go +++ b/mesh/status.go @@ -2,6 +2,7 @@ package mesh import ( "fmt" + "net" ) type Status struct { @@ -19,6 +20,7 @@ type Status struct { Connections []LocalConnectionStatus Targets []string OverlayDiagnostics interface{} + TrustedSubnets []string } type PeerStatus struct { @@ -69,7 +71,8 @@ func NewStatus(router *Router) *Status { NewBroadcastRouteStatusSlice(router.Routes), NewLocalConnectionStatusSlice(router.ConnectionMaker), NewTargetSlice(router.ConnectionMaker), - router.Overlay.Diagnostics()} + router.Overlay.Diagnostics(), + NewTrustedSubnetsSlice(router.TrustedSubnets)} } func NewPeerStatusSlice(peers *Peers) []PeerStatus { @@ -147,6 +150,13 @@ func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus } lc, _ := conn.(*LocalConnection) info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote()) + if lc.Router.UsingPassword() { + if lc.Untrusted() { + info = fmt.Sprintf("%-11v %v", "encrypted", info) + } else { + info = fmt.Sprintf("%-11v %v", "unencrypted", info) + } + } slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, info}) } for address, target := range cm.targets { @@ -191,3 +201,11 @@ func NewTargetSlice(cm *ConnectionMaker) []string { } return <-resultChan } + +func NewTrustedSubnetsSlice(trustedSubnets []*net.IPNet) []string { + trustedSubnetStrs := []string{} + for _, trustedSubnet := range trustedSubnets { + trustedSubnetStrs = append(trustedSubnetStrs, trustedSubnet.String()) + } + return trustedSubnetStrs +} diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 1dc84925..2fed6787 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -27,11 +27,11 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } return count }, - "upstreamServers": func(servers []string) string { - if len(servers) == 0 { + "printList": func(list []string) string { + if len(list) == 0 { return "none" } - return strings.Join(servers, ", ") + return strings.Join(list, ", ") }, "printIPAMRanges": func(router weave.NetworkRouterStatus, status ipam.Status) string { var buffer bytes.Buffer @@ -153,6 +153,7 @@ var statusTemplate = defTemplate("status", `\ Targets: {{len .Router.Targets}} Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}} Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}} +TrustedSubnets: {{printList .Router.TrustedSubnets}} {{if .IPAM}}\ Service: ipam @@ -176,7 +177,7 @@ var statusTemplate = defTemplate("status", `\ Service: dns Domain: {{.DNS.Domain}} - Upstream: {{upstreamServers .DNS.Upstream}} + Upstream: {{printList .DNS.Upstream}} TTL: {{.DNS.TTL}} Entries: {{countDNSEntries .DNS.Entries}} {{end}}\ From e0cb9cf24ddb59ea0db4899c79466a678a0e0a5f Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 7 Dec 2015 15:32:39 +0000 Subject: [PATCH 102/135] Restore leading whitespace to `weave status` --- prog/weaver/http.go | 46 ++++++++++++++++++++++----------------------- weave | 8 ++++---- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 2fed6787..a0cca5c8 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -138,48 +138,48 @@ func defTemplate(name string, text string) *template.Template { } var statusTemplate = defTemplate("status", `\ - Version: {{.Version}} + Version: {{.Version}} - Service: router - Protocol: {{.Router.Protocol}} \ + Service: router + Protocol: {{.Router.Protocol}} \ {{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\ {{.Router.ProtocolMaxVersion}}\ {{else}}\ {{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\ {{end}} - Name: {{.Router.Name}}({{.Router.NickName}}) - Encryption: {{printState .Router.Encryption}} - PeerDiscovery: {{printState .Router.PeerDiscovery}} - Targets: {{len .Router.Targets}} - Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}} - Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}} -TrustedSubnets: {{printList .Router.TrustedSubnets}} + Name: {{.Router.Name}}({{.Router.NickName}}) + Encryption: {{printState .Router.Encryption}} + PeerDiscovery: {{printState .Router.PeerDiscovery}} + Targets: {{len .Router.Targets}} + Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}} + Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}} + TrustedSubnets: {{printList .Router.TrustedSubnets}} {{if .IPAM}}\ - Service: ipam + Service: ipam {{if .IPAM.Entries}}\ {{if allIPAMOwnersUnreachable .IPAM}}\ - Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead + Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead {{else if len .IPAM.PendingAllocates}}\ - Status: waiting for IP range grant from peers + Status: waiting for IP range grant from peers {{else}}\ - Status: ready + Status: ready {{end}}\ {{else if .IPAM.Paxos}}\ - Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}}) + Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}}) {{else}}\ - Status: idle + Status: idle {{end}}\ - Range: {{.IPAM.Range}} - DefaultSubnet: {{.IPAM.DefaultSubnet}} + Range: {{.IPAM.Range}} + DefaultSubnet: {{.IPAM.DefaultSubnet}} {{end}}\ {{if .DNS}}\ - Service: dns - Domain: {{.DNS.Domain}} - Upstream: {{printList .DNS.Upstream}} - TTL: {{.DNS.TTL}} - Entries: {{countDNSEntries .DNS.Entries}} + Service: dns + Domain: {{.DNS.Domain}} + Upstream: {{printList .DNS.Upstream}} + TTL: {{.DNS.TTL}} + Entries: {{countDNSEntries .DNS.Entries}} {{end}}\ `) diff --git a/weave b/weave index feca703d..9d2cef47 100755 --- a/weave +++ b/weave @@ -1892,13 +1892,13 @@ EOF fi if [ -z "$SUB_STATUS" ] && check_running $PROXY_CONTAINER_NAME 2>/dev/null && PROXY_ADDRS=$(proxy_addrs) ; then echo - echo " Service: proxy" - echo " Address: $PROXY_ADDRS" + echo " Service: proxy" + echo " Address: $PROXY_ADDRS" fi if [ -z "$SUB_STATUS" ] && check_running $PLUGIN_CONTAINER_NAME 2>/dev/null ; then echo - echo " Service: plugin" - echo " DriverName: weave" + echo " Service: plugin" + echo " DriverName: weave" fi [ -n "$SUB_STATUS" ] || echo [ $res -eq 0 ] From b9792f8f96ba396f14a7e470459932bb8da2aa39 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 7 Dec 2015 16:04:49 +0000 Subject: [PATCH 103/135] Document trusted subnets Update features and troubleshooting markdown. --- site/features.md | 18 ++++++++++----- site/troubleshooting.md | 50 ++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/site/features.md b/site/features.md index 4dbbb7ab..e7e949a1 100644 --- a/site/features.md +++ b/site/features.md @@ -63,7 +63,9 @@ Weave automatically chooses the fastest available method to transport data between peers. The most performant of these ('fastdp') offers near-native throughput and latency but does not support encryption; consequently supplying a password will cause the router to fall back -to a slower mode ('sleeve') that does. +to a slower mode ('sleeve') that does, for connections that traverse +untrusted networks (see the [security](#security) section for more +details). Even when encryption is not in use, certain adverse network conditions will cause this fallback to occur dynamically; in these circumstances, @@ -321,10 +323,16 @@ way to generate a random password which satsifies this requirement is < /dev/urandom tr -dc A-Za-z0-9 | head -c9 ; echo -The same password must be specified for all weave peers. Note that -supplying a password will [cause weave to fall back to a slower -method](#fast-data-path) for transporting data between -peers. +The same password must be specified for all weave peers; by default +both control and data plane traffic will then use authenticated +encryption. If some of your peers are colocated in a trusted network +(for example within the boundary of your own datacentre) you can use +the `--trusted-subnets` argument to `weave launch` to selectively +disable data plane encryption as an optimisation. Both peers must +consider the other to be in a trusted subnet for this to take place - +if they do not, weave will [fall back to a slower +method](#fast-data-path) for transporting data between peers as fast +datapath does not support encryption. Be aware that: diff --git a/site/troubleshooting.md b/site/troubleshooting.md index bc5bbb2b..22b81758 100644 --- a/site/troubleshooting.md +++ b/site/troubleshooting.md @@ -68,29 +68,30 @@ A status summary can be obtained with `weave status`: ```` $ weave status - Version: 1.1.0 + Version: 1.1.0 - Service: router - Protocol: weave 1..2 - Name: 4a:0f:f6:ec:1c:93(host1) - Encryption: disabled - PeerDiscovery: enabled - Targets: [192.168.48.14 192.168.48.15] - Connections: 5 (1 established, 1 pending, 1 retrying, 1 failed, 1 connecting) - Peers: 3 (with 5 established, 1 pending connections) + Service: router + Protocol: weave 1..2 + Name: 4a:0f:f6:ec:1c:93(host1) + Encryption: disabled + PeerDiscovery: enabled + Targets: [192.168.48.14 192.168.48.15] + Connections: 5 (1 established, 1 pending, 1 retrying, 1 failed, 1 connecting) + Peers: 3 (with 5 established, 1 pending connections) + TrustedSubnets: none - Service: ipam - Consensus: achieved - Range: 10.32.0.0-10.47.255.255 - DefaultSubnet: 10.32.0.0/12 + Service: ipam + Consensus: achieved + Range: 10.32.0.0-10.47.255.255 + DefaultSubnet: 10.32.0.0/12 - Service: dns - Domain: weave.local. - TTL: 1 - Entries: 9 + Service: dns + Domain: weave.local. + TTL: 1 + Entries: 9 - Service: proxy - Address: tcp://127.0.0.1:12375 + Service: proxy + Address: tcp://127.0.0.1:12375 ```` @@ -129,6 +130,9 @@ state. Further details are available with number of connections peers have to other peers. Further details are available with [`weave status peers`](#weave-status-peers). +'TrustedSubnets' shows subnets which the router trusts as specified by +the `--trusted-subnets` option to `weave launch`. + There are further sections for the [IP address allocator](ipam.html#troubleshooting), [weaveDNS](weavedns.html#troubleshooting), and [Weave Docker API @@ -141,8 +145,8 @@ obtained with `weave status connections`: ```` $ weave status connections -<- 192.168.48.12:33866 established fastdp 7e:21:4a:70:2f:45(host2) -<- 192.168.48.13:60773 pending fastdp 7e:ae:cd:d5:23:8d(host3) +<- 192.168.48.12:33866 established unencrypted fastdp 7e:21:4a:70:2f:45(host2) +<- 192.168.48.13:60773 pending encrypted fastdp 7e:ae:cd:d5:23:8d(host3) -> 192.168.48.14:6783 retrying dial tcp4 192.168.48.14:6783: no route to host -> 192.168.48.15:6783 failed dial tcp4 192.168.48.15:6783: no route to host, retry: 2015-08-06 18:55:38.246910357 +0000 UTC -> 192.168.48.16:6783 connecting @@ -162,8 +166,8 @@ The columns are as follows: heartbeat * `established` - TCP connection and corresponding UDP path are up * Info - the failure reason for failed and retrying connections, or - the data transport method, remote peer name and nickname for - pending and established connections + the encryption mode, data transport method, remote peer name and + nickname for pending and established connections ### List peers From 85d4b1f1379fe449d836ceb186f7eb0c4a2c5e82 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 7 Dec 2015 16:36:23 +0000 Subject: [PATCH 104/135] Smoke test trusted subnets --- test/110_encryption_2_test.sh | 3 +++ test/115_optional_encryption_2_test.sh | 30 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100755 test/115_optional_encryption_2_test.sh diff --git a/test/110_encryption_2_test.sh b/test/110_encryption_2_test.sh index 5f46a463..205a9928 100755 --- a/test/110_encryption_2_test.sh +++ b/test/110_encryption_2_test.sh @@ -14,4 +14,7 @@ start_container $HOST1 $C1/24 --name=c1 start_container $HOST2 $C2/24 --name=c2 assert_raises "exec_on $HOST1 c1 $PING $C2" +assert_raises "weave_on $HOST1 status connections | grep encrypted" +assert_raises "weave_on $HOST2 status connections | grep encrypted" + end_suite diff --git a/test/115_optional_encryption_2_test.sh b/test/115_optional_encryption_2_test.sh new file mode 100755 index 00000000..a84f2e65 --- /dev/null +++ b/test/115_optional_encryption_2_test.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +. ./config.sh + +start_suite "Optional encryption via trusted subnets" + +# Determine subnet for hosts given either an IP or name. We need to resolve +# these entries on the remote hosts to make sure we get the private +# IP addresses in the Circle/GCE context +HOST1_IP=$($SSH $HOST1 "getent hosts $HOST1" | grep $HOST1 | cut -d ' ' -f 1) +HOST2_IP=$($SSH $HOST2 "getent hosts $HOST2" | grep $HOST2 | cut -d ' ' -f 1) +HOST1_CIDR=$($SSH $HOST1 "ip addr show" | grep -oP $HOST1_IP/[0-9]+) +HOST2_CIDR=$($SSH $HOST2 "ip addr show" | grep -oP $HOST2_IP/[0-9]+) + +# Check asymmetric trust - connections should be encrypted +weave_on $HOST1 launch --password wfvAwt7sj --trusted-subnets $HOST2_CIDR +weave_on $HOST2 launch --password wfvAwt7sj $HOST1 +assert_raises "weave_on $HOST1 status connections | grep encrypted" +assert_raises "weave_on $HOST2 status connections | grep encrypted" + +weave_on $HOST1 stop +weave_on $HOST2 stop + +# Check symmetric trust - overlay in plaintext +weave_on $HOST1 launch --password wfvAwt7sj --trusted-subnets $HOST2_CIDR +weave_on $HOST2 launch --password wfvAwt7sj --trusted-subnets $HOST1_CIDR $HOST1 +assert_raises "weave_on $HOST1 status connections | grep unencrypted" +assert_raises "weave_on $HOST2 status connections | grep unencrypted" + +end_suite From c341399498d50461c595491b52903e865aca329f Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 15 Dec 2015 13:52:28 +0000 Subject: [PATCH 105/135] Document 'json' function --- site/troubleshooting.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/site/troubleshooting.md b/site/troubleshooting.md index bc5bbb2b..a3df5a23 100644 --- a/site/troubleshooting.md +++ b/site/troubleshooting.md @@ -228,6 +228,12 @@ inspect`: $ weave report -f {% raw %}'{{.DNS.Domain}}'{% endraw %} weave.local. +Weave adds a template function, `json`, which can be applied to get +results in JSON format. + + $ weave report -f {% raw %}'{{json .DNS}}'{% endraw %} + {% raw %}{"Domain":"weave.local.","Upstream":["8.8.8.8","8.8.4.4"],"Address":"172.17.0.1:53","TTL":1,"Entries":null}{% endraw %} + ### List attached containers weave ps From e12dbb460536461443b937113512be58f7b4e3e6 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 15 Dec 2015 11:47:11 +0000 Subject: [PATCH 106/135] Disable pcap optimisation of encrypted sleeve Prior to optional encryption support, enabling encryption disabled the fast datapath overlay (because it doesn't support encryption) but left the OVS datapath netdev in place for bridging (so that users could switch encryption on and off without resetting the bridge). In this situation the ODP miss handler is guaranteed to be invoked for every packet, so as an optimisation the weave script configured the router to use pcap to capture packets from the bridge as that is slightly more efficient. The introduction of optional encryption means the guarantee no longer holds, and so the optimisation must be removed. --- weave | 40 ++-------------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/weave b/weave index 9d2cef47..7a412b49 100755 --- a/weave +++ b/weave @@ -298,8 +298,6 @@ DATAPATH=datapath BRIDGE_IFNAME=link-${BRIDGE} DATAPATH_IFNAME=${DATAPATH}-link CONTAINER_IFNAME=ethwe -# ROUTER_HOSTNETNS_IFNAME is only used for fastdp with encryption -ROUTER_HOSTNETNS_IFNAME=veth-weave PORT=${WEAVE_PORT:-6783} HTTP_PORT=6784 PROXY_PORT=12375 @@ -742,27 +740,7 @@ ask_version() { } router_opts_fastdp() { - if [ -z "$WEAVE_PASSWORD" ] ; then - echo "--datapath $DATAPATH" - else - # When using encryption, we still do bridging on the ODP - # datapath, because you can 'weave launch' without encryption - # and then later restart the router with encryption, or vice - # versa. Encryption disables the use of the fastdp Overlay, - # but the router could still use the fastdp Bridge to receive - # packets. However, pcap has better performance when sniffing - # every packet. So we pass --iface to use the pcap Bridge. - # - # Why don't we simply pass "--iface $BRIDGE". We could, - # except for the fact that NetworkManager likes to down the - # odp $BRIDGE netdev (at least under ubuntu), and you can only - # use pcap on an interface that is up. We avoid that by use - # pcap via a veth pair (NetworkManager leaves them alone). - # Having a netdev in the host netns called "ethwe" might - # surprise people, so it is called $ROUTER_HOSTNETNS_IFNAME - # instead. - echo "--datapath $DATAPATH --iface $ROUTER_HOSTNETNS_IFNAME" - fi + echo "--datapath $DATAPATH" } router_opts_bridge() { @@ -778,15 +756,7 @@ router_opts_bridged_fastdp() { ###################################################################### setup_router_iface_fastdp() { - if [ -n "$WEAVE_PASSWORD" ] ; then - # See router_opts_fastdp - # No-op if already attached - if ip link show $LOCAL_IFNAME >/dev/null 2>&1 ; then - return 0 - fi - connect_container_to_bridge $ROUTER_HOSTNETNS_IFNAME && - ip link set $ROUTER_HOSTNETNS_IFNAME up - fi + true } setup_router_iface_bridge() { @@ -1581,8 +1551,6 @@ launch_router() { if [ "$BRIDGE_TYPE" != bridge ] ; then NETHOST_OPT="--net=host" HTTP_IP=127.0.0.1 - # In case there is a lingering veth-weave netdev - ip link del $ROUTER_HOSTNETNS_IFNAME >/dev/null 2>&1 || true fi # Set WEAVE_DOCKER_ARGS in the environment in order to supply @@ -1635,10 +1603,6 @@ attach_router() { stop_router() { stop $CONTAINER_NAME "Weave" conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true - # Remove the veth-weave netdev in a fastdp context - if detect_bridge_type && [ "$BRIDGE_TYPE" != bridge ] ; then - ip link del $ROUTER_HOSTNETNS_IFNAME >/dev/null 2>&1 || true - fi } launch_proxy() { From a4ca389f2e822dc39cf11b7c3ca5dd4a8b3ff2cc Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 26 Nov 2015 17:36:30 +0000 Subject: [PATCH 107/135] Make the plugin scope (local or global) an option --- plugin/net/driver.go | 11 ++++++----- prog/plugin/main.go | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/plugin/net/driver.go b/plugin/net/driver.go index 5eb5c984..34c5c306 100644 --- a/plugin/net/driver.go +++ b/plugin/net/driver.go @@ -23,14 +23,16 @@ const ( type driver struct { version string nameserver string + scope string sync.RWMutex endpoints map[string]struct{} } -func New(client *docker.Client, version string, nameserver string) (skel.Driver, error) { +func New(client *docker.Client, version string, nameserver string, scope string) (skel.Driver, error) { driver := &driver{ nameserver: nameserver, version: version, + scope: scope, endpoints: make(map[string]struct{}), } @@ -48,11 +50,10 @@ func errorf(format string, a ...interface{}) error { // === protocol handlers -var caps = &api.GetCapabilityResponse{ - Scope: "global", -} - func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) { + var caps = &api.GetCapabilityResponse{ + Scope: driver.scope, + } Log.Debugf("Get capabilities: responded with %+v", caps) return caps, nil } diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 4ae75164..71d8a3ac 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -23,12 +23,14 @@ func main() { address string nameserver string logLevel string + scope string ) flag.BoolVar(&justVersion, "version", false, "print version and exit") 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(&nameserver, "nameserver", "", "nameserver to provide to containers") + flag.StringVar(&scope, "scope", "global", "plugin scope (local or global)") flag.Parse() @@ -46,7 +48,7 @@ func main() { Log.Fatalf("unable to connect to docker: %s", err) } - d, err := netplugin.New(dockerClient, version, nameserver) + d, err := netplugin.New(dockerClient, version, nameserver, scope) if err != nil { Log.Fatalf("unable to create driver: %s", err) } From e2f936fde37d429d0e6f334e51301409f4732d36 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 11 Dec 2015 11:39:23 +0000 Subject: [PATCH 108/135] Rework version check - extract function so we can use the check elsewhere - use shell variable expansion rather than exec-ing 'cut' --- weave | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/weave b/weave index ac3e894c..38d862d5 100755 --- a/weave +++ b/weave @@ -120,25 +120,33 @@ exec_remote() { $WEAVEEXEC_DOCKER_ARGS $EXEC_IMAGE --local "$@" } +# Given $1 and $2 as semantic version numbers like 3.1.2, return [ $1 < $2 ] +version_lt() { + VERSION_MAJOR=${1%.*.*} + REST=${1%.*} VERSION_MINOR=${REST#*.} + VERSION_PATCH=${1#*.*.} + + MIN_VERSION_MAJOR=${2%.*.*} + REST=${2%.*} MIN_VERSION_MINOR=${REST#*.} + MIN_VERSION_PATCH=${2#*.*.} + + if [ \( "$VERSION_MAJOR" -lt "$MIN_VERSION_MAJOR" \) -o \ + \( "$VERSION_MAJOR" -eq "$MIN_VERSION_MAJOR" -a \ + \( "$VERSION_MINOR" -lt "$MIN_VERSION_MINOR" -o \ + \( "$VERSION_MINOR" -eq "$MIN_VERSION_MINOR" -a \ + \( "$VERSION_PATCH" -lt "$MIN_VERSION_PATCH" \) \) \) \) ] ; then + return 0 + fi + return 1 +} + check_docker_version() { if ! DOCKER_VERSION=$(docker -v | sed -n -e 's|^Docker version \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*|\1|p') || [ -z "$DOCKER_VERSION" ] ; then echo "ERROR: Unable to parse docker version" >&2 exit 1 fi - DOCKER_VERSION_MAJOR=$(echo "$DOCKER_VERSION" | cut -d. -f 1) - DOCKER_VERSION_MINOR=$(echo "$DOCKER_VERSION" | cut -d. -f 2) - DOCKER_VERSION_PATCH=$(echo "$DOCKER_VERSION" | cut -d. -f 3) - - MIN_DOCKER_VERSION_MAJOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 1) - MIN_DOCKER_VERSION_MINOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 2) - MIN_DOCKER_VERSION_PATCH=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 3) - - if [ \( "$DOCKER_VERSION_MAJOR" -lt "$MIN_DOCKER_VERSION_MAJOR" \) -o \ - \( "$DOCKER_VERSION_MAJOR" -eq "$MIN_DOCKER_VERSION_MAJOR" -a \ - \( "$DOCKER_VERSION_MINOR" -lt "$MIN_DOCKER_VERSION_MINOR" -o \ - \( "$DOCKER_VERSION_MINOR" -eq "$MIN_DOCKER_VERSION_MINOR" -a \ - \( "$DOCKER_VERSION_PATCH" -lt "$MIN_DOCKER_VERSION_PATCH" \) \) \) \) ] ; then + if version_lt $DOCKER_VERSION $MIN_DOCKER_VERSION ; then echo "ERROR: weave requires Docker version $MIN_DOCKER_VERSION or later; you are running $DOCKER_VERSION" >&2 exit 1 fi From b709cdeab532df260b3d8a42f78dc5ce23012018 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 29 Nov 2015 17:59:26 +0000 Subject: [PATCH 109/135] Create local and global network from the same driver and create local-scope weave network by default on `weave launch` --- prog/plugin/main.go | 125 +++++++++++++++++++++++++++++++++----------- weave | 24 ++++++++- 2 files changed, 117 insertions(+), 32 deletions(-) diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 71d8a3ac..3c3b97af 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -6,8 +6,11 @@ import ( "net" "os" "os/signal" + "strings" "syscall" + "github.com/docker/libnetwork/ipamapi" + go_docker "github.com/fsouza/go-dockerclient" . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/common/docker" ipamplugin "github.com/weaveworks/weave/plugin/ipam" @@ -17,20 +20,26 @@ import ( var version = "(unreleased version)" +const ( + meshNetworkName = "weave" +) + func main() { var ( - justVersion bool - address string - nameserver string - logLevel string - scope string + justVersion bool + address string + nameserver string + meshAddress string + logLevel string + createDefault bool ) flag.BoolVar(&justVersion, "version", false, "print version and exit") 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(&nameserver, "nameserver", "", "nameserver to provide to containers") - flag.StringVar(&scope, "scope", "global", "plugin scope (local or global)") + flag.StringVar(&meshAddress, "meshsocket", "", "socket on which to listen for mesh mode") + flag.BoolVar(&createDefault, "create-default-network", false, "create default network") flag.Parse() @@ -39,53 +48,107 @@ func main() { os.Exit(0) } + if createDefault && meshAddress == "" { + meshAddress = "/run/docker/plugins/weavemesh.sock" + } SetLogLevel(logLevel) - Log.Println("Weave plugin", version, "Command line options:", os.Args) + Log.Println("Weave plugin", version, "Command line options:", os.Args[1:]) dockerClient, err := docker.NewClient("unix:///var/run/docker.sock") if err != nil { Log.Fatalf("unable to connect to docker: %s", err) } - d, err := netplugin.New(dockerClient, version, nameserver, scope) - if err != nil { - Log.Fatalf("unable to create driver: %s", err) + var globalListener, meshListener net.Listener + endChan := make(chan error, 1) + if address != "" { + globalListener, err := listenAndServe(dockerClient, address, nameserver, endChan, "global", false) + if err != nil { + Log.Fatalf("unable to create driver: %s", err) + } + defer globalListener.Close() + } + if meshAddress != "" { + meshListener, err := listenAndServe(dockerClient, meshAddress, nameserver, endChan, "local", true) + if err != nil { + Log.Fatalf("unable to create driver: %s", err) + } + defer meshListener.Close() } - i, err := ipamplugin.NewIpam(dockerClient, version) - if err != nil { - Log.Fatalf("unable to create driver: %s", err) + if createDefault { + createNetwork(dockerClient, meshAddress) } - var listener net.Listener - - // remove socket from last invocation - if err := os.Remove(address); err != nil && !os.IsNotExist(err) { - Log.Fatal(err) - } - listener, err = net.Listen("unix", address) - if err != nil { - Log.Fatal(err) - } - defer listener.Close() - sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM) - endChan := make(chan error, 1) - go func() { - endChan <- skel.Listen(listener, d, i) - }() - select { case sig := <-sigChan: Log.Debugf("Caught signal %s; shutting down", sig) + if err := dockerClient.Client.RemoveNetwork(meshNetworkName); err != nil { + Log.Fatal(err) + } case err := <-endChan: if err != nil { Log.Errorf("Error from listener: %s", err) - listener.Close() + globalListener.Close() + meshListener.Close() os.Exit(1) } } } + +func listenAndServe(dockerClient *docker.Client, address, nameserver string, endChan chan<- error, scope string, withIpam bool) (net.Listener, error) { + d, err := netplugin.New(dockerClient, version, nameserver, scope) + if err != nil { + return nil, err + } + + var i ipamapi.Ipam + if withIpam { + if i, err = ipamplugin.NewIpam(dockerClient, version); err != nil { + return nil, err + } + } + + var listener net.Listener + + // remove sockets from last invocation + if err := os.Remove(address); err != nil && !os.IsNotExist(err) { + return nil, err + } + listener, err = net.Listen("unix", address) + if err != nil { + return nil, err + } + Log.Printf("Listening on %s for %s scope", address, scope) + + go func() { + endChan <- skel.Listen(listener, d, i) + }() + + return listener, nil +} + +func createNetwork(dockerClient *docker.Client, address string) { + if _, err := dockerClient.Client.NetworkInfo(meshNetworkName); err == nil { + Log.Printf("Docker network '%s' already exists", meshNetworkName) + } else if _, ok := err.(*go_docker.NoSuchNetwork); ok { + driverName := strings.TrimSuffix(address, ".sock") + if i := strings.LastIndex(driverName, "/"); i >= 0 { + driverName = driverName[i+1:] + } + options := go_docker.CreateNetworkOptions{ + Name: meshNetworkName, + CheckDuplicate: true, + Driver: driverName, + IPAM: go_docker.IPAMOptions{Driver: driverName}, + } + _, err := dockerClient.Client.CreateNetwork(options) + if err != nil { + Log.Fatalf("Error creating network: %s", err) + } + } +} diff --git a/weave b/weave index 38d862d5..ce795ca9 100755 --- a/weave +++ b/weave @@ -111,6 +111,7 @@ exec_remote() { -e WEAVE_MTU \ -e WEAVE_NO_FASTDP \ -e WEAVE_NO_BRIDGED_FASTDP \ + -e WEAVE_NO_PLUGIN \ -e DOCKER_BRIDGE \ -e DOCKER_CLIENT_HOST="$DOCKER_CLIENT_HOST" \ -e DOCKER_CLIENT_ARGS \ @@ -152,6 +153,22 @@ check_docker_version() { fi } +check_docker_server_api_version() { + # Cope with various versions of `docker version` output format + if ! DOCKER_API_VERSION=$(docker version -f '{{.Server.APIVersion}}' 2> /dev/null ) ; then + if ! DOCKER_API_VERSION=$(docker version -f '{{.Server.ApiVersion}}' 2> /dev/null ) ; then + if ! DOCKER_API_VERSION=$(docker version | sed -n -e 's|^Server API version: *\([0-9][0-9]*\.[0-9][0-9]\).*|\1|p') ; then + echo "ERROR: Unable to determine docker version" >&2 + exit 1 + fi + fi + fi + + if version_lt ${DOCKER_API_VERSION}.0 ${1}.0 ; then + return 1 + fi +} + enforce_docker_bridge_addr_assign_type() { if ! ADDR_ASSIGN_TYPE=$(cat /sys/class/net/$DOCKER_BRIDGE/addr_assign_type 2>/dev/null) ; then echo "Could not determine address assignment type of $DOCKER_BRIDGE" >&2 @@ -1812,6 +1829,10 @@ EOF COMMON_ARGS=$(common_launch_args "$@") launch_router "$@" launch_proxy $COMMON_ARGS + if [ -z $WEAVE_NO_PLUGIN ] && check_docker_server_api_version 1.21 ; then + check_not_running $PLUGIN_CONTAINER_NAME $BASE_PLUGIN_IMAGE + launch_plugin $COMMON_ARGS --create-default-network + fi ;; launch-router) deprecation_warnings "$@" @@ -2088,6 +2109,7 @@ EOF ;; stop) [ $# -eq 0 ] || usage + stop_plugin stop_router stop_proxy ;; @@ -2107,7 +2129,7 @@ EOF [ $# -eq 0 ] || usage warn_if_stopping_proxy_in_env call_weave DELETE /peer >/dev/null 2>&1 || true - for NAME in $CONTAINER_NAME $PROXY_CONTAINER_NAME ; do + for NAME in $PLUGIN_CONTAINER_NAME $CONTAINER_NAME $PROXY_CONTAINER_NAME ; do docker stop $NAME >/dev/null 2>&1 || true docker rm -f $NAME >/dev/null 2>&1 || true done From 9542b77a34ea7fb683a13ba2adad46780bd31fb6 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 1 Dec 2015 17:51:41 +0000 Subject: [PATCH 110/135] Extend smoke-test framework for weaveplugin --- test/config.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/config.sh b/test/config.sh index f50e03b5..6cecffab 100644 --- a/test/config.sh +++ b/test/config.sh @@ -154,6 +154,13 @@ start_container_with_dns() { weave_on $host run --with-dns "$@" -t $DNS_IMAGE /bin/sh } +start_container_local_plugin() { + host=$1 + shift 1 + # using ssh rather than docker -H because CircleCI docker client is older + $SSH $host docker run "$@" -dt --net=weave $SMALL_IMAGE /bin/sh +} + proxy_start_container() { host=$1 shift 1 @@ -217,8 +224,12 @@ assert_dns_ptr_record() { start_suite() { for host in $HOSTS; do [ -z "$DEBUG" ] || echo "Cleaning up on $host: removing all containers and resetting weave" + PLUGIN_ID=$(docker_on $host ps -aq --filter=name=weaveplugin) + PLUGIN_FILTER="cat" + [ -n "$PLUGIN_ID" ] && PLUGIN_FILTER="grep -v $PLUGIN_ID" + rm_containers $host $(docker_on $host ps -aq 2>/dev/null | $PLUGIN_FILTER) + run_on $host "docker network ls | grep -q ' weave ' && docker network rm weave" || true weave_on $host reset 2>/dev/null - rm_containers $host $(docker_on $host ps -aq 2>/dev/null) done whitely echo "$@" } From 153900936255e1f7845a48250064ce79a901f06e Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 1 Dec 2015 17:52:06 +0000 Subject: [PATCH 111/135] Simple smoke-test for 'local' plugin network --- test/800_plugin_cross_hosts_2_test.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100755 test/800_plugin_cross_hosts_2_test.sh diff --git a/test/800_plugin_cross_hosts_2_test.sh b/test/800_plugin_cross_hosts_2_test.sh new file mode 100755 index 00000000..47f82d50 --- /dev/null +++ b/test/800_plugin_cross_hosts_2_test.sh @@ -0,0 +1,21 @@ +#! /bin/bash + +. ./config.sh + +C1_NAME=c1.weave.local +C2_NAME=seetwo.weave.local + +start_suite "Ping and DNS over cross-host weave network (via 'local' plugin)" + +DNS_IP=$(weave_on $host docker-bridge-ip) + +weave_on $HOST1 launch +weave_on $HOST2 launch $HOST1 + +start_container_local_plugin $HOST1 --name=c1 --hostname=$C1_NAME --dns=$DNS_IP +start_container_local_plugin $HOST2 --name=c2 --hostname=$C2_NAME --dns=$DNS_IP + +assert_raises "exec_on $HOST1 c1 $PING $C2_NAME" +assert_raises "exec_on $HOST2 c2 $PING $C1_NAME" + +end_suite From 776ccac00a3d83d10405249c2fa868028252d7c0 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 2 Dec 2015 13:19:24 +0000 Subject: [PATCH 112/135] Pin docker API version to 1.21 --- prog/plugin/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 3c3b97af..b2570136 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -55,7 +55,8 @@ func main() { Log.Println("Weave plugin", version, "Command line options:", os.Args[1:]) - dockerClient, err := docker.NewClient("unix:///var/run/docker.sock") + // API 1.21 is the first version that supports docker network commands + dockerClient, err := docker.NewVersionedClient("unix:///var/run/docker.sock", "1.21") if err != nil { Log.Fatalf("unable to connect to docker: %s", err) } From 791fc9d6b1ec7ae36ba8ccab3b7fc24093b4e131 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 4 Dec 2015 17:49:09 +0000 Subject: [PATCH 113/135] Return fake 'gateway address' to avoid requiring consensus during initialization --- plugin/ipam/driver.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/ipam/driver.go b/plugin/ipam/driver.go index 1fbdf91b..766aa9a1 100644 --- a/plugin/ipam/driver.go +++ b/plugin/ipam/driver.go @@ -4,6 +4,7 @@ import ( "net" "github.com/docker/libnetwork/ipamapi" + "github.com/docker/libnetwork/netlabel" "github.com/weaveworks/weave/api" . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/common/docker" @@ -32,7 +33,10 @@ func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[strin Log.Debugln("RequestPool", addressSpace, pool, subPool, options) cidr, err := i.weave.DefaultSubnet() Log.Debugln("RequestPool returning ", cidr, err) - return "weavepool", cidr, nil, err + // Pass back a fake "gateway address"; we don't actually use it, + // so just give the network address. + data := map[string]string{netlabel.Gateway: cidr.String()} + return "weavepool", cidr, data, err } func (i *ipam) ReleasePool(poolID string) error { From 41482aff012a307b5e5131df11fce2d72dfbb69e Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 2 Dec 2015 16:11:18 +0000 Subject: [PATCH 114/135] Update plugin README and documentation --- plugin/README.md | 62 ++---------------------------- site/features.md | 15 ++------ site/plugin.md | 84 +++++++++++++++++++++++++++++++++++++++++ site/troubleshooting.md | 3 ++ weave | 1 + 5 files changed, 94 insertions(+), 71 deletions(-) create mode 100644 site/plugin.md diff --git a/plugin/README.md b/plugin/README.md index 4af2efc7..1e246c1c 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -3,63 +3,7 @@ A [plugin](http://docs.docker.com/engine/extend/plugin_api/) to integrate [weave Net](http://weave.works/net/) with Docker. -## Setup: +The plugin runs automatically when you `weave launch`, provided your +Docker daemon is version 1.9 or newer. -Before you can use this plugin, you must [install weave](https://github.com/weaveworks/weave#installation) and [configure docker with a "cluster store"](https://docs.docker.com/engine/userguide/networking/get-started-overlay/#step-1-set-up-a-key-value-store). - - $ weave launch - $ weave launch-plugin - $ docker network create --driver=weave weave - -When you start subsequent weave peers you have to tell them about other peers, e.g. - - $ weave launch - $ weave launch-plugin - -## Starting a container: - - $ docker run --net=weave -ti ubuntu - -## Plugin command-line arguments - - * `--socket=`, which is the socket on which to listen for the - [plugin protocol](http://docs.docker.com/engine/extend/plugin_api/). - This defaults to what Docker expects, that is - `"/run/docker/plugins/weave.sock"`, but you may wish to - change it, for instance if you're running more than one instance of - the driver for some reason. - - * `--log-level=debug|info|warning|error`, which tells the plugin - how much information to emit for debugging. - -## Points to note - -Network plugins require Docker version 1.9 or later. - -### Docker needs a "cluster store" - -Weave operates as a "globally scoped" libnetwork driver, which means -libnetwork will assume all networks and endpoints are commonly known -to all hosts. It does this by using a shared database which you must -provide. - -As a consequence, you need to supply Docker with the address of a "cluster -store" when you start it; for example, an etcd installation. - -There's no specific documentation for using a cluster store, but the -first part of [this guide](https://github.com/docker/docker/blob/master/docs/userguide/networking/get-started-overlay.md) may help. - -### WeaveDNS not supported - -Weave's built-in service discovery mechanism is not currently -supported by the plugin. However, Docker provides a rudimentary -discovery mechanism by writing all user-provided container names and -hostnames into every container's `/etc/hosts`. - -### Restarting - -We start the plugin with a restart policy of 'always', because -Docker attempts to recreate networks on startup which means the plugin -container has to be started. If it isn't started automatically by Docker -with a restart policy, it's not possible to start it manually because -Docker is blocked. +More detail [here](http://docs.weave.works/weave/latest_release/plugin.html). diff --git a/site/features.md b/site/features.md index e7e949a1..f69393b7 100644 --- a/site/features.md +++ b/site/features.md @@ -93,21 +93,12 @@ re-attached to the weave network by the weave Docker API proxy. ### Docker network plugin -Alternatively, you can use weave as a Docker plugin. First you must -configure the Docker daemon to use a cluster store as documented -[here](https://docs.docker.com/engine/userguide/networking/dockernetworks/#an-overlay-network); -you can then launch weave and create a network: - - $ weave launch - $ weave launch-plugin - $ docker network create --driver=weave weave - -and then start a container: +Alternatively, you can use weave as a Docker plugin. A Docker network +named `weave` is created by `weave launch`, which you can use like this: $ docker run --net=weave -ti ubuntu -For more details see the -[plugin README](https://github.com/weaveworks/docker-plugin). +For more details see the [plugin documentation](plugin.html). ### Address allocation diff --git a/site/plugin.md b/site/plugin.md new file mode 100644 index 00000000..eb4489b1 --- /dev/null +++ b/site/plugin.md @@ -0,0 +1,84 @@ +--- +title: Using Weave Net via Docker Networking +layout: default +--- + +New in Docker version 1.9 is a plugin mechanism to add different +network providers. + +To install, download the `weave` script as per the [readme][]. + +The Weave Net plugin runs automatically when you `weave launch`. You +must still tell the weave peers to connect to one another, either via +`weave launch` or `weave connect`. + +## Starting a container: + + $ docker run --net=weave -ti ubuntu + +## Using WeaveDNS for Service Discovery: + +You need to pass the additional arguments `--dns` and `-dns-search`, +for which we provide a helper in the weave script: + + $ docker run --net=weave -h foo.weave.local $(weave dns-args) -tdi ubuntu + $ docker run --net=weave -h bar.weave.local $(weave dns-args) -ti ubuntu + # ping foo + +For more details see the [Weave Service Discovery documentation][service-discovery]. + +## Under the hood + +The Weave Net plugin actually provides *two* network drivers to Docker +- one named `weavemesh` that can operate without a [cluster +store](#cluster-store) (like Docker's overlay driver) and one named +`weave` that can only work with one. + +### `weavemesh` driver + +* Weave handles all co-ordination between hosts (referred to by Docker as a "local scope" driver) +* Supports only a single network (we create one named `weave` for you automatically) +* Uses Weave's partition tolerant IPAM + +### `weave` driver + +* This runs in what Docker call "global scope"; requires a cluster store +* Supports multiple networks (these must be created with `docker network create --driver weave ...`) +* Used with Docker's cluster store based IPAM + +There's no specific documentation from Docker on using a cluster +store, but the first part of [Getting Started with Docker Multi-host Networking][docker-net] +should point the way. + +Note that in the case of multiple docker networks, all containers are +on the same virtual network but Docker allocates their addresses on +different subnets so they cannot talk to each other directly. + +## Plugin command-line arguments + +If you want to give some arguments to the plugin independently, don't +use `weave launch`; instead run: + + $ weave launch-router [other peers] + $ weave launch-plugin [plugin arguments] + +The plugin command-line arguments are: + + * `--log-level=debug|info|warning|error`, which tells the plugin + how much information to emit for debugging. + * `--mesh-network-name=`: set it to blank to disable creation + of a default network, or a name of your own choice. + +## Restarting + +We start the plugin with a restart policy, because Docker attempts to +recreate networks on startup. If it isn't started automatically by +Docker with a restart policy, it's not possible to start it manually +because Docker is blocked. + +Specifically, we use the policy `on-failure:3`, so Docker will give up +after three attempts if something just isn't working. + +[readme]: https://github.com/weaveworks/weave/blob/master/README.md#installation +[service-discovery]: weavedns.html +[docker-net]: https://github.com/docker/docker/blob/master/docs/userguide/networking/get-started-overlay.md diff --git a/site/troubleshooting.md b/site/troubleshooting.md index 22b81758..154ce539 100644 --- a/site/troubleshooting.md +++ b/site/troubleshooting.md @@ -93,6 +93,9 @@ $ weave status Service: proxy Address: tcp://127.0.0.1:12375 + Service: plugin + DriverName: weave + ```` The terms used here are explained further at diff --git a/weave b/weave index ce795ca9..31316d39 100755 --- a/weave +++ b/weave @@ -57,6 +57,7 @@ weave launch-proxy [-H ] [--with-dns | --without-dns] weave launch-plugin weave env [--restore] weave config +weave dns-args weave connect [--replace] [ ...] weave forget ... weave status [targets | connections | peers | dns] From 0e9991f7d4a2f6bb6d92877ed3b29b149e8eb2de Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 11 Dec 2015 16:35:16 +0000 Subject: [PATCH 115/135] Make plugin create 'weave' network by default --- prog/plugin/main.go | 35 ++++++++++++++--------------------- weave | 2 +- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/prog/plugin/main.go b/prog/plugin/main.go index b2570136..4da4c71e 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -20,26 +20,22 @@ import ( var version = "(unreleased version)" -const ( - meshNetworkName = "weave" -) - func main() { var ( - justVersion bool - address string - nameserver string - meshAddress string - logLevel string - createDefault bool + justVersion bool + address string + nameserver string + meshAddress string + logLevel string + meshNetworkName string ) flag.BoolVar(&justVersion, "version", false, "print version and exit") 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(&nameserver, "nameserver", "", "nameserver to provide to containers") - flag.StringVar(&meshAddress, "meshsocket", "", "socket on which to listen for mesh mode") - flag.BoolVar(&createDefault, "create-default-network", false, "create default network") + flag.StringVar(&meshAddress, "meshsocket", "/run/docker/plugins/weavemesh.sock", "socket on which to listen in mesh mode") + flag.StringVar(&meshNetworkName, "mesh-network-name", "weave", "network name to create in mesh mode") flag.Parse() @@ -48,9 +44,6 @@ func main() { os.Exit(0) } - if createDefault && meshAddress == "" { - meshAddress = "/run/docker/plugins/weavemesh.sock" - } SetLogLevel(logLevel) Log.Println("Weave plugin", version, "Command line options:", os.Args[1:]) @@ -78,8 +71,8 @@ func main() { defer meshListener.Close() } - if createDefault { - createNetwork(dockerClient, meshAddress) + if meshNetworkName != "" { + createNetwork(dockerClient, meshNetworkName, meshAddress) } sigChan := make(chan os.Signal, 1) @@ -133,16 +126,16 @@ func listenAndServe(dockerClient *docker.Client, address, nameserver string, end return listener, nil } -func createNetwork(dockerClient *docker.Client, address string) { - if _, err := dockerClient.Client.NetworkInfo(meshNetworkName); err == nil { - Log.Printf("Docker network '%s' already exists", meshNetworkName) +func createNetwork(dockerClient *docker.Client, networkName, address string) { + if _, err := dockerClient.Client.NetworkInfo(networkName); err == nil { + Log.Printf("Docker network '%s' already exists", networkName) } else if _, ok := err.(*go_docker.NoSuchNetwork); ok { driverName := strings.TrimSuffix(address, ".sock") if i := strings.LastIndex(driverName, "/"); i >= 0 { driverName = driverName[i+1:] } options := go_docker.CreateNetworkOptions{ - Name: meshNetworkName, + Name: networkName, CheckDuplicate: true, Driver: driverName, IPAM: go_docker.IPAMOptions{Driver: driverName}, diff --git a/weave b/weave index 31316d39..aa76b409 100755 --- a/weave +++ b/weave @@ -1832,7 +1832,7 @@ EOF launch_proxy $COMMON_ARGS if [ -z $WEAVE_NO_PLUGIN ] && check_docker_server_api_version 1.21 ; then check_not_running $PLUGIN_CONTAINER_NAME $BASE_PLUGIN_IMAGE - launch_plugin $COMMON_ARGS --create-default-network + launch_plugin $COMMON_ARGS fi ;; launch-router) From ca1cce323277e67c188e9e85b34f85f1f1921c9a Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 16 Nov 2015 15:10:48 +0000 Subject: [PATCH 116/135] Makefile should use DOCKER_HOST env var --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index cb468e93..b0dc5aca 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,7 @@ $(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go go build $(BUILD_FLAGS) -o $@ ./$(@D) $(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE) - $(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver + $(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVER_IMAGE) prog/weaver touch $@ $(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) @@ -116,7 +116,7 @@ $(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER cp $(NETCHECK_EXE) prog/weaveexec/netcheck cp $(DOCKERTLSARGS_EXE) prog/weaveexec/docker_tls_args cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz - $(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec + $(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec touch $@ $(DOCKERPLUGIN_UPTODATE): prog/plugin/Dockerfile $(DOCKERPLUGIN_EXE) @@ -124,7 +124,7 @@ $(DOCKERPLUGIN_UPTODATE): prog/plugin/Dockerfile $(DOCKERPLUGIN_EXE) touch $@ $(WEAVE_EXPORT): $(IMAGES_UPTODATE) - $(SUDO) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@ + $(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@ $(DOCKER_DISTRIB): curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL) @@ -142,16 +142,16 @@ $(RUNNER_EXE): tools/.git make -C tools/runner $(PUBLISH): publish_%: $(IMAGES_UPTODATE) - $(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) - $(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) + $(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) + $(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) ifneq ($(UPDATE_LATEST),false) - $(SUDO) docker push $(DOCKERHUB_USER)/$*:latest + $(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest endif publish: $(PUBLISH) clean-bin: - -$(SUDO) docker rmi $(IMAGES) + -$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES) go clean -r $(addprefix ./,$(dir $(EXES))) rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT) From 0afc3db383802217fe8bfbfe0935386ac906ed8a Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 16 Nov 2015 15:16:14 +0000 Subject: [PATCH 117/135] First pass at passing DOCKER_HOST=unix://* through to the weave and proxy --- prog/weaveproxy/main.go | 8 +++++++- proxy/common.go | 1 + proxy/proxy.go | 22 ++++++++++++++++------ weave | 22 +++++++++++++++++----- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/prog/weaveproxy/main.go b/prog/weaveproxy/main.go index 6de9a8b4..0cd13eed 100644 --- a/prog/weaveproxy/main.go +++ b/prog/weaveproxy/main.go @@ -12,7 +12,8 @@ import ( ) var ( - version = "(unreleased version)" + version = "(unreleased version)" + defaultDockerHost = "unix:///var/run/docker.sock" ) func main() { @@ -61,6 +62,11 @@ func main() { Log.Infoln("weave proxy", version) Log.Infoln("Command line arguments:", strings.Join(os.Args[1:], " ")) + c.DockerHost = defaultDockerHost + if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost != "" { + c.DockerHost = dockerHost + } + p, err := proxy.NewProxy(c) if err != nil { Log.Fatalf("Could not start proxy: %s", err) diff --git a/proxy/common.go b/proxy/common.go index 9a927cca..a4810c66 100644 --- a/proxy/common.go +++ b/proxy/common.go @@ -39,6 +39,7 @@ func callWeave(args ...string) ([]byte, []byte, error) { } propagateEnv("DOCKER_BRIDGE") + propagateEnv("DOCKER_HOST") // Propogage WEAVE_DEBUG, to make debugging easier. propagateEnv("WEAVE_DEBUG") diff --git a/proxy/proxy.go b/proxy/proxy.go index 9c8b15f7..6771d964 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -25,8 +25,6 @@ const ( defaultCaFile = "ca.pem" defaultKeyFile = "key.pem" defaultCertFile = "cert.pem" - dockerSock = "/var/run/docker.sock" - dockerSockUnix = "unix://" + dockerSock weaveSock = "/var/run/weave/weave.sock" weaveSockUnix = "unix://" + weaveSock @@ -61,6 +59,7 @@ type Config struct { WithDNS bool WithoutDNS bool NoMulticastRoute bool + DockerHost string } type wait struct { @@ -94,7 +93,7 @@ func NewProxy(c Config) (*Proxy, error) { // to insulate ourselves from breaking changes to the API, as // happened in 1.20 (Docker 1.8.0) when the presentation of // volumes changed in `inspect`. - client, err := weavedocker.NewVersionedClient(dockerSockUnix, "1.18") + client, err := weavedocker.NewVersionedClient(c.DockerHost, "1.18") if err != nil { return nil, err } @@ -140,7 +139,16 @@ func (proxy *Proxy) AttachExistingContainers() { } func (proxy *Proxy) Dial() (net.Conn, error) { - return net.Dial("unix", dockerSock) + proto := "tcp" + addr := proxy.Config.DockerHost + switch { + case strings.HasPrefix(addr, "unix://"): + proto = "unix" + addr = strings.TrimPrefix(addr, "unix://") + case strings.HasPrefix(addr, "tcp://"): + addr = strings.TrimPrefix(addr, "tcp://") + } + return net.Dial(proto, addr) } func (proxy *Proxy) findWeaveWaitVolumes() error { @@ -321,8 +329,10 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) { if err != nil { return nil, "", err } - if err = copyOwnerAndPermissions(dockerSock, addr); err != nil { - return nil, "", err + if strings.HasPrefix(proxy.Config.DockerHost, "unix://") { + if err = copyOwnerAndPermissions(strings.TrimPrefix(proxy.Config.DockerHost, "unix://"), addr); err != nil { + return nil, "", err + } } default: diff --git a/weave b/weave index aa76b409..27abdf1f 100755 --- a/weave +++ b/weave @@ -96,9 +96,21 @@ usage() { exit 1 } +docker_sock() { + if echo "$DOCKER_HOST" | grep -q "^unix://" >/dev/null; then + echo "${DOCKER_HOST#unix://}" + else + echo "/var/run/docker.sock" + fi +} + +docker_sock_volume_mount() { + echo "-v $(docker_sock):$(docker_sock) -e DOCKER_HOST=unix://$(docker_sock)" +} + exec_remote() { docker $DOCKER_CLIENT_ARGS run --rm --privileged --net=host \ - -v /var/run/docker.sock:/var/run/docker.sock \ + $(docker_sock_volume_mount) \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e DOCKERHUB_USER="$DOCKERHUB_USER" \ @@ -1609,7 +1621,7 @@ launch_router() { # additional parameters, such as resource limits, to docker # when launching the weave container. ROUTER_CONTAINER=$(docker run --privileged -d --name=$CONTAINER_NAME \ - -v /var/run/docker.sock:/var/run/docker.sock \ + $(docker_sock_volume_mount) \ -p $PORT:$CONTAINER_PORT/tcp -p $PORT:$CONTAINER_PORT/udp \ ${NETHOST_OPT:-$DNS_PORT_MAPPING} \ -e WEAVE_PASSWORD \ @@ -1621,7 +1633,7 @@ launch_router() { --dns-effective-listen-address $DOCKER_BRIDGE_IP \ ${NETHOST_OPT:+$DNS_ROUTER_OPTS} $NO_DNS_OPT \ --http-addr $HTTP_IP:$HTTP_PORT \ - --docker-api "unix:///var/run/docker.sock" "$@") + --docker-api "unix://$(docker_sock)" "$@") with_container_netns_or_die $CONTAINER_NAME setup_router_iface_$BRIDGE_TYPE attach_router } @@ -1666,7 +1678,7 @@ launch_proxy() { mkdir -p /var/run/weave PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \ $PROXY_VOLUMES \ - -v /var/run/docker.sock:/var/run/docker.sock \ + $(docker_sock_volume_mount) \ -v /var/run/weave:/var/run/weave \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ @@ -1689,7 +1701,7 @@ launch_plugin() { PLUGIN_CONTAINER=$(docker run --privileged -d --name=$PLUGIN_CONTAINER_NAME \ --restart=always \ --net=host \ - -v /var/run/docker.sock:/var/run/docker.sock \ + $(docker_sock_volume_mount) \ -v /run/docker/plugins:/run/docker/plugins \ $PLUGIN_IMAGE "$@") } From f28ad350bdd82c7b60a990b37a513233b30b92f7 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 20 Nov 2015 12:39:57 +0000 Subject: [PATCH 118/135] make weave and the proxy work with tcp-only dockers via DOCKER_HOST --- common/docker/client.go | 8 ++++++++ prog/weaver/main.go | 8 +++++++- weave | 16 ++++++++++++---- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/common/docker/client.go b/common/docker/client.go index bd4b78f6..0a823690 100644 --- a/common/docker/client.go +++ b/common/docker/client.go @@ -2,6 +2,8 @@ package docker import ( "errors" + "strings" + "github.com/fsouza/go-dockerclient" . "github.com/weaveworks/weave/common" @@ -19,6 +21,9 @@ type Client struct { // NewClient creates a new Docker client and checks we can talk to Docker func NewClient(apiPath string) (*Client, error) { + if apiPath != "" && !strings.Contains(apiPath, "://") { + apiPath = "tcp://" + apiPath + } dc, err := docker.NewClient(apiPath) if err != nil { return nil, err @@ -29,6 +34,9 @@ func NewClient(apiPath string) (*Client, error) { } func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) { + if !strings.Contains(apiPath, "://") { + apiPath = "tcp://" + apiPath + } dc, err := docker.NewVersionedClient(apiPath, apiVersionString) if err != nil { return nil, err diff --git a/prog/weaver/main.go b/prog/weaver/main.go index 51acd0c3..a4ff46e8 100644 --- a/prog/weaver/main.go +++ b/prog/weaver/main.go @@ -70,8 +70,14 @@ func main() { iface *net.Interface datapathName string trustedSubnetStr string + + defaultDockerHost = "unix:///var/run/docker.sock" ) + if val := os.Getenv("DOCKER_HOST"); val != "" { + defaultDockerHost = val + } + mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit") mflag.BoolVar(&createDatapath, []string{"-create-datapath"}, false, "create ODP datapath and exit") mflag.BoolVar(&deleteDatapath, []string{"-delete-datapath"}, false, "delete ODP datapath and exit") @@ -93,7 +99,7 @@ func main() { mflag.StringVar(&iprangeCIDR, []string{"#iprange", "#-iprange", "-ipalloc-range"}, "", "IP address range reserved for automatic allocation, in CIDR notation") mflag.StringVar(&ipsubnetCIDR, []string{"#ipsubnet", "#-ipsubnet", "-ipalloc-default-subnet"}, "", "subnet to allocate within by default, in CIDR notation") mflag.IntVar(&peerCount, []string{"#initpeercount", "#-initpeercount", "-init-peer-count"}, 0, "number of peers in network (for IP address allocation)") - mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, "", "Docker API endpoint, e.g. unix:///var/run/docker.sock") + mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, defaultDockerHost, "Docker API endpoint") mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server") mflag.StringVar(&dnsDomain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for") mflag.StringVar(&dnsListenAddress, []string{"-dns-listen-address"}, nameserver.DefaultListenAddress, "address to listen on for DNS requests") diff --git a/weave b/weave index 27abdf1f..01becaf2 100755 --- a/weave +++ b/weave @@ -97,15 +97,19 @@ usage() { } docker_sock() { + if [ -z "$DOCKER_HOST" ]; then + echo "/var/run/docker.sock" + return + fi if echo "$DOCKER_HOST" | grep -q "^unix://" >/dev/null; then echo "${DOCKER_HOST#unix://}" - else - echo "/var/run/docker.sock" fi } docker_sock_volume_mount() { - echo "-v $(docker_sock):$(docker_sock) -e DOCKER_HOST=unix://$(docker_sock)" + if [ -n "$(docker_sock)" ]; then + echo "-v $(docker_sock):$(docker_sock)" + fi } exec_remote() { @@ -125,6 +129,7 @@ exec_remote() { -e WEAVE_NO_FASTDP \ -e WEAVE_NO_BRIDGED_FASTDP \ -e WEAVE_NO_PLUGIN \ + -e DOCKER_HOST \ -e DOCKER_BRIDGE \ -e DOCKER_CLIENT_HOST="$DOCKER_CLIENT_HOST" \ -e DOCKER_CLIENT_ARGS \ @@ -1624,6 +1629,7 @@ launch_router() { $(docker_sock_volume_mount) \ -p $PORT:$CONTAINER_PORT/tcp -p $PORT:$CONTAINER_PORT/udp \ ${NETHOST_OPT:-$DNS_PORT_MAPPING} \ + -e DOCKER_HOST \ -e WEAVE_PASSWORD \ -e WEAVE_CIDR=none \ $WEAVE_DOCKER_ARGS $IMAGE $COVERAGE_ARGS \ @@ -1633,7 +1639,7 @@ launch_router() { --dns-effective-listen-address $DOCKER_BRIDGE_IP \ ${NETHOST_OPT:+$DNS_ROUTER_OPTS} $NO_DNS_OPT \ --http-addr $HTTP_IP:$HTTP_PORT \ - --docker-api "unix://$(docker_sock)" "$@") + "$@") with_container_netns_or_die $CONTAINER_NAME setup_router_iface_$BRIDGE_TYPE attach_router } @@ -1683,6 +1689,7 @@ launch_proxy() { -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e WEAVE_CIDR=none \ + -e DOCKER_HOST \ -e DOCKER_BRIDGE \ -e WEAVE_DEBUG \ -e COVERAGE \ @@ -1703,6 +1710,7 @@ launch_plugin() { --net=host \ $(docker_sock_volume_mount) \ -v /run/docker/plugins:/run/docker/plugins \ + -e DOCKER_HOST \ $PLUGIN_IMAGE "$@") } From a4ef96690efb3302b3c1994e9fb378f2eef87208 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 30 Nov 2015 16:33:53 +0000 Subject: [PATCH 119/135] support alternate DOCKER_HOST settings when launching the plugin --- common/docker/client.go | 18 ++++++++++++++---- prog/plugin/main.go | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/common/docker/client.go b/common/docker/client.go index 0a823690..d2b5dab0 100644 --- a/common/docker/client.go +++ b/common/docker/client.go @@ -30,7 +30,7 @@ func NewClient(apiPath string) (*Client, error) { } client := &Client{dc} - return client, client.checkWorking(apiPath) + return client, client.checkWorking() } func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) { @@ -43,15 +43,25 @@ func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error } client := &Client{dc} - return client, client.checkWorking(apiPath) + return client, client.checkWorking() } -func (c *Client) checkWorking(apiPath string) error { +func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) { + dc, err := docker.NewVersionedClientFromEnv(apiVersionString) + if err != nil { + return nil, err + } + client := &Client{dc} + + return client, client.checkWorking() +} + +func (c *Client) checkWorking() error { env, err := c.Version() if err != nil { return err } - Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env) + Log.Infof("[docker] Using Docker API on %s: %v", c.Endpoint(), env) return nil } diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 4da4c71e..b9a5a581 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -49,7 +49,7 @@ func main() { Log.Println("Weave plugin", version, "Command line options:", os.Args[1:]) // API 1.21 is the first version that supports docker network commands - dockerClient, err := docker.NewVersionedClient("unix:///var/run/docker.sock", "1.21") + dockerClient, err := docker.NewVersionedClientFromEnv("1.21") if err != nil { Log.Fatalf("unable to connect to docker: %s", err) } From 1a1d8b7c74cee24c9da73e1fc2afee1bf4393e96 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 15 Dec 2015 14:51:57 +0000 Subject: [PATCH 120/135] Prefix interface names with 'veth' so NetworkManager ignores them --- weave | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weave b/weave index ac3e894c..4d455598 100755 --- a/weave +++ b/weave @@ -319,8 +319,8 @@ CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave} BRIDGE=weave # This value is overridden when the datapath is used unbridged DATAPATH=datapath -BRIDGE_IFNAME=link-${BRIDGE} -DATAPATH_IFNAME=${DATAPATH}-link +BRIDGE_IFNAME=vethwe-bridge +DATAPATH_IFNAME=vethwe-datapath CONTAINER_IFNAME=ethwe PORT=${WEAVE_PORT:-6783} HTTP_PORT=6784 From 88fad251aac18ab193c196e7092f39d65e5ac415 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 1 Dec 2015 15:42:32 +0000 Subject: [PATCH 121/135] add test for proxy against docker on non-standard unix socket Means we need to rebuild the gce images (also small improvement re tmpdir) --- test/690_proxy_config_test.sh | 11 ++++++++--- test/gce.sh | 4 ++-- vagrant-common.rb | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/test/690_proxy_config_test.sh b/test/690_proxy_config_test.sh index 6293a3f5..11ddcbda 100755 --- a/test/690_proxy_config_test.sh +++ b/test/690_proxy_config_test.sh @@ -5,7 +5,7 @@ start_suite "Various launch-proxy configurations" # Booting it over unix socket listens on unix socket -run_on $HOST1 COVERAGE=$COVERAGE sudo -E weave launch-proxy +run_on $HOST1 COVERAGE=$COVERAGE weave launch-proxy assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" assert_raises "proxy docker_on $HOST1 ps" 1 weave_on $HOST1 stop-proxy @@ -17,17 +17,22 @@ assert_raises "proxy docker_on $HOST1 ps" weave_on $HOST1 stop-proxy # Booting it over tcp (no prefix) listens on tcp -DOCKER_CLIENT_ARGS="-H $HOST1:$DOCKER_PORT" $WEAVE launch-proxy +DOCKER_HOST=tcp://$HOST1:$DOCKER_PORT $WEAVE launch-proxy assert_raises "run_on $HOST1 sudo docker -H unix:///var/run/weave/weave.sock ps" 1 assert_raises "proxy docker_on $HOST1 ps" weave_on $HOST1 stop-proxy # Booting it with -H outside /var/run/weave, still works -socket="$(mktemp -d)/weave.sock" +socket="$($SSH $HOST1 mktemp -d)/weave.sock" weave_on $HOST1 launch-proxy -H unix://$socket assert_raises "run_on $HOST1 sudo docker -H unix:///$socket ps" 0 weave_on $HOST1 stop-proxy +# Booting it against non-standard docker unix sock +run_on $HOST1 "DOCKER_HOST=unix:///var/run/alt-docker.sock COVERAGE=$COVERAGE weave launch-proxy -H tcp://0.0.0.0:12375" +assert_raises "proxy docker_on $HOST1 ps" +weave_on $HOST1 stop-proxy + # Booting it over tls errors assert_raises "DOCKER_CLIENT_ARGS='--tls' weave_on $HOST1 launch-proxy" 1 assert_raises "DOCKER_CERT_PATH='./tls' DOCKER_TLS_VERIFY=1 weave_on $HOST1 launch-proxy" 1 diff --git a/test/gce.sh b/test/gce.sh index ae22739a..e42b9209 100755 --- a/test/gce.sh +++ b/test/gce.sh @@ -11,7 +11,7 @@ set -e : ${SSH_KEY_FILE:=$HOME/.ssh/gce_ssh_key} : ${PROJECT:=positive-cocoa-90213} : ${IMAGE:=ubuntu-14-04} -: ${TEMPLATE_NAME:=test-template-8} +: ${TEMPLATE_NAME:=test-template-9} : ${ZONE:=us-central1-a} : ${NUM_HOSTS:=5} SUFFIX="" @@ -73,7 +73,7 @@ curl -sSL https://get.docker.com/ | sh apt-get update -qq; apt-get install -q -y --force-yes --no-install-recommends ethtool; usermod -a -G docker vagrant; -echo 'DOCKER_OPTS="-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 -s overlay"' >> /etc/default/docker; +echo 'DOCKER_OPTS="-H unix:///var/run/docker.sock -H unix:///var/run/alt-docker.sock -H tcp://0.0.0.0:2375 -s overlay"' >> /etc/default/docker; service docker restart EOF # It seems we need a short delay for docker to start up, so I put this in diff --git a/vagrant-common.rb b/vagrant-common.rb index 15efa7ac..1cfeb599 100644 --- a/vagrant-common.rb +++ b/vagrant-common.rb @@ -45,7 +45,7 @@ end def tweak_docker_daemon(vm) vm.provision :shell, :inline => <