Change in remote IPAM API payload

- Avoid net.IP and net.IPNet types to ease marshalling/unmarshalling
  at client and server side

Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
Alessandro Boch 2015-10-12 13:03:28 -07:00
parent c454b1084d
commit 8d56508190
3 changed files with 263 additions and 12 deletions

View file

@ -2,10 +2,6 @@
// messages between libnetwork and the remote ipam plugin // messages between libnetwork and the remote ipam plugin
package api package api
import (
"net"
)
// Response is the basic response structure used in all responses // Response is the basic response structure used in all responses
type Response struct { type Response struct {
Error string Error string
@ -41,7 +37,7 @@ type RequestPoolRequest struct {
type RequestPoolResponse struct { type RequestPoolResponse struct {
Response Response
PoolID string PoolID string
Pool *net.IPNet Pool string // CIDR format
Data map[string]string Data map[string]string
} }
@ -58,21 +54,21 @@ type ReleasePoolResponse struct {
// RequestAddressRequest represents the expected data in a ``request address`` request message // RequestAddressRequest represents the expected data in a ``request address`` request message
type RequestAddressRequest struct { type RequestAddressRequest struct {
PoolID string PoolID string
Address net.IP Address string
Options map[string]string Options map[string]string
} }
// RequestAddressResponse represents the expected data in the response message to a ``request address`` request // RequestAddressResponse represents the expected data in the response message to a ``request address`` request
type RequestAddressResponse struct { type RequestAddressResponse struct {
Response Response
Address *net.IPNet Address string // in CIDR format
Data map[string]string Data map[string]string
} }
// ReleaseAddressRequest represents the expected data in a ``release address`` request message // ReleaseAddressRequest represents the expected data in a ``release address`` request message
type ReleaseAddressRequest struct { type ReleaseAddressRequest struct {
PoolID string PoolID string
Address net.IP Address string
} }
// ReleaseAddressResponse represents the response message to a ``release address`` request // ReleaseAddressResponse represents the response message to a ``release address`` request

View file

@ -8,6 +8,7 @@ import (
"github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/ipams/remote/api" "github.com/docker/libnetwork/ipams/remote/api"
"github.com/docker/libnetwork/types"
) )
type allocator struct { type allocator struct {
@ -64,7 +65,8 @@ func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[
if err := a.call("RequestPool", req, res); err != nil { if err := a.call("RequestPool", req, res); err != nil {
return "", nil, nil, err return "", nil, nil, err
} }
return res.PoolID, res.Pool, res.Data, nil retPool, err := types.ParseCIDR(res.Pool)
return res.PoolID, retPool, res.Data, err
} }
// ReleasePool removes an address pool from the specified address space // ReleasePool removes an address pool from the specified address space
@ -76,17 +78,26 @@ func (a *allocator) ReleasePool(poolID string) error {
// RequestAddress requests an address from the address pool // RequestAddress requests an address from the address pool
func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) { func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
req := &api.RequestAddressRequest{PoolID: poolID, Address: address, Options: options} var prefAddress string
if address != nil {
prefAddress = address.String()
}
req := &api.RequestAddressRequest{PoolID: poolID, Address: prefAddress, Options: options}
res := &api.RequestAddressResponse{} res := &api.RequestAddressResponse{}
if err := a.call("RequestAddress", req, res); err != nil { if err := a.call("RequestAddress", req, res); err != nil {
return nil, nil, err return nil, nil, err
} }
return res.Address, res.Data, nil retAddress, err := types.ParseCIDR(res.Address)
return retAddress, res.Data, err
} }
// ReleaseAddress releases the address from the specified address pool // ReleaseAddress releases the address from the specified address pool
func (a *allocator) ReleaseAddress(poolID string, address net.IP) error { func (a *allocator) ReleaseAddress(poolID string, address net.IP) error {
req := &api.ReleaseAddressRequest{PoolID: poolID, Address: address} var relAddress string
if address != nil {
relAddress = address.String()
}
req := &api.ReleaseAddressRequest{PoolID: poolID, Address: relAddress}
res := &api.ReleaseAddressResponse{} res := &api.ReleaseAddressResponse{}
return a.call("ReleaseAddress", req, res) return a.call("ReleaseAddress", req, res)
} }

View file

@ -0,0 +1,244 @@
package remote
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/ipamapi"
_ "github.com/docker/libnetwork/testutils"
)
func decodeToMap(r *http.Request) (res map[string]interface{}, err error) {
err = json.NewDecoder(r.Body).Decode(&res)
return
}
func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]interface{}) interface{}) {
mux.HandleFunc(fmt.Sprintf("/%s.%s", ipamapi.PluginEndpointType, method), func(w http.ResponseWriter, r *http.Request) {
ask, err := decodeToMap(r)
if err != nil {
t.Fatal(err)
}
answer := h(ask)
err = json.NewEncoder(w).Encode(&answer)
if err != nil {
t.Fatal(err)
}
})
}
func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(mux)
if server == nil {
t.Fatal("Failed to start a HTTP Server")
}
if err := ioutil.WriteFile(fmt.Sprintf("/etc/docker/plugins/%s.spec", name), []byte(server.URL), 0644); err != nil {
t.Fatal(err)
}
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprintf(w, `{"Implements": ["%s"]}`, ipamapi.PluginEndpointType)
})
return func() {
if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
t.Fatal(err)
}
server.Close()
}
}
func TestGetDefaultAddressSpaces(t *testing.T) {
var plugin = "test-ipam-driver-addr-spaces"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetDefaultAddressSpaces", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"LocalDefaultAddressSpace": "white",
"GlobalDefaultAddressSpace": "blue",
}
})
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newAllocator(plugin, p.Client)
l, g, err := d.(*allocator).GetDefaultAddressSpaces()
if err != nil {
t.Fatal(err)
}
if l != "white" || g != "blue" {
t.Fatalf("Unexpected default local and global address spaces: %s, %s", l, g)
}
}
func TestRemoteDriver(t *testing.T) {
var plugin = "test-ipam-driver"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetDefaultAddressSpaces", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"LocalDefaultAddressSpace": "white",
"GlobalDefaultAddressSpace": "blue",
}
})
handle(t, mux, "RequestPool", func(msg map[string]interface{}) interface{} {
as := "white"
if v, ok := msg["AddressSpace"]; ok && v.(string) != "" {
as = v.(string)
}
pl := "172.18.0.0/16"
sp := ""
if v, ok := msg["Pool"]; ok && v.(string) != "" {
pl = v.(string)
}
if v, ok := msg["SubPool"]; ok && v.(string) != "" {
sp = v.(string)
}
pid := fmt.Sprintf("%s/%s", as, pl)
if sp != "" {
pid = fmt.Sprintf("%s/%s", pid, sp)
}
return map[string]interface{}{
"PoolID": pid,
"Pool": pl,
"Data": map[string]string{"DNS": "8.8.8.8"},
}
})
handle(t, mux, "ReleasePool", func(msg map[string]interface{}) interface{} {
if _, ok := msg["PoolID"]; !ok {
t.Fatalf("Missing PoolID in Release request")
}
return map[string]interface{}{}
})
handle(t, mux, "RequestAddress", func(msg map[string]interface{}) interface{} {
if _, ok := msg["PoolID"]; !ok {
t.Fatalf("Missing PoolID in address request")
}
prefAddr := ""
if v, ok := msg["Address"]; ok {
prefAddr = v.(string)
}
ip := prefAddr
if ip == "" {
ip = "172.20.0.34"
}
ip = fmt.Sprintf("%s/16", ip)
return map[string]interface{}{
"Address": ip,
}
})
handle(t, mux, "ReleaseAddress", func(msg map[string]interface{}) interface{} {
if _, ok := msg["PoolID"]; !ok {
t.Fatalf("Missing PoolID in address request")
}
if _, ok := msg["Address"]; !ok {
t.Fatalf("Missing Address in release address request")
}
return map[string]interface{}{}
})
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newAllocator(plugin, p.Client)
l, g, err := d.(*allocator).GetDefaultAddressSpaces()
if err != nil {
t.Fatal(err)
}
if l != "white" || g != "blue" {
t.Fatalf("Unexpected default local/global address spaces: %s, %s", l, g)
}
// Request any pool
poolID, pool, _, err := d.RequestPool("white", "", "", nil, false)
if err != nil {
t.Fatal(err)
}
if poolID != "white/172.18.0.0/16" {
t.Fatalf("Unexpected pool id: %s", poolID)
}
if pool == nil || pool.String() != "172.18.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool)
}
// Request specific pool
poolID2, pool2, ops, err := d.RequestPool("white", "172.20.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
if poolID2 != "white/172.20.0.0/16" {
t.Fatalf("Unexpected pool id: %s", poolID2)
}
if pool2 == nil || pool2.String() != "172.20.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool2)
}
if dns, ok := ops["DNS"]; !ok || dns != "8.8.8.8" {
t.Fatalf("Missing options")
}
// Request specific pool and subpool
poolID3, pool3, _, err := d.RequestPool("white", "172.20.0.0/16", "172.20.3.0/24" /*nil*/, map[string]string{"culo": "yes"}, false)
if err != nil {
t.Fatal(err)
}
if poolID3 != "white/172.20.0.0/16/172.20.3.0/24" {
t.Fatalf("Unexpected pool id: %s", poolID3)
}
if pool3 == nil || pool3.String() != "172.20.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool3)
}
// Request any address
addr, _, err := d.RequestAddress(poolID2, nil, nil)
if err != nil {
t.Fatal(err)
}
if addr == nil || addr.String() != "172.20.0.34/16" {
t.Fatalf("Unexpected address: %s", addr)
}
// Request specific address
addr2, _, err := d.RequestAddress(poolID2, net.ParseIP("172.20.1.45"), nil)
if err != nil {
t.Fatal(err)
}
if addr2 == nil || addr2.String() != "172.20.1.45/16" {
t.Fatalf("Unexpected address: %s", addr2)
}
// Release address
err = d.ReleaseAddress(poolID, net.ParseIP("172.18.1.45"))
if err != nil {
t.Fatal(err)
}
}