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) { common.InitDefaultLogging(true) 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 parseAndCheckCIDR(cidrStr string) address.CIDR { _, cidr, err := address.ParseCIDR(cidrStr) if err != nil { Log.Fatal(err) } if cidr.Size() < ipam.MinSubnetSize { Log.Fatalf("Allocation range smaller than minimum size %d: %s", ipam.MinSubnetSize, cidrStr) } return cidr }
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) 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 }
// 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 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 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) }) }