diff --git a/ipam/allocate.go b/ipam/allocate.go index 8146e0c9..fbc3a685 100644 --- a/ipam/allocate.go +++ b/ipam/allocate.go @@ -15,6 +15,7 @@ type allocate struct { resultChan chan<- allocateResult ident string r address.CIDR // Subnet we are trying to allocate within + isContainer bool hasBeenCancelled func() bool } @@ -46,7 +47,7 @@ func (g *allocate) Try(alloc *Allocator) bool { g.ident = addr.String() } alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r) - alloc.addOwned(g.ident, address.MakeCIDR(g.r, addr)) + alloc.addOwned(g.ident, address.MakeCIDR(g.r, addr), g.isContainer) g.resultChan <- allocateResult{addr, nil} return true } diff --git a/ipam/allocator.go b/ipam/allocator.go index b7aaae1c..741390fa 100644 --- a/ipam/allocator.go +++ b/ipam/allocator.go @@ -41,6 +41,12 @@ type operation interface { ForContainer(ident string) bool } +// This type is persisted hence all fields exported +type ownedData struct { + IsContainer bool + Cidrs []address.CIDR +} + // Allocator brings together Ring and space.Set, and does the // necessary plumbing. Runs as a single-threaded Actor, so no locks // are used around data structures. @@ -48,18 +54,18 @@ type Allocator struct { actionChan chan<- func() stopChan chan<- struct{} 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 - owned map[string][]address.CIDR // who owns what addresses, indexed by container-ID - nicknames map[mesh.PeerName]string // so we can map nicknames for rmpeer - pendingAllocates []operation // held until we get some free space - pendingClaims []operation // held until we know who owns the space - pendingPrimes []operation // held while our ring is empty - dead map[string]time.Time // containers we heard were dead, and when - db db.DB // persistence - gossip mesh.Gossip // our link to the outside world for sending messages + 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 + owned map[string]ownedData // who owns what addresses, indexed by container-ID + nicknames map[mesh.PeerName]string // so we can map nicknames for rmpeer + pendingAllocates []operation // held until we get some free space + pendingClaims []operation // held until we know who owns the space + pendingPrimes []operation // held while our ring is empty + dead map[string]time.Time // containers we heard were dead, and when + db db.DB // persistence + gossip mesh.Gossip // our link to the outside world for sending messages paxos paxos.Participant awaitingConsensus bool ticker *time.Ticker @@ -94,7 +100,7 @@ func NewAllocator(config Config) *Allocator { seed: config.Seed, universe: config.Universe, ring: ring.New(config.Universe.Start, config.Universe.End, config.OurName), - owned: make(map[string][]address.CIDR), + owned: make(map[string]ownedData), db: config.Db, paxos: participant, nicknames: map[mesh.PeerName]string{config.OurName: config.OurNickname}, @@ -233,9 +239,15 @@ func (alloc *Allocator) Prime() { // Allocate (Sync) - get new IP address for container with given name in range // if there isn't any space in that range we block indefinitely -func (alloc *Allocator) Allocate(ident string, r address.CIDR, hasBeenCancelled func() bool) (address.Address, error) { +func (alloc *Allocator) Allocate(ident string, r address.CIDR, isContainer bool, hasBeenCancelled func() bool) (address.Address, error) { resultChan := make(chan allocateResult) - op := &allocate{resultChan: resultChan, ident: ident, r: r, hasBeenCancelled: hasBeenCancelled} + op := &allocate{ + resultChan: resultChan, + ident: ident, + r: r, + isContainer: isContainer, + hasBeenCancelled: hasBeenCancelled, + } alloc.doOperation(op, &alloc.pendingAllocates) result := <-resultChan return result.addr, result.err @@ -251,9 +263,15 @@ func (alloc *Allocator) Lookup(ident string, r address.Range) ([]address.CIDR, e } // Claim an address that we think we should own (Sync) -func (alloc *Allocator) Claim(ident string, cidr address.CIDR, noErrorOnUnknown bool) error { +func (alloc *Allocator) Claim(ident string, cidr address.CIDR, isContainer, noErrorOnUnknown bool) error { resultChan := make(chan error) - op := &claim{resultChan: resultChan, ident: ident, cidr: cidr, noErrorOnUnknown: noErrorOnUnknown} + op := &claim{ + resultChan: resultChan, + ident: ident, + cidr: cidr, + isContainer: isContainer, + noErrorOnUnknown: noErrorOnUnknown, + } alloc.doOperation(op, &alloc.pendingClaims) return <-resultChan } @@ -876,8 +894,8 @@ func (alloc *Allocator) loadPersistedData() { alloc.space.UpdateRanges(alloc.ring.OwnedRanges()) } if ownedFound { - for _, cidrs := range alloc.owned { - for _, cidr := range cidrs { + for _, d := range alloc.owned { + for _, cidr := range d.Cidrs { alloc.space.Claim(cidr.Addr) } } @@ -914,8 +932,11 @@ func (alloc *Allocator) hasOwned(ident string) bool { } // NB: addr must not be owned by ident already -func (alloc *Allocator) addOwned(ident string, cidr address.CIDR) { - alloc.owned[ident] = append(alloc.owned[ident], cidr) +func (alloc *Allocator) addOwned(ident string, cidr address.CIDR, isContainer bool) { + d := alloc.owned[ident] + d.IsContainer = isContainer + d.Cidrs = append(d.Cidrs, cidr) + alloc.owned[ident] = d alloc.persistOwned() } @@ -923,17 +944,18 @@ func (alloc *Allocator) removeAllOwned(ident string) []address.CIDR { a := alloc.owned[ident] delete(alloc.owned, ident) alloc.persistOwned() - return a + return a.Cidrs } func (alloc *Allocator) removeOwned(ident string, addrToFree address.Address) bool { - cidrs, _ := alloc.owned[ident] - for i, ownedCidr := range cidrs { + d := alloc.owned[ident] + for i, ownedCidr := range d.Cidrs { if ownedCidr.Addr == addrToFree { - if len(cidrs) == 1 { + if len(d.Cidrs) == 1 { delete(alloc.owned, ident) } else { - alloc.owned[ident] = append(cidrs[:i], cidrs[i+1:]...) + d.Cidrs = append(d.Cidrs[:i], d.Cidrs[i+1:]...) + alloc.owned[ident] = d } alloc.persistOwned() return true @@ -944,7 +966,7 @@ func (alloc *Allocator) removeOwned(ident string, addrToFree address.Address) bo func (alloc *Allocator) ownedInRange(ident string, r address.Range) []address.CIDR { var c []address.CIDR - for _, cidr := range alloc.owned[ident] { + for _, cidr := range alloc.owned[ident].Cidrs { if r.Contains(cidr.Addr) { c = append(c, cidr) } @@ -953,8 +975,8 @@ func (alloc *Allocator) ownedInRange(ident string, r address.Range) []address.CI } func (alloc *Allocator) findOwner(addr address.Address) string { - for ident, cidrs := range alloc.owned { - for _, candidate := range cidrs { + for ident, d := range alloc.owned { + for _, candidate := range d.Cidrs { if candidate.Addr == addr { return ident } @@ -966,11 +988,15 @@ func (alloc *Allocator) findOwner(addr address.Address) string { // For each ID in the 'owned' map, remove the entry if it isn't in the map func (alloc *Allocator) syncOwned(ids map[string]struct{}) { changed := false - for ident, cidrs := range alloc.owned { + for ident, d := range alloc.owned { + if !d.IsContainer { + continue + } if _, found := ids[ident]; !found { - for _, cidr := range cidrs { + for _, cidr := range d.Cidrs { alloc.space.Free(cidr.Addr) } + alloc.debugf("Deleting old entry %s: %v", ident, d.Cidrs) delete(alloc.owned, ident) changed = true } diff --git a/ipam/allocator_test.go b/ipam/allocator_test.go index a3b1f050..c3d34d99 100644 --- a/ipam/allocator_test.go +++ b/ipam/allocator_test.go @@ -41,11 +41,11 @@ func TestAllocFree(t *testing.T) { cidr2, _ := address.ParseCIDR(subnet2) alloc.claimRingForTesting() - addr1, err := alloc.Allocate(container1, cidr1, returnFalse) + addr1, err := alloc.Allocate(container1, cidr1, true, returnFalse) require.NoError(t, err) require.Equal(t, testAddr1, addr1.String(), "address") - addr2, err := alloc.Allocate(container1, cidr2, returnFalse) + addr2, err := alloc.Allocate(container1, cidr2, true, returnFalse) require.NoError(t, err) require.Equal(t, testAddr2, addr2.String(), "address") @@ -54,28 +54,28 @@ func TestAllocFree(t *testing.T) { require.Equal(t, []address.CIDR{address.MakeCIDR(cidr1, addr1), address.MakeCIDR(cidr2, addr2)}, addrs) // Ask for another address for a different container and check it's different - addr1b, _ := alloc.Allocate(container2, cidr1, returnFalse) + addr1b, _ := alloc.Allocate(container2, cidr1, true, returnFalse) if addr1b.String() == testAddr1 { t.Fatalf("Expected different address but got %s", addr1b.String()) } // Ask for the first container again and we should get the same addresses again - addr1a, _ := alloc.Allocate(container1, cidr1, returnFalse) + addr1a, _ := alloc.Allocate(container1, cidr1, true, returnFalse) require.Equal(t, testAddr1, addr1a.String(), "address") - addr2a, _ := alloc.Allocate(container1, cidr2, returnFalse) + addr2a, _ := alloc.Allocate(container1, cidr2, true, returnFalse) require.Equal(t, testAddr2, addr2a.String(), "address") // Now delete the first container, and we should get its addresses back require.NoError(t, alloc.Delete(container1)) - addr3, _ := alloc.Allocate(container3, cidr1, returnFalse) + addr3, _ := alloc.Allocate(container3, cidr1, true, returnFalse) require.Equal(t, testAddr1, addr3.String(), "address") - addr4, _ := alloc.Allocate(container3, cidr2, returnFalse) + addr4, _ := alloc.Allocate(container3, cidr2, true, returnFalse) require.Equal(t, testAddr2, addr4.String(), "address") alloc.ContainerDied(container2) // Resurrect - addr1c, err := alloc.Allocate(container2, cidr1, returnFalse) + addr1c, err := alloc.Allocate(container2, cidr1, true, returnFalse) require.NoError(t, err) require.Equal(t, addr1b, addr1c, "address") @@ -109,7 +109,7 @@ func TestBootstrap(t *testing.T) { ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus done := make(chan bool) go func() { - alloc1.Allocate("somecontainer", subnet, returnFalse) + alloc1.Allocate("somecontainer", subnet, true, returnFalse) done <- true }() time.Sleep(100 * time.Millisecond) @@ -170,32 +170,31 @@ func TestAllocatorClaim(t *testing.T) { addr1, _ := address.ParseCIDR(testAddr1) // First claim should trigger "dunno, I'm going to wait" - err := alloc.Claim(container3, addr1, true) + err := alloc.Claim(container3, addr1, true, true) require.NoError(t, err) - // Do one allocate to ensure paxos is all done - alloc.Allocate("unused", subnet, returnFalse) + alloc.Prime() // Do an allocate on the other peer, which we will try to claim later - addrx, err := allocs[0].Allocate(container1, subnet, returnFalse) + addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse) // Now try the claim again - err = alloc.Claim(container3, addr1, true) + err = alloc.Claim(container3, addr1, true, true) require.NoError(t, err) // Check we get this address back if we try an allocate - addr3, _ := alloc.Allocate(container3, subnet, returnFalse) + addr3, _ := alloc.Allocate(container3, subnet, true, returnFalse) require.Equal(t, testAddr1, address.MakeCIDR(subnet, addr3).String(), "address") // one more claim should still work - err = alloc.Claim(container3, addr1, true) + err = alloc.Claim(container3, addr1, true, true) require.NoError(t, err) // claim for a different container should fail - err = alloc.Claim(container1, addr1, true) + err = alloc.Claim(container1, addr1, true, true) require.Error(t, err) // claiming the address allocated on the other peer should fail - err = alloc.Claim(container1, address.MakeCIDR(subnet, addrx), true) + err = alloc.Claim(container1, address.MakeCIDR(subnet, addrx), true, true) require.Error(t, err, "claiming address allocated on other peer should fail") // Check an address outside of our universe addr2, _ := address.ParseCIDR(testAddr2) - err = alloc.Claim(container1, addr2, true) + err = alloc.Claim(container1, addr2, true, true) require.NoError(t, err) } @@ -230,9 +229,9 @@ func TestCancel(t *testing.T) { alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode()) // Get some IPs, so each allocator has some space - res1, _ := alloc1.Allocate("foo", subnet, returnFalse) + res1, _ := alloc1.Allocate("foo", subnet, true, returnFalse) common.Log.Debugf("res1 = %s", res1.String()) - res2, _ := alloc2.Allocate("bar", subnet, returnFalse) + res2, _ := alloc2.Allocate("bar", subnet, true, returnFalse) common.Log.Debugf("res2 = %s", res2.String()) if res1 == res2 { require.FailNow(t, "Error: got same ips!") @@ -244,12 +243,12 @@ func TestCancel(t *testing.T) { // Use up all the IPs that alloc1 owns, so the allocation after this will prompt a request to alloc2 for i := 0; alloc1.NumFreeAddresses(subnet.HostRange()) > 0; i++ { - alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, returnFalse) + alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, true, returnFalse) } cancelChan := make(chan bool, 1) doneChan := make(chan bool) go func() { - _, ok := alloc1.Allocate("baz", subnet, + _, ok := alloc1.Allocate("baz", subnet, true, func() bool { select { case <-cancelChan: @@ -284,7 +283,7 @@ func TestCancelOnDied(t *testing.T) { doneChan := make(chan bool) f := func() { - _, ok := alloc1.Allocate(container1, subnet, returnFalse) + _, ok := alloc1.Allocate(container1, subnet, true, returnFalse) doneChan <- ok == nil } @@ -315,11 +314,11 @@ func TestGossipShutdown(t *testing.T) { defer alloc.Stop() alloc.claimRingForTesting() - alloc.Allocate(container1, subnet, returnFalse) + alloc.Allocate(container1, subnet, true, returnFalse) alloc.Shutdown() - _, err := alloc.Allocate(container2, subnet, returnFalse) // trying to allocate after shutdown should fail + _, err := alloc.Allocate(container2, subnet, true, returnFalse) // trying to allocate after shutdown should fail require.False(t, err == nil, "no address") CheckAllExpectedMessagesSent(alloc) @@ -333,10 +332,10 @@ func TestTransfer(t *testing.T) { alloc1 := allocs[1] alloc2 := allocs[2] - _, err := alloc1.Allocate("foo", subnet, returnFalse) + _, err := alloc1.Allocate("foo", subnet, true, returnFalse) require.True(t, err == nil, "Failed to get address") - _, err = alloc2.Allocate("bar", subnet, returnFalse) + _, err = alloc2.Allocate("bar", subnet, true, returnFalse) require.True(t, err == nil, "Failed to get address") // simulation of periodic gossip @@ -360,7 +359,7 @@ func TestTransfer(t *testing.T) { require.Equal(t, address.Count(1024), alloc0.NumFreeAddresses(subnet.Range())) - _, err = alloc0.Allocate("foo", subnet, returnFalse) + _, err = alloc0.Allocate("foo", subnet, true, returnFalse) require.True(t, err == nil, "Failed to get address") alloc0.Stop() } @@ -373,7 +372,7 @@ func TestFakeRouterSimple(t *testing.T) { alloc1 := allocs[0] //alloc2 := allocs[1] - _, err := alloc1.Allocate("foo", subnet, returnFalse) + _, err := alloc1.Allocate("foo", subnet, true, returnFalse) require.NoError(t, err, "Failed to get address") } @@ -454,7 +453,7 @@ func TestAllocatorFuzz(t *testing.T) { allocIndex := rand.Int31n(nodes) alloc := allocs[allocIndex] //common.Log.Infof("Allocate: asking allocator %d", allocIndex) - addr, err := alloc.Allocate(name, subnet, returnFalse) + addr, err := alloc.Allocate(name, subnet, true, returnFalse) if err != nil { panic(fmt.Sprintf("Could not allocate addr")) @@ -511,7 +510,7 @@ func TestAllocatorFuzz(t *testing.T) { //common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc) - newAddr, _ := alloc.Allocate(res.name, subnet, returnFalse) + newAddr, _ := alloc.Allocate(res.name, subnet, true, returnFalse) oldAddr, _ := address.ParseIP(addr) if newAddr != oldAddr { panic(fmt.Sprintf("Got different address for repeat request for %s: %s != %s", res.name, newAddr, oldAddr)) @@ -532,7 +531,7 @@ func TestAllocatorFuzz(t *testing.T) { addressIndex := rand.Int31n(int32(subnet.Size())) alloc := allocs[allocIndex] addr := address.Add(subnet.Addr, address.Offset(addressIndex)) - err := alloc.Claim(name, address.MakeCIDR(subnet, addr), true) + err := alloc.Claim(name, address.MakeCIDR(subnet, addr), true, true) if err == nil { noteAllocation(allocIndex, name, addr) } @@ -598,7 +597,7 @@ func TestSpaceRequest(t *testing.T) { defer stopNetworkOfAllocators(allocs, router) alloc1 := allocs[0] - addr, err := alloc1.Allocate(container1, subnet, returnFalse) + addr, err := alloc1.Allocate(container1, subnet, true, returnFalse) require.Nil(t, err, "") // free it again so the donation splits the range neatly err = alloc1.Free(container1, addr) @@ -610,7 +609,7 @@ func TestSpaceRequest(t *testing.T) { alloc2.SetInterfaces(router.Connect(alloc2.ourName, alloc2)) alloc2.Start() defer alloc2.Stop() - alloc2.Allocate(container2, subnet, returnFalse) + alloc2.Allocate(container2, subnet, true, returnFalse) // Test whether the universe has been split into two equal halves (GH #2009) require.Equal(t, cidrRanges("10.32.0.0/13"), alloc1.OwnedRanges(), "") diff --git a/ipam/claim.go b/ipam/claim.go index 8dc208f8..467a182e 100644 --- a/ipam/claim.go +++ b/ipam/claim.go @@ -13,6 +13,7 @@ type claim struct { resultChan chan<- error ident string cidr address.CIDR + isContainer bool noErrorOnUnknown bool } @@ -35,7 +36,7 @@ func (c *claim) Try(alloc *Allocator) bool { if !alloc.ring.Contains(c.cidr.Addr) { // Address not within our universe; assume user knows what they are doing alloc.infof("Address %s claimed by %s - not in our range", c.cidr, c.ident) - alloc.addOwned(c.ident, c.cidr) + alloc.addOwned(c.ident, c.cidr, c.isContainer) c.sendResult(nil) return true } @@ -79,7 +80,7 @@ func (c *claim) Try(alloc *Allocator) bool { if c.ident == "_" { // Special "I don't have a unique ID" identifier c.ident = c.cidr.String() } - alloc.addOwned(c.ident, c.cidr) + alloc.addOwned(c.ident, c.cidr, c.isContainer) c.sendResult(nil) } else { c.sendResult(err) diff --git a/ipam/http.go b/ipam/http.go index 6fd6a528..a4baf30e 100644 --- a/ipam/http.go +++ b/ipam/http.go @@ -40,7 +40,7 @@ func writeAddresses(w http.ResponseWriter, cidrs []address.CIDR) { func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) { closedChan := w.(http.CloseNotifier).CloseNotify() - addr, err := alloc.Allocate(ident, subnet, + addr, err := alloc.Allocate(ident, subnet, checkAlive, func() bool { select { case <-closedChan: @@ -74,7 +74,8 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID vars := mux.Vars(r) if cidr, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], false); ok { noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true" - if err := alloc.Claim(vars["id"], cidr, noErrorOnUnknown); err != nil { + checkAlive := r.FormValue("check-alive") == "true" + if err := alloc.Claim(vars["id"], cidr, checkAlive, noErrorOnUnknown); err != nil { badRequest(w, fmt.Errorf("Unable to claim: %s", err)) return } diff --git a/test/370_persist_ipam_2_test.sh b/test/370_persist_ipam_2_test.sh index e8d56bb2..91d4203c 100755 --- a/test/370_persist_ipam_2_test.sh +++ b/test/370_persist_ipam_2_test.sh @@ -13,19 +13,26 @@ launch_router_with_db() { launch_router_with_db $HOST1 $HOST2 launch_router_with_db $HOST2 $HOST1 +EXPOSE=$(weave_on $HOST1 expose) start_container $HOST1 --name=c1 C1=$(container_ip $HOST1 c1) start_container $HOST2 --name=c2 assert_raises "exec_on $HOST2 c2 $PING $C1" +start_container $HOST2 --name=c3a +C3a=$(container_ip $HOST2 c3a) stop_weave_on $HOST1 stop_weave_on $HOST2 +# Stop one container while weave is down; the address should be freed on launch +docker_on $HOST2 rm -f c3a + # Start just HOST2; if nothing persisted it would form its own ring launch_router_with_db $HOST2 start_container $HOST2 --name=c3 C3=$(container_ip $HOST2 c3) assert_raises "[ $C3 != $C1 ]" +assert_raises "[ $C3 = $C3a ]" # Restart HOST1 and see if it remembers to connect to HOST2 # @@ -34,4 +41,11 @@ assert_raises "[ $C3 != $C1 ]" # launch_router_with_db $HOST1 # assert_raises "exec_on $HOST2 c2 $PING $C1" +# Restart the router without going through the reclaim procedure on launch +docker_on $HOST1 restart weave +# Check that weave:expose address hasn't been lost +start_container $HOST1 --name=c4 +C4=$(container_ip $HOST1 c4) +assert_raises "[ $C4 != $EXPOSE ]" + end_suite diff --git a/weave b/weave index f5147995..fc05fc8f 100755 --- a/weave +++ b/weave @@ -1379,6 +1379,12 @@ check_overlap() { # Claim addresses for a container in IPAM. Expects to be called from # with_container_addresses. ipam_reclaim() { + for CIDR in $3 ; do + http_call $HTTP_ADDR PUT "/ip/$1/$CIDR?noErrorOnUnknown=true&?check-alive=true" || true + done +} + +ipam_reclaim_no_check_alive() { for CIDR in $3 ; do http_call $HTTP_ADDR PUT /ip/$1/$CIDR?noErrorOnUnknown=true || true done @@ -1433,7 +1439,7 @@ ipam_cidrs() { # Assignment of a plain IP address; warn if it clashes but carry on check_overlap $arg || true # Abort on failure, but not 4 (=404), which means IPAM is disabled - when_weave_running http_call $HTTP_ADDR PUT /ip/$CONTAINER_ID/$arg || [ $? -eq 4 ] || return 1 + when_weave_running http_call $HTTP_ADDR PUT /ip/$CONTAINER_ID/$arg$CHECK_ALIVE || [ $? -eq 4 ] || return 1 fi ALL_CIDRS="$ALL_CIDRS $arg" fi @@ -1740,7 +1746,8 @@ fetch_router_args() { populate_router() { if [ -n "$IPRANGE" ] ; then # Tell the newly-started weave IP allocator about existing weave IPs - with_container_addresses ipam_reclaim weave:expose $(docker ps -q --no-trunc) + with_container_addresses ipam_reclaim_no_check_alive weave:expose + with_container_addresses ipam_reclaim $(docker ps -q --no-trunc) fi if [ -z "$NO_DNS_OPT" ] ; then # Tell the newly-started weaveDNS about existing weave IPs @@ -2170,7 +2177,7 @@ EOF RES=$(docker restart $1) CONTAINER=$(container_id $1) for CIDR in $ALL_CIDRS ; do - call_weave PUT /ip/$CONTAINER/$CIDR + call_weave PUT /ip/$CONTAINER/$CIDR?check-alive=true done with_container_netns_or_die $1 attach $ALL_CIDRS when_weave_running with_container_fqdn $1 put_dns_fqdn $ALL_CIDRS