Browse code

Make v2/Plugin accesses safe.

v2/Plugin struct had fields that were
- purely used by the manager.
- unsafely exposed without proper locking.
This change fixes this, by moving relevant fields to the manager as well
as making remaining fields as private and providing proper accessors for
them.

Signed-off-by: Anusha Ragunathan <anusha@docker.com>

Anusha Ragunathan authored on 2016/12/02 04:36:56
Showing 7 changed files
... ...
@@ -37,7 +37,11 @@ func (pm *Manager) Disable(name string) error {
37 37
 	if err != nil {
38 38
 		return err
39 39
 	}
40
-	if err := pm.disable(p); err != nil {
40
+	pm.mu.RLock()
41
+	c := pm.cMap[p]
42
+	pm.mu.RUnlock()
43
+
44
+	if err := pm.disable(p, c); err != nil {
41 45
 		return err
42 46
 	}
43 47
 	pm.pluginEventLogger(p.GetID(), name, "disable")
... ...
@@ -46,14 +50,13 @@ func (pm *Manager) Disable(name string) error {
46 46
 
47 47
 // Enable activates a plugin, which implies that they are ready to be used by containers.
48 48
 func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
49
-
50 49
 	p, err := pm.pluginStore.GetByName(name)
51 50
 	if err != nil {
52 51
 		return err
53 52
 	}
54 53
 
55
-	p.TimeoutInSecs = config.Timeout
56
-	if err := pm.enable(p, false); err != nil {
54
+	c := &controller{timeoutInSecs: config.Timeout}
55
+	if err := pm.enable(p, c, false); err != nil {
57 56
 		return err
58 57
 	}
59 58
 	pm.pluginEventLogger(p.GetID(), name, "enable")
... ...
@@ -267,25 +270,25 @@ func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.A
267 267
 // Remove deletes plugin's root directory.
268 268
 func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
269 269
 	p, err := pm.pluginStore.GetByName(name)
270
+	pm.mu.RLock()
271
+	c := pm.cMap[p]
272
+	pm.mu.RUnlock()
273
+
270 274
 	if err != nil {
271 275
 		return err
272 276
 	}
273 277
 
274 278
 	if !config.ForceRemove {
275
-		p.RLock()
276
-		if p.RefCount > 0 {
277
-			p.RUnlock()
279
+		if p.GetRefCount() > 0 {
278 280
 			return fmt.Errorf("plugin %s is in use", p.Name())
279 281
 		}
280
-		p.RUnlock()
281
-
282 282
 		if p.IsEnabled() {
283 283
 			return fmt.Errorf("plugin %s is enabled", p.Name())
284 284
 		}
285 285
 	}
286 286
 
287 287
 	if p.IsEnabled() {
288
-		if err := pm.disable(p); err != nil {
288
+		if err := pm.disable(p, c); err != nil {
289 289
 			logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
290 290
 		}
291 291
 	}
... ...
@@ -19,7 +19,7 @@ var (
19 19
 )
20 20
 
21 21
 func (pm *Manager) restorePlugin(p *v2.Plugin) error {
22
-	p.RuntimeSourcePath = filepath.Join(pm.runRoot, p.GetID())
22
+	p.Restore(pm.runRoot)
23 23
 	if p.IsEnabled() {
24 24
 		return pm.restore(p)
25 25
 	}
... ...
@@ -37,6 +37,15 @@ type Manager struct {
37 37
 	registryService   registry.Service
38 38
 	liveRestore       bool
39 39
 	pluginEventLogger eventLogger
40
+	mu                sync.RWMutex // protects cMap
41
+	cMap              map[*v2.Plugin]*controller
42
+}
43
+
44
+// controller represents the manager's control on a plugin.
45
+type controller struct {
46
+	restart       bool
47
+	exitChan      chan bool
48
+	timeoutInSecs int
40 49
 }
41 50
 
42 51
 // GetManager returns the singleton plugin Manager
... ...
@@ -67,7 +76,8 @@ func Init(root string, ps *store.Store, remote libcontainerd.Remote, rs registry
67 67
 	if err != nil {
68 68
 		return err
69 69
 	}
70
-	if err := manager.init(); err != nil {
70
+	manager.cMap = make(map[*v2.Plugin]*controller)
71
+	if err := manager.reload(); err != nil {
71 72
 		return err
72 73
 	}
73 74
 	return nil
... ...
@@ -83,22 +93,27 @@ func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
83 83
 		if err != nil {
84 84
 			return err
85 85
 		}
86
-		p.RLock()
87
-		if p.ExitChan != nil {
88
-			close(p.ExitChan)
86
+
87
+		pm.mu.RLock()
88
+		c := pm.cMap[p]
89
+
90
+		if c.exitChan != nil {
91
+			close(c.exitChan)
89 92
 		}
90
-		restart := p.Restart
91
-		p.RUnlock()
93
+		restart := c.restart
94
+		pm.mu.RUnlock()
95
+
92 96
 		p.RemoveFromDisk()
93 97
 		if restart {
94
-			pm.enable(p, true)
98
+			pm.enable(p, c, true)
95 99
 		}
96 100
 	}
97 101
 
98 102
 	return nil
99 103
 }
100 104
 
101
-func (pm *Manager) init() error {
105
+// reload is used on daemon restarts to load the manager's state
106
+func (pm *Manager) reload() error {
102 107
 	dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
103 108
 	if err != nil {
104 109
 		if os.IsNotExist(err) {
... ...
@@ -117,6 +132,8 @@ func (pm *Manager) init() error {
117 117
 	var group sync.WaitGroup
118 118
 	group.Add(len(plugins))
119 119
 	for _, p := range plugins {
120
+		c := &controller{}
121
+		pm.cMap[p] = c
120 122
 		go func(p *v2.Plugin) {
121 123
 			defer group.Done()
122 124
 			if err := pm.restorePlugin(p); err != nil {
... ...
@@ -129,7 +146,7 @@ func (pm *Manager) init() error {
129 129
 
130 130
 			if requiresManualRestore {
131 131
 				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
132
-				if err := pm.enable(p, true); err != nil {
132
+				if err := pm.enable(p, c, true); err != nil {
133 133
 					logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err)
134 134
 				}
135 135
 			}
... ...
@@ -16,7 +16,7 @@ import (
16 16
 	specs "github.com/opencontainers/runtime-spec/specs-go"
17 17
 )
18 18
 
19
-func (pm *Manager) enable(p *v2.Plugin, force bool) error {
19
+func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
20 20
 	if p.IsEnabled() && !force {
21 21
 		return fmt.Errorf("plugin %s is already enabled", p.Name())
22 22
 	}
... ...
@@ -24,23 +24,26 @@ func (pm *Manager) enable(p *v2.Plugin, force bool) error {
24 24
 	if err != nil {
25 25
 		return err
26 26
 	}
27
-	p.Lock()
28
-	p.Restart = true
29
-	p.ExitChan = make(chan bool)
30
-	p.Unlock()
27
+
28
+	c.restart = true
29
+	c.exitChan = make(chan bool)
30
+
31
+	pm.mu.Lock()
32
+	pm.cMap[p] = c
33
+	pm.mu.Unlock()
34
+
31 35
 	if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
32 36
 		return err
33 37
 	}
34 38
 
35
-	p.PClient, err = plugins.NewClientWithTimeout("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil, p.TimeoutInSecs)
39
+	client, err := plugins.NewClientWithTimeout("unix://"+filepath.Join(p.GetRuntimeSourcePath(), p.GetSocket()), nil, c.timeoutInSecs)
36 40
 	if err != nil {
37
-		p.Lock()
38
-		p.Restart = false
39
-		p.Unlock()
40
-		shutdownPlugin(p, pm.containerdClient)
41
+		c.restart = false
42
+		shutdownPlugin(p, c, pm.containerdClient)
41 43
 		return err
42 44
 	}
43 45
 
46
+	p.SetPClient(client)
44 47
 	pm.pluginStore.SetState(p, true)
45 48
 	pm.pluginStore.CallHandler(p)
46 49
 
... ...
@@ -51,7 +54,7 @@ func (pm *Manager) restore(p *v2.Plugin) error {
51 51
 	return pm.containerdClient.Restore(p.GetID(), attachToLog(p.GetID()))
52 52
 }
53 53
 
54
-func shutdownPlugin(p *v2.Plugin, containerdClient libcontainerd.Client) {
54
+func shutdownPlugin(p *v2.Plugin, c *controller, containerdClient libcontainerd.Client) {
55 55
 	pluginID := p.GetID()
56 56
 
57 57
 	err := containerdClient.Signal(pluginID, int(syscall.SIGTERM))
... ...
@@ -59,7 +62,7 @@ func shutdownPlugin(p *v2.Plugin, containerdClient libcontainerd.Client) {
59 59
 		logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err)
60 60
 	} else {
61 61
 		select {
62
-		case <-p.ExitChan:
62
+		case <-c.exitChan:
63 63
 			logrus.Debug("Clean shutdown of plugin")
64 64
 		case <-time.After(time.Second * 10):
65 65
 			logrus.Debug("Force shutdown plugin")
... ...
@@ -70,15 +73,13 @@ func shutdownPlugin(p *v2.Plugin, containerdClient libcontainerd.Client) {
70 70
 	}
71 71
 }
72 72
 
73
-func (pm *Manager) disable(p *v2.Plugin) error {
73
+func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
74 74
 	if !p.IsEnabled() {
75 75
 		return fmt.Errorf("plugin %s is already disabled", p.Name())
76 76
 	}
77
-	p.Lock()
78
-	p.Restart = false
79
-	p.Unlock()
80 77
 
81
-	shutdownPlugin(p, pm.containerdClient)
78
+	c.restart = false
79
+	shutdownPlugin(p, c, pm.containerdClient)
82 80
 	pm.pluginStore.SetState(p, false)
83 81
 	return nil
84 82
 }
... ...
@@ -87,15 +88,17 @@ func (pm *Manager) disable(p *v2.Plugin) error {
87 87
 func (pm *Manager) Shutdown() {
88 88
 	plugins := pm.pluginStore.GetAll()
89 89
 	for _, p := range plugins {
90
+		pm.mu.RLock()
91
+		c := pm.cMap[p]
92
+		pm.mu.RUnlock()
93
+
90 94
 		if pm.liveRestore && p.IsEnabled() {
91 95
 			logrus.Debug("Plugin active when liveRestore is set, skipping shutdown")
92 96
 			continue
93 97
 		}
94 98
 		if pm.containerdClient != nil && p.IsEnabled() {
95
-			p.Lock()
96
-			p.Restart = false
97
-			p.Unlock()
98
-			shutdownPlugin(p, pm.containerdClient)
99
+			c.restart = false
100
+			shutdownPlugin(p, c, pm.containerdClient)
99 101
 		}
100 102
 	}
101 103
 }
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	specs "github.com/opencontainers/runtime-spec/specs-go"
8 8
 )
9 9
 
10
-func (pm *Manager) enable(p *v2.Plugin, force bool) error {
10
+func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
11 11
 	return fmt.Errorf("Not implemented")
12 12
 }
13 13
 
... ...
@@ -15,7 +15,7 @@ func (pm *Manager) initSpec(p *v2.Plugin) (*specs.Spec, error) {
15 15
 	return nil, fmt.Errorf("Not implemented")
16 16
 }
17 17
 
18
-func (pm *Manager) disable(p *v2.Plugin) error {
18
+func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
19 19
 	return fmt.Errorf("Not implemented")
20 20
 }
21 21
 
... ...
@@ -9,7 +9,7 @@ import (
9 9
 	specs "github.com/opencontainers/runtime-spec/specs-go"
10 10
 )
11 11
 
12
-func (pm *Manager) enable(p *v2.Plugin, force bool) error {
12
+func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
13 13
 	return fmt.Errorf("Not implemented")
14 14
 }
15 15
 
... ...
@@ -17,7 +17,7 @@ func (pm *Manager) initSpec(p *v2.Plugin) (*specs.Spec, error) {
17 17
 	return nil, fmt.Errorf("Not implemented")
18 18
 }
19 19
 
20
-func (pm *Manager) disable(p *v2.Plugin) error {
20
+func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
21 21
 	return fmt.Errorf("Not implemented")
22 22
 }
23 23
 
... ...
@@ -174,9 +174,7 @@ func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlug
174 174
 		}
175 175
 		p, err = ps.GetByName(fullName)
176 176
 		if err == nil {
177
-			p.Lock()
178
-			p.RefCount += mode
179
-			p.Unlock()
177
+			p.SetRefCount(mode + p.GetRefCount())
180 178
 			if p.IsEnabled() {
181 179
 				return p.FilterByCap(capability)
182 180
 			}
... ...
@@ -17,15 +17,12 @@ import (
17 17
 
18 18
 // Plugin represents an individual plugin.
19 19
 type Plugin struct {
20
-	sync.RWMutex
21
-	PluginObj         types.Plugin    `json:"plugin"`
22
-	PClient           *plugins.Client `json:"-"`
23
-	RuntimeSourcePath string          `json:"-"`
24
-	RefCount          int             `json:"-"`
25
-	Restart           bool            `json:"-"`
26
-	ExitChan          chan bool       `json:"-"`
27
-	LibRoot           string          `json:"-"`
28
-	TimeoutInSecs     int             `json:"-"`
20
+	mu                sync.RWMutex
21
+	PluginObj         types.Plugin `json:"plugin"`
22
+	pClient           *plugins.Client
23
+	runtimeSourcePath string
24
+	refCount          int
25
+	libRoot           string
29 26
 }
30 27
 
31 28
 const defaultPluginRuntimeDestination = "/run/docker/plugins"
... ...
@@ -47,14 +44,39 @@ func newPluginObj(name, id, tag string) types.Plugin {
47 47
 func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
48 48
 	return &Plugin{
49 49
 		PluginObj:         newPluginObj(name, id, tag),
50
-		RuntimeSourcePath: filepath.Join(runRoot, id),
51
-		LibRoot:           libRoot,
50
+		runtimeSourcePath: filepath.Join(runRoot, id),
51
+		libRoot:           libRoot,
52 52
 	}
53 53
 }
54 54
 
55
+// Restore restores the plugin
56
+func (p *Plugin) Restore(runRoot string) {
57
+	p.runtimeSourcePath = filepath.Join(runRoot, p.GetID())
58
+}
59
+
60
+// GetRuntimeSourcePath gets the Source (host) path of the plugin socket
61
+// This path gets bind mounted into the plugin.
62
+func (p *Plugin) GetRuntimeSourcePath() string {
63
+	p.mu.RLock()
64
+	defer p.mu.RUnlock()
65
+
66
+	return p.runtimeSourcePath
67
+}
68
+
55 69
 // Client returns the plugin client.
56 70
 func (p *Plugin) Client() *plugins.Client {
57
-	return p.PClient
71
+	p.mu.RLock()
72
+	defer p.mu.RUnlock()
73
+
74
+	return p.pClient
75
+}
76
+
77
+// SetPClient set the plugin client.
78
+func (p *Plugin) SetPClient(client *plugins.Client) {
79
+	p.mu.Lock()
80
+	defer p.mu.Unlock()
81
+
82
+	p.pClient = client
58 83
 }
59 84
 
60 85
 // IsV1 returns true for V1 plugins and false otherwise.
... ...
@@ -85,12 +107,12 @@ func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
85 85
 
86 86
 // RemoveFromDisk deletes the plugin's runtime files from disk.
87 87
 func (p *Plugin) RemoveFromDisk() error {
88
-	return os.RemoveAll(p.RuntimeSourcePath)
88
+	return os.RemoveAll(p.runtimeSourcePath)
89 89
 }
90 90
 
91 91
 // InitPlugin populates the plugin object from the plugin config file.
92 92
 func (p *Plugin) InitPlugin() error {
93
-	dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json"))
93
+	dt, err := os.Open(filepath.Join(p.libRoot, p.PluginObj.ID, "config.json"))
94 94
 	if err != nil {
95 95
 		return err
96 96
 	}
... ...
@@ -118,7 +140,7 @@ func (p *Plugin) InitPlugin() error {
118 118
 }
119 119
 
120 120
 func (p *Plugin) writeSettings() error {
121
-	f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json"))
121
+	f, err := os.Create(filepath.Join(p.libRoot, p.PluginObj.ID, "plugin-settings.json"))
122 122
 	if err != nil {
123 123
 		return err
124 124
 	}
... ...
@@ -129,8 +151,8 @@ func (p *Plugin) writeSettings() error {
129 129
 
130 130
 // Set is used to pass arguments to the plugin.
131 131
 func (p *Plugin) Set(args []string) error {
132
-	p.Lock()
133
-	defer p.Unlock()
132
+	p.mu.Lock()
133
+	defer p.mu.Unlock()
134 134
 
135 135
 	if p.PluginObj.Enabled {
136 136
 		return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
... ...
@@ -218,36 +240,52 @@ next:
218 218
 
219 219
 // IsEnabled returns the active state of the plugin.
220 220
 func (p *Plugin) IsEnabled() bool {
221
-	p.RLock()
222
-	defer p.RUnlock()
221
+	p.mu.RLock()
222
+	defer p.mu.RUnlock()
223 223
 
224 224
 	return p.PluginObj.Enabled
225 225
 }
226 226
 
227 227
 // GetID returns the plugin's ID.
228 228
 func (p *Plugin) GetID() string {
229
-	p.RLock()
230
-	defer p.RUnlock()
229
+	p.mu.RLock()
230
+	defer p.mu.RUnlock()
231 231
 
232 232
 	return p.PluginObj.ID
233 233
 }
234 234
 
235 235
 // GetSocket returns the plugin socket.
236 236
 func (p *Plugin) GetSocket() string {
237
-	p.RLock()
238
-	defer p.RUnlock()
237
+	p.mu.RLock()
238
+	defer p.mu.RUnlock()
239 239
 
240 240
 	return p.PluginObj.Config.Interface.Socket
241 241
 }
242 242
 
243 243
 // GetTypes returns the interface types of a plugin.
244 244
 func (p *Plugin) GetTypes() []types.PluginInterfaceType {
245
-	p.RLock()
246
-	defer p.RUnlock()
245
+	p.mu.RLock()
246
+	defer p.mu.RUnlock()
247 247
 
248 248
 	return p.PluginObj.Config.Interface.Types
249 249
 }
250 250
 
251
+// GetRefCount returns the reference count.
252
+func (p *Plugin) GetRefCount() int {
253
+	p.mu.RLock()
254
+	defer p.mu.RUnlock()
255
+
256
+	return p.refCount
257
+}
258
+
259
+// SetRefCount sets the reference count.
260
+func (p *Plugin) SetRefCount(count int) {
261
+	p.mu.Lock()
262
+	defer p.mu.Unlock()
263
+
264
+	p.refCount = count
265
+}
266
+
251 267
 // InitSpec creates an OCI spec from the plugin's config.
252 268
 func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
253 269
 	rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
... ...
@@ -262,7 +300,7 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
262 262
 	}
263 263
 
264 264
 	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
265
-		Source:      &p.RuntimeSourcePath,
265
+		Source:      &p.runtimeSourcePath,
266 266
 		Destination: defaultPluginRuntimeDestination,
267 267
 		Type:        "bind",
268 268
 		Options:     []string{"rbind", "rshared"},