Browse code

libnetwork/drvregistry: split up the registries

There is no benefit to having a single registry for both IPAM drivers
and network drivers. IPAM drivers are registered in a separate namespace
from network drivers, have separate registration methods, separate
accessor methods and do not interact with network drivers within a
DrvRegistry in any way. The only point of commonality is

interface { GetPluginGetter() plugingetter.PluginGetter }

which is only used by the respective remote drivers and therefore should
be outside of the scope of a driver registry.

Create new, separate registry types for network drivers and IPAM
drivers, respectively. These types are "legacy-free". Neither type has
GetPluginGetter methods. The IPAMs registry does not have an
IPAMDefaultAddressSpaces method as that information can be queried
directly from the driver using its GetDefaultAddressSpaces method.
The Networks registry does not have an AddDriver method as that method
is a trivial wrapper around calling one of its arguments with its other
arguments.

Refactor DrvRegistry in terms of the new IPAMs and Networks registries
so that existing code in libnetwork and Swarmkit will continue to work.

Signed-off-by: Cory Snider <csnider@mirantis.com>

Cory Snider authored on 2023/01/25 07:27:22
Showing 7 changed files
... ...
@@ -156,12 +156,16 @@ type JoinInfo interface {
156 156
 	AddTableEntry(tableName string, key string, value []byte) error
157 157
 }
158 158
 
159
+// Registerer provides a way for network drivers to be dynamically registered.
160
+type Registerer interface {
161
+	RegisterDriver(name string, driver Driver, capability Capability) error
162
+}
163
+
159 164
 // DriverCallback provides a Callback interface for Drivers into LibNetwork
160 165
 type DriverCallback interface {
166
+	Registerer
161 167
 	// GetPluginGetter returns the pluginv2 getter.
162 168
 	GetPluginGetter() plugingetter.PluginGetter
163
-	// RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a driver instance
164
-	RegisterDriver(name string, driver Driver, capability Capability) error
165 169
 }
166 170
 
167 171
 // Capability represents the high level capabilities of the drivers which libnetwork can make use of
... ...
@@ -1,155 +1,58 @@
1 1
 package drvregistry
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
6
-	"strings"
7
-	"sync"
8 5
 
9 6
 	"github.com/docker/docker/libnetwork/driverapi"
10 7
 	"github.com/docker/docker/libnetwork/ipamapi"
11
-	"github.com/docker/docker/libnetwork/types"
12 8
 	"github.com/docker/docker/pkg/plugingetter"
13 9
 )
14 10
 
15
-type driverData struct {
16
-	driver     driverapi.Driver
17
-	capability driverapi.Capability
18
-}
19
-
20
-type ipamData struct {
21
-	driver     ipamapi.Ipam
22
-	capability *ipamapi.Capability
23
-	// default address spaces are provided by ipam driver at registration time
24
-	defaultLocalAddressSpace, defaultGlobalAddressSpace string
25
-}
26
-
27
-type driverTable map[string]*driverData
28
-type ipamTable map[string]*ipamData
29
-
30 11
 // DrvRegistry holds the registry of all network drivers and IPAM drivers that it knows about.
31 12
 type DrvRegistry struct {
32
-	sync.Mutex
33
-	drivers      driverTable
34
-	ipamDrivers  ipamTable
35
-	dfn          DriverNotifyFunc
13
+	Networks
14
+	IPAMs
36 15
 	pluginGetter plugingetter.PluginGetter
37 16
 }
38 17
 
39
-// Functors definition
18
+var _ driverapi.DriverCallback = (*DrvRegistry)(nil)
19
+var _ ipamapi.Callback = (*DrvRegistry)(nil)
40 20
 
41 21
 // InitFunc defines the driver initialization function signature.
42 22
 type InitFunc func(driverapi.DriverCallback, map[string]interface{}) error
43 23
 
44
-// IPAMWalkFunc defines the IPAM driver table walker function signature.
45
-type IPAMWalkFunc func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool
46
-
47
-// DriverWalkFunc defines the network driver table walker function signature.
48
-type DriverWalkFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) bool
49
-
50
-// DriverNotifyFunc defines the notify function signature when a new network driver gets registered.
51
-type DriverNotifyFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) error
52
-
53 24
 // Placeholder is a type for function arguments which need to be present for Swarmkit
54 25
 // to compile, but for which the only acceptable value is nil.
55 26
 type Placeholder *struct{}
56 27
 
57
-// New returns a new driver registry handle.
28
+// New returns a new legacy driver registry.
29
+//
30
+// Deprecated: use the separate [Networks] and [IPAMs] registries.
58 31
 func New(lDs, gDs Placeholder, dfn DriverNotifyFunc, ifn Placeholder, pg plugingetter.PluginGetter) (*DrvRegistry, error) {
59
-	r := &DrvRegistry{
60
-		drivers:      make(driverTable),
61
-		ipamDrivers:  make(ipamTable),
62
-		dfn:          dfn,
32
+	return &DrvRegistry{
33
+		Networks:     Networks{Notify: dfn},
63 34
 		pluginGetter: pg,
64
-	}
65
-
66
-	return r, nil
35
+	}, nil
67 36
 }
68 37
 
69 38
 // AddDriver adds a network driver to the registry.
70
-func (r *DrvRegistry) AddDriver(ntype string, fn InitFunc, config map[string]interface{}) error {
39
+//
40
+// Deprecated: call fn(r, config) directly.
41
+func (r *DrvRegistry) AddDriver(_ string, fn InitFunc, config map[string]interface{}) error {
71 42
 	return fn(r, config)
72 43
 }
73 44
 
74
-// WalkIPAMs walks the IPAM drivers registered in the registry and invokes the passed walk function and each one of them.
75
-func (r *DrvRegistry) WalkIPAMs(ifn IPAMWalkFunc) {
76
-	type ipamVal struct {
77
-		name string
78
-		data *ipamData
79
-	}
80
-
81
-	r.Lock()
82
-	ivl := make([]ipamVal, 0, len(r.ipamDrivers))
83
-	for k, v := range r.ipamDrivers {
84
-		ivl = append(ivl, ipamVal{name: k, data: v})
85
-	}
86
-	r.Unlock()
87
-
88
-	for _, iv := range ivl {
89
-		if ifn(iv.name, iv.data.driver, iv.data.capability) {
90
-			break
91
-		}
92
-	}
93
-}
94
-
95
-// WalkDrivers walks the network drivers registered in the registry and invokes the passed walk function and each one of them.
96
-func (r *DrvRegistry) WalkDrivers(dfn DriverWalkFunc) {
97
-	type driverVal struct {
98
-		name string
99
-		data *driverData
100
-	}
101
-
102
-	r.Lock()
103
-	dvl := make([]driverVal, 0, len(r.drivers))
104
-	for k, v := range r.drivers {
105
-		dvl = append(dvl, driverVal{name: k, data: v})
106
-	}
107
-	r.Unlock()
108
-
109
-	for _, dv := range dvl {
110
-		if dfn(dv.name, dv.data.driver, dv.data.capability) {
111
-			break
112
-		}
113
-	}
114
-}
115
-
116
-// Driver returns the actual network driver instance and its capability  which registered with the passed name.
117
-func (r *DrvRegistry) Driver(name string) (driverapi.Driver, *driverapi.Capability) {
118
-	r.Lock()
119
-	defer r.Unlock()
120
-
121
-	d, ok := r.drivers[name]
122
-	if !ok {
123
-		return nil, nil
124
-	}
125
-
126
-	return d.driver, &d.capability
127
-}
128
-
129
-// IPAM returns the actual IPAM driver instance and its capability which registered with the passed name.
130
-func (r *DrvRegistry) IPAM(name string) (ipamapi.Ipam, *ipamapi.Capability) {
131
-	r.Lock()
132
-	defer r.Unlock()
133
-
134
-	i, ok := r.ipamDrivers[name]
135
-	if !ok {
136
-		return nil, nil
137
-	}
138
-
139
-	return i.driver, i.capability
140
-}
141
-
142 45
 // IPAMDefaultAddressSpaces returns the default address space strings for the passed IPAM driver name.
46
+//
47
+// Deprecated: call GetDefaultAddressSpaces() on the IPAM driver.
143 48
 func (r *DrvRegistry) IPAMDefaultAddressSpaces(name string) (string, string, error) {
144
-	r.Lock()
145
-	defer r.Unlock()
49
+	d, _ := r.IPAM(name)
146 50
 
147
-	i, ok := r.ipamDrivers[name]
148
-	if !ok {
51
+	if d == nil {
149 52
 		return "", "", fmt.Errorf("ipam %s not found", name)
150 53
 	}
151 54
 
152
-	return i.defaultLocalAddressSpace, i.defaultGlobalAddressSpace, nil
55
+	return d.GetDefaultAddressSpaces()
153 56
 }
154 57
 
155 58
 // GetPluginGetter returns the plugingetter
... ...
@@ -157,65 +60,12 @@ func (r *DrvRegistry) GetPluginGetter() plugingetter.PluginGetter {
157 157
 	return r.pluginGetter
158 158
 }
159 159
 
160
-// RegisterDriver registers the network driver when it gets discovered.
161
-func (r *DrvRegistry) RegisterDriver(ntype string, driver driverapi.Driver, capability driverapi.Capability) error {
162
-	if strings.TrimSpace(ntype) == "" {
163
-		return errors.New("network type string cannot be empty")
164
-	}
165
-
166
-	r.Lock()
167
-	dd, ok := r.drivers[ntype]
168
-	r.Unlock()
169
-
170
-	if ok && dd.driver.IsBuiltIn() {
171
-		return driverapi.ErrActiveRegistration(ntype)
172
-	}
173
-
174
-	if r.dfn != nil {
175
-		if err := r.dfn(ntype, driver, capability); err != nil {
176
-			return err
177
-		}
178
-	}
179
-
180
-	dData := &driverData{driver, capability}
181
-
182
-	r.Lock()
183
-	r.drivers[ntype] = dData
184
-	r.Unlock()
185
-
186
-	return nil
187
-}
188
-
189
-func (r *DrvRegistry) registerIpamDriver(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
190
-	if strings.TrimSpace(name) == "" {
191
-		return errors.New("ipam driver name string cannot be empty")
192
-	}
193
-
194
-	r.Lock()
195
-	dd, ok := r.ipamDrivers[name]
196
-	r.Unlock()
197
-	if ok && dd.driver.IsBuiltIn() {
198
-		return types.ForbiddenErrorf("ipam driver %q already registered", name)
199
-	}
160
+// Driver returns the network driver instance registered under name, and its capability.
161
+func (r *DrvRegistry) Driver(name string) (driverapi.Driver, *driverapi.Capability) {
162
+	d, c := r.Networks.Driver(name)
200 163
 
201
-	locAS, glbAS, err := driver.GetDefaultAddressSpaces()
202
-	if err != nil {
203
-		return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err)
164
+	if c == (driverapi.Capability{}) {
165
+		return d, nil
204 166
 	}
205
-
206
-	r.Lock()
207
-	r.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS, capability: caps}
208
-	r.Unlock()
209
-
210
-	return nil
211
-}
212
-
213
-// RegisterIpamDriver registers the IPAM driver discovered with default capabilities.
214
-func (r *DrvRegistry) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
215
-	return r.registerIpamDriver(name, driver, &ipamapi.Capability{})
216
-}
217
-
218
-// RegisterIpamDriverWithCapabilities registers the IPAM driver discovered with specified capabilities.
219
-func (r *DrvRegistry) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
220
-	return r.registerIpamDriver(name, driver, caps)
167
+	return d, &c
221 168
 }
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"testing"
7 7
 
8 8
 	"github.com/docker/docker/libnetwork/datastore"
9
-	"github.com/docker/docker/libnetwork/discoverapi"
10 9
 	"github.com/docker/docker/libnetwork/driverapi"
11 10
 	"github.com/docker/docker/libnetwork/ipamapi"
12 11
 	builtinIpam "github.com/docker/docker/libnetwork/ipams/builtin"
... ...
@@ -18,48 +17,16 @@ import (
18 18
 
19 19
 const mockDriverName = "mock-driver"
20 20
 
21
-type mockDriver struct{}
22
-
23
-var md = mockDriver{}
24
-
25
-func mockDriverInit(reg driverapi.DriverCallback, opt map[string]interface{}) error {
26
-	return reg.RegisterDriver(mockDriverName, &md, driverapi.Capability{DataScope: datastore.LocalScope})
27
-}
28
-
29
-func (m *mockDriver) CreateNetwork(nid string, options map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
30
-	return nil
31
-}
32
-
33
-func (m *mockDriver) DeleteNetwork(nid string) error {
34
-	return nil
35
-}
36
-
37
-func (m *mockDriver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, options map[string]interface{}) error {
38
-	return nil
39
-}
40
-
41
-func (m *mockDriver) DeleteEndpoint(nid, eid string) error {
42
-	return nil
21
+type mockDriver struct {
22
+	driverapi.Driver
43 23
 }
44 24
 
45
-func (m *mockDriver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
46
-	return nil, nil
47
-}
25
+var mockDriverCaps = driverapi.Capability{DataScope: datastore.LocalScope}
48 26
 
49
-func (m *mockDriver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
50
-	return nil
51
-}
52
-
53
-func (m *mockDriver) Leave(nid, eid string) error {
54
-	return nil
55
-}
56
-
57
-func (m *mockDriver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
58
-	return nil
59
-}
27
+var md = mockDriver{}
60 28
 
61
-func (m *mockDriver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
62
-	return nil
29
+func mockDriverInit(reg driverapi.DriverCallback, opt map[string]interface{}) error {
30
+	return reg.RegisterDriver(mockDriverName, &md, mockDriverCaps)
63 31
 }
64 32
 
65 33
 func (m *mockDriver) Type() string {
... ...
@@ -70,29 +37,6 @@ func (m *mockDriver) IsBuiltIn() bool {
70 70
 	return true
71 71
 }
72 72
 
73
-func (m *mockDriver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
74
-	return nil
75
-}
76
-
77
-func (m *mockDriver) RevokeExternalConnectivity(nid, eid string) error {
78
-	return nil
79
-}
80
-
81
-func (m *mockDriver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
82
-	return nil, nil
83
-}
84
-
85
-func (m *mockDriver) NetworkFree(id string) error {
86
-	return nil
87
-}
88
-
89
-func (m *mockDriver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
90
-}
91
-
92
-func (m *mockDriver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
93
-	return "", nil
94
-}
95
-
96 73
 func getNew(t *testing.T) *DrvRegistry {
97 74
 	reg, err := New(nil, nil, nil, nil, nil)
98 75
 	if err != nil {
99 76
new file mode 100644
... ...
@@ -0,0 +1,84 @@
0
+package drvregistry
1
+
2
+import (
3
+	"errors"
4
+	"strings"
5
+	"sync"
6
+
7
+	"github.com/docker/docker/libnetwork/ipamapi"
8
+	"github.com/docker/docker/libnetwork/types"
9
+)
10
+
11
+type ipamDriver struct {
12
+	driver     ipamapi.Ipam
13
+	capability *ipamapi.Capability
14
+}
15
+
16
+// IPAMs is a registry of IPAM drivers. The zero value is an empty IPAM driver
17
+// registry, ready to use.
18
+type IPAMs struct {
19
+	mu      sync.Mutex
20
+	drivers map[string]ipamDriver
21
+}
22
+
23
+var _ ipamapi.Registerer = (*IPAMs)(nil)
24
+
25
+// IPAM returns the actual IPAM driver instance and its capability which registered with the passed name.
26
+func (ir *IPAMs) IPAM(name string) (ipamapi.Ipam, *ipamapi.Capability) {
27
+	ir.mu.Lock()
28
+	defer ir.mu.Unlock()
29
+
30
+	d := ir.drivers[name]
31
+	return d.driver, d.capability
32
+}
33
+
34
+// RegisterIpamDriverWithCapabilities registers the IPAM driver discovered with specified capabilities.
35
+func (ir *IPAMs) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
36
+	if strings.TrimSpace(name) == "" {
37
+		return errors.New("ipam driver name string cannot be empty")
38
+	}
39
+
40
+	ir.mu.Lock()
41
+	defer ir.mu.Unlock()
42
+
43
+	dd, ok := ir.drivers[name]
44
+	if ok && dd.driver.IsBuiltIn() {
45
+		return types.ForbiddenErrorf("ipam driver %q already registered", name)
46
+	}
47
+
48
+	if ir.drivers == nil {
49
+		ir.drivers = make(map[string]ipamDriver)
50
+	}
51
+	ir.drivers[name] = ipamDriver{driver: driver, capability: caps}
52
+
53
+	return nil
54
+}
55
+
56
+// RegisterIpamDriver registers the IPAM driver discovered with default capabilities.
57
+func (ir *IPAMs) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
58
+	return ir.RegisterIpamDriverWithCapabilities(name, driver, &ipamapi.Capability{})
59
+}
60
+
61
+// IPAMWalkFunc defines the IPAM driver table walker function signature.
62
+type IPAMWalkFunc func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool
63
+
64
+// WalkIPAMs walks the IPAM drivers registered in the registry and invokes the passed walk function and each one of them.
65
+func (ir *IPAMs) WalkIPAMs(ifn IPAMWalkFunc) {
66
+	type ipamVal struct {
67
+		name string
68
+		data ipamDriver
69
+	}
70
+
71
+	ir.mu.Lock()
72
+	ivl := make([]ipamVal, 0, len(ir.drivers))
73
+	for k, v := range ir.drivers {
74
+		ivl = append(ivl, ipamVal{name: k, data: v})
75
+	}
76
+	ir.mu.Unlock()
77
+
78
+	for _, iv := range ivl {
79
+		if ifn(iv.name, iv.data.driver, iv.data.capability) {
80
+			break
81
+		}
82
+	}
83
+}
0 84
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+package drvregistry
1
+
2
+import (
3
+	"runtime"
4
+	"sort"
5
+	"testing"
6
+
7
+	"github.com/docker/docker/libnetwork/ipamapi"
8
+	builtinIpam "github.com/docker/docker/libnetwork/ipams/builtin"
9
+	nullIpam "github.com/docker/docker/libnetwork/ipams/null"
10
+	remoteIpam "github.com/docker/docker/libnetwork/ipams/remote"
11
+	"gotest.tools/v3/assert"
12
+	is "gotest.tools/v3/assert/cmp"
13
+)
14
+
15
+func getNewIPAMs(t *testing.T) *IPAMs {
16
+	r := &IPAMs{}
17
+
18
+	assert.Assert(t, builtinIpam.Register(r))
19
+	assert.Assert(t, remoteIpam.Register(r, nil))
20
+	assert.Assert(t, nullIpam.Register(r))
21
+
22
+	return r
23
+}
24
+
25
+func TestIPAMs(t *testing.T) {
26
+	t.Run("IPAM", func(t *testing.T) {
27
+		reg := getNewIPAMs(t)
28
+
29
+		i, cap := reg.IPAM("default")
30
+		assert.Check(t, i != nil)
31
+		assert.Check(t, cap != nil)
32
+	})
33
+
34
+	t.Run("WalkIPAMs", func(t *testing.T) {
35
+		reg := getNewIPAMs(t)
36
+
37
+		ipams := make([]string, 0, 2)
38
+		reg.WalkIPAMs(func(name string, driver ipamapi.Ipam, cap *ipamapi.Capability) bool {
39
+			ipams = append(ipams, name)
40
+			return false
41
+		})
42
+
43
+		sort.Strings(ipams)
44
+		expected := []string{"default", "null"}
45
+		if runtime.GOOS == "windows" {
46
+			expected = append(expected, "windows")
47
+		}
48
+		assert.Check(t, is.DeepEqual(ipams, expected))
49
+	})
50
+}
0 51
new file mode 100644
... ...
@@ -0,0 +1,93 @@
0
+package drvregistry
1
+
2
+import (
3
+	"errors"
4
+	"strings"
5
+	"sync"
6
+
7
+	"github.com/docker/docker/libnetwork/driverapi"
8
+)
9
+
10
+// DriverWalkFunc defines the network driver table walker function signature.
11
+type DriverWalkFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) bool
12
+
13
+// DriverNotifyFunc defines the notify function signature when a new network driver gets registered.
14
+type DriverNotifyFunc func(name string, driver driverapi.Driver, capability driverapi.Capability) error
15
+
16
+type driverData struct {
17
+	driver     driverapi.Driver
18
+	capability driverapi.Capability
19
+}
20
+
21
+// Networks is a registry of network drivers. The zero value is an empty network
22
+// driver registry, ready to use.
23
+type Networks struct {
24
+	// Notify is called whenever a network driver is registered.
25
+	Notify DriverNotifyFunc
26
+
27
+	mu      sync.Mutex
28
+	drivers map[string]driverData
29
+}
30
+
31
+var _ driverapi.Registerer = (*Networks)(nil)
32
+
33
+// WalkDrivers walks the network drivers registered in the registry and invokes the passed walk function and each one of them.
34
+func (nr *Networks) WalkDrivers(dfn DriverWalkFunc) {
35
+	type driverVal struct {
36
+		name string
37
+		data driverData
38
+	}
39
+
40
+	nr.mu.Lock()
41
+	dvl := make([]driverVal, 0, len(nr.drivers))
42
+	for k, v := range nr.drivers {
43
+		dvl = append(dvl, driverVal{name: k, data: v})
44
+	}
45
+	nr.mu.Unlock()
46
+
47
+	for _, dv := range dvl {
48
+		if dfn(dv.name, dv.data.driver, dv.data.capability) {
49
+			break
50
+		}
51
+	}
52
+}
53
+
54
+// Driver returns the network driver instance registered under name, and its capability.
55
+func (nr *Networks) Driver(name string) (driverapi.Driver, driverapi.Capability) {
56
+	nr.mu.Lock()
57
+	defer nr.mu.Unlock()
58
+
59
+	d := nr.drivers[name]
60
+	return d.driver, d.capability
61
+}
62
+
63
+// RegisterDriver registers the network driver with nr.
64
+func (nr *Networks) RegisterDriver(ntype string, driver driverapi.Driver, capability driverapi.Capability) error {
65
+	if strings.TrimSpace(ntype) == "" {
66
+		return errors.New("network type string cannot be empty")
67
+	}
68
+
69
+	nr.mu.Lock()
70
+	dd, ok := nr.drivers[ntype]
71
+	nr.mu.Unlock()
72
+
73
+	if ok && dd.driver.IsBuiltIn() {
74
+		return driverapi.ErrActiveRegistration(ntype)
75
+	}
76
+
77
+	if nr.Notify != nil {
78
+		if err := nr.Notify(ntype, driver, capability); err != nil {
79
+			return err
80
+		}
81
+	}
82
+
83
+	nr.mu.Lock()
84
+	defer nr.mu.Unlock()
85
+
86
+	if nr.drivers == nil {
87
+		nr.drivers = make(map[string]driverData)
88
+	}
89
+	nr.drivers[ntype] = driverData{driver: driver, capability: capability}
90
+
91
+	return nil
92
+}
0 93
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+package drvregistry
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/libnetwork/driverapi"
6
+	"gotest.tools/v3/assert"
7
+	is "gotest.tools/v3/assert/cmp"
8
+)
9
+
10
+func TestNetworks(t *testing.T) {
11
+	t.Run("RegisterDriver", func(t *testing.T) {
12
+		var reg Networks
13
+		err := reg.RegisterDriver(mockDriverName, &md, mockDriverCaps)
14
+		assert.NilError(t, err)
15
+	})
16
+
17
+	t.Run("RegisterDuplicateDriver", func(t *testing.T) {
18
+		var reg Networks
19
+		err := reg.RegisterDriver(mockDriverName, &md, mockDriverCaps)
20
+		assert.NilError(t, err)
21
+
22
+		// Try adding the same driver
23
+		err = reg.RegisterDriver(mockDriverName, &md, mockDriverCaps)
24
+		assert.Check(t, is.ErrorContains(err, ""))
25
+	})
26
+
27
+	t.Run("Driver", func(t *testing.T) {
28
+		var reg Networks
29
+		err := reg.RegisterDriver(mockDriverName, &md, mockDriverCaps)
30
+		assert.NilError(t, err)
31
+
32
+		d, cap := reg.Driver(mockDriverName)
33
+		assert.Check(t, d != nil)
34
+		assert.Check(t, is.DeepEqual(cap, mockDriverCaps))
35
+	})
36
+
37
+	t.Run("WalkDrivers", func(t *testing.T) {
38
+		var reg Networks
39
+		err := reg.RegisterDriver(mockDriverName, &md, mockDriverCaps)
40
+		assert.NilError(t, err)
41
+
42
+		var driverName string
43
+		reg.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
44
+			driverName = name
45
+			return false
46
+		})
47
+
48
+		assert.Check(t, is.Equal(driverName, mockDriverName))
49
+	})
50
+}