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 7608dd7457..4c126984b8 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -981,7 +981,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 {