diff --git a/libnetwork/drivers/remote/driver.go b/libnetwork/drivers/remote/driver.go index 0d4ee2ca78..ffeb720ca7 100644 --- a/libnetwork/drivers/remote/driver.go +++ b/libnetwork/drivers/remote/driver.go @@ -1,7 +1,8 @@ package remote import ( - "errors" + "fmt" + "net" log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/plugins" @@ -9,59 +10,202 @@ import ( "github.com/docker/libnetwork/types" ) -var errNoCallback = errors.New("No Callback handler registered with Driver") - type driver struct { endpoint *plugins.Client networkType string } -// Init does the necessary work to register remote drivers +func newDriver(name string, client *plugins.Client) driverapi.Driver { + return &driver{networkType: name, endpoint: client} +} + +// Init makes sure a remote driver is registered when a network driver +// plugin is activated. func Init(dc driverapi.DriverCallback) error { plugins.Handle(driverapi.NetworkPluginEndpointType, func(name string, client *plugins.Client) { - - // TODO : Handhake with the Remote Plugin goes here - - newDriver := &driver{networkType: name, endpoint: client} - if err := dc.RegisterDriver(name, newDriver); err != nil { - log.Errorf("Error registering Driver for %s due to %v", name, err) + if err := dc.RegisterDriver(name, newDriver(name, client)); err != nil { + log.Errorf("error registering driver for %s due to %v", name, err) } }) return nil } +// Config is not implemented for remote drivers, since it is assumed +// to be supplied to the remote process out-of-band (e.g., as command +// line arguments). func (d *driver) Config(option map[string]interface{}) error { return &driverapi.ErrNotImplemented{} } -func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error { - return &driverapi.ErrNotImplemented{} +func (d *driver) call(methodName string, arg interface{}, retVal maybeError) error { + method := driverapi.NetworkPluginEndpointType + "." + methodName + err := d.endpoint.Call(method, arg, retVal) + if err != nil { + return err + } + if e := retVal.getError(); e != "" { + return fmt.Errorf("remote: %s", e) + } + return nil +} + +func (d *driver) CreateNetwork(id types.UUID, options map[string]interface{}) error { + create := &createNetworkRequest{ + NetworkID: string(id), + Options: options, + } + return d.call("CreateNetwork", create, &createNetworkResponse{}) } func (d *driver) DeleteNetwork(nid types.UUID) error { - return &driverapi.ErrNotImplemented{} + delete := &deleteNetworkRequest{NetworkID: string(nid)} + return d.call("DeleteNetwork", delete, &deleteNetworkResponse{}) } func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error { - return &driverapi.ErrNotImplemented{} + if epInfo == nil { + return fmt.Errorf("must not be called with nil EndpointInfo") + } + + reqIfaces := make([]*endpointInterface, len(epInfo.Interfaces())) + for i, iface := range epInfo.Interfaces() { + addr4 := iface.Address() + addr6 := iface.AddressIPv6() + reqIfaces[i] = &endpointInterface{ + ID: iface.ID(), + Address: addr4.String(), + AddressIPv6: addr6.String(), + MacAddress: iface.MacAddress().String(), + } + } + create := &createEndpointRequest{ + NetworkID: string(nid), + EndpointID: string(eid), + Interfaces: reqIfaces, + Options: epOptions, + } + var res createEndpointResponse + if err := d.call("CreateEndpoint", create, &res); err != nil { + return err + } + + ifaces, err := res.parseInterfaces() + if err != nil { + return err + } + if len(reqIfaces) > 0 && len(ifaces) > 0 { + // We're not supposed to add interfaces if there already are + // some. Attempt to roll back + return errorWithRollback("driver attempted to add more interfaces", d.DeleteEndpoint(nid, eid)) + } + for _, iface := range ifaces { + var addr4, addr6 net.IPNet + if iface.Address != nil { + addr4 = *(iface.Address) + } + if iface.AddressIPv6 != nil { + addr6 = *(iface.AddressIPv6) + } + if err := epInfo.AddInterface(iface.ID, iface.MacAddress, addr4, addr6); err != nil { + return errorWithRollback(fmt.Sprintf("failed to AddInterface %v: %s", iface, err), d.DeleteEndpoint(nid, eid)) + } + } + return nil +} + +func errorWithRollback(msg string, err error) error { + rollback := "rolled back" + if err != nil { + rollback = "failed to roll back: " + err.Error() + } + return fmt.Errorf("%s; %s", msg, rollback) } func (d *driver) DeleteEndpoint(nid, eid types.UUID) error { - return &driverapi.ErrNotImplemented{} + delete := &deleteEndpointRequest{ + NetworkID: string(nid), + EndpointID: string(eid), + } + return d.call("DeleteEndpoint", delete, &deleteEndpointResponse{}) } func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) { - return nil, &driverapi.ErrNotImplemented{} + info := &endpointInfoRequest{ + NetworkID: string(nid), + EndpointID: string(eid), + } + var res endpointInfoResponse + if err := d.call("EndpointOperInfo", info, &res); err != nil { + return nil, err + } + return res.Value, nil } // Join method is invoked when a Sandbox is attached to an endpoint. func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error { - return &driverapi.ErrNotImplemented{} + join := &joinRequest{ + NetworkID: string(nid), + EndpointID: string(eid), + SandboxKey: sboxKey, + Options: options, + } + var ( + res joinResponse + err error + ) + if err = d.call("Join", join, &res); err != nil { + return err + } + + // Expect each interface ID given by CreateEndpoint to have an + // entry at that index in the names supplied here. In other words, + // if you supply 0..n interfaces with IDs 0..n above, you should + // supply the names in the same order. + ifaceNames := res.InterfaceNames + for _, iface := range jinfo.InterfaceNames() { + i := iface.ID() + if i >= len(ifaceNames) || i < 0 { + return fmt.Errorf("no correlating interface %d in supplied interface names", i) + } + supplied := ifaceNames[i] + if err := iface.SetNames(supplied.SrcName, supplied.DstName); err != nil { + return errorWithRollback(fmt.Sprintf("failed to set interface name: %s", err), d.Leave(nid, eid)) + } + } + + var addr net.IP + if res.Gateway != "" { + if addr = net.ParseIP(res.Gateway); addr == nil { + return fmt.Errorf(`unable to parse Gateway "%s"`, res.Gateway) + } + if jinfo.SetGateway(addr) != nil { + return errorWithRollback(fmt.Sprintf("failed to set gateway: %v", addr), d.Leave(nid, eid)) + } + } + if res.GatewayIPv6 != "" { + if addr = net.ParseIP(res.GatewayIPv6); addr == nil { + return fmt.Errorf(`unable to parse GatewayIPv6 "%s"`, res.GatewayIPv6) + } + if jinfo.SetGatewayIPv6(addr) != nil { + return errorWithRollback(fmt.Sprintf("failed to set gateway IPv6: %v", addr), d.Leave(nid, eid)) + } + } + if jinfo.SetHostsPath(res.HostsPath) != nil { + return errorWithRollback(fmt.Sprintf("failed to set hosts path: %s", res.HostsPath), d.Leave(nid, eid)) + } + if jinfo.SetResolvConfPath(res.ResolvConfPath) != nil { + return errorWithRollback(fmt.Sprintf("failed to set resolv.conf path: %s", res.ResolvConfPath), d.Leave(nid, eid)) + } + return nil } // Leave method is invoked when a Sandbox detaches from an endpoint. func (d *driver) Leave(nid, eid types.UUID) error { - return &driverapi.ErrNotImplemented{} + leave := &leaveRequest{ + NetworkID: string(nid), + EndpointID: string(eid), + } + return d.call("Leave", leave, &leaveResponse{}) } func (d *driver) Type() string { diff --git a/libnetwork/drivers/remote/driver_test.go b/libnetwork/drivers/remote/driver_test.go new file mode 100644 index 0000000000..a9fb8b4c16 --- /dev/null +++ b/libnetwork/drivers/remote/driver_test.go @@ -0,0 +1,397 @@ +package remote + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "testing" + + "github.com/docker/docker/pkg/plugins" + "github.com/docker/libnetwork/driverapi" + _ "github.com/docker/libnetwork/netutils" + "github.com/docker/libnetwork/types" +) + +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", driverapi.NetworkPluginEndpointType, 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("/usr/share/docker/plugins", 0755); err != nil { + t.Fatal(err) + } + + listener, err := net.Listen("unix", fmt.Sprintf("/usr/share/docker/plugins/%s.sock", name)) + if err != nil { + t.Fatal("Could not listen to the plugin socket") + } + + mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType) + }) + + go http.Serve(listener, mux) + + return func() { + listener.Close() + if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil { + t.Fatal(err) + } + } +} + +type testEndpoint struct { + t *testing.T + id int + src string + dst string + address string + addressIPv6 string + macAddress string + gateway string + gatewayIPv6 string + resolvConfPath string + hostsPath string +} + +func (test *testEndpoint) Interfaces() []driverapi.InterfaceInfo { + // return an empty one so we don't trip the check for existing + // interfaces; we don't care about this after that + return []driverapi.InterfaceInfo{} +} + +func (test *testEndpoint) AddInterface(ID int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error { + if ID != test.id { + test.t.Fatalf("Wrong ID passed to AddInterface: %d", ID) + } + ip4, net4, _ := net.ParseCIDR(test.address) + ip6, net6, _ := net.ParseCIDR(test.addressIPv6) + if ip4 != nil { + net4.IP = ip4 + if !types.CompareIPNet(net4, &ipv4) { + test.t.Fatalf("Wrong address given %+v", ipv4) + } + } + if ip6 != nil { + net6.IP = ip6 + if !types.CompareIPNet(net6, &ipv6) { + test.t.Fatalf("Wrong address (IPv6) given %+v", ipv6) + } + } + if test.macAddress != "" && mac.String() != test.macAddress { + test.t.Fatalf("Wrong MAC address given %v", mac) + } + return nil +} + +func (test *testEndpoint) InterfaceNames() []driverapi.InterfaceNameInfo { + return []driverapi.InterfaceNameInfo{test} +} + +func compareIPs(t *testing.T, kind string, shouldBe string, supplied net.IP) { + ip := net.ParseIP(shouldBe) + if ip == nil { + t.Fatalf(`Invalid IP to test against: "%s"`, shouldBe) + } + if !ip.Equal(supplied) { + t.Fatalf(`%s IPs are not equal: expected "%s", got %v`, kind, shouldBe, supplied) + } +} + +func (test *testEndpoint) SetGateway(ipv4 net.IP) error { + compareIPs(test.t, "Gateway", test.gateway, ipv4) + return nil +} + +func (test *testEndpoint) SetGatewayIPv6(ipv6 net.IP) error { + compareIPs(test.t, "GatewayIPv6", test.gatewayIPv6, ipv6) + return nil +} + +func (test *testEndpoint) SetHostsPath(p string) error { + if p != test.hostsPath { + test.t.Fatalf(`Wrong HostsPath; expected "%s", got "%s"`, test.hostsPath, p) + } + return nil +} + +func (test *testEndpoint) SetResolvConfPath(p string) error { + if p != test.resolvConfPath { + test.t.Fatalf(`Wrong ResolvConfPath; expected "%s", got "%s"`, test.resolvConfPath, p) + } + return nil +} + +func (test *testEndpoint) SetNames(src string, dst string) error { + if test.src != src { + test.t.Fatalf(`Wrong SrcName; expected "%s", got "%s"`, test.src, src) + } + if test.dst != dst { + test.t.Fatalf(`Wrong DstName; expected "%s", got "%s"`, test.dst, dst) + } + return nil +} + +func (test *testEndpoint) ID() int { + return test.id +} + +func TestRemoteDriver(t *testing.T) { + var plugin = "test-net-driver" + + ep := &testEndpoint{ + t: t, + src: "vethsrc", + dst: "vethdst", + address: "192.168.5.7/16", + addressIPv6: "2001:DB8::5:7/48", + macAddress: "7a:56:78:34:12:da", + gateway: "192.168.0.1", + gatewayIPv6: "2001:DB8::1", + hostsPath: "/here/comes/the/host/path", + resolvConfPath: "/there/goes/the/resolv/conf", + } + + mux := http.NewServeMux() + defer setupPlugin(t, plugin, mux)() + + var networkID string + + handle(t, mux, "CreateNetwork", func(msg map[string]interface{}) interface{} { + nid := msg["NetworkID"] + var ok bool + if networkID, ok = nid.(string); !ok { + t.Fatal("RPC did not include network ID string") + } + return map[string]interface{}{} + }) + handle(t, mux, "DeleteNetwork", func(msg map[string]interface{}) interface{} { + if nid, ok := msg["NetworkID"]; !ok || nid != networkID { + t.Fatal("Network ID missing or does not match that created") + } + return map[string]interface{}{} + }) + handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} { + iface := map[string]interface{}{ + "ID": ep.id, + "Address": ep.address, + "AddressIPv6": ep.addressIPv6, + "MacAddress": ep.macAddress, + } + return map[string]interface{}{ + "Interfaces": []interface{}{iface}, + } + }) + handle(t, mux, "Join", func(msg map[string]interface{}) interface{} { + options := msg["Options"].(map[string]interface{}) + foo, ok := options["foo"].(string) + if !ok || foo != "fooValue" { + t.Fatalf("Did not receive expected foo string in request options: %+v", msg) + } + return map[string]interface{}{ + "Gateway": ep.gateway, + "GatewayIPv6": ep.gatewayIPv6, + "HostsPath": ep.hostsPath, + "ResolvConfPath": ep.resolvConfPath, + "InterfaceNames": []map[string]interface{}{ + map[string]interface{}{ + "SrcName": ep.src, + "DstName": ep.dst, + }, + }, + } + }) + handle(t, mux, "Leave", func(msg map[string]interface{}) interface{} { + return map[string]string{} + }) + handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} { + return map[string]interface{}{} + }) + handle(t, mux, "EndpointOperInfo", func(msg map[string]interface{}) interface{} { + return map[string]interface{}{ + "Value": map[string]string{ + "Arbitrary": "key", + "Value": "pairs?", + }, + } + }) + + p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType) + if err != nil { + t.Fatal(err) + } + + driver := newDriver(plugin, p.Client) + if driver.Type() != plugin { + t.Fatal("Driver type does not match that given") + } + + netID := types.UUID("dummy-network") + err = driver.CreateNetwork(netID, map[string]interface{}{}) + if err != nil { + t.Fatal(err) + } + + endID := types.UUID("dummy-endpoint") + err = driver.CreateEndpoint(netID, endID, ep, map[string]interface{}{}) + if err != nil { + t.Fatal(err) + } + + joinOpts := map[string]interface{}{"foo": "fooValue"} + err = driver.Join(netID, endID, "sandbox-key", ep, joinOpts) + if err != nil { + t.Fatal(err) + } + if _, err = driver.EndpointOperInfo(netID, endID); err != nil { + t.Fatal(err) + } + if err = driver.Leave(netID, endID); err != nil { + t.Fatal(err) + } + if err = driver.DeleteEndpoint(netID, endID); err != nil { + t.Fatal(err) + } + if err = driver.DeleteNetwork(netID); err != nil { + t.Fatal(err) + } +} + +type failEndpoint struct { + t *testing.T +} + +func (f *failEndpoint) Interfaces() []*driverapi.InterfaceInfo { + f.t.Fatal("Unexpected call of Interfaces") + return nil +} +func (f *failEndpoint) AddInterface(int, net.HardwareAddr, net.IPNet, net.IPNet) error { + f.t.Fatal("Unexpected call of AddInterface") + return nil +} + +func TestDriverError(t *testing.T) { + var plugin = "test-net-driver-error" + + mux := http.NewServeMux() + defer setupPlugin(t, plugin, mux)() + + handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} { + return map[string]interface{}{ + "Err": "this should get raised as an error", + } + }) + + p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType) + if err != nil { + t.Fatal(err) + } + + driver := newDriver(plugin, p.Client) + + if err := driver.CreateEndpoint(types.UUID("dummy"), types.UUID("dummy"), &testEndpoint{t: t}, map[string]interface{}{}); err == nil { + t.Fatalf("Expected error from driver") + } +} + +func TestMissingValues(t *testing.T) { + var plugin = "test-net-driver-missing" + + mux := http.NewServeMux() + defer setupPlugin(t, plugin, mux)() + + ep := &testEndpoint{ + t: t, + id: 0, + } + + handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} { + iface := map[string]interface{}{ + "ID": ep.id, + "Address": ep.address, + "AddressIPv6": ep.addressIPv6, + "MacAddress": ep.macAddress, + } + return map[string]interface{}{ + "Interfaces": []interface{}{iface}, + } + }) + + p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType) + if err != nil { + t.Fatal(err) + } + driver := newDriver(plugin, p.Client) + + if err := driver.CreateEndpoint(types.UUID("dummy"), types.UUID("dummy"), ep, map[string]interface{}{}); err != nil { + t.Fatal(err) + } +} + +type rollbackEndpoint struct { +} + +func (r *rollbackEndpoint) Interfaces() []driverapi.InterfaceInfo { + return []driverapi.InterfaceInfo{} +} + +func (r *rollbackEndpoint) AddInterface(_ int, _ net.HardwareAddr, _ net.IPNet, _ net.IPNet) error { + return fmt.Errorf("fail this to trigger a rollback") +} + +func TestRollback(t *testing.T) { + var plugin = "test-net-driver-rollback" + + mux := http.NewServeMux() + defer setupPlugin(t, plugin, mux)() + + rolledback := false + + handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} { + iface := map[string]interface{}{ + "ID": 0, + "Address": "192.168.4.5/16", + "AddressIPv6": "", + "MacAddress": "7a:12:34:56:78:90", + } + return map[string]interface{}{ + "Interfaces": []interface{}{iface}, + } + }) + handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} { + rolledback = true + return map[string]interface{}{} + }) + + p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType) + if err != nil { + t.Fatal(err) + } + driver := newDriver(plugin, p.Client) + + ep := &rollbackEndpoint{} + + if err := driver.CreateEndpoint(types.UUID("dummy"), types.UUID("dummy"), ep, map[string]interface{}{}); err == nil { + t.Fatalf("Expected error from driver") + } + if !rolledback { + t.Fatalf("Expected to have had DeleteEndpoint called") + } +} diff --git a/libnetwork/drivers/remote/messages.go b/libnetwork/drivers/remote/messages.go new file mode 100644 index 0000000000..8e03a16daf --- /dev/null +++ b/libnetwork/drivers/remote/messages.go @@ -0,0 +1,143 @@ +package remote + +import "net" + +type response struct { + Err string +} + +type maybeError interface { + getError() string +} + +func (r *response) getError() string { + return r.Err +} + +type createNetworkRequest struct { + NetworkID string + Options map[string]interface{} +} + +type createNetworkResponse struct { + response +} + +type deleteNetworkRequest struct { + NetworkID string +} + +type deleteNetworkResponse struct { + response +} + +type createEndpointRequest struct { + NetworkID string + EndpointID string + Interfaces []*endpointInterface + Options map[string]interface{} +} + +type endpointInterface struct { + ID int + Address string + AddressIPv6 string + MacAddress string +} + +type createEndpointResponse struct { + response + Interfaces []*endpointInterface +} + +func toAddr(ipAddr string) (*net.IPNet, error) { + ip, ipnet, err := net.ParseCIDR(ipAddr) + if err != nil { + return nil, err + } + ipnet.IP = ip + return ipnet, nil +} + +type iface struct { + ID int + Address *net.IPNet + AddressIPv6 *net.IPNet + MacAddress net.HardwareAddr +} + +func (r *createEndpointResponse) parseInterfaces() ([]*iface, error) { + var ( + ifaces = make([]*iface, len(r.Interfaces)) + ) + for i, inIf := range r.Interfaces { + var err error + outIf := &iface{ID: inIf.ID} + if inIf.Address != "" { + if outIf.Address, err = toAddr(inIf.Address); err != nil { + return nil, err + } + } + if inIf.AddressIPv6 != "" { + if outIf.AddressIPv6, err = toAddr(inIf.AddressIPv6); err != nil { + return nil, err + } + } + if inIf.MacAddress != "" { + if outIf.MacAddress, err = net.ParseMAC(inIf.MacAddress); err != nil { + return nil, err + } + } + ifaces[i] = outIf + } + return ifaces, nil +} + +type deleteEndpointRequest struct { + NetworkID string + EndpointID string +} + +type deleteEndpointResponse struct { + response +} + +type endpointInfoRequest struct { + NetworkID string + EndpointID string +} + +type endpointInfoResponse struct { + response + Value map[string]interface{} +} + +type joinRequest struct { + NetworkID string + EndpointID string + SandboxKey string + Options map[string]interface{} +} + +type ifaceName struct { + SrcName string + DstName string +} + +type joinResponse struct { + response + InterfaceNames []*ifaceName + Gateway string + GatewayIPv6 string + HostsPath string + ResolvConfPath string +} + +type leaveRequest struct { + NetworkID string + EndpointID string +} + +type leaveResponse struct { + response +} diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index 9414f5209b..cc811178ca 100644 --- a/libnetwork/libnetwork_test.go +++ b/libnetwork/libnetwork_test.go @@ -1277,6 +1277,10 @@ func TestValidRemoteDriver(t *testing.T) { w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType) }) + mux.HandleFunc(fmt.Sprintf("/%s.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + fmt.Fprintf(w, "null") + }) if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil { t.Fatal(err) @@ -1299,9 +1303,7 @@ func TestValidRemoteDriver(t *testing.T) { _, err = controller.NewNetwork("valid-network-driver", "dummy", libnetwork.NetworkOptionGeneric(getEmptyGenericOption())) if err != nil { - if _, ok := err.(*driverapi.ErrNotImplemented); !ok { - t.Fatal(err) - } + t.Fatal(err) } }