func TestAllocFree(t *testing.T) { const ( container1 = "abcdef" container2 = "baddf00d" container3 = "b01df00d" universe = "10.0.3.0/26" subnet1 = "10.0.3.0/28" subnet2 = "10.0.3.32/28" testAddr1 = "10.0.3.1" testAddr2 = "10.0.3.33" spaceSize = 62 // 64 IP addresses in /26, minus .0 and .63 ) alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1) defer alloc.Stop() _, cidr1, _ := address.ParseCIDR(subnet1) _, cidr2, _ := address.ParseCIDR(subnet2) alloc.claimRingForTesting() addr1, err := alloc.Allocate(container1, cidr1.HostRange(), returnFalse) require.NoError(t, err) require.Equal(t, testAddr1, addr1.String(), "address") addr2, err := alloc.Allocate(container1, cidr2.HostRange(), returnFalse) require.NoError(t, err) require.Equal(t, testAddr2, addr2.String(), "address") // Ask for another address for a different container and check it's different addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), 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.HostRange(), returnFalse) require.Equal(t, testAddr1, addr1a.String(), "address") addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), 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.HostRange(), returnFalse) require.Equal(t, testAddr1, addr3.String(), "address") addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), returnFalse) require.Equal(t, testAddr2, addr4.String(), "address") alloc.ContainerDied(container2) // Resurrect addr1c, err := alloc.Allocate(container2, cidr1.HostRange(), returnFalse) require.NoError(t, err) require.Equal(t, addr1b, addr1c, "address") alloc.ContainerDied(container3) alloc.Encode() // sync up // Move the clock forward and clear out the dead container alloc.actionChan <- func() { alloc.now = func() time.Time { return time.Now().Add(containerDiedTimeout * 2) } } alloc.actionChan <- func() { alloc.removeDeadContainers() } require.Equal(t, address.Offset(spaceSize-1), alloc.NumFreeAddresses(subnet)) }
func TestAllocFree(t *testing.T) { const ( container1 = "abcdef" container2 = "baddf00d" container3 = "b01df00d" universe = "10.0.3.0/26" subnet1 = "10.0.3.0/28" subnet2 = "10.0.3.32/28" testAddr1 = "10.0.3.1" testAddr2 = "10.0.3.33" spaceSize = 62 // 64 IP addresses in /26, minus .0 and .63 ) alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1) defer alloc.Stop() _, cidr1, _ := address.ParseCIDR(subnet1) _, cidr2, _ := address.ParseCIDR(subnet2) alloc.claimRingForTesting() addr1, err := alloc.Allocate(container1, cidr1.HostRange(), nil) require.NoError(t, err) require.Equal(t, testAddr1, addr1.String(), "address") addr2, err := alloc.Allocate(container1, cidr2.HostRange(), nil) require.NoError(t, err) require.Equal(t, testAddr2, addr2.String(), "address") // Ask for another address for a different container and check it's different addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), nil) 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.HostRange(), nil) require.Equal(t, testAddr1, addr1a.String(), "address") addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), nil) 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.HostRange(), nil) require.Equal(t, testAddr1, addr3.String(), "address") addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), nil) require.Equal(t, testAddr2, addr4.String(), "address") alloc.ContainerDied(container2) alloc.ContainerDied(container3) require.Equal(t, address.Offset(spaceSize), alloc.NumFreeAddresses(subnet)) }
func impTestHTTPCancel(t *testing.T) { var ( containerID = "deadbeef" testCIDR1 = "10.0.3.0/29" ) alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1) defer alloc.Stop() alloc.claimRingForTesting() _, cidr, _ := address.ParseCIDR(testCIDR1) port := listenHTTP(alloc, cidr) // Stop the alloc so nothing actually works unpause := alloc.pause() // Ask the http server for a new address done := make(chan *http.Response) req, _ := http.NewRequest("POST", allocURL(port, testCIDR1, containerID), nil) go func() { res, _ := http.DefaultClient.Do(req) done <- res }() time.Sleep(100 * time.Millisecond) fmt.Println("Cancelling allocate") http.DefaultTransport.(*http.Transport).CancelRequest(req) unpause() res := <-done if res != nil { require.FailNow(t, "Error: Allocate returned non-nil") } }
func TestBadHttp(t *testing.T) { var ( containerID = "deadbeef" testCIDR1 = "10.0.0.0/8" ) alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1) defer alloc.Stop() _, cidr, _ := address.ParseCIDR(testCIDR1) port := listenHTTP(alloc, cidr) alloc.claimRingForTesting() cidr1 := HTTPPost(t, allocURL(port, testCIDR1, containerID)) parts := strings.Split(cidr1, "/") testAddr1 := parts[0] // Verb that's not handled resp, err := doHTTP("HEAD", fmt.Sprintf("http://localhost:%d/ip/%s/%s", port, containerID, testAddr1)) require.NoError(t, err) require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response") // Mis-spelled URL resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/xip/%s/", port, containerID)) require.NoError(t, err) require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response") // Malformed URL resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/ip/%s/foo/bar/baz", port, containerID)) require.NoError(t, err) require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response") }
func TestHTTPCancel(t *testing.T) { var ( containerID = "deadbeef" testCIDR1 = "10.0.3.0/29" ) // Say quorum=2, so the allocate won't go ahead alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 2) defer alloc.Stop() ExpectBroadcastMessage(alloc, nil) // trying to form consensus cidr, _ := address.ParseCIDR(testCIDR1) port := listenHTTP(alloc, cidr) // Ask the http server for a new address req, _ := http.NewRequest("POST", allocURL(port, testCIDR1, containerID), nil) // On another goroutine, wait for a bit then cancel the request go func() { time.Sleep(100 * time.Millisecond) common.Log.Debug("Cancelling allocate") http.DefaultTransport.(*http.Transport).CancelRequest(req) }() res, _ := http.DefaultClient.Do(req) if res != nil { body, _ := ioutil.ReadAll(res.Body) require.FailNow(t, "Error: Allocate returned non-nil", string(body)) } }
func parseAndCheckCIDR(cidrStr string) address.CIDR { _, cidr, err := address.ParseCIDR(cidrStr) checkFatal(err) if cidr.Size() < ipam.MinSubnetSize { Log.Fatalf("Allocation range smaller than minimum size %d: %s", ipam.MinSubnetSize, cidrStr) } return cidr }
func TestAllocatorClaim(t *testing.T) { const ( container1 = "abcdef" container3 = "b01df00d" universe = "10.0.3.0/24" testAddr1 = "10.0.3.2/24" testAddr2 = "10.0.4.2/24" ) allocs, router, subnet := makeNetworkOfAllocators(2, universe) defer stopNetworkOfAllocators(allocs, router) alloc := allocs[1] addr1, _ := address.ParseCIDR(testAddr1) // First claim should trigger "dunno, I'm going to wait" err := alloc.SimplyClaim(container3, addr1) require.NoError(t, err) alloc.Prime() // Do an allocate on the other peer, which we will try to claim later addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse) router.Flush() // Now try the claim again err = alloc.SimplyClaim(container3, addr1) require.NoError(t, err) // Check we get this address back if we try an allocate addr3, _ := alloc.SimplyAllocate(container3, subnet) require.Equal(t, testAddr1, address.MakeCIDR(subnet, addr3).String(), "address") // one more claim should still work err = alloc.SimplyClaim(container3, addr1) require.NoError(t, err) // claim for a different container should fail err = alloc.SimplyClaim(container1, addr1) require.Error(t, err) // claiming the address allocated on the other peer should fail err = alloc.SimplyClaim(container1, address.MakeCIDR(subnet, addrx)) 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.SimplyClaim(container1, addr2) require.NoError(t, err) }
func parseCIDR(w http.ResponseWriter, cidrStr string) (address.CIDR, bool) { subnetAddr, cidr, err := address.ParseCIDR(cidrStr) if err != nil { badRequest(w, err) return address.CIDR{}, false } if cidr.Start != subnetAddr { badRequest(w, fmt.Errorf("Invalid subnet %s - bits after network prefix are not all zero", cidrStr)) return address.CIDR{}, false } return cidr, true }
func ParseCIDRSubnet(cidrStr string) (cidr address.CIDR, err error) { cidr, err = address.ParseCIDR(cidrStr) if err != nil { return } if !cidr.IsSubnet() { err = fmt.Errorf("invalid subnet - bits after network prefix are not all zero: %s", cidrStr) } if cidr.Size() < MinSubnetSize { err = fmt.Errorf("invalid subnet - smaller than minimum size %d: %s", MinSubnetSize, cidrStr) } return }
func parseCIDR(w http.ResponseWriter, cidrStr string, net bool) (address.CIDR, bool) { var cidr address.CIDR var err error if net { cidr, err = ParseCIDRSubnet(cidrStr) } else { cidr, err = address.ParseCIDR(cidrStr) } if err != nil { badRequest(w, err) return address.CIDR{}, false } return cidr, true }
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) { peername, err := router.PeerNameFromString(name) if err != nil { panic(err) } _, cidr, err := address.ParseCIDR(cidrStr) if err != nil { panic(err) } alloc := NewAllocator(peername, router.PeerUID(rand.Int63()), "nick-"+name, cidr.Range(), quorum, func(router.PeerName) bool { return true }) return alloc, cidr.HostRange() }
func TestHttp(t *testing.T) { var ( containerID = "deadbeef" container2 = "baddf00d" container3 = "b01df00d" universe = "10.0.0.0/8" testCIDR1 = "10.0.3.8/29" testCIDR2 = "10.2.0.0/16" testAddr1 = "10.0.3.9/29" testAddr2 = "10.2.0.1/16" ) alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", universe, 1) _, cidr, _ := address.ParseCIDR(universe) port := listenHTTP(alloc, cidr) alloc.claimRingForTesting() // Allocate an address in each subnet, and check we got what we expected cidr1a := HTTPPost(t, allocURL(port, testCIDR1, containerID)) require.Equal(t, testAddr1, cidr1a, "address") cidr2a := HTTPPost(t, allocURL(port, testCIDR2, containerID)) require.Equal(t, testAddr2, cidr2a, "address") // Now, make the same requests again to check the operation is idempotent check := HTTPGet(t, allocURL(port, testCIDR1, containerID)) require.Equal(t, cidr1a, check, "address") check = HTTPGet(t, allocURL(port, testCIDR2, containerID)) require.Equal(t, cidr2a, check, "address") // Ask the http server for a pair of addresses for another container and check they're different cidr1b := HTTPPost(t, allocURL(port, testCIDR1, container2)) require.False(t, cidr1b == testAddr1, "address") cidr2b := HTTPPost(t, allocURL(port, testCIDR2, container2)) require.False(t, cidr2b == testAddr2, "address") // Now free the first container, and we should get its addresses back when we ask doHTTP("DELETE", identURL(port, containerID)) cidr1c := HTTPPost(t, allocURL(port, testCIDR1, container3)) require.Equal(t, testAddr1, cidr1c, "address") cidr2c := HTTPPost(t, allocURL(port, testCIDR2, container3)) require.Equal(t, testAddr2, cidr2c, "address") // Would like to shut down the http server at the end of this test // but it's complicated. // See https://groups.google.com/forum/#!topic/golang-nuts/vLHWa5sHnCE }
func cidrRanges(s string) []address.Range { c, _ := address.ParseCIDR(s) return []address.Range{c.Range()} }
// 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 { badRequest(w, fmt.Errorf("Unable to claim: %s", err)) return } w.WriteHeader(204) }) router.Methods("GET").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) cidr := vars["ip"] + "/" + vars["prefixlen"] _, subnet, err := address.ParseCIDR(cidr) if err != nil { badRequest(w, err) return } addr, err := alloc.Lookup(vars["id"], subnet.HostRange()) if err != nil { http.NotFound(w, r) return } fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen) }) router.Methods("GET").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { addr, err := alloc.Lookup(mux.Vars(r)["id"], defaultSubnet.HostRange()) if err != nil { http.NotFound(w, r) return } fmt.Fprintf(w, "%s/%d", addr, defaultSubnet.PrefixLen) }) router.Methods("POST").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { closedChan := w.(http.CloseNotifier).CloseNotify() vars := mux.Vars(r) ident := vars["id"] cidrStr := vars["ip"] + "/" + vars["prefixlen"] subnetAddr, cidr, err := address.ParseCIDR(cidrStr) if err != nil { badRequest(w, err) return } if cidr.Start != subnetAddr { badRequest(w, fmt.Errorf("Invalid subnet %s - bits after network prefix are not all zero", cidrStr)) return } addr, err := alloc.Allocate(ident, cidr.HostRange(), closedChan) if err != nil { badRequest(w, fmt.Errorf("Unable to allocate: %s", err)) return } if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) { common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, addr) alloc.Free(ident, addr) return } fmt.Fprintf(w, "%s/%d", addr, cidr.PrefixLen) }) router.Methods("POST").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { closedChan := w.(http.CloseNotifier).CloseNotify() ident := mux.Vars(r)["id"] newAddr, err := alloc.Allocate(ident, defaultSubnet.HostRange(), closedChan) if err != nil { badRequest(w, err) return } if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) { common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, newAddr) alloc.Free(ident, newAddr) return } fmt.Fprintf(w, "%s/%d", newAddr, defaultSubnet.PrefixLen) }) router.Methods("DELETE").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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.Free(ident, ip); err != nil { badRequest(w, fmt.Errorf("Unable to free: %s", err)) return } w.WriteHeader(204) }) router.Methods("DELETE").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ident := mux.Vars(r)["id"] if err := alloc.Delete(ident); err != nil { badRequest(w, err) return } w.WriteHeader(204) }) router.Methods("DELETE").Path("/peer").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { alloc.Shutdown() w.WriteHeader(204) }) router.Methods("DELETE").Path("/peer/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ident := mux.Vars(r)["id"] if err := alloc.AdminTakeoverRanges(ident); err != nil { badRequest(w, err) return } w.WriteHeader(204) }) }