mirror of
https://github.com/moby/moby.git
synced 2026-08-03 14:41:03 +00:00
When macvlan is given no '--gateway' option, an address is allocated from IPAM and a default route is set up via that gateway. But, the gateway address is not assigned to anything in the Docker macvlan network - it must be external, and IPAM shouldn't try to guess it. When IPv6 auto-configuration is enabled in the network the macvlan is connected to, the macvlan driver races against it to set up the gateway. When autoconfig wins, container creation fails because the default route already exists. So ... - disable IPAM gateway address allocation for macvlan - update the Join code to allow for no configured gateway - always disable 'docker_gwbridge' connection for macvlan networks, so it's not hooked up when there is no gateway address. Libnet assumes an endpoint with no statically configured default gateway or route does not provide external connectivity. So, it disables external DNS access, and will not select the endpoint as gateway for containers. So, where an IPAM allocated gateway address would have been assigned before, tell libnet to assume there will be an auto-configured gateway. Signed-off-by: Rob Murray <rob.murray@docker.com>
645 lines
16 KiB
Go
645 lines
16 KiB
Go
package remote
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/moby/moby/v2/daemon/libnetwork/discoverapi"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/options"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/scope"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
|
"github.com/moby/moby/v2/pkg/plugins"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]any) any) {
|
|
mux.HandleFunc(fmt.Sprintf("/%s.%s", driverapi.NetworkPluginEndpointType, method), func(w http.ResponseWriter, r *http.Request) {
|
|
var ask map[string]any
|
|
err := json.NewDecoder(r.Body).Decode(&ask)
|
|
if err != nil && !errors.Is(err, io.EOF) {
|
|
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() {
|
|
specPath := "/etc/docker/plugins"
|
|
if runtime.GOOS == "windows" {
|
|
specPath = filepath.Join(os.Getenv("programdata"), "docker", "plugins")
|
|
}
|
|
|
|
if err := os.MkdirAll(specPath, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() {
|
|
if t.Failed() {
|
|
_ = os.RemoveAll(specPath)
|
|
}
|
|
}()
|
|
|
|
server := httptest.NewServer(mux)
|
|
if server == nil {
|
|
t.Fatal("Failed to start an HTTP Server")
|
|
}
|
|
|
|
if err := os.WriteFile(filepath.Join(specPath, name+".spec"), []byte(server.URL), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", plugins.VersionMimetype)
|
|
fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType)
|
|
})
|
|
|
|
return func() {
|
|
if err := os.RemoveAll(specPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
server.Close()
|
|
}
|
|
}
|
|
|
|
type testEndpoint struct {
|
|
t *testing.T
|
|
srcName string
|
|
dstPrefix string
|
|
dstName string
|
|
address string
|
|
addressIPv6 string
|
|
macAddress string
|
|
gateway string
|
|
gatewayIPv6 string
|
|
resolvConfPath string
|
|
hostsPath string
|
|
nextHop string
|
|
destination string
|
|
routeType types.RouteType
|
|
disableGatewayService bool
|
|
}
|
|
|
|
func (test *testEndpoint) Address() *net.IPNet {
|
|
if test.address == "" {
|
|
return nil
|
|
}
|
|
nw, _ := types.ParseCIDR(test.address)
|
|
return nw
|
|
}
|
|
|
|
func (test *testEndpoint) AddressIPv6() *net.IPNet {
|
|
if test.addressIPv6 == "" {
|
|
return nil
|
|
}
|
|
nw, _ := types.ParseCIDR(test.addressIPv6)
|
|
return nw
|
|
}
|
|
|
|
func (test *testEndpoint) MacAddress() net.HardwareAddr {
|
|
if test.macAddress == "" {
|
|
return nil
|
|
}
|
|
mac, _ := net.ParseMAC(test.macAddress)
|
|
return mac
|
|
}
|
|
|
|
func (test *testEndpoint) SetMacAddress(mac net.HardwareAddr) error {
|
|
if test.macAddress != "" {
|
|
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", test.macAddress, mac)
|
|
}
|
|
if mac == nil {
|
|
return types.InvalidParameterErrorf("tried to set nil MAC address to endpoint interface")
|
|
}
|
|
test.macAddress = mac.String()
|
|
return nil
|
|
}
|
|
|
|
func (test *testEndpoint) SetIPAddress(address *net.IPNet) error {
|
|
if address.IP == nil {
|
|
return types.InvalidParameterErrorf("tried to set nil IP address to endpoint interface")
|
|
}
|
|
if address.IP.To4() == nil {
|
|
return setAddress(&test.addressIPv6, address)
|
|
}
|
|
return setAddress(&test.address, address)
|
|
}
|
|
|
|
func setAddress(ifaceAddr *string, address *net.IPNet) error {
|
|
if *ifaceAddr != "" {
|
|
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
|
|
}
|
|
*ifaceAddr = address.String()
|
|
return nil
|
|
}
|
|
|
|
func (test *testEndpoint) InterfaceName() driverapi.InterfaceNameInfo {
|
|
return 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 compareIPNets(t *testing.T, kind string, shouldBe string, supplied net.IPNet) {
|
|
_, ipNet, _ := net.ParseCIDR(shouldBe)
|
|
if ipNet == nil {
|
|
t.Fatalf(`Invalid IP network to test against: "%s"`, shouldBe)
|
|
}
|
|
if !types.CompareIPNet(ipNet, &supplied) {
|
|
t.Fatalf(`%s IP networks 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) NetnsPath() string { return "" }
|
|
|
|
func (test *testEndpoint) SetCreatedInContainer(bool) {}
|
|
|
|
func (test *testEndpoint) SetNames(srcName, dstPrefix, dstName string) error {
|
|
assert.Equal(test.t, test.srcName, srcName)
|
|
assert.Equal(test.t, test.dstPrefix, dstPrefix)
|
|
assert.Equal(test.t, test.dstName, dstName)
|
|
return nil
|
|
}
|
|
|
|
func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType types.RouteType, nextHop net.IP) error {
|
|
compareIPNets(test.t, "Destination", test.destination, *destination)
|
|
compareIPs(test.t, "NextHop", test.nextHop, nextHop)
|
|
|
|
if test.routeType != routeType {
|
|
test.t.Fatalf(`Wrong RouteType; expected "%d", got "%d"`, test.routeType, routeType)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (test *testEndpoint) DisableGatewayService() {
|
|
test.disableGatewayService = true
|
|
}
|
|
|
|
func (test *testEndpoint) ForceGw4() {}
|
|
func (test *testEndpoint) ForceGw6() {}
|
|
|
|
func (test *testEndpoint) AddTableEntry(tableName string, key string, value []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func TestGetEmptyCapabilities(t *testing.T) {
|
|
plugin := "test-net-driver-empty-cap"
|
|
|
|
mux := http.NewServeMux()
|
|
defer setupPlugin(t, plugin, mux)()
|
|
|
|
handle(t, mux, "GetCapabilities", func(msg map[string]any) any {
|
|
return map[string]any{}
|
|
})
|
|
|
|
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client, err := getPluginClient(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d := newDriver(plugin, client)
|
|
if d.Type() != plugin {
|
|
t.Fatal("Driver type does not match that given")
|
|
}
|
|
|
|
_, err = d.getCapabilities()
|
|
if err == nil {
|
|
t.Fatal("There should be error reported when get empty capability")
|
|
}
|
|
}
|
|
|
|
func TestGetExtraCapabilities(t *testing.T) {
|
|
plugin := "test-net-driver-extra-cap"
|
|
|
|
mux := http.NewServeMux()
|
|
defer setupPlugin(t, plugin, mux)()
|
|
|
|
handle(t, mux, "GetCapabilities", func(msg map[string]any) any {
|
|
return map[string]any{
|
|
"Scope": "local",
|
|
"foo": "bar",
|
|
"ConnectivityScope": "global",
|
|
}
|
|
})
|
|
|
|
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client, err := getPluginClient(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d := newDriver(plugin, client)
|
|
if d.Type() != plugin {
|
|
t.Fatal("Driver type does not match that given")
|
|
}
|
|
|
|
c, err := d.getCapabilities()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
} else if c.DataScope != scope.Local {
|
|
t.Fatalf("get capability '%s', expecting 'local'", c.DataScope)
|
|
} else if c.ConnectivityScope != scope.Global {
|
|
t.Fatalf("get capability '%s', expecting %q", c.ConnectivityScope, scope.Global)
|
|
}
|
|
}
|
|
|
|
func TestGetInvalidCapabilities(t *testing.T) {
|
|
plugin := "test-net-driver-invalid-cap"
|
|
|
|
mux := http.NewServeMux()
|
|
defer setupPlugin(t, plugin, mux)()
|
|
|
|
handle(t, mux, "GetCapabilities", func(msg map[string]any) any {
|
|
return map[string]any{
|
|
"Scope": "fake",
|
|
}
|
|
})
|
|
|
|
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client, err := getPluginClient(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d := newDriver(plugin, client)
|
|
if d.Type() != plugin {
|
|
t.Fatal("Driver type does not match that given")
|
|
}
|
|
|
|
_, err = d.getCapabilities()
|
|
if err == nil {
|
|
t.Fatal("There should be error reported when get invalid capability")
|
|
}
|
|
}
|
|
|
|
func TestRemoteDriver(t *testing.T) {
|
|
plugin := "test-net-driver"
|
|
|
|
ep := &testEndpoint{
|
|
t: t,
|
|
srcName: "vethsrc",
|
|
dstPrefix: "vethdst",
|
|
address: "192.168.5.7/16",
|
|
addressIPv6: "2001:DB8::5:7/48",
|
|
macAddress: "ab:cd:ef:ee:ee:ee",
|
|
gateway: "192.168.0.1",
|
|
gatewayIPv6: "2001:DB8::1",
|
|
hostsPath: "/here/comes/the/host/path",
|
|
resolvConfPath: "/there/goes/the/resolv/conf",
|
|
destination: "10.0.0.0/8",
|
|
nextHop: "10.0.0.1",
|
|
routeType: types.CONNECTED,
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
defer setupPlugin(t, plugin, mux)()
|
|
|
|
var networkID string
|
|
|
|
handle(t, mux, "GetCapabilities", func(msg map[string]any) any {
|
|
return map[string]any{
|
|
"Scope": "global",
|
|
"GwAllocChecker": true,
|
|
}
|
|
})
|
|
handle(t, mux, "GwAllocCheck", func(msg map[string]any) any {
|
|
options := msg["Options"].(map[string]any)
|
|
return map[string]any{
|
|
"SkipIPv4": options["skip4"].(bool),
|
|
"SkipIPv6": options["skip6"].(bool),
|
|
}
|
|
})
|
|
handle(t, mux, "CreateNetwork", func(msg map[string]any) any {
|
|
nid := msg["NetworkID"]
|
|
var ok bool
|
|
if networkID, ok = nid.(string); !ok {
|
|
t.Fatal("RPC did not include network ID string")
|
|
}
|
|
return map[string]any{}
|
|
})
|
|
handle(t, mux, "DeleteNetwork", func(msg map[string]any) any {
|
|
if nid, ok := msg["NetworkID"]; !ok || nid != networkID {
|
|
t.Fatal("Network ID missing or does not match that created")
|
|
}
|
|
return map[string]any{}
|
|
})
|
|
handle(t, mux, "CreateEndpoint", func(msg map[string]any) any {
|
|
iface := map[string]any{
|
|
"MacAddress": ep.macAddress,
|
|
"Address": ep.address,
|
|
"AddressIPv6": ep.addressIPv6,
|
|
}
|
|
return map[string]any{
|
|
"Interface": iface,
|
|
}
|
|
})
|
|
handle(t, mux, "Join", func(msg map[string]any) any {
|
|
opts := msg["Options"].(map[string]any)
|
|
foo, ok := opts["foo"].(string)
|
|
if !ok || foo != "fooValue" {
|
|
t.Fatalf("Did not receive expected foo string in request options: %+v", msg)
|
|
}
|
|
return map[string]any{
|
|
"Gateway": ep.gateway,
|
|
"GatewayIPv6": ep.gatewayIPv6,
|
|
"HostsPath": ep.hostsPath,
|
|
"ResolvConfPath": ep.resolvConfPath,
|
|
"InterfaceName": map[string]any{
|
|
"SrcName": ep.srcName,
|
|
"DstPrefix": ep.dstPrefix,
|
|
"DstName": ep.dstName,
|
|
},
|
|
"StaticRoutes": []map[string]any{
|
|
{
|
|
"Destination": ep.destination,
|
|
"RouteType": ep.routeType,
|
|
"NextHop": ep.nextHop,
|
|
},
|
|
},
|
|
}
|
|
})
|
|
handle(t, mux, "Leave", func(msg map[string]any) any {
|
|
return map[string]string{}
|
|
})
|
|
handle(t, mux, "DeleteEndpoint", func(msg map[string]any) any {
|
|
return map[string]any{}
|
|
})
|
|
handle(t, mux, "EndpointOperInfo", func(msg map[string]any) any {
|
|
return map[string]any{
|
|
"Value": map[string]string{
|
|
"Arbitrary": "key",
|
|
"Value": "pairs?",
|
|
},
|
|
}
|
|
})
|
|
handle(t, mux, "DiscoverNew", func(msg map[string]any) any {
|
|
return map[string]string{}
|
|
})
|
|
handle(t, mux, "DiscoverDelete", func(msg map[string]any) any {
|
|
return map[string]any{}
|
|
})
|
|
|
|
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client, err := getPluginClient(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d := newDriver(plugin, client)
|
|
if d.Type() != plugin {
|
|
t.Fatal("Driver type does not match that given")
|
|
}
|
|
|
|
c, err := d.getCapabilities()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
} else if c.DataScope != scope.Global {
|
|
t.Fatalf("get capability '%s', expecting 'global'", c.DataScope)
|
|
}
|
|
|
|
skipIPv4, skipIPv6, err := d.GetSkipGwAlloc(options.Generic{"skip4": true, "skip6": false})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
} else if !skipIPv4 || skipIPv6 {
|
|
t.Fatalf("GetSkipGwAlloc, got skipIPv4:%t skipIPv6:%t", skipIPv4, skipIPv6)
|
|
}
|
|
skipIPv4, skipIPv6, err = d.GetSkipGwAlloc(options.Generic{"skip4": false, "skip6": true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
} else if skipIPv4 || !skipIPv6 {
|
|
t.Fatalf("GetSkipGwAlloc, got skipIPv4:%t skipIPv6:%t", skipIPv4, skipIPv6)
|
|
}
|
|
|
|
netID := "dummy-network"
|
|
err = d.CreateNetwork(context.Background(), netID, map[string]any{}, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
endID := "dummy-endpoint"
|
|
ifInfo := &testEndpoint{}
|
|
err = d.CreateEndpoint(context.Background(), netID, endID, ifInfo, map[string]any{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !bytes.Equal(ep.MacAddress(), ifInfo.MacAddress()) || !types.CompareIPNet(ep.Address(), ifInfo.Address()) ||
|
|
!types.CompareIPNet(ep.AddressIPv6(), ifInfo.AddressIPv6()) {
|
|
t.Fatalf("Unexpected InterfaceInfo data. Expected (%s, %s, %s). Got (%v, %v, %v)",
|
|
ep.MacAddress(), ep.Address(), ep.AddressIPv6(),
|
|
ifInfo.MacAddress(), ifInfo.Address(), ifInfo.AddressIPv6())
|
|
}
|
|
|
|
joinOpts := map[string]any{"foo": "fooValue"}
|
|
err = d.Join(context.Background(), netID, endID, "sandbox-key", ep, nil, joinOpts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = d.EndpointOperInfo(netID, endID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = d.Leave(netID, endID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = d.DeleteEndpoint(netID, endID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = d.DeleteNetwork(netID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data := discoverapi.NodeDiscoveryData{
|
|
Address: "192.168.1.1",
|
|
}
|
|
if err = d.DiscoverNew(discoverapi.NodeDiscovery, data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = d.DiscoverDelete(discoverapi.NodeDiscovery, data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestDriverError(t *testing.T) {
|
|
plugin := "test-net-driver-error"
|
|
|
|
mux := http.NewServeMux()
|
|
defer setupPlugin(t, plugin, mux)()
|
|
|
|
handle(t, mux, "CreateEndpoint", func(msg map[string]any) any {
|
|
return map[string]any{
|
|
"Err": "this should get raised as an error",
|
|
}
|
|
})
|
|
|
|
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client, err := getPluginClient(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d := newDriver(plugin, client)
|
|
if err := d.CreateEndpoint(context.Background(), "dummy", "dummy", &testEndpoint{t: t}, map[string]any{}); err == nil {
|
|
t.Fatal("Expected error from driver")
|
|
}
|
|
}
|
|
|
|
func TestMissingValues(t *testing.T) {
|
|
plugin := "test-net-driver-missing"
|
|
|
|
mux := http.NewServeMux()
|
|
defer setupPlugin(t, plugin, mux)()
|
|
|
|
ep := &testEndpoint{
|
|
t: t,
|
|
}
|
|
|
|
handle(t, mux, "CreateEndpoint", func(msg map[string]any) any {
|
|
iface := map[string]any{
|
|
"Address": ep.address,
|
|
"AddressIPv6": ep.addressIPv6,
|
|
"MacAddress": ep.macAddress,
|
|
}
|
|
return map[string]any{
|
|
"Interface": iface,
|
|
}
|
|
})
|
|
|
|
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client, err := getPluginClient(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d := newDriver(plugin, client)
|
|
if err := d.CreateEndpoint(context.Background(), "dummy", "dummy", ep, map[string]any{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
type rollbackEndpoint struct{}
|
|
|
|
func (r *rollbackEndpoint) Interface() *rollbackEndpoint {
|
|
return r
|
|
}
|
|
|
|
func (r *rollbackEndpoint) MacAddress() net.HardwareAddr {
|
|
return nil
|
|
}
|
|
|
|
func (r *rollbackEndpoint) Address() *net.IPNet {
|
|
return nil
|
|
}
|
|
|
|
func (r *rollbackEndpoint) AddressIPv6() *net.IPNet {
|
|
return nil
|
|
}
|
|
|
|
func (r *rollbackEndpoint) SetMacAddress(mac net.HardwareAddr) error {
|
|
return errors.New("invalid mac")
|
|
}
|
|
|
|
func (r *rollbackEndpoint) SetIPAddress(ip *net.IPNet) error {
|
|
return errors.New("invalid ip")
|
|
}
|
|
|
|
func (r *rollbackEndpoint) NetnsPath() string { return "" }
|
|
|
|
func (r *rollbackEndpoint) SetCreatedInContainer(bool) {}
|
|
|
|
func TestRollback(t *testing.T) {
|
|
plugin := "test-net-driver-rollback"
|
|
|
|
mux := http.NewServeMux()
|
|
defer setupPlugin(t, plugin, mux)()
|
|
|
|
rolledback := false
|
|
|
|
handle(t, mux, "CreateEndpoint", func(msg map[string]any) any {
|
|
iface := map[string]any{
|
|
"Address": "192.168.4.5/16",
|
|
"AddressIPv6": "",
|
|
"MacAddress": "7a:12:34:56:78:90",
|
|
}
|
|
return map[string]any{
|
|
"Interface": any(iface),
|
|
}
|
|
})
|
|
handle(t, mux, "DeleteEndpoint", func(msg map[string]any) any {
|
|
rolledback = true
|
|
return map[string]any{}
|
|
})
|
|
|
|
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client, err := getPluginClient(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d := newDriver(plugin, client)
|
|
ep := &rollbackEndpoint{}
|
|
if err := d.CreateEndpoint(context.Background(), "dummy", "dummy", ep.Interface(), map[string]any{}); err == nil {
|
|
t.Fatal("Expected error from driver")
|
|
}
|
|
if !rolledback {
|
|
t.Fatal("Expected to have had DeleteEndpoint called")
|
|
}
|
|
}
|