diff --git a/db/boltdb.go b/db/boltdb.go
index 0d6b1b86..26b4a651 100644
--- a/db/boltdb.go
+++ b/db/boltdb.go
@@ -9,7 +9,7 @@ import (
)
type DB interface {
- Load(string, interface{}) error
+ Load(string, interface{}) (bool, error)
Save(string, interface{}) error
}
@@ -50,11 +50,13 @@ func NewBoltDB(dbPathname string) (*BoltDB, error) {
return &BoltDB{db: db}, err
}
-func (d *BoltDB) Load(ident string, data interface{}) error {
- return d.db.View(func(tx *bolt.Tx) error {
+func (d *BoltDB) Load(ident string, data interface{}) (bool, error) {
+ found := true
+ err := d.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(topBucket)
v := b.Get([]byte(ident))
if v == nil {
+ found = false
return nil
}
reader := bytes.NewReader(v)
@@ -62,6 +64,7 @@ func (d *BoltDB) Load(ident string, data interface{}) error {
err := decoder.Decode(data)
return err
})
+ return found, err
}
func (d *BoltDB) Save(ident string, data interface{}) error {
diff --git a/ipam/allocator.go b/ipam/allocator.go
index 6940bd5a..bdc77f08 100644
--- a/ipam/allocator.go
+++ b/ipam/allocator.go
@@ -47,6 +47,7 @@ type operation interface {
type Allocator struct {
actionChan chan<- func()
ourName mesh.PeerName
+ seed []mesh.PeerName // optional user supplied ring seed
universe address.Range // superset of all ranges
ring *ring.Ring // information on ranges owned by all peers
space space.Space // more detail on ranges owned by us
@@ -70,6 +71,7 @@ type Config struct {
OurName mesh.PeerName
OurUID mesh.PeerUID
OurNickname string
+ Seed []mesh.PeerName
Universe address.Range
Quorum uint
Db db.DB
@@ -80,6 +82,7 @@ type Config struct {
func NewAllocator(config Config) *Allocator {
return &Allocator{
ourName: config.OurName,
+ seed: config.Seed,
universe: config.Universe,
ring: ring.New(config.Universe.Start, config.Universe.End, config.OurName),
owned: make(map[string][]address.CIDR),
@@ -835,31 +838,49 @@ func (alloc *Allocator) persistRing() {
}
func (alloc *Allocator) loadPersistedData() {
- checkPeerName := alloc.ourName
- if err := alloc.db.Load(nameIdent, &checkPeerName); err != nil {
+ var checkPeerName mesh.PeerName
+ nameFound, err := alloc.db.Load(nameIdent, &checkPeerName)
+ if err != nil {
alloc.fatalf("Error loading persisted peer name: %s", err)
- return
}
- if checkPeerName != alloc.ourName {
+ ringFound, err := alloc.db.Load(ringIdent, &alloc.ring)
+ if err != nil {
+ alloc.fatalf("Error loading persisted IPAM data: %s", err)
+ }
+ ownedFound, err := alloc.db.Load(ownedIdent, &alloc.owned)
+ if err != nil {
+ alloc.fatalf("Error loading persisted address data: %s", err)
+ }
+
+ if nameFound {
+ if checkPeerName == alloc.ourName {
+ if ringFound {
+ if len(alloc.seed) != 0 {
+ alloc.infof("Found persisted IPAM data, ignoring supplied IPAM seed")
+ }
+ alloc.space.UpdateRanges(alloc.ring.OwnedRanges())
+ }
+ if ownedFound {
+ for _, cidrs := range alloc.owned {
+ for _, cidr := range cidrs {
+ alloc.space.Claim(cidr.Addr)
+ }
+ }
+ }
+ return
+ }
alloc.infof("Deleting persisted data for peername %s", checkPeerName)
alloc.persistRing()
alloc.persistOwned()
- return
}
- if err := alloc.db.Load(ringIdent, &alloc.ring); err != nil {
- alloc.fatalf("Error loading persisted ring data: %s", err)
- }
- if alloc.ring != nil {
- alloc.space.UpdateRanges(alloc.ring.OwnedRanges())
- }
- if err := alloc.db.Load(ownedIdent, &alloc.owned); err != nil {
- alloc.fatalf("Error loading persisted address data: %s", err)
- }
- for _, cidrs := range alloc.owned {
- for _, cidr := range cidrs {
- alloc.space.Claim(cidr.Addr)
- }
+
+ if len(alloc.seed) != 0 {
+ alloc.infof("Initialising with supplied IPAM seed")
+ alloc.createRing(alloc.seed)
+ } else {
+ alloc.infof("Initialising via deferred consensus")
}
+
}
func (alloc *Allocator) persistOwned() {
diff --git a/ipam/testutils_test.go b/ipam/testutils_test.go
index 6547a6aa..09e21dc3 100644
--- a/ipam/testutils_test.go
+++ b/ipam/testutils_test.go
@@ -125,8 +125,8 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
type mockDB struct{}
-func (d *mockDB) Load(_ string, _ interface{}) error { return nil }
-func (d *mockDB) Save(_ string, _ interface{}) error { return nil }
+func (d *mockDB) Load(_ string, _ interface{}) (bool, error) { return false, nil }
+func (d *mockDB) Save(_ string, _ interface{}) error { return nil }
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.CIDR) {
peername, err := mesh.PeerNameFromString(name)
diff --git a/prog/weaver/main.go b/prog/weaver/main.go
index b3d77f5c..0311238a 100644
--- a/prog/weaver/main.go
+++ b/prog/weaver/main.go
@@ -91,6 +91,7 @@ func main() {
ipsubnetCIDR string
peerCount int
observer bool
+ ipamSeedStr string
dockerAPI string
peers []string
noDNS bool
@@ -125,6 +126,7 @@ func main() {
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.BoolVar(&observer, []string{"-observer"}, false, "do not participate in paxos, observe only")
+ mflag.StringVar(&ipamSeedStr, []string{"-ipam-seed"}, "", "Comma-separated list of peer names amongst which address space is shared initially")
mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, defaultDockerHost, "Docker API endpoint")
mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server")
mflag.StringVar(&dnsConfig.Domain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for")
@@ -229,11 +231,11 @@ func main() {
allocator *ipam.Allocator
defaultSubnet address.CIDR
)
- if observer && peerCount > 0 {
- Log.Fatal("At most one of --observer and --init-peer-count must be specified.")
+ if observer && peerCount > 0 || peerCount > 0 && len(ipamSeedStr) > 0 || len(ipamSeedStr) > 0 && observer {
+ Log.Fatal("At most one of --observer, --ipam-seed or --init-peer-count may be specified.")
}
if iprangeCIDR != "" {
- allocator, defaultSubnet = createAllocator(router.Router, iprangeCIDR, ipsubnetCIDR, determineQuorum(observer, peerCount, peers), db, isKnownPeer)
+ allocator, defaultSubnet = createAllocator(router.Router, ipamSeedStr, iprangeCIDR, ipsubnetCIDR, determineQuorum(observer, peerCount, peers), db, isKnownPeer)
observeContainers(allocator)
ids, err := dockerCli.AllContainerIDs()
checkFatal(err)
@@ -359,7 +361,8 @@ func parseAndCheckCIDR(cidrStr string) address.CIDR {
return cidr
}
-func createAllocator(router *mesh.Router, ipRangeStr string, defaultSubnetStr string, quorum uint, db db.DB, isKnownPeer func(mesh.PeerName) bool) (*ipam.Allocator, address.CIDR) {
+func createAllocator(router *mesh.Router, ipamSeedStr, ipRangeStr string, defaultSubnetStr string, quorum uint, db db.DB, isKnownPeer func(mesh.PeerName) bool) (*ipam.Allocator, address.CIDR) {
+ seed := parseIPAMSeed(ipamSeedStr)
ipRange := parseAndCheckCIDR(ipRangeStr)
defaultSubnet := ipRange
if defaultSubnetStr != "" {
@@ -373,6 +376,7 @@ func createAllocator(router *mesh.Router, ipRangeStr string, defaultSubnetStr st
OurName: router.Ourself.Peer.Name,
OurUID: router.Ourself.Peer.UID,
OurNickname: router.Ourself.Peer.NickName,
+ Seed: seed,
Universe: ipRange.Range(),
Quorum: quorum,
Db: db,
@@ -469,6 +473,23 @@ func parseTrustedSubnets(trustedSubnetStr string) []*net.IPNet {
return trustedSubnets
}
+func parseIPAMSeed(ipamSeed string) []mesh.PeerName {
+ peerNames := []mesh.PeerName{}
+ if ipamSeed == "" {
+ return peerNames
+ }
+
+ for _, peerNameStr := range strings.Split(ipamSeed, ",") {
+ peerName, err := mesh.PeerNameFromUserInput(peerNameStr)
+ if err != nil {
+ Log.Fatal("Unable to parse IPAM seed: ", err)
+ }
+ peerNames = append(peerNames, peerName)
+ }
+
+ return peerNames
+}
+
func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
protocol := "tcp"
if strings.HasPrefix(httpAddr, "/") {
diff --git a/router/network_router.go b/router/network_router.go
index 029d9029..f167dd4a 100644
--- a/router/network_router.go
+++ b/router/network_router.go
@@ -230,7 +230,7 @@ func (router *NetworkRouter) ForgetConnections(peers []string) {
func (router *NetworkRouter) InitialPeers(peers []string) ([]string, error) {
var storedPeers []string
- if err := router.db.Load(peersIdent, &storedPeers); err != nil {
+ if _, err := router.db.Load(peersIdent, &storedPeers); err != nil {
return nil, err
}
diff --git a/site/ipam/overview-init-ipam.md b/site/ipam/overview-init-ipam.md
index 4a20c708..35a3652c 100644
--- a/site/ipam/overview-init-ipam.md
+++ b/site/ipam/overview-init-ipam.md
@@ -22,6 +22,36 @@ The following automatic IP address managment topics are discussed:
### Initializing Peers on a Weave Network
+There are two options for initialising IPAM - with a statically
+configured seed or automatically via one-off consensus. The two
+options have different tradeoffs, so pick the one that suits your
+deployment best.
+
+#### Via Seed
+
+Configuration via seed requires you to provide a list of peer names
+(via the `--ipam-seed` parameter) amongst which the address space will
+be shared initially - this means that you will also have to specify
+peer names explicitly on launch:
+
+ host1$ weave launch --name ::1 --ipam-seed ::1,::2,::3
+ host2$ weave launch --name ::2 --ipam-seed ::1,::2,::3
+ host3$ weave launch --name ::3 --ipam-seed ::1,::2,::3
+
+In this configuration each peer knows in advance how the address space
+has been divided up, and will be able to perform allocations from the
+outset even under conditions of partition - no consensus is required.
+
+#### Via One-off Consensus
+
+Alternatively, you can let weave determine the seed automatically via
+a consensus algorithm; since you don't need to provide it with a list
+of peer names anymore you can also let weave generate those randomly
+for you too. However, in order for weave to form a single consensus
+reliably you must now instead tell each peer how many peers there are
+in total either by listing them as target peers or via the
+`--init-peer-count` parameter.
+
Just once, when the first automatic IP address allocation is requested
in the whole network, Weave Net needs a majority of peers to be present in
order to avoid formation of isolated groups, which can lead to
diff --git a/test/380_ipam_seed_2_test.sh b/test/380_ipam_seed_2_test.sh
new file mode 100755
index 00000000..f380a0c0
--- /dev/null
+++ b/test/380_ipam_seed_2_test.sh
@@ -0,0 +1,22 @@
+#! /bin/bash
+
+. ./config.sh
+
+start_suite "Specify initial IPAM seed"
+
+# Launch two disconnected routers
+weave_on $HOST1 launch --name ::1 --ipam-seed ::1,::2
+weave_on $HOST2 launch --name ::2 --ipam-seed ::1,::2
+
+# Ensure allocations can proceed
+assert_raises "timeout 10 cat <( start_container $HOST1 --name c1)"
+assert_raises "timeout 10 cat <( start_container $HOST2 --name c2)"
+
+# Connect routers
+weave_on $HOST2 connect $HOST1
+
+# Check connectivity
+assert_raises "exec_on $HOST1 c1 $PING c2"
+assert_raises "exec_on $HOST2 c2 $PING c1"
+
+end_suite
diff --git a/vendor/github.com/weaveworks/mesh b/vendor/github.com/weaveworks/mesh
index 240359e7..fca6b1fe 160000
--- a/vendor/github.com/weaveworks/mesh
+++ b/vendor/github.com/weaveworks/mesh
@@ -1 +1 @@
-Subproject commit 240359e72610b8bafc7c44a9a01ade0222be4358
+Subproject commit fca6b1feb02905ca1a9673230d2b11b1c175a256
diff --git a/weave b/weave
index 07f2375c..33283aaa 100755
--- a/weave
+++ b/weave
@@ -40,6 +40,7 @@ weave --help | help
weave launch
launch-router [--password ] [--name ] [--nickname ]
+ [--ipam-seed ,...]
[--ipalloc-range [--ipalloc-default-subnet ]]
[--init-peer-count ] [--no-discovery]
[--trusted-subnets ,...] ...