Merge branch '1.0' into latest_release_doc_updates
This commit is contained in:
commit
a6e2296e0a
52 files changed
+614
-245
No files matched your search
@@ -1,3 +1,15 @@
|
||||
## Release 1.0.2
|
||||
|
||||
This release fixes a number of bugs, including some security
|
||||
vulnerabilities in the Weave Docker API proxy, hangs and failures in
|
||||
address allocation, and sporadic failures in name resolution.
|
||||
|
||||
More details in the
|
||||
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.0.2).
|
||||
|
||||
The release is fully compatible with other 1.0.x versions, so existing
|
||||
clusters can be upgraded incrementally.
|
||||
|
||||
## Release 1.0.1
|
||||
|
||||
This is a bug fix release, addressing the following issue:
|
||||
|
||||
@@ -38,7 +38,7 @@ all: $(EXPORTS)
|
||||
|
||||
travis: $(EXES)
|
||||
|
||||
update: $(EXES)
|
||||
update:
|
||||
go get -u -f -v -tags -netgo $(addprefix ./,$(dir $(EXES)))
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
||||
|
||||
@@ -26,6 +26,7 @@ dependencies:
|
||||
docker save weaveworks/weave-build >$WEAVE_BUILD;
|
||||
fi
|
||||
post:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install bc
|
||||
- pip install requests
|
||||
- curl https://sdk.cloud.google.com | bash
|
||||
|
||||
+9
-3
@@ -2,6 +2,7 @@ package ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
)
|
||||
|
||||
@@ -42,9 +43,14 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
}
|
||||
|
||||
// out of space
|
||||
if donor, err := alloc.ring.ChoosePeerToAskForSpace(g.r.Start, g.r.End); err == nil {
|
||||
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
|
||||
alloc.sendSpaceRequest(donor, g.r)
|
||||
donors := alloc.ring.ChoosePeersToAskForSpace(g.r.Start, g.r.End)
|
||||
for _, donor := range donors {
|
||||
if err := alloc.sendSpaceRequest(donor, g.r); err != nil {
|
||||
alloc.debugln("Problem asking peer", donor, "for space:", err)
|
||||
} else {
|
||||
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
+4
-5
@@ -202,10 +202,9 @@ func (alloc *Allocator) Lookup(ident string, r address.Range) (address.Address,
|
||||
}
|
||||
|
||||
// Claim an address that we think we should own (Sync)
|
||||
func (alloc *Allocator) Claim(ident string, addr address.Address, cancelChan <-chan bool) error {
|
||||
func (alloc *Allocator) Claim(ident string, addr address.Address) error {
|
||||
resultChan := make(chan error)
|
||||
op := &claim{resultChan: resultChan, ident: ident, addr: addr,
|
||||
hasBeenCancelled: hasBeenCancelled(cancelChan)}
|
||||
op := &claim{resultChan: resultChan, ident: ident, addr: addr}
|
||||
alloc.doOperation(op, &alloc.pendingClaims)
|
||||
return <-resultChan
|
||||
}
|
||||
@@ -575,14 +574,14 @@ func (alloc *Allocator) propose() {
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) {
|
||||
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) error {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
if err := enc.Encode(r); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
msg := router.Concat([]byte{msgSpaceRequest}, buf.Bytes())
|
||||
alloc.gossip.GossipUnicast(dest, msg)
|
||||
return alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendRingUpdate(dest router.PeerName) {
|
||||
|
||||
@@ -143,7 +143,7 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
alloc.claimRingForTesting()
|
||||
addr1, _ := address.ParseIP(testAddr1)
|
||||
|
||||
err := alloc.Claim(container3, addr1, nil)
|
||||
err := alloc.Claim(container3, addr1)
|
||||
wt.AssertNoErr(t, err)
|
||||
// Check we get this address back if we try an allocate
|
||||
addr3, _ := alloc.Allocate(container3, subnet, nil)
|
||||
@@ -168,7 +168,7 @@ func TestCancel(t *testing.T) {
|
||||
CIDR = "10.0.1.7/26"
|
||||
)
|
||||
|
||||
router := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
|
||||
router := TestGossipRouter{make(map[router.PeerName]chan interface{}), 0.0}
|
||||
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", CIDR, 2)
|
||||
alloc1.SetInterfaces(router.connect(alloc1.ourName, alloc1))
|
||||
@@ -256,13 +256,17 @@ func TestTransfer(t *testing.T) {
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
|
||||
router.GossipBroadcast(alloc2.Gossip())
|
||||
router.flush()
|
||||
router.GossipBroadcast(alloc3.Gossip())
|
||||
router.flush()
|
||||
router.removePeer(alloc2.ourName)
|
||||
router.removePeer(alloc3.ourName)
|
||||
alloc2.Stop()
|
||||
alloc3.Stop()
|
||||
router.flush()
|
||||
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
|
||||
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
|
||||
router.flush()
|
||||
|
||||
wt.AssertEquals(t, alloc1.NumFreeAddresses(subnet), address.Offset(1022))
|
||||
|
||||
@@ -282,7 +286,8 @@ func TestFakeRouterSimple(t *testing.T) {
|
||||
alloc1 := allocs[0]
|
||||
//alloc2 := allocs[1]
|
||||
|
||||
alloc1.Allocate("foo", subnet, nil)
|
||||
_, err := alloc1.Allocate("foo", subnet, nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
}
|
||||
|
||||
func TestAllocatorFuzz(t *testing.T) {
|
||||
|
||||
+24
-16
@@ -3,28 +3,35 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
resultChan chan<- error
|
||||
ident string
|
||||
addr address.Address
|
||||
hasBeenCancelled func() bool
|
||||
resultChan chan<- error
|
||||
ident string
|
||||
addr address.Address
|
||||
}
|
||||
|
||||
func (c *claim) sendResult(result error) {
|
||||
// Make sure we only send a result once, since listener stops listening after that
|
||||
if c.resultChan != nil {
|
||||
c.resultChan <- result
|
||||
close(c.resultChan)
|
||||
c.resultChan = nil
|
||||
}
|
||||
if result != nil {
|
||||
common.Error.Println("[allocator] " + result.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Try returns true for success (or failure), false if we need to try again later
|
||||
func (c *claim) Try(alloc *Allocator) bool {
|
||||
if (c.hasBeenCancelled)() {
|
||||
c.Cancel()
|
||||
return true
|
||||
}
|
||||
|
||||
if !alloc.ring.Contains(c.addr) {
|
||||
// Address not within our universe; assume user knows what they are doing
|
||||
alloc.infof("Ignored address %s claimed by %s - not in our universe\n", c.addr, c.ident)
|
||||
c.resultChan <- nil
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -34,6 +41,7 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
owner := alloc.ring.Owner(c.addr)
|
||||
if owner == router.UnknownPeerName {
|
||||
alloc.infof("Ring is empty; will try later.\n", c.addr, owner)
|
||||
c.sendResult(nil) // don't make the caller wait
|
||||
return false
|
||||
}
|
||||
if owner != alloc.ourName {
|
||||
@@ -41,34 +49,34 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
}
|
||||
c.resultChan <- fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name)
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name))
|
||||
return true
|
||||
}
|
||||
// We are the owner, check we haven't given it to another container
|
||||
existingIdent := alloc.findOwner(c.addr)
|
||||
if existingIdent == c.ident {
|
||||
// same identifier is claiming same address; that's OK
|
||||
c.resultChan <- nil
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
if existingIdent == "" {
|
||||
err := alloc.space.Claim(c.addr)
|
||||
if err != nil {
|
||||
c.resultChan <- err
|
||||
c.sendResult(err)
|
||||
return true
|
||||
}
|
||||
alloc.debugln("Claimed", c.addr, "for", c.ident)
|
||||
alloc.addOwned(c.ident, c.addr)
|
||||
c.resultChan <- nil
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
// Addr already owned by container on this machine
|
||||
c.resultChan <- fmt.Errorf("address %s is already owned by %s", c.addr.String(), existingIdent)
|
||||
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.addr.String(), existingIdent))
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *claim) Cancel() {
|
||||
c.resultChan <- fmt.Errorf("Operation cancelled.")
|
||||
c.sendResult(fmt.Errorf("Operation cancelled."))
|
||||
}
|
||||
|
||||
func (c *claim) String() string {
|
||||
|
||||
+1
-2
@@ -19,14 +19,13 @@ func badRequest(w http.ResponseWriter, err error) {
|
||||
// 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("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
closedChan := w.(http.CloseNotifier).CloseNotify()
|
||||
vars := mux.Vars(r)
|
||||
ident := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
if ip, err := address.ParseIP(ipStr); err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
} else if err := alloc.Claim(ident, ip, closedChan); err != nil {
|
||||
} else if err := alloc.Claim(ident, ip); err != nil {
|
||||
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package paxos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ package paxos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/weaveworks/weave/router"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
type TestNode struct {
|
||||
|
||||
+28
-19
@@ -348,9 +348,9 @@ func (r *Ring) String() string {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// ReportFree is used by the allocator to tell the ring
|
||||
// how many free ips are in a given range, so that ChoosePeerToAskForSpace
|
||||
// can make more intelligent decisions.
|
||||
// ReportFree is used by the allocator to tell the ring how many free
|
||||
// ips are in a given range, so that ChoosePeersToAskForSpace can make
|
||||
// more intelligent decisions.
|
||||
func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
|
||||
r.assertInvariants()
|
||||
defer r.assertInvariants()
|
||||
@@ -392,9 +392,20 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
|
||||
}
|
||||
}
|
||||
|
||||
// ChoosePeerToAskForSpace chooses a weighted-random peer to ask
|
||||
// for space in the range [start, end). Assumes start<end.
|
||||
func (r *Ring) ChoosePeerToAskForSpace(start, end address.Address) (result router.PeerName, err error) {
|
||||
type weightedPeer struct {
|
||||
weight float64
|
||||
peername router.PeerName
|
||||
}
|
||||
type weightedPeers []weightedPeer
|
||||
|
||||
// Note Less is using > so that bigger weights sort earlier
|
||||
func (ws weightedPeers) Less(i, j int) bool { return ws[i].weight > ws[j].weight }
|
||||
func (ws weightedPeers) Len() int { return len(ws) }
|
||||
func (ws weightedPeers) Swap(i, j int) { ws[i], ws[j] = ws[j], ws[i] }
|
||||
|
||||
// ChoosePeersToAskForSpace returns all peers we can ask for space in
|
||||
// the range [start, end), in weighted-random order. Assumes start<end.
|
||||
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.PeerName {
|
||||
var (
|
||||
sum address.Offset
|
||||
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
|
||||
@@ -423,21 +434,19 @@ func (r *Ring) ChoosePeerToAskForSpace(start, end address.Address) (result route
|
||||
sum += entry.Free
|
||||
}
|
||||
|
||||
if sum == 0 {
|
||||
err = ErrNoFreeSpace
|
||||
return
|
||||
}
|
||||
|
||||
// Pick random peer, weighted by total free space
|
||||
rn := rand.Int63n(int64(sum))
|
||||
// Compute weighted random numbers, then sort.
|
||||
// This isn't perfect, e.g. an item with weight 2 will get chosen more than
|
||||
// twice as often as an item with weight 1, but it's good enough for our purposes.
|
||||
ws := make(weightedPeers, 0, len(totalSpacePerPeer))
|
||||
for peername, space := range totalSpacePerPeer {
|
||||
rn -= int64(space)
|
||||
if rn < 0 {
|
||||
return peername, nil
|
||||
}
|
||||
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
|
||||
}
|
||||
|
||||
panic("Should never reach this point")
|
||||
sort.Sort(ws)
|
||||
result := make([]router.PeerName, len(ws))
|
||||
for i, wp := range ws {
|
||||
result[i] = wp.peername
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Ring) PickPeerForTransfer() router.PeerName {
|
||||
|
||||
+20
-17
@@ -345,39 +345,42 @@ func TestGossip(t *testing.T) {
|
||||
func TestFindFree(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
|
||||
_, err := ring1.ChoosePeerToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
peers := ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
_, err = ring1.ChoosePeerToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
|
||||
// We shouldn't return outselves
|
||||
ring1.ReportFree(map[address.Address]address.Offset{start: 10})
|
||||
_, err = ring1.ChoosePeerToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
|
||||
{Token: middle, Peer: peer1name, Free: 1}}
|
||||
_, err = ring1.ChoosePeerToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
ring1.assertInvariants()
|
||||
|
||||
// We should return others
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
|
||||
peer, err := ring1.ChoosePeerToAskForSpace(start, end)
|
||||
wt.AssertSuccess(t, err)
|
||||
wt.AssertEquals(t, peer, peer2name)
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
|
||||
wt.AssertEquals(t, peers[0], peer2name)
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1},
|
||||
{Token: middle, Peer: peer3name, Free: 1}}
|
||||
peer, err = ring1.ChoosePeerToAskForSpace(start, middle)
|
||||
wt.AssertSuccess(t, err)
|
||||
wt.AssertEquals(t, peer, peer2name)
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, middle)
|
||||
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
|
||||
wt.AssertEquals(t, peers[0], peer2name)
|
||||
|
||||
peer, err = ring1.ChoosePeerToAskForSpace(middle, end)
|
||||
wt.AssertSuccess(t, err)
|
||||
wt.AssertEquals(t, peer, peer3name)
|
||||
peers = ring1.ChoosePeersToAskForSpace(middle, end)
|
||||
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
|
||||
wt.AssertEquals(t, peers[0], peer3name)
|
||||
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 2, "Expected two peers")
|
||||
ring1.assertInvariants()
|
||||
}
|
||||
|
||||
|
||||
+58
-36
@@ -200,33 +200,47 @@ func AssertNothingSentErr(t *testing.T, ch <-chan error) {
|
||||
}
|
||||
|
||||
// Router to convey gossip from one gossiper to another, for testing
|
||||
type gossipMessage struct {
|
||||
isUnicast bool
|
||||
sender *router.PeerName
|
||||
buf []byte // for unicast
|
||||
data router.GossipData // for broadcast
|
||||
exitChan chan bool
|
||||
type unicastMessage struct {
|
||||
sender router.PeerName
|
||||
buf []byte
|
||||
}
|
||||
type broadcastMessage struct {
|
||||
data router.GossipData
|
||||
}
|
||||
type exitMessage struct {
|
||||
exitChan chan struct{}
|
||||
}
|
||||
type flushMessage struct {
|
||||
flushChan chan struct{}
|
||||
}
|
||||
|
||||
type TestGossipRouter struct {
|
||||
gossipChans map[router.PeerName]chan gossipMessage
|
||||
gossipChans map[router.PeerName]chan interface{}
|
||||
loss float32 // 0.0 means no loss
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) GossipBroadcast(update router.GossipData) error {
|
||||
for _, gossipChan := range grouter.gossipChans {
|
||||
select {
|
||||
case gossipChan <- gossipMessage{data: update}:
|
||||
case gossipChan <- broadcastMessage{data: update}:
|
||||
default: // drop the message if we cannot send it
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) flush() {
|
||||
for _, gossipChan := range grouter.gossipChans {
|
||||
flushChan := make(chan struct{})
|
||||
gossipChan <- flushMessage{flushChan: flushChan}
|
||||
<-flushChan
|
||||
}
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) removePeer(peer router.PeerName) {
|
||||
gossipChan := grouter.gossipChans[peer]
|
||||
resultChan := make(chan bool)
|
||||
gossipChan <- gossipMessage{exitChan: resultChan}
|
||||
resultChan := make(chan struct{})
|
||||
gossipChan <- exitMessage{exitChan: resultChan}
|
||||
<-resultChan
|
||||
delete(grouter.gossipChans, peer)
|
||||
}
|
||||
@@ -236,39 +250,47 @@ type TestGossipRouterClient struct {
|
||||
sender router.PeerName
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
|
||||
gossipChan := make(chan gossipMessage, 100)
|
||||
func (grouter *TestGossipRouter) run(gossiper router.Gossiper, gossipChan chan interface{}) {
|
||||
gossipTimer := time.Tick(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case gossip := <-gossipChan:
|
||||
switch message := gossip.(type) {
|
||||
case exitMessage:
|
||||
close(message.exitChan)
|
||||
return
|
||||
|
||||
go func() {
|
||||
gossipTimer := time.Tick(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case message := <-gossipChan:
|
||||
if message.exitChan != nil {
|
||||
message.exitChan <- true
|
||||
return
|
||||
}
|
||||
case flushMessage:
|
||||
close(message.flushChan)
|
||||
|
||||
case unicastMessage:
|
||||
if rand.Float32() > (1.0 - grouter.loss) {
|
||||
continue
|
||||
}
|
||||
if err := gossiper.OnGossipUnicast(message.sender, message.buf); err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", message.sender, err))
|
||||
}
|
||||
|
||||
if message.isUnicast {
|
||||
if err := gossiper.OnGossipUnicast(*message.sender, message.buf); err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", sender, err))
|
||||
}
|
||||
} else {
|
||||
for _, msg := range message.data.Encode() {
|
||||
if _, err := gossiper.OnGossipBroadcast(msg); err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip broadcast to %s: %s", sender, err))
|
||||
}
|
||||
case broadcastMessage:
|
||||
if rand.Float32() > (1.0 - grouter.loss) {
|
||||
continue
|
||||
}
|
||||
for _, msg := range message.data.Encode() {
|
||||
if _, err := gossiper.OnGossipBroadcast(msg); err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip broadcast: %s", err))
|
||||
}
|
||||
}
|
||||
case <-gossipTimer:
|
||||
grouter.GossipBroadcast(gossiper.Gossip())
|
||||
}
|
||||
case <-gossipTimer:
|
||||
grouter.GossipBroadcast(gossiper.Gossip())
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
|
||||
gossipChan := make(chan interface{}, 100)
|
||||
|
||||
go grouter.run(gossiper, gossipChan)
|
||||
|
||||
grouter.gossipChans[sender] = gossipChan
|
||||
return TestGossipRouterClient{grouter, sender}
|
||||
@@ -276,7 +298,7 @@ func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router
|
||||
|
||||
func (client TestGossipRouterClient) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
||||
select {
|
||||
case client.router.gossipChans[dstPeerName] <- gossipMessage{isUnicast: true, sender: &client.sender, buf: buf}:
|
||||
case client.router.gossipChans[dstPeerName] <- unicastMessage{sender: client.sender, buf: buf}:
|
||||
default: // drop the message if we cannot send it
|
||||
common.Error.Printf("Dropping message")
|
||||
}
|
||||
@@ -289,7 +311,7 @@ func (client TestGossipRouterClient) GossipBroadcast(update router.GossipData) e
|
||||
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRouter, address.Range) {
|
||||
|
||||
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
|
||||
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan interface{}), 0.0}
|
||||
allocs := make([]*Allocator, size)
|
||||
var subnet address.Range
|
||||
|
||||
@@ -303,7 +325,7 @@ func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRou
|
||||
}
|
||||
|
||||
gossipRouter.GossipBroadcast(allocs[size-1].Gossip())
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
gossipRouter.flush()
|
||||
return allocs, gossipRouter, subnet
|
||||
}
|
||||
|
||||
|
||||
@@ -395,6 +395,9 @@ func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
|
||||
}
|
||||
Debug.Printf("[dns msgid %d] Given answer by %s for query %s",
|
||||
r.MsgHdr.Id, server, q.Name)
|
||||
if reply.Len() > maxLen {
|
||||
reply.Compress = true
|
||||
}
|
||||
w.WriteMsg(reply)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package nameserver
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
@@ -32,11 +34,12 @@ func TestUDPDNSServer(t *testing.T) {
|
||||
setupForTest(t)
|
||||
|
||||
const (
|
||||
successTestName = "test1.weave.local."
|
||||
failTestName = "fail.weave.local."
|
||||
nonLocalName = "weave.works."
|
||||
testAddr1 = "10.2.2.1"
|
||||
containerID = "somecontainer"
|
||||
successTestName = "test1.weave.local."
|
||||
failTestName = "fail.weave.local."
|
||||
compressedTestName = "compressed.weave.works."
|
||||
nonLocalName = "weave.works."
|
||||
testAddr1 = "10.2.2.1"
|
||||
containerID = "somecontainer"
|
||||
)
|
||||
testCIDR1 := testAddr1 + "/24"
|
||||
|
||||
@@ -72,6 +75,15 @@ func TestUDPDNSServer(t *testing.T) {
|
||||
m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."}
|
||||
} else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR {
|
||||
m.Rcode = dns.RcodeNameError
|
||||
} else if q.Name == compressedTestName {
|
||||
// Construct a response that is >512 when uncompressed, <512 when compressed
|
||||
header := makeHeader(m, &q, 300)
|
||||
for m.Len() <= minUDPSize {
|
||||
ip := address.Address(rand.Uint32()).IP4()
|
||||
m.Answer = append(m.Answer, &dns.A{Hdr: *header, A: ip})
|
||||
}
|
||||
m.Compress = true
|
||||
wt.AssertTrue(t, m.Len() <= minUDPSize, fmt.Sprintf("length: %d", m.Len()))
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
@@ -125,6 +137,9 @@ func TestUDPDNSServer(t *testing.T) {
|
||||
|
||||
assertExchange(t, testRDNSnonlocal, dns.TypePTR, testPort, 1, -1, 0)
|
||||
|
||||
_, r = assertExchange(t, compressedTestName, dns.TypeA, testPort, -1, -1, 0)
|
||||
wt.AssertTrue(t, r.Len() > minUDPSize, "unexpected message length")
|
||||
|
||||
// Not testing MDNS functionality of server here (yet), since it
|
||||
// needs two servers, each listening on its own address
|
||||
}
|
||||
|
||||
@@ -103,9 +103,11 @@ func (zone *ZoneDb) domainLookup(target string, lfun ZoneLookupFunc) (res []Zone
|
||||
for _, answer := range lanswers {
|
||||
r, err := remoteIdent.addIPToName(answer, now)
|
||||
if err != nil {
|
||||
zone.mx.Unlock()
|
||||
Warning.Printf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
|
||||
return nil, err
|
||||
if _, ok := err.(DuplicateError); !ok {
|
||||
zone.mx.Unlock()
|
||||
Warning.Printf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
uniq.add(r)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
@@ -10,11 +11,17 @@ const (
|
||||
sleepTime = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
// Wait `wait` seconds for an interface to come up. Pass zero to check once
|
||||
// and return immediately, or a negative value to wait indefinitely.
|
||||
func EnsureInterface(ifaceName string, wait int) (iface *net.Interface, err error) {
|
||||
if iface, err = findInterface(ifaceName); err == nil || wait == 0 {
|
||||
return
|
||||
}
|
||||
for i := time.Duration(wait) * time.Second / sleepTime; err != nil && i > 0; i-- {
|
||||
i := int64(math.MaxInt64)
|
||||
if wait > 0 {
|
||||
i = int64(time.Duration(wait) * time.Second / sleepTime)
|
||||
}
|
||||
for ; err != nil && i > 0; i-- {
|
||||
time.Sleep(sleepTime)
|
||||
iface, err = findInterface(ifaceName)
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@ MAINTAINER Weaveworks Inc <help@weave.works>
|
||||
WORKDIR /home/weave
|
||||
ADD ./weavedns /home/weave/
|
||||
EXPOSE 53/udp
|
||||
ENTRYPOINT ["/home/weave/weavedns", "-wait", "20"]
|
||||
ENTRYPOINT ["/home/weave/weavedns"]
|
||||
@@ -46,7 +46,7 @@ func main() {
|
||||
flag.StringVar(&ifaceName, "iface", "", "name of interface to use for multicast")
|
||||
flag.StringVar(&apiPath, "api", "unix:///var/run/docker.sock", "path to Docker API socket")
|
||||
flag.StringVar(&domain, "domain", weavedns.DefaultLocalDomain, "local domain (ie, 'weave.local.')")
|
||||
flag.IntVar(&wait, "wait", 0, "number of seconds to wait for interface to be created and come up")
|
||||
flag.IntVar(&wait, "wait", -1, "number of seconds to wait for interfaces to come up (0=don't wait, -1=wait forever)")
|
||||
flag.IntVar(&dnsPort, "dnsport", weavedns.DefaultServerPort, "port to listen to DNS requests")
|
||||
flag.StringVar(&httpIfaceName, "httpiface", "", "interface on which to listen for HTTP requests (defaults to empty string which listens on all interfaces)")
|
||||
flag.IntVar(&httpPort, "httpport", weavedns.DefaultHTTPPort, "port to listen to HTTP requests")
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/getopt"
|
||||
"github.com/pborman/getopt"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/proxy"
|
||||
)
|
||||
|
||||
@@ -2,4 +2,4 @@ FROM scratch
|
||||
MAINTAINER Weaveworks Inc <help@weave.works>
|
||||
WORKDIR /home/weave
|
||||
ADD ./weaver /home/weave/
|
||||
ENTRYPOINT ["/home/weave/weaver", "-wait", "20"]
|
||||
ENTRYPOINT ["/home/weave/weaver"]
|
||||
+2
-2
@@ -11,8 +11,8 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/gopacket/layers"
|
||||
"github.com/davecheney/profile"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/gorilla/mux"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
@@ -64,7 +64,7 @@ func main() {
|
||||
flag.StringVar(&routerName, "name", "", "name of router (defaults to MAC of interface)")
|
||||
flag.StringVar(&nickName, "nickname", "", "nickname of peer (defaults to hostname)")
|
||||
flag.StringVar(&password, "password", "", "network password")
|
||||
flag.IntVar(&wait, "wait", 0, "number of seconds to wait for interface to be created and come up (0 = don't wait)")
|
||||
flag.IntVar(&wait, "wait", -1, "number of seconds to wait for interface to come up (0=don't wait, -1=wait forever)")
|
||||
flag.BoolVar(&debug, "debug", false, "enable debug logging")
|
||||
flag.BoolVar(&pktdebug, "pktdebug", false, "enable per-packet debug logging")
|
||||
flag.StringVar(&prof, "profile", "", "enable profiling and write profiles to given path")
|
||||
|
||||
+14
-18
@@ -6,33 +6,29 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/net"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/proxy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) <= 1 {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args := os.Args[1:]
|
||||
signalWait := 20
|
||||
if args[0] == "-s" {
|
||||
signalWait = 0
|
||||
args = args[1:]
|
||||
}
|
||||
interfaceWait := 20 - signalWait
|
||||
|
||||
usr2 := make(chan os.Signal)
|
||||
signal.Notify(usr2, syscall.SIGUSR2)
|
||||
select {
|
||||
case <-usr2:
|
||||
case <-time.After(time.Duration(signalWait) * time.Second):
|
||||
if len(args) > 0 && args[0] == "-s" {
|
||||
args = args[1:]
|
||||
} else {
|
||||
usr2 := make(chan os.Signal)
|
||||
signal.Notify(usr2, syscall.SIGUSR2)
|
||||
<-usr2
|
||||
}
|
||||
_, err := net.EnsureInterface("ethwe", interfaceWait)
|
||||
|
||||
_, err := weavenet.EnsureInterface("ethwe", -1)
|
||||
checkErr(err)
|
||||
|
||||
if len(args) == 0 {
|
||||
checkErr(proxy.ErrNoCommandSpecified)
|
||||
}
|
||||
|
||||
binary, err := exec.LookPath(args[0])
|
||||
checkErr(err)
|
||||
|
||||
|
||||
+8
-1
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
|
||||
@@ -22,7 +23,13 @@ func callWeave(args ...string) ([]byte, error) {
|
||||
args = append([]string{"--local"}, args...)
|
||||
Debug.Print("Calling weave", args)
|
||||
cmd := exec.Command("./weave", args...)
|
||||
cmd.Env = []string{"PROCFS=/hostproc", "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
|
||||
cmd.Env = []string{
|
||||
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"PROCFS=/hostproc",
|
||||
}
|
||||
if bridge := os.Getenv("DOCKER_BRIDGE"); bridge != "" {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_BRIDGE=%s", bridge))
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
return out, err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package proxy
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -13,6 +14,12 @@ import (
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
)
|
||||
|
||||
const MaxDockerHostname = 64
|
||||
|
||||
var (
|
||||
ErrNoCommandSpecified = errors.New("No command specified")
|
||||
)
|
||||
|
||||
type createContainerInterceptor struct{ proxy *Proxy }
|
||||
|
||||
type createContainerRequestBody struct {
|
||||
@@ -21,9 +28,9 @@ type createContainerRequestBody struct {
|
||||
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
|
||||
}
|
||||
|
||||
// Replacement for docker.NoSuchImage, which does not contain the
|
||||
// image name, which in turn breaks docker clients post 1.7.0 since
|
||||
// they expect the image name to be present in errors.
|
||||
// ErrNoSuchImage replaces docker.NoSuchImage, which does not contain the image
|
||||
// name, which in turn breaks docker clients post 1.7.0 since they expect the
|
||||
// image name to be present in errors.
|
||||
type ErrNoSuchImage struct {
|
||||
Name string
|
||||
}
|
||||
@@ -44,15 +51,17 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if cidrs, ok := i.proxy.weaveCIDRsFromConfig(container.Config); ok {
|
||||
if cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig); err != nil {
|
||||
Info.Printf("Ignoring container due to %s", err)
|
||||
} else {
|
||||
Info.Printf("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
|
||||
if container.HostConfig == nil {
|
||||
container.HostConfig = &docker.HostConfig{}
|
||||
}
|
||||
container.HostConfig.VolumesFrom = append(container.HostConfig.VolumesFrom, "weaveproxy")
|
||||
if container.Config == nil {
|
||||
container.Config = &docker.Config{}
|
||||
}
|
||||
i.addWeaveWaitVolume(container.HostConfig)
|
||||
if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,6 +80,18 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) addWeaveWaitVolume(hostConfig *docker.HostConfig) {
|
||||
var binds []string
|
||||
for _, bind := range hostConfig.Binds {
|
||||
s := strings.Split(bind, ":")
|
||||
if len(s) >= 2 && s[1] == "/w" {
|
||||
continue
|
||||
}
|
||||
binds = append(binds, bind)
|
||||
}
|
||||
hostConfig.Binds = append(binds, fmt.Sprintf("%s:/w:ro", i.proxy.weaveWaitVolume))
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error {
|
||||
if len(container.Entrypoint) == 0 {
|
||||
image, err := i.proxy.client.InspectImage(container.Image)
|
||||
@@ -89,7 +110,14 @@ func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Co
|
||||
}
|
||||
}
|
||||
|
||||
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
|
||||
if len(container.Entrypoint) == 0 && len(container.Cmd) == 0 {
|
||||
return ErrNoCommandSpecified
|
||||
}
|
||||
|
||||
if len(container.Entrypoint) == 0 || container.Entrypoint[0] != weaveWaitEntrypoint[0] {
|
||||
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -107,9 +135,14 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque
|
||||
|
||||
name := r.URL.Query().Get("name")
|
||||
if container.Hostname == "" && name != "" {
|
||||
container.Hostname = name
|
||||
// Strip trailing period because it's unusual to see it used on the end of a host name
|
||||
container.Domainname = strings.TrimSuffix(dnsDomain, ".")
|
||||
trimmedDNSDomain := strings.TrimSuffix(dnsDomain, ".")
|
||||
if len(name)+1+len(trimmedDNSDomain) > MaxDockerHostname {
|
||||
Warning.Printf("Container name [%s] too long to be used as hostname", name)
|
||||
} else {
|
||||
container.Hostname = name
|
||||
container.Domainname = trimmedDNSDomain
|
||||
}
|
||||
}
|
||||
|
||||
if len(container.HostConfig.DNSSearch) == 0 {
|
||||
|
||||
@@ -29,7 +29,11 @@ func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if cidrs, ok := i.proxy.weaveCIDRsFromConfig(container.Config); ok {
|
||||
_, hasWeaveWait := container.Volumes["/w"]
|
||||
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
|
||||
if err != nil {
|
||||
Info.Printf("Ignoring container %s due to %s", container.ID, err)
|
||||
} else if hasWeaveWait {
|
||||
Info.Printf("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
|
||||
cmd := append(weaveWaitEntrypoint, "-s")
|
||||
options.Cmd = append(cmd, options.Cmd...)
|
||||
|
||||
+50
-8
@@ -2,6 +2,8 @@ package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -22,14 +24,18 @@ var (
|
||||
containerCreateRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/create$")
|
||||
containerStartRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/[^/]*/(re)?start$")
|
||||
execCreateRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/[^/]*/exec$")
|
||||
|
||||
ErrWeaveCIDRNone = errors.New("WEAVE_CIDR=none")
|
||||
ErrNoDefaultIPAM = errors.New("--no-default-ipam option")
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
Config
|
||||
|
||||
dial func() (net.Conn, error)
|
||||
client *docker.Client
|
||||
dockerBridgeIP string
|
||||
dial func() (net.Conn, error)
|
||||
client *docker.Client
|
||||
dockerBridgeIP string
|
||||
weaveWaitVolume string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -75,14 +81,41 @@ func NewProxy(c Config) (*Proxy, error) {
|
||||
Error.Fatalf("Could not configure tls for proxy: %s", err)
|
||||
}
|
||||
|
||||
p.client, err = docker.NewClient(p.DockerAddr)
|
||||
// We pin the protocol version to 1.15 (which corresponds to Docker 1.3.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.
|
||||
p.client, err = docker.NewVersionedClient(p.DockerAddr, "1.15")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = p.findWeaveWaitVolume(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) findWeaveWaitVolume() error {
|
||||
container, err := proxy.client.InspectContainer("weaveproxy")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not find the weavewait volume: %s", err)
|
||||
}
|
||||
|
||||
if container.Volumes == nil {
|
||||
return fmt.Errorf("Could not find the weavewait volume")
|
||||
}
|
||||
|
||||
volume, ok := container.Volumes["/w"]
|
||||
if !ok {
|
||||
return fmt.Errorf("Could not find the weavewait volume")
|
||||
}
|
||||
|
||||
proxy.weaveWaitVolume = volume
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Info.Printf("%s %s", r.Method, r.URL)
|
||||
path := r.URL.Path
|
||||
@@ -115,14 +148,23 @@ func (proxy *Proxy) ListenAndServe() error {
|
||||
return (&http.Server{Handler: proxy}).Serve(listener)
|
||||
}
|
||||
|
||||
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config) ([]string, bool) {
|
||||
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *docker.HostConfig) ([]string, error) {
|
||||
if hostConfig != nil &&
|
||||
hostConfig.NetworkMode != "" &&
|
||||
hostConfig.NetworkMode != "default" &&
|
||||
hostConfig.NetworkMode != "bridge" {
|
||||
return nil, fmt.Errorf("--net option: %q", hostConfig.NetworkMode)
|
||||
}
|
||||
for _, e := range config.Env {
|
||||
if strings.HasPrefix(e, "WEAVE_CIDR=") {
|
||||
if e[11:] == "none" {
|
||||
return nil, false
|
||||
return nil, ErrWeaveCIDRNone
|
||||
}
|
||||
return strings.Fields(e[11:]), true
|
||||
return strings.Fields(e[11:]), nil
|
||||
}
|
||||
}
|
||||
return nil, !proxy.NoDefaultIPAM
|
||||
if proxy.NoDefaultIPAM {
|
||||
return nil, ErrNoDefaultIPAM
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
+20
-14
@@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"sync"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
@@ -94,25 +95,30 @@ func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.Cl
|
||||
return
|
||||
}
|
||||
|
||||
upDone := make(chan struct{})
|
||||
downDone := make(chan struct{})
|
||||
go copyStream(down, io.MultiReader(rem, up), upDone)
|
||||
go copyStream(up, downBuf, downDone)
|
||||
<-upDone
|
||||
<-downDone
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go copyStream(down, io.MultiReader(rem, up), &wg)
|
||||
go copyStream(up, downBuf, &wg)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func copyStream(dst io.Writer, src io.Reader, done chan struct{}) {
|
||||
defer close(done)
|
||||
type closeWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
func copyStream(dst io.WriteCloser, src io.Reader, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
Warning.Print(err)
|
||||
}
|
||||
if c, ok := dst.(interface {
|
||||
CloseWrite() error
|
||||
}); ok {
|
||||
if err := c.CloseWrite(); err != nil {
|
||||
Warning.Printf("Error closing connection: %s", err)
|
||||
}
|
||||
var err error
|
||||
if c, ok := dst.(closeWriter); ok {
|
||||
err = c.CloseWrite()
|
||||
} else {
|
||||
err = dst.Close()
|
||||
}
|
||||
if err != nil {
|
||||
Warning.Printf("Error closing connection: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,18 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
}
|
||||
|
||||
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
|
||||
if r.StatusCode < 200 || r.StatusCode >= 300 { // Docker didn't do the start
|
||||
return nil
|
||||
}
|
||||
|
||||
container, err := inspectContainerInPath(i.proxy.client, r.Request.URL.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cidrs, ok := i.proxy.weaveCIDRsFromConfig(container.Config)
|
||||
if !ok {
|
||||
Debug.Print("No Weave CIDR, ignoring")
|
||||
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
|
||||
if err != nil {
|
||||
Info.Printf("Ignoring container %s due to %s", container.ID, err)
|
||||
return nil
|
||||
}
|
||||
Info.Printf("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
|
||||
|
||||
@@ -231,7 +231,7 @@ func (conn *LocalConnection) ReceivedHeartbeat(remoteUDPAddr *net.UDPAddr, connU
|
||||
if oldRemoteUDPAddr == nil {
|
||||
return conn.sendFastHeartbeats()
|
||||
} else if oldRemoteUDPAddr.String() != remoteUDPAddr.String() {
|
||||
log.Println("Peer", conn.remote, "moved from", old, "to", remoteUDPAddr)
|
||||
log.Println("Peer", conn.remote, "moved from", oldRemoteUDPAddr, "to", remoteUDPAddr)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
+4
-3
@@ -1,15 +1,16 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"code.google.com/p/go-bit/bit"
|
||||
"code.google.com/p/go.crypto/nacl/box"
|
||||
"code.google.com/p/go.crypto/nacl/secretbox"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/andybalholm/go-bit"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
)
|
||||
|
||||
func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) {
|
||||
|
||||
@@ -2,10 +2,11 @@ package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/gopacket"
|
||||
"code.google.com/p/gopacket/layers"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
type EthernetDecoder struct {
|
||||
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"code.google.com/p/gopacket"
|
||||
"code.google.com/p/gopacket/layers"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
type ForwardedFrame struct {
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
@@ -58,7 +59,12 @@ func (c *GossipChannel) deliverUnicast(srcName PeerName, origPayload []byte, dec
|
||||
return err
|
||||
}
|
||||
if c.ourself.Name != destName {
|
||||
return c.relayUnicast(destName, origPayload)
|
||||
if err := c.relayUnicast(destName, origPayload); err != nil {
|
||||
// just log errors from relayUnicast; a problem between us and destination
|
||||
// is not enough reason to break the connection from the source
|
||||
c.log(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var payload []byte
|
||||
if err := dec.Decode(&payload); err != nil {
|
||||
@@ -160,15 +166,15 @@ func (c *GossipChannel) GossipBroadcast(update GossipData) error {
|
||||
return c.relayBroadcast(c.ourself.Name, update)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) error {
|
||||
func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) (err error) {
|
||||
if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found {
|
||||
c.log("unknown relay destination:", dstPeerName)
|
||||
err = fmt.Errorf("unknown relay destination: %s", dstPeerName)
|
||||
} else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found {
|
||||
c.log("unable to find connection to relay peer", relayPeerName)
|
||||
err = fmt.Errorf("unable to find connection to relay peer %s", relayPeerName)
|
||||
} else {
|
||||
conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf})
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"testing"
|
||||
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func TestFieldValidator(t *testing.T) {
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"testing"
|
||||
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// Add to peers a connection from peers.ourself to p
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"code.google.com/p/gopacket/pcap"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/gopacket/pcap"
|
||||
)
|
||||
|
||||
type PcapIO struct {
|
||||
|
||||
@@ -2,9 +2,10 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// TODO we should also test:
|
||||
|
||||
+10
-7
@@ -11,21 +11,22 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"code.google.com/p/gopacket/layers"
|
||||
"github.com/google/gopacket/layers"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
macMaxAge = 10 * time.Minute // [1]
|
||||
tcpAcceptDelay = 1 * time.Millisecond // [2]
|
||||
macMaxAge = 10 * time.Minute // [1]
|
||||
acceptMaxTokens = 100 // [2]
|
||||
acceptTokenDelay = 100 * time.Millisecond // [3]
|
||||
)
|
||||
|
||||
// [1] should be greater than typical ARP cache expiries, i.e. > 3/2 *
|
||||
// /proc/sys/net/ipv4_neigh/*/base_reachable_time_ms on Linux
|
||||
|
||||
// [2] time to wait between accepting tcp connections. This guards
|
||||
// against brute-force attacks on the password when encryption is in
|
||||
// use. It is also a basic DoS defence.
|
||||
// [2] capacity of token bucket for rate limiting accepts
|
||||
|
||||
// [3] control rate at which new tokens are added to the bucket
|
||||
|
||||
type LogFrameFunc func(string, []byte, *layers.Ethernet)
|
||||
|
||||
@@ -50,6 +51,7 @@ type Router struct {
|
||||
gossipChannels GossipChannels
|
||||
TopologyGossip Gossip
|
||||
UDPListener *net.UDPConn
|
||||
acceptLimiter *TokenBucket
|
||||
}
|
||||
|
||||
type PacketSource interface {
|
||||
@@ -81,6 +83,7 @@ func NewRouter(config Config, name PeerName, nickName string) *Router {
|
||||
router.Routes = NewRoutes(router.Ourself, router.Peers)
|
||||
router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port, router.PeerDiscovery)
|
||||
router.TopologyGossip = router.NewGossip("topology", router)
|
||||
router.acceptLimiter = NewTokenBucket(acceptMaxTokens, acceptTokenDelay)
|
||||
return router
|
||||
}
|
||||
|
||||
@@ -212,7 +215,7 @@ func (router *Router) listenTCP(localPort int) {
|
||||
continue
|
||||
}
|
||||
router.acceptTCP(tcpConn)
|
||||
time.Sleep(tcpAcceptDelay)
|
||||
router.acceptLimiter.Wait()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenBucket struct {
|
||||
capacity int64 // Maximum capacity of bucket
|
||||
tokenInterval time.Duration // Token replenishment rate
|
||||
refillDuration time.Duration // Time to refill from empty
|
||||
earliestUnspentToken time.Time
|
||||
}
|
||||
|
||||
func NewTokenBucket(capacity int64, tokenInterval time.Duration) *TokenBucket {
|
||||
tb := TokenBucket{
|
||||
capacity: capacity,
|
||||
tokenInterval: tokenInterval,
|
||||
refillDuration: tokenInterval * time.Duration(capacity)}
|
||||
|
||||
tb.earliestUnspentToken = tb.capacityToken()
|
||||
|
||||
return &tb
|
||||
}
|
||||
|
||||
func (tb *TokenBucket) Wait() {
|
||||
// If earliest unspent token is in the future, sleep until then
|
||||
time.Sleep(tb.earliestUnspentToken.Sub(time.Now()))
|
||||
|
||||
// Alternatively, enforce bucket capacity if necessary
|
||||
capacityToken := tb.capacityToken()
|
||||
if tb.earliestUnspentToken.Before(capacityToken) {
|
||||
tb.earliestUnspentToken = capacityToken
|
||||
}
|
||||
|
||||
// 'Remove' a token from the bucket
|
||||
tb.earliestUnspentToken = tb.earliestUnspentToken.Add(tb.tokenInterval)
|
||||
}
|
||||
|
||||
// Determine the historic token timestamp representing a full bucket
|
||||
func (tb *TokenBucket) capacityToken() time.Time {
|
||||
return time.Now().Add(-tb.refillDuration).Truncate(tb.tokenInterval)
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"code.google.com/p/gopacket"
|
||||
"code.google.com/p/gopacket/layers"
|
||||
"log"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
type UDPSender interface {
|
||||
|
||||
+34
-1
@@ -278,7 +278,7 @@ password, and the result is hashed through SHA256, to form the final
|
||||
ephemeral session key. Thus the supplied password is never exchanged
|
||||
directly, and is thoroughly mixed into the shared secret. Furthermore,
|
||||
the rate at which TCP connections are accepted is limited by weave to
|
||||
1kHz, which twarts online dictionary attacks on reasonably strong
|
||||
10Hz, which twarts online dictionary attacks on reasonably strong
|
||||
passwords.
|
||||
|
||||
The shared key formed by Diffie-Hellman is 256 bits long, appending
|
||||
@@ -293,6 +293,39 @@ not be able to form valid ephemeral session keys.
|
||||
The same ephemeral session key is used for both TCP and UDP traffic
|
||||
between two peers.
|
||||
|
||||
<a name="csprng"></a> Generating fresh keys for every connection
|
||||
provides forward secrecy at the cost of placing a demand on the Linux
|
||||
CSPRNG (accessed by `GenerateKey` via `/dev/urandom`) proportional to
|
||||
the number of inbound connection attempts. Weave has accept throttling
|
||||
to mitigate against denial of service attacks that seek to deplete the
|
||||
CSPRNG entropy pool, however even at the lower bound of ten requests
|
||||
per second there may not be enough entropy gathered on a headless
|
||||
system to keep pace.
|
||||
|
||||
Under such conditions, the consequences will be limited to slowing
|
||||
down processes reading from the blocking `/dev/random` device as the
|
||||
kernel waits for enough new entropy to be harvested. It is important
|
||||
to note that contrary to intuition this low entropy state does not
|
||||
compromise the ongoing use of `/dev/urandom` - [expert
|
||||
opinion](http://blog.cr.yp.to/20140205-entropy.html)
|
||||
asserts that as long as the CSPRNG is seeded with enough entropy (e.g.
|
||||
256 bits) before random number generation commences then the output is
|
||||
entirely safe for use as key material.
|
||||
|
||||
By way of comparison, this is exactly how OpenSSL works - it reads 256
|
||||
bits of entropy at startup, and uses that to seed an internal CSPRNG
|
||||
which is used thenceforth to generate keys. Whilst we could have taken
|
||||
the same approach and built our own CSPRNG to work around the
|
||||
potential `/dev/random` blocking issue, we thought it was much more
|
||||
prudent to rely on the [heavily
|
||||
scrutinised](http://eprint.iacr.org/2012/251.pdf) Linux random number
|
||||
generator as [advised
|
||||
here](http://cr.yp.to/highspeed/coolnacl-20120725.pdf) (page 10,
|
||||
'Centralizing randomness'). The aforementioned notwithstanding, if
|
||||
weave's demand on `/dev/urandom` is causing you problems with blocking
|
||||
`/dev/random` reads, please get in touch with us - we'd love to hear
|
||||
about your use case.
|
||||
|
||||
#### TCP
|
||||
|
||||
TCP connection are only used to exchange topology information between
|
||||
|
||||
@@ -33,4 +33,8 @@ assert_expected_fqdn "$HOSTNAME" -h $HOSTNAME --name=$NAME
|
||||
assert_expected_fqdn "$HOSTNAME" --hostname=$HOSTNAME --name=$NAME
|
||||
assert_expected_fqdn "$HOSTNAME" --hostname $HOSTNAME --name=$NAME
|
||||
|
||||
# Ensure we can launch a container with a name that is too long for hostname derivation
|
||||
SIXTY_FIVE_CHARS=01234567890123456789012345678901234567890123456789012345678901234
|
||||
assert_raises "start_container $HOST1 --name=$SIXTY_FIVE_CHARS"
|
||||
|
||||
end_suite
|
||||
@@ -4,13 +4,19 @@
|
||||
|
||||
start_suite "Run docker-py test suite against the proxy"
|
||||
|
||||
docker_on $HOST1 pull joffrey/docker-py >/dev/null
|
||||
|
||||
weave_on $HOST1 launch-proxy --no-default-ipam
|
||||
assert_raises "docker_on $HOST1 ps | grep weaveproxy"
|
||||
assert_raises "docker_on $HOST1 run \
|
||||
-e NOT_ON_HOST=true \
|
||||
-e DOCKER_HOST=tcp://172.17.42.1:12375 \
|
||||
-v /tmp:/tmp \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
joffrey/docker-py python tests/integration_test.py"
|
||||
|
||||
if docker_on $HOST1 run \
|
||||
-e NOT_ON_HOST=true \
|
||||
-e DOCKER_HOST=tcp://172.17.42.1:12375 \
|
||||
-v /tmp:/tmp \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
joffrey/docker-py python tests/integration_test.py ; then
|
||||
assert_raises "true"
|
||||
else
|
||||
assert_raises "false"
|
||||
fi
|
||||
|
||||
end_suite
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
. ./config.sh
|
||||
|
||||
entrypoint() {
|
||||
docker_on $HOST1 inspect --format="{{.Config.Entrypoint}}" "$@"
|
||||
}
|
||||
|
||||
start_suite "Proxy waits for weave to be ready before running container commands"
|
||||
weave_on $HOST1 launch-proxy
|
||||
BASE_IMAGE=busybox
|
||||
@@ -10,6 +14,28 @@ if (docker_on $HOST1 images $BASE_IMAGE | grep -q $BASE_IMAGE); then
|
||||
docker_on $HOST1 rmi $BASE_IMAGE
|
||||
fi
|
||||
|
||||
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' $BASE_IMAGE $CHECK_ETHWE_UP"
|
||||
assert_raises "proxy docker_on $HOST1 run --name c1 -e 'WEAVE_CIDR=10.2.1.1/24' $BASE_IMAGE $CHECK_ETHWE_UP"
|
||||
|
||||
# Check committed containers only have one weavewait prepended
|
||||
COMMITTED_IMAGE=$(proxy docker_on $HOST1 commit c1)
|
||||
assert_raises "proxy docker_on $HOST1 run --name c2 $COMMITTED_IMAGE"
|
||||
assert "entrypoint c2" "$(entrypoint $COMMITTED_IMAGE)"
|
||||
|
||||
# Check exec works on containers without weavewait
|
||||
docker_on $HOST1 run -dit --name c3 $SMALL_IMAGE /bin/sh
|
||||
assert_raises "proxy docker_on $HOST1 exec c3 true"
|
||||
|
||||
# Check we can't modify weavewait
|
||||
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.2/24' $BASE_IMAGE touch /w/w" 1
|
||||
|
||||
# Check only user-specified volumes and /w are mounted
|
||||
dirs_sans_proxy=$(docker_on $HOST1 run --rm $SMALL_IMAGE mount | wc -l)
|
||||
dirs_with_proxy=$(proxy docker_on $HOST1 run --rm -v /tmp/1:/srv1 -v /tmp/2:/srv2 -e 'WEAVE_CIDR=10.2.1.3/24' $SMALL_IMAGE mount | wc -l)
|
||||
assert "echo $((dirs_with_proxy-3))" $dirs_sans_proxy
|
||||
|
||||
# Check errors are returned (when docker returns an error code)
|
||||
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.3/24' $SMALL_IMAGE foo 2>&1 | grep 'exec: \"foo\": executable file not found in \$PATH'"
|
||||
# Check errors still happen when no command is specified
|
||||
assert_raises "proxy docker_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.3/24' $SMALL_IMAGE 2>&1 | grep 'Error response from daemon: No command specified'"
|
||||
|
||||
end_suite
|
||||
@@ -8,8 +8,8 @@ C1_NAME=c1.weave.local
|
||||
C2_NAME=seetwo.weave.local
|
||||
|
||||
boot_containers() {
|
||||
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $C2_NAME $DNS_IMAGE /bin/sh
|
||||
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C1/24 -dt --name=c1 $DNS_IMAGE /bin/sh
|
||||
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $C2_NAME
|
||||
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C1/24 -dt --name=c1
|
||||
}
|
||||
|
||||
kill_containers() {
|
||||
|
||||
@@ -9,19 +9,15 @@ C4=10.2.0.99
|
||||
DOMAIN=weave.local
|
||||
NAME=seeone.$DOMAIN
|
||||
|
||||
start_container() {
|
||||
proxy docker_on $HOST1 run "$@" -dt $DNS_IMAGE /bin/sh
|
||||
}
|
||||
|
||||
start_suite "Resolve unqualified names"
|
||||
|
||||
weave_on $HOST1 launch-dns 10.2.254.1/24
|
||||
weave_on $HOST1 launch-proxy
|
||||
|
||||
start_container -e WEAVE_CIDR=$C1/24 --name=c1 -h $NAME
|
||||
start_container -e WEAVE_CIDR=$C2/24 --name=c2 -h seetwo.$DOMAIN
|
||||
start_container -e WEAVE_CIDR=$C3/24 --name=c3 --dns-search=$DOMAIN
|
||||
container=$(start_container -e WEAVE_CIDR=$C4/24)
|
||||
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C1/24 --name=c1 -h $NAME
|
||||
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C2/24 --name=c2 -h seetwo.$DOMAIN
|
||||
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C3/24 --name=c3 --dns-search=$DOMAIN
|
||||
container=$(proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C4/24)
|
||||
|
||||
check() {
|
||||
assert "proxy exec_on $HOST1 $1 getent hosts seeone | tr -s ' '" "$C1 $NAME"
|
||||
|
||||
@@ -10,8 +10,8 @@ start_suite "Proxy restart reattaches networking to containers"
|
||||
|
||||
launch_dns_on $HOST1 10.2.254.1/24
|
||||
weave_on $HOST1 launch-proxy --with-dns
|
||||
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $NAME $SMALL_IMAGE /bin/sh
|
||||
proxy docker_on $HOST1 run -e WEAVE_CIDR=$C1/24 -dt --name=c1 $DNS_IMAGE /bin/sh
|
||||
proxy_start_container $HOST1 -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $NAME
|
||||
proxy_start_container_with_dns $HOST1 -e WEAVE_CIDR=$C1/24 -dt --name=c1
|
||||
|
||||
proxy docker_on $HOST1 restart c2
|
||||
assert_raises "proxy exec_on $HOST1 c2 $CHECK_ETHWE_UP"
|
||||
|
||||
+12
-10
@@ -4,12 +4,6 @@
|
||||
|
||||
UNIVERSE=10.2.2.0/24
|
||||
|
||||
start() {
|
||||
host=$1
|
||||
shift
|
||||
proxy docker_on "$host" run "$@" -dt $SMALL_IMAGE /bin/sh
|
||||
}
|
||||
|
||||
assert_no_ethwe() {
|
||||
assert_raises "container_ip $1 $2" 1
|
||||
assert_raises "proxy exec_on $1 $2 ip link show | grep -v ethwe"
|
||||
@@ -22,17 +16,25 @@ weave_on $HOST2 launch -iprange $UNIVERSE $HOST1
|
||||
weave_on $HOST1 launch-proxy
|
||||
weave_on $HOST2 launch-proxy --no-default-ipam
|
||||
|
||||
start $HOST1 --name=auto
|
||||
start $HOST1 --name=none -e WEAVE_CIDR=none
|
||||
start $HOST2 --name=zero -e WEAVE_CIDR=
|
||||
start $HOST2 --name=no-default
|
||||
proxy_start_container $HOST1 --name=auto
|
||||
proxy_start_container $HOST1 --name=none -e WEAVE_CIDR=none
|
||||
proxy_start_container $HOST2 --name=zero -e WEAVE_CIDR=
|
||||
proxy_start_container $HOST2 --name=no-default
|
||||
proxy_start_container $HOST1 --name=bridge --net=bridge
|
||||
proxy_start_container $HOST1 --name=host --net=host
|
||||
proxy_start_container $HOST1 --name=other --net=container:auto
|
||||
|
||||
AUTO=$(container_ip $HOST1 auto)
|
||||
ZERO=$(container_ip $HOST2 zero)
|
||||
BRIDGE=$(container_ip $HOST1 bridge)
|
||||
OTHER=$(container_ip $HOST1 other)
|
||||
assert_raises "proxy exec_on $HOST1 auto $PING $ZERO"
|
||||
assert_raises "proxy exec_on $HOST2 zero $PING $AUTO"
|
||||
assert_raises "proxy exec_on $HOST2 zero $PING $BRIDGE"
|
||||
assert_raises "proxy exec_on $HOST2 zero $PING $OTHER"
|
||||
|
||||
assert_no_ethwe $HOST1 none
|
||||
assert_no_ethwe $HOST2 no-default
|
||||
assert_no_ethwe $HOST1 host
|
||||
|
||||
end_suite
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#! /bin/bash
|
||||
|
||||
. ./config.sh
|
||||
|
||||
C1=10.2.1.13
|
||||
|
||||
start_suite "Start containers with 'weave run' via proxy"
|
||||
|
||||
weave_on $HOST1 launch
|
||||
weave_on $HOST1 launch-proxy
|
||||
|
||||
proxy start_container $HOST1 $C1/24 --name=c1
|
||||
# check we get exactly one IP back, the one specified
|
||||
assert "weave_on $HOST1 ps c1 | cut -d ' ' -f 3-" $C1/24
|
||||
|
||||
end_suite
|
||||
+13
-1
@@ -120,6 +120,18 @@ start_container_with_dns() {
|
||||
weave_on $host run --with-dns "$@" -t $DNS_IMAGE /bin/sh
|
||||
}
|
||||
|
||||
proxy_start_container() {
|
||||
host=$1
|
||||
shift 1
|
||||
proxy docker_on $host run "$@" -dt $SMALL_IMAGE /bin/sh
|
||||
}
|
||||
|
||||
proxy_start_container_with_dns() {
|
||||
host=$1
|
||||
shift 1
|
||||
proxy docker_on $host run "$@" -dt $DNS_IMAGE /bin/sh
|
||||
}
|
||||
|
||||
rm_containers() {
|
||||
host=$1
|
||||
shift
|
||||
@@ -170,8 +182,8 @@ assert_dns_ptr_record() {
|
||||
start_suite() {
|
||||
for host in $HOST1 $HOST2; do
|
||||
[ -z "$DEBUG" ] || echo "Cleaning up on $host: removing all containers and resetting weave"
|
||||
rm_containers $host $(docker_on $host ps -aq 2>/dev/null)
|
||||
weave_on $host reset 2>/dev/null
|
||||
rm_containers $host $(docker_on $host ps -aq 2>/dev/null)
|
||||
done
|
||||
whitely echo "$@"
|
||||
}
|
||||
|
||||
@@ -93,6 +93,22 @@ check_docker_version() {
|
||||
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
|
||||
return
|
||||
fi
|
||||
# From include/uapi/linux/netdevice.h
|
||||
# #define NET_ADDR_PERM 0 /* address is permanent (default) */
|
||||
# #define NET_ADDR_RANDOM 1 /* address is generated randomly */
|
||||
# #define NET_ADDR_STOLEN 2 /* address is stolen from other device */
|
||||
# #define NET_ADDR_SET 3 /* address is set using dev_set_mac_address() */
|
||||
if [ $ADDR_ASSIGN_TYPE != 3 ] ; then
|
||||
echo "Setting $DOCKER_BRIDGE MAC (mitigate https://github.com/docker/docker/issues/14908)" >&2
|
||||
ip link set dev $DOCKER_BRIDGE address $(random_mac) || true
|
||||
fi
|
||||
}
|
||||
|
||||
is_cidr() {
|
||||
echo "$1" | grep -E "^$CIDR_REGEXP$" >/dev/null
|
||||
}
|
||||
@@ -142,7 +158,7 @@ if [ "$1" = "run" ] ; then
|
||||
shift $(dns_arg_count "$@")
|
||||
collect_cidr_args "$@"
|
||||
shift $CIDR_ARG_COUNT
|
||||
CONTAINER=$(docker $DOCKER_CLIENT_ARGS run $DNS_ARGS -d "$@")
|
||||
CONTAINER=$(docker $DOCKER_CLIENT_ARGS run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
|
||||
exec_remote attach $CIDR_ARGS --or-die $CONTAINER
|
||||
echo $CONTAINER
|
||||
exit 0
|
||||
@@ -359,7 +375,7 @@ netnsenter() {
|
||||
}
|
||||
|
||||
connect_container_to_bridge() {
|
||||
if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "$PROCFS/$$/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink $PROCFS/$$/ns/net)" ] ; then
|
||||
if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "/proc/$$/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink /proc/$$/ns/net)" ] ; then
|
||||
echo "Container is running in the host network namespace, and therefore cannot be" >&2
|
||||
echo "connected to weave. Perhaps the container was started with --net=host." >&2
|
||||
return 1
|
||||
@@ -433,7 +449,9 @@ attach() {
|
||||
netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME
|
||||
fi
|
||||
|
||||
arp_update $CONTAINER_IFNAME $1 "netnsenter"
|
||||
for ADDR in "$@" ; do
|
||||
arp_update $CONTAINER_IFNAME $ADDR "netnsenter"
|
||||
done
|
||||
}
|
||||
|
||||
detach() {
|
||||
@@ -664,8 +682,11 @@ dns_args() {
|
||||
WITHOUT_DNS=
|
||||
while [ $# -gt 0 ] ; do
|
||||
case "$1" in
|
||||
--dns-search=*)
|
||||
DNS_SEARCH_SPECIFIED=1
|
||||
--with-dns)
|
||||
WITH_DNS=1
|
||||
;;
|
||||
--without-dns)
|
||||
WITHOUT_DNS=1
|
||||
;;
|
||||
--name)
|
||||
NAME_ARG="$2"
|
||||
@@ -677,11 +698,8 @@ dns_args() {
|
||||
-h|--hostname|--hostname=*)
|
||||
HOSTNAME_SPECIFIED=1
|
||||
;;
|
||||
--with-dns)
|
||||
WITH_DNS=1
|
||||
;;
|
||||
--without-dns)
|
||||
WITHOUT_DNS=1
|
||||
--dns-search|--dns-search=*)
|
||||
DNS_SEARCH_SPECIFIED=1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
@@ -693,8 +711,13 @@ dns_args() {
|
||||
DNS_ARGS="--dns $DOCKER_BRIDGE_IP"
|
||||
if [ -n "$NAME_ARG" -a -z "$HOSTNAME_SPECIFIED" ] ; then
|
||||
DOMAIN=$(dns_domain)
|
||||
DNS_ARGS="$DNS_ARGS --hostname=$NAME_ARG.${DOMAIN%.}"
|
||||
HOSTNAME_SPECIFIED=1
|
||||
HOSTNAME="$NAME_ARG.${DOMAIN%.}"
|
||||
if [ ${#HOSTNAME} -gt 64 ] ; then
|
||||
echo "Container name too long to be used as hostname" >&2
|
||||
else
|
||||
DNS_ARGS="$DNS_ARGS --hostname=$HOSTNAME"
|
||||
HOSTNAME_SPECIFIED=1
|
||||
fi
|
||||
fi
|
||||
if [ -z "$DNS_SEARCH_SPECIFIED" ] ; then
|
||||
if [ -z "$HOSTNAME_SPECIFIED" ] ; then
|
||||
@@ -859,7 +882,7 @@ ipam_cidrs_or_die() {
|
||||
# TODO: Handle args with spaces
|
||||
tls_arg() {
|
||||
filename=${3:-$2}
|
||||
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/$filename.pem"
|
||||
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/$filename.pem:ro"
|
||||
PROXY_ARGS="$PROXY_ARGS --tls$2 /home/weave/tls/$filename.pem"
|
||||
}
|
||||
|
||||
@@ -971,6 +994,7 @@ case "$COMMAND" in
|
||||
;;
|
||||
launch)
|
||||
check_not_running $CONTAINER_NAME $BASE_IMAGE
|
||||
enforce_docker_bridge_addr_assign_type
|
||||
create_bridge
|
||||
# We set the router name to the bridge mac since that is
|
||||
# stable across re-creations of the containers.
|
||||
@@ -1017,10 +1041,15 @@ case "$COMMAND" in
|
||||
if [ -z "$IPRANGE_SPECIFIED" ] ; then
|
||||
IPRANGE="10.128.0.0/10"
|
||||
if command_exists netcheck && ! netcheck $IPRANGE ; then
|
||||
echo "Default -iprange $IPRANGE overlaps with existing route." >&2
|
||||
echo "Please pick another range and set it on all hosts." >&2
|
||||
echo "ERROR: Default -iprange $IPRANGE overlaps with existing route on host." >&2
|
||||
echo "You must pick another range and set it on all hosts." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if command_exists netcheck && ! netcheck $IPRANGE ; then
|
||||
echo "WARNING: Specified -iprange $IPRANGE overlaps with existing route on host." >&2
|
||||
echo "Unless this is deliberate, you must pick another range and set it on all hosts." >&2
|
||||
fi
|
||||
fi
|
||||
# Set WEAVE_DOCKER_ARGS in the environment in order to supply
|
||||
# additional parameters, such as resource limits, to docker
|
||||
@@ -1071,6 +1100,7 @@ case "$COMMAND" in
|
||||
-v /proc:/hostproc \
|
||||
-e PROCFS=/hostproc \
|
||||
-e WEAVE_CIDR=none \
|
||||
-e DOCKER_BRIDGE \
|
||||
--entrypoint=/home/weave/weaveproxy \
|
||||
$WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $PROXY_ARGS)
|
||||
wait_for_log $PROXY_CONTAINER_NAME "proxy listening"
|
||||
@@ -1130,7 +1160,7 @@ case "$COMMAND" in
|
||||
shift $(dns_arg_count "$@")
|
||||
collect_cidr_args "$@"
|
||||
shift $CIDR_ARG_COUNT
|
||||
CONTAINER=$(docker run $DNS_ARGS -d "$@")
|
||||
CONTAINER=$(docker run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
|
||||
create_bridge
|
||||
ipam_cidrs_or_die $CONTAINER $CIDR_ARGS
|
||||
with_container_netns_or_die $CONTAINER attach $ALL_CIDRS
|
||||
|
||||
Reference in new issue
Block a user