From f539be8a63002610a38fec9f1aa9d3dff7895cbb Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan <mrjana@docker.com> Date: Mon, 29 Feb 2016 18:17:04 -0800 Subject: [PATCH] Create driver registry package Currently driver management logic is tightly coupled with libnetwork package and that makes it very difficult to modularize it and use it separately. This PR modularizes the driver management logic by creating a driver registry package. Signed-off-by: Jana Radhakrishnan <mrjana@docker.com> --- libnetwork/controller.go | 262 +++++++++------------ libnetwork/drivers.go | 84 ------- libnetwork/drvregistry/drvregistry.go | 241 +++++++++++++++++++ libnetwork/drvregistry/drvregistry_test.go | 161 +++++++++++++ libnetwork/endpoint.go | 2 +- libnetwork/libnetwork_internal_test.go | 56 +---- libnetwork/network.go | 86 +++---- 7 files changed, 568 insertions(+), 324 deletions(-) delete mode 100644 libnetwork/drivers.go create mode 100644 libnetwork/drvregistry/drvregistry.go create mode 100644 libnetwork/drvregistry/drvregistry_test.go diff --git a/libnetwork/controller.go b/libnetwork/controller.go index 0b5ee8746c..812814a19a 100644 --- a/libnetwork/controller.go +++ b/libnetwork/controller.go @@ -58,6 +58,7 @@ import ( "github.com/docker/libnetwork/datastore" "github.com/docker/libnetwork/discoverapi" "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/drvregistry" "github.com/docker/libnetwork/hostdiscovery" "github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/netlabel" @@ -119,26 +120,11 @@ type NetworkWalker func(nw Network) bool // When the function returns true, the walk will stop. type SandboxWalker func(sb Sandbox) bool -type driverData struct { - driver driverapi.Driver - capability driverapi.Capability -} - -type ipamData struct { - driver ipamapi.Ipam - capability *ipamapi.Capability - // default address spaces are provided by ipam driver at registration time - defaultLocalAddressSpace, defaultGlobalAddressSpace string -} - -type driverTable map[string]*driverData -type ipamTable map[string]*ipamData type sandboxTable map[string]*sandbox type controller struct { id string - drivers driverTable - ipamDrivers ipamTable + drvRegistry *drvregistry.DrvRegistry sandboxes sandboxTable cfg *config.Config stores []datastore.DataStore @@ -153,21 +139,44 @@ type controller struct { sync.Mutex } +type initializer struct { + fn drvregistry.InitFunc + ntype string +} + // New creates a new instance of network controller. func New(cfgOptions ...config.Option) (NetworkController, error) { c := &controller{ - id: stringid.GenerateRandomID(), - cfg: config.ParseConfigOptions(cfgOptions...), - sandboxes: sandboxTable{}, - drivers: driverTable{}, - ipamDrivers: ipamTable{}, - svcDb: make(map[string]svcInfo), + id: stringid.GenerateRandomID(), + cfg: config.ParseConfigOptions(cfgOptions...), + sandboxes: sandboxTable{}, + svcDb: make(map[string]svcInfo), } if err := c.initStores(); err != nil { return nil, err } + drvRegistry, err := drvregistry.New(c.getStore(datastore.LocalScope), c.getStore(datastore.GlobalScope), c.RegisterDriver, nil) + if err != nil { + return nil, err + } + + for _, i := range getInitializers() { + var dcfg map[string]interface{} + + // External plugins don't need config passed through daemon. They can + // bootstrap themselves + if i.ntype != "remote" { + dcfg = c.makeDriverConfig(i.ntype) + } + + if err := drvRegistry.AddDriver(i.ntype, i.fn, dcfg); err != nil { + return nil, err + } + } + c.drvRegistry = drvRegistry + if c.cfg != nil && c.cfg.Cluster.Watcher != nil { if err := c.initDiscovery(c.cfg.Cluster.Watcher); err != nil { // Failing to initialize discovery is a bad situation to be in. @@ -176,15 +185,6 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { } } - if err := initDrivers(c); err != nil { - return nil, err - } - - if err := initIpams(c, c.getStore(datastore.LocalScope), - c.getStore(datastore.GlobalScope)); err != nil { - return nil, err - } - c.sandboxCleanup() c.cleanupLocalEndpoints() c.networkCleanup() @@ -196,6 +196,43 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { return c, nil } +func (c *controller) makeDriverConfig(ntype string) map[string]interface{} { + if c.cfg == nil { + return nil + } + + config := make(map[string]interface{}) + + for _, label := range c.cfg.Daemon.Labels { + if !strings.HasPrefix(netlabel.Key(label), netlabel.DriverPrefix+"."+ntype) { + continue + } + + config[netlabel.Key(label)] = netlabel.Value(label) + } + + drvCfg, ok := c.cfg.Daemon.DriverCfg[ntype] + if ok { + for k, v := range drvCfg.(map[string]interface{}) { + config[k] = v + } + } + + for k, v := range c.cfg.Scopes { + if !v.IsValid() { + continue + } + config[netlabel.MakeKVClient(k)] = discoverapi.DatastoreConfigData{ + Scope: k, + Provider: v.Client.Provider, + Address: v.Client.Address, + Config: v.Client.Config, + } + } + + return config +} + var procReloadConfig = make(chan (bool), 1) func (c *controller) ReloadConfiguration(cfgOptions ...config.Option) error { @@ -255,19 +292,21 @@ func (c *controller) ReloadConfiguration(cfgOptions ...config.Option) error { return nil } - for nm, id := range c.getIpamDrivers() { - err := id.driver.DiscoverNew(discoverapi.DatastoreConfig, *dsConfig) + c.drvRegistry.WalkIPAMs(func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool { + err := driver.DiscoverNew(discoverapi.DatastoreConfig, *dsConfig) if err != nil { - log.Errorf("Failed to set datastore in driver %s: %v", nm, err) + log.Errorf("Failed to set datastore in driver %s: %v", name, err) } - } + return false + }) - for nm, id := range c.getNetDrivers() { - err := id.driver.DiscoverNew(discoverapi.DatastoreConfig, *dsConfig) + c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool { + err := driver.DiscoverNew(discoverapi.DatastoreConfig, *dsConfig) if err != nil { - log.Errorf("Failed to set datastore in driver %s: %v", nm, err) + log.Errorf("Failed to set datastore in driver %s: %v", name, err) } - } + return false + }) return nil } @@ -333,34 +372,30 @@ func (c *controller) hostLeaveCallback(nodes []net.IP) { } func (c *controller) processNodeDiscovery(nodes []net.IP, add bool) { - c.Lock() - drivers := []*driverData{} - for _, d := range c.drivers { - drivers = append(drivers, d) - } - c.Unlock() - - for _, d := range drivers { - c.pushNodeDiscovery(d, nodes, add) - } + c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool { + c.pushNodeDiscovery(driver, capability, nodes, add) + return false + }) } -func (c *controller) pushNodeDiscovery(d *driverData, nodes []net.IP, add bool) { +func (c *controller) pushNodeDiscovery(d driverapi.Driver, cap driverapi.Capability, nodes []net.IP, add bool) { var self net.IP if c.cfg != nil { addr := strings.Split(c.cfg.Cluster.Address, ":") self = net.ParseIP(addr[0]) } - if d == nil || d.capability.DataScope != datastore.GlobalScope || nodes == nil { + + if d == nil || cap.DataScope != datastore.GlobalScope || nodes == nil { return } + for _, node := range nodes { nodeData := discoverapi.NodeDiscoveryData{Address: node.String(), Self: node.Equal(self)} var err error if add { - err = d.driver.DiscoverNew(discoverapi.NodeDiscovery, nodeData) + err = d.DiscoverNew(discoverapi.NodeDiscovery, nodeData) } else { - err = d.driver.DiscoverDelete(discoverapi.NodeDiscovery, nodeData) + err = d.DiscoverDelete(discoverapi.NodeDiscovery, nodeData) } if err != nil { log.Debugf("discovery notification error : %v", err) @@ -378,59 +413,17 @@ func (c *controller) Config() config.Config { } func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, capability driverapi.Capability) error { - if !config.IsValidName(networkType) { - return ErrInvalidName(networkType) - } - c.Lock() - if _, ok := c.drivers[networkType]; ok { - c.Unlock() - return driverapi.ErrActiveRegistration(networkType) - } - dData := &driverData{driver, capability} - c.drivers[networkType] = dData hd := c.discovery c.Unlock() if hd != nil { - c.pushNodeDiscovery(dData, hd.Fetch(), true) + c.pushNodeDiscovery(driver, capability, hd.Fetch(), true) } return nil } -func (c *controller) registerIpamDriver(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error { - if !config.IsValidName(name) { - return ErrInvalidName(name) - } - - c.Lock() - _, ok := c.ipamDrivers[name] - c.Unlock() - if ok { - return types.ForbiddenErrorf("ipam driver %q already registered", name) - } - locAS, glbAS, err := driver.GetDefaultAddressSpaces() - if err != nil { - return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err) - } - c.Lock() - c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS, capability: caps} - c.Unlock() - - log.Debugf("Registering ipam driver: %q", name) - - return nil -} - -func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error { - return c.registerIpamDriver(name, driver, &ipamapi.Capability{}) -} - -func (c *controller) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error { - return c.registerIpamDriver(name, driver, caps) -} - // NewNetwork creates a new network of the specified network type. The options // are network specific and modeled in a generic way. func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) { @@ -745,78 +738,47 @@ func SandboxKeyWalker(out *Sandbox, key string) SandboxWalker { } } -func (c *controller) loadDriver(networkType string) (*driverData, error) { +func (c *controller) loadDriver(networkType string) error { // Plugins pkg performs lazy loading of plugins that acts as remote drivers. // As per the design, this Get call will result in remote driver discovery if there is a corresponding plugin available. _, err := plugins.Get(networkType, driverapi.NetworkPluginEndpointType) if err != nil { if err == plugins.ErrNotFound { - return nil, types.NotFoundErrorf(err.Error()) + return types.NotFoundErrorf(err.Error()) } - return nil, err + return err } - c.Lock() - defer c.Unlock() - dd, ok := c.drivers[networkType] - if !ok { - return nil, ErrInvalidNetworkDriver(networkType) - } - return dd, nil + + return nil } -func (c *controller) loadIpamDriver(name string) (*ipamData, error) { +func (c *controller) loadIPAMDriver(name string) error { if _, err := plugins.Get(name, ipamapi.PluginEndpointType); err != nil { if err == plugins.ErrNotFound { - return nil, types.NotFoundErrorf(err.Error()) + return types.NotFoundErrorf(err.Error()) } - return nil, err + return err } - c.Lock() - id, ok := c.ipamDrivers[name] - c.Unlock() - if !ok { - return nil, types.BadRequestErrorf("invalid ipam driver: %q", name) - } - return id, nil + + return nil } -func (c *controller) getIPAM(name string) (id *ipamData, err error) { - var ok bool - c.Lock() - id, ok = c.ipamDrivers[name] - c.Unlock() - if !ok { - id, err = c.loadIpamDriver(name) - } - return id, err -} +func (c *controller) getIPAMDriver(name string) (ipamapi.Ipam, *ipamapi.Capability, error) { + id, cap := c.drvRegistry.IPAM(name) + if id == nil { + // Might be a plugin name. Try loading it + if err := c.loadIPAMDriver(name); err != nil { + return nil, nil, err + } -func (c *controller) getIpamDriver(name string) (ipamapi.Ipam, error) { - id, err := c.getIPAM(name) - if err != nil { - return nil, err + // Now that we resolved the plugin, try again looking up the registry + id, cap = c.drvRegistry.IPAM(name) + if id == nil { + return nil, nil, types.BadRequestErrorf("invalid ipam driver: %q", name) + } } - return id.driver, nil -} -func (c *controller) getIpamDrivers() ipamTable { - c.Lock() - defer c.Unlock() - table := ipamTable{} - for i, d := range c.ipamDrivers { - table[i] = d - } - return table -} - -func (c *controller) getNetDrivers() driverTable { - c.Lock() - defer c.Unlock() - table := driverTable{} - for i, d := range c.drivers { - table[i] = d - } - return table + return id, cap, nil } func (c *controller) Stop() { diff --git a/libnetwork/drivers.go b/libnetwork/drivers.go deleted file mode 100644 index 566d330ff4..0000000000 --- a/libnetwork/drivers.go +++ /dev/null @@ -1,84 +0,0 @@ -package libnetwork - -import ( - "strings" - - "github.com/docker/libnetwork/discoverapi" - "github.com/docker/libnetwork/driverapi" - "github.com/docker/libnetwork/ipamapi" - "github.com/docker/libnetwork/netlabel" - - builtinIpam "github.com/docker/libnetwork/ipams/builtin" - nullIpam "github.com/docker/libnetwork/ipams/null" - remoteIpam "github.com/docker/libnetwork/ipams/remote" -) - -type initializer struct { - fn func(driverapi.DriverCallback, map[string]interface{}) error - ntype string -} - -func initDrivers(c *controller) error { - for _, i := range getInitializers() { - if err := i.fn(c, makeDriverConfig(c, i.ntype)); err != nil { - return err - } - } - - return nil -} - -func makeDriverConfig(c *controller, ntype string) map[string]interface{} { - if c.cfg == nil { - return nil - } - - config := make(map[string]interface{}) - - for _, label := range c.cfg.Daemon.Labels { - if !strings.HasPrefix(netlabel.Key(label), netlabel.DriverPrefix+"."+ntype) { - continue - } - - config[netlabel.Key(label)] = netlabel.Value(label) - } - - drvCfg, ok := c.cfg.Daemon.DriverCfg[ntype] - if ok { - for k, v := range drvCfg.(map[string]interface{}) { - config[k] = v - } - } - - // We don't send datastore configs to external plugins - if ntype == "remote" { - return config - } - - for k, v := range c.cfg.Scopes { - if !v.IsValid() { - continue - } - config[netlabel.MakeKVClient(k)] = discoverapi.DatastoreConfigData{ - Scope: k, - Provider: v.Client.Provider, - Address: v.Client.Address, - Config: v.Client.Config, - } - } - - return config -} - -func initIpams(ic ipamapi.Callback, lDs, gDs interface{}) error { - for _, fn := range [](func(ipamapi.Callback, interface{}, interface{}) error){ - builtinIpam.Init, - remoteIpam.Init, - nullIpam.Init, - } { - if err := fn(ic, lDs, gDs); err != nil { - return err - } - } - return nil -} diff --git a/libnetwork/drvregistry/drvregistry.go b/libnetwork/drvregistry/drvregistry.go new file mode 100644 index 0000000000..6c1804dfa9 --- /dev/null +++ b/libnetwork/drvregistry/drvregistry.go @@ -0,0 +1,241 @@ +package drvregistry + +import ( + "fmt" + "strings" + "sync" + + "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/ipamapi" + "github.com/docker/libnetwork/types" + + builtinIpam "github.com/docker/libnetwork/ipams/builtin" + nullIpam "github.com/docker/libnetwork/ipams/null" + remoteIpam "github.com/docker/libnetwork/ipams/remote" +) + +type driverData struct { + driver driverapi.Driver + capability driverapi.Capability +} + +type ipamData struct { + driver ipamapi.Ipam + capability *ipamapi.Capability + // default address spaces are provided by ipam driver at registration time + defaultLocalAddressSpace, defaultGlobalAddressSpace string +} + +type driverTable map[string]*driverData +type ipamTable map[string]*ipamData + +// DrvRegistry holds the registry of all network drivers and IPAM drivers that it knows about. +type DrvRegistry struct { + sync.Mutex + drivers driverTable + ipamDrivers ipamTable + dfn DriverNotifyFunc + ifn IPAMNotifyFunc +} + +// Functors definition + +// InitFunc defines the driver initialization function signature. +type InitFunc func(driverapi.DriverCallback, map[string]interface{}) error + +// IPAMWalkFunc defines the IPAM driver table walker function signature. +type IPAMWalkFunc func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool + +// DriverWalkFunc defines the network driver table walker function signature. +type DriverWalkFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) bool + +// IPAMNotifyFunc defines the notify function signature when a new IPAM driver gets registered. +type IPAMNotifyFunc func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) error + +// DriverNotifyFunc defines the notify function signature when a new network driver gets registered. +type DriverNotifyFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) error + +// New retruns a new driver registry handle. +func New(lDs, gDs interface{}, dfn DriverNotifyFunc, ifn IPAMNotifyFunc) (*DrvRegistry, error) { + r := &DrvRegistry{ + drivers: make(driverTable), + ipamDrivers: make(ipamTable), + dfn: dfn, + ifn: ifn, + } + + if err := r.initIPAMs(lDs, gDs); err != nil { + return nil, err + } + + return r, nil +} + +// AddDriver adds a network driver to the registry. +func (r *DrvRegistry) AddDriver(ntype string, fn InitFunc, config map[string]interface{}) error { + return fn(r, config) +} + +// WalkIPAMs walks the IPAM drivers registered in the registry and invokes the passed walk function and each one of them. +func (r *DrvRegistry) WalkIPAMs(ifn IPAMWalkFunc) { + type ipamVal struct { + name string + data *ipamData + } + + r.Lock() + ivl := make([]ipamVal, 0, len(r.ipamDrivers)) + for k, v := range r.ipamDrivers { + ivl = append(ivl, ipamVal{name: k, data: v}) + } + r.Unlock() + + for _, iv := range ivl { + if ifn(iv.name, iv.data.driver, iv.data.capability) { + break + } + } +} + +// WalkDrivers walks the network drivers registered in the registry and invokes the passed walk function and each one of them. +func (r *DrvRegistry) WalkDrivers(dfn DriverWalkFunc) { + type driverVal struct { + name string + data *driverData + } + + r.Lock() + dvl := make([]driverVal, 0, len(r.drivers)) + for k, v := range r.drivers { + dvl = append(dvl, driverVal{name: k, data: v}) + } + r.Unlock() + + for _, dv := range dvl { + if dfn(dv.name, dv.data.driver, dv.data.capability) { + break + } + } +} + +// Driver returns the actual network driver instance and its capability which registered with the passed name. +func (r *DrvRegistry) Driver(name string) (driverapi.Driver, *driverapi.Capability) { + r.Lock() + defer r.Unlock() + + d, ok := r.drivers[name] + if !ok { + return nil, nil + } + + return d.driver, &d.capability +} + +// IPAM returns the actual IPAM driver instance and its capability which registered with the passed name. +func (r *DrvRegistry) IPAM(name string) (ipamapi.Ipam, *ipamapi.Capability) { + r.Lock() + defer r.Unlock() + + i, ok := r.ipamDrivers[name] + if !ok { + return nil, nil + } + + return i.driver, i.capability +} + +// IPAMDefaultAddressSpaces returns the default address space strings for the passed IPAM driver name. +func (r *DrvRegistry) IPAMDefaultAddressSpaces(name string) (string, string, error) { + r.Lock() + defer r.Unlock() + + i, ok := r.ipamDrivers[name] + if !ok { + return "", "", fmt.Errorf("ipam %s not found", name) + } + + return i.defaultLocalAddressSpace, i.defaultGlobalAddressSpace, nil +} + +func (r *DrvRegistry) initIPAMs(lDs, gDs interface{}) error { + for _, fn := range [](func(ipamapi.Callback, interface{}, interface{}) error){ + builtinIpam.Init, + remoteIpam.Init, + nullIpam.Init, + } { + if err := fn(r, lDs, gDs); err != nil { + return err + } + } + + return nil +} + +// RegisterDriver registers the network driver when it gets discovered. +func (r *DrvRegistry) RegisterDriver(ntype string, driver driverapi.Driver, capability driverapi.Capability) error { + if strings.TrimSpace(ntype) == "" { + return fmt.Errorf("network type string cannot be empty") + } + + r.Lock() + _, ok := r.drivers[ntype] + r.Unlock() + + if ok { + return driverapi.ErrActiveRegistration(ntype) + } + + if r.dfn != nil { + if err := r.dfn(ntype, driver, capability); err != nil { + return err + } + } + + dData := &driverData{driver, capability} + + r.Lock() + r.drivers[ntype] = dData + r.Unlock() + + return nil +} + +func (r *DrvRegistry) registerIpamDriver(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error { + if strings.TrimSpace(name) == "" { + return fmt.Errorf("ipam driver name string cannot be empty") + } + + r.Lock() + _, ok := r.ipamDrivers[name] + r.Unlock() + if ok { + return types.ForbiddenErrorf("ipam driver %q already registered", name) + } + + locAS, glbAS, err := driver.GetDefaultAddressSpaces() + if err != nil { + return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err) + } + + if r.ifn != nil { + if err := r.ifn(name, driver, caps); err != nil { + return err + } + } + + r.Lock() + r.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS, capability: caps} + r.Unlock() + + return nil +} + +// RegisterIpamDriver registers the IPAM driver discovered with default capabilities. +func (r *DrvRegistry) RegisterIpamDriver(name string, driver ipamapi.Ipam) error { + return r.registerIpamDriver(name, driver, &ipamapi.Capability{}) +} + +// RegisterIpamDriverWithCapabilities registers the IPAM driver discovered with specified capabilities. +func (r *DrvRegistry) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error { + return r.registerIpamDriver(name, driver, caps) +} diff --git a/libnetwork/drvregistry/drvregistry_test.go b/libnetwork/drvregistry/drvregistry_test.go new file mode 100644 index 0000000000..d5d873c4c8 --- /dev/null +++ b/libnetwork/drvregistry/drvregistry_test.go @@ -0,0 +1,161 @@ +package drvregistry + +import ( + "flag" + "sort" + "testing" + + "github.com/docker/libnetwork/datastore" + "github.com/docker/libnetwork/discoverapi" + "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/ipamapi" + "github.com/stretchr/testify/assert" +) + +var runningInContainer = flag.Bool("incontainer", false, + "Indicates if the test is running in a container") + +const mockDriverName = "mock-driver" + +type mockDriver struct{} + +var md = mockDriver{} + +func mockDriverInit(reg driverapi.DriverCallback, opt map[string]interface{}) error { + return reg.RegisterDriver(mockDriverName, &md, driverapi.Capability{DataScope: datastore.LocalScope}) +} + +func (m *mockDriver) CreateNetwork(nid string, options map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { + return nil +} + +func (m *mockDriver) DeleteNetwork(nid string) error { + return nil +} + +func (m *mockDriver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, options map[string]interface{}) error { + return nil +} + +func (m *mockDriver) DeleteEndpoint(nid, eid string) error { + return nil +} + +func (m *mockDriver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) { + return nil, nil +} + +func (m *mockDriver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error { + return nil +} + +func (m *mockDriver) Leave(nid, eid string) error { + return nil +} + +func (m *mockDriver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error { + return nil +} + +func (m *mockDriver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error { + return nil +} + +func (m *mockDriver) Type() string { + return mockDriverName +} + +func (m *mockDriver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error { + return nil +} + +func (m *mockDriver) RevokeExternalConnectivity(nid, eid string) error { + return nil +} + +func getNew(t *testing.T) *DrvRegistry { + reg, err := New(nil, nil, nil, nil) + if err != nil { + t.Fatal(err) + } + + return reg +} + +func TestNew(t *testing.T) { + getNew(t) +} + +func TestAddDriver(t *testing.T) { + reg := getNew(t) + + err := reg.AddDriver(mockDriverName, mockDriverInit, nil) + assert.NoError(t, err) +} + +func TestAddDuplicateDriver(t *testing.T) { + reg := getNew(t) + + err := reg.AddDriver(mockDriverName, mockDriverInit, nil) + assert.NoError(t, err) + + // Try adding the same driver + err = reg.AddDriver(mockDriverName, mockDriverInit, nil) + assert.Error(t, err) +} + +func TestIPAMDefaultAddressSpaces(t *testing.T) { + reg := getNew(t) + + as1, as2, err := reg.IPAMDefaultAddressSpaces("default") + assert.NoError(t, err) + assert.NotEqual(t, as1, "") + assert.NotEqual(t, as2, "") +} + +func TestDriver(t *testing.T) { + reg := getNew(t) + + err := reg.AddDriver(mockDriverName, mockDriverInit, nil) + assert.NoError(t, err) + + d, cap := reg.Driver(mockDriverName) + assert.NotEqual(t, d, nil) + assert.NotEqual(t, cap, nil) +} + +func TestIPAM(t *testing.T) { + reg := getNew(t) + + i, cap := reg.IPAM("default") + assert.NotEqual(t, i, nil) + assert.NotEqual(t, cap, nil) +} + +func TestWalkIPAMs(t *testing.T) { + reg := getNew(t) + + ipams := make([]string, 0, 2) + reg.WalkIPAMs(func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool { + ipams = append(ipams, name) + return false + }) + + sort.Strings(ipams) + assert.Equal(t, ipams, []string{"default", "null"}) +} + +func TestWalkDrivers(t *testing.T) { + reg := getNew(t) + + err := reg.AddDriver(mockDriverName, mockDriverInit, nil) + assert.NoError(t, err) + + var driverName string + reg.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool { + driverName = name + return false + }) + + assert.Equal(t, driverName, mockDriverName) +} diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index 55b3a8e1ab..09f26f6e7c 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -977,7 +977,7 @@ func (ep *endpoint) releaseAddress() { log.Debugf("Releasing addresses for endpoint %s's interface on network %s", ep.Name(), n.Name()) - ipam, err := n.getController().getIpamDriver(n.ipamType) + ipam, _, err := n.getController().getIPAMDriver(n.ipamType) if err != nil { log.Warnf("Failed to retrieve ipam driver to release interface address on delete of endpoint %s (%s): %v", ep.Name(), ep.ID(), err) return diff --git a/libnetwork/libnetwork_internal_test.go b/libnetwork/libnetwork_internal_test.go index b62e43bc39..6094a03b8a 100644 --- a/libnetwork/libnetwork_internal_test.go +++ b/libnetwork/libnetwork_internal_test.go @@ -15,50 +15,6 @@ import ( "github.com/docker/libnetwork/types" ) -func TestDriverRegistration(t *testing.T) { - bridgeNetType := "bridge" - c, err := New() - if err != nil { - t.Fatal(err) - } - defer c.Stop() - err = c.(*controller).RegisterDriver(bridgeNetType, nil, driverapi.Capability{}) - if err == nil { - t.Fatalf("Expecting the RegisterDriver to fail for %s", bridgeNetType) - } - if _, ok := err.(driverapi.ErrActiveRegistration); !ok { - t.Fatalf("Failed for unexpected reason: %v", err) - } - err = c.(*controller).RegisterDriver("test-dummy", nil, driverapi.Capability{}) - if err != nil { - t.Fatalf("Test failed with an error %v", err) - } -} - -func TestIpamDriverRegistration(t *testing.T) { - c, err := New() - if err != nil { - t.Fatal(err) - } - defer c.Stop() - - err = c.(*controller).RegisterIpamDriver("", nil) - if err == nil { - t.Fatalf("Expected failure, but succeeded") - } - if _, ok := err.(types.BadRequestError); !ok { - t.Fatalf("Failed for unexpected reason: %v", err) - } - - err = c.(*controller).RegisterIpamDriver(ipamapi.DefaultIPAM, nil) - if err == nil { - t.Fatalf("Expected failure, but succeeded") - } - if _, ok := err.(types.ForbiddenError); !ok { - t.Fatalf("Failed for unexpected reason: %v", err) - } -} - func TestNetworkMarshalling(t *testing.T) { n := &network{ name: "Miao", @@ -375,8 +331,10 @@ func TestIpamReleaseOnNetDriverFailures(t *testing.T) { defer c.Stop() cc := c.(*controller) - bd := badDriver{failNetworkCreation: true} - cc.drivers[badDriverName] = &driverData{driver: &bd, capability: driverapi.Capability{DataScope: datastore.LocalScope}} + + if err := cc.drvRegistry.AddDriver(badDriverName, badDriverInit, nil); err != nil { + t.Fatal(err) + } // Test whether ipam state release is invoked on network create failure from net driver // by checking whether subsequent network creation requesting same gateway IP succeeds @@ -429,6 +387,12 @@ type badDriver struct { failNetworkCreation bool } +var bd = badDriver{failNetworkCreation: true} + +func badDriverInit(reg driverapi.DriverCallback, opt map[string]interface{}) error { + return reg.RegisterDriver(badDriverName, &bd, driverapi.Capability{DataScope: datastore.LocalScope}) +} + func (b *badDriver) CreateNetwork(nid string, options map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { if b.failNetworkCreation { return fmt.Errorf("I will not create any network") diff --git a/libnetwork/network.go b/libnetwork/network.go index a14550cd7e..c73642569a 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -620,49 +620,52 @@ func (n *network) processOptions(options ...NetworkOption) { } } -func (n *network) driverScope() string { +func (n *network) resolveDriver(name string, load bool) (driverapi.Driver, *driverapi.Capability, error) { c := n.getController() - c.Lock() // Check if a driver for the specified network type is available - dd, ok := c.drivers[n.networkType] - c.Unlock() + d, cap := c.drvRegistry.Driver(name) + if d == nil { + if load { + var err error + err = c.loadDriver(name) + if err != nil { + return nil, nil, err + } - if !ok { - var err error - dd, err = c.loadDriver(n.networkType) - if err != nil { - // If driver could not be resolved simply return an empty string - return "" + d, cap = c.drvRegistry.Driver(name) + if d == nil { + return nil, nil, fmt.Errorf("could not resolve driver %s in registry", name) + } + } else { + // don't fail if driver loading is not required + return nil, nil, nil } } - return dd.capability.DataScope + return d, cap, nil +} + +func (n *network) driverScope() string { + _, cap, err := n.resolveDriver(n.networkType, true) + if err != nil { + // If driver could not be resolved simply return an empty string + return "" + } + + return cap.DataScope } func (n *network) driver(load bool) (driverapi.Driver, error) { - c := n.getController() - - c.Lock() - // Check if a driver for the specified network type is available - dd, ok := c.drivers[n.networkType] - c.Unlock() - - if !ok && load { - var err error - dd, err = c.loadDriver(n.networkType) - if err != nil { - return nil, err - } - } else if !ok { - // don't fail if driver loading is not required - return nil, nil + d, cap, err := n.resolveDriver(n.networkType, load) + if err != nil { + return nil, err } n.Lock() - n.scope = dd.capability.DataScope + n.scope = cap.DataScope n.Unlock() - return dd.driver, nil + return d, nil } func (n *network) Delete() error { @@ -786,12 +789,12 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi } } - ipam, err := n.getController().getIPAM(n.ipamType) + ipam, cap, err := n.getController().getIPAMDriver(n.ipamType) if err != nil { return nil, err } - if ipam.capability.RequiresMACAddress { + if cap.RequiresMACAddress { if ep.iface.mac == nil { ep.iface.mac = netutils.GenerateRandomMAC() } @@ -801,7 +804,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String() } - if err = ep.assignAddress(ipam.driver, true, n.enableIPv6 && !n.postIPv6); err != nil { + if err = ep.assignAddress(ipam, true, n.enableIPv6 && !n.postIPv6); err != nil { return nil, err } defer func() { @@ -821,7 +824,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi } }() - if err = ep.assignAddress(ipam.driver, false, n.enableIPv6 && n.postIPv6); err != nil { + if err = ep.assignAddress(ipam, false, n.enableIPv6 && n.postIPv6); err != nil { return nil, err } @@ -1065,7 +1068,7 @@ func (n *network) ipamAllocate() error { return nil } - ipam, err := n.getController().getIpamDriver(n.ipamType) + ipam, _, err := n.getController().getIPAMDriver(n.ipamType) if err != nil { return err } @@ -1189,7 +1192,7 @@ func (n *network) ipamRelease() { if n.Type() == "host" || n.Type() == "null" { return } - ipam, err := n.getController().getIpamDriver(n.ipamType) + ipam, _, err := n.getController().getIPAMDriver(n.ipamType) if err != nil { log.Warnf("Failed to retrieve ipam driver to release address pool(s) on delete of network %s (%s): %v", n.Name(), n.ID(), err) return @@ -1279,17 +1282,14 @@ func (n *network) getIPData(ipVer int) []driverapi.IPAMData { } func (n *network) deriveAddressSpace() (string, error) { - c := n.getController() - c.Lock() - ipd, ok := c.ipamDrivers[n.ipamType] - c.Unlock() - if !ok { - return "", types.NotFoundErrorf("could not find ipam driver %s to get default address space", n.ipamType) + local, global, err := n.getController().drvRegistry.IPAMDefaultAddressSpaces(n.ipamType) + if err != nil { + return "", types.NotFoundErrorf("failed to get default address space: %v", err) } if n.DataScope() == datastore.GlobalScope { - return ipd.defaultGlobalAddressSpace, nil + return global, nil } - return ipd.defaultLocalAddressSpace, nil + return local, nil } func (n *network) Info() NetworkInfo {