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 ,...] ...