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>
(cherry picked from commit b35490a8ba)
Signed-off-by: Victor Vieux <vieux@docker.com>
This commit is contained in:
Anusha Ragunathan 2016-12-01 11:36:56 -08:00 committed by Victor Vieux
parent f081b22a4a
commit 213ee20706
7 changed files with 133 additions and 74 deletions

View File

@ -37,7 +37,11 @@ func (pm *Manager) Disable(name string) error {
if err != nil { if err != nil {
return err return err
} }
if err := pm.disable(p); err != nil { pm.mu.RLock()
c := pm.cMap[p]
pm.mu.RUnlock()
if err := pm.disable(p, c); err != nil {
return err return err
} }
pm.pluginEventLogger(p.GetID(), name, "disable") pm.pluginEventLogger(p.GetID(), name, "disable")
@ -46,14 +50,13 @@ func (pm *Manager) Disable(name string) error {
// Enable activates a plugin, which implies that they are ready to be used by containers. // Enable activates a plugin, which implies that they are ready to be used by containers.
func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error { func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
p, err := pm.pluginStore.GetByName(name) p, err := pm.pluginStore.GetByName(name)
if err != nil { if err != nil {
return err return err
} }
p.TimeoutInSecs = config.Timeout c := &controller{timeoutInSecs: config.Timeout}
if err := pm.enable(p, false); err != nil { if err := pm.enable(p, c, false); err != nil {
return err return err
} }
pm.pluginEventLogger(p.GetID(), name, "enable") pm.pluginEventLogger(p.GetID(), name, "enable")
@ -267,25 +270,25 @@ func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.A
// Remove deletes plugin's root directory. // Remove deletes plugin's root directory.
func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error { func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
p, err := pm.pluginStore.GetByName(name) p, err := pm.pluginStore.GetByName(name)
pm.mu.RLock()
c := pm.cMap[p]
pm.mu.RUnlock()
if err != nil { if err != nil {
return err return err
} }
if !config.ForceRemove { if !config.ForceRemove {
p.RLock() if p.GetRefCount() > 0 {
if p.RefCount > 0 {
p.RUnlock()
return fmt.Errorf("plugin %s is in use", p.Name()) return fmt.Errorf("plugin %s is in use", p.Name())
} }
p.RUnlock()
if p.IsEnabled() { if p.IsEnabled() {
return fmt.Errorf("plugin %s is enabled", p.Name()) return fmt.Errorf("plugin %s is enabled", p.Name())
} }
} }
if p.IsEnabled() { if p.IsEnabled() {
if err := pm.disable(p); err != nil { if err := pm.disable(p, c); err != nil {
logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err) logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
} }
} }

View File

@ -19,7 +19,7 @@ var (
) )
func (pm *Manager) restorePlugin(p *v2.Plugin) error { func (pm *Manager) restorePlugin(p *v2.Plugin) error {
p.RuntimeSourcePath = filepath.Join(pm.runRoot, p.GetID()) p.Restore(pm.runRoot)
if p.IsEnabled() { if p.IsEnabled() {
return pm.restore(p) return pm.restore(p)
} }
@ -37,6 +37,15 @@ type Manager struct {
registryService registry.Service registryService registry.Service
liveRestore bool liveRestore bool
pluginEventLogger eventLogger pluginEventLogger eventLogger
mu sync.RWMutex // protects cMap
cMap map[*v2.Plugin]*controller
}
// controller represents the manager's control on a plugin.
type controller struct {
restart bool
exitChan chan bool
timeoutInSecs int
} }
// GetManager returns the singleton plugin Manager // GetManager returns the singleton plugin Manager
@ -67,7 +76,8 @@ func Init(root string, ps *store.Store, remote libcontainerd.Remote, rs registry
if err != nil { if err != nil {
return err return err
} }
if err := manager.init(); err != nil { manager.cMap = make(map[*v2.Plugin]*controller)
if err := manager.reload(); err != nil {
return err return err
} }
return nil return nil
@ -83,22 +93,27 @@ func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
if err != nil { if err != nil {
return err return err
} }
p.RLock()
if p.ExitChan != nil { pm.mu.RLock()
close(p.ExitChan) c := pm.cMap[p]
if c.exitChan != nil {
close(c.exitChan)
} }
restart := p.Restart restart := c.restart
p.RUnlock() pm.mu.RUnlock()
p.RemoveFromDisk() p.RemoveFromDisk()
if restart { if restart {
pm.enable(p, true) pm.enable(p, c, true)
} }
} }
return nil return nil
} }
func (pm *Manager) init() error { // reload is used on daemon restarts to load the manager's state
func (pm *Manager) reload() error {
dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json")) dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -117,6 +132,8 @@ func (pm *Manager) init() error {
var group sync.WaitGroup var group sync.WaitGroup
group.Add(len(plugins)) group.Add(len(plugins))
for _, p := range plugins { for _, p := range plugins {
c := &controller{}
pm.cMap[p] = c
go func(p *v2.Plugin) { go func(p *v2.Plugin) {
defer group.Done() defer group.Done()
if err := pm.restorePlugin(p); err != nil { if err := pm.restorePlugin(p); err != nil {
@ -129,7 +146,7 @@ func (pm *Manager) init() error {
if requiresManualRestore { if requiresManualRestore {
// if liveRestore is not enabled, the plugin will be stopped now so we should enable it // if liveRestore is not enabled, the plugin will be stopped now so we should enable it
if err := pm.enable(p, true); err != nil { if err := pm.enable(p, c, true); err != nil {
logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err) logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err)
} }
} }

View File

@ -16,7 +16,7 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
func (pm *Manager) enable(p *v2.Plugin, force bool) error { func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
if p.IsEnabled() && !force { if p.IsEnabled() && !force {
return fmt.Errorf("plugin %s is already enabled", p.Name()) return fmt.Errorf("plugin %s is already enabled", p.Name())
} }
@ -24,23 +24,26 @@ func (pm *Manager) enable(p *v2.Plugin, force bool) error {
if err != nil { if err != nil {
return err return err
} }
p.Lock()
p.Restart = true c.restart = true
p.ExitChan = make(chan bool) c.exitChan = make(chan bool)
p.Unlock()
pm.mu.Lock()
pm.cMap[p] = c
pm.mu.Unlock()
if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil { if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
return err return err
} }
p.PClient, err = plugins.NewClientWithTimeout("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil, p.TimeoutInSecs) client, err := plugins.NewClientWithTimeout("unix://"+filepath.Join(p.GetRuntimeSourcePath(), p.GetSocket()), nil, c.timeoutInSecs)
if err != nil { if err != nil {
p.Lock() c.restart = false
p.Restart = false shutdownPlugin(p, c, pm.containerdClient)
p.Unlock()
shutdownPlugin(p, pm.containerdClient)
return err return err
} }
p.SetPClient(client)
pm.pluginStore.SetState(p, true) pm.pluginStore.SetState(p, true)
pm.pluginStore.CallHandler(p) pm.pluginStore.CallHandler(p)
@ -51,7 +54,7 @@ func (pm *Manager) restore(p *v2.Plugin) error {
return pm.containerdClient.Restore(p.GetID(), attachToLog(p.GetID())) return pm.containerdClient.Restore(p.GetID(), attachToLog(p.GetID()))
} }
func shutdownPlugin(p *v2.Plugin, containerdClient libcontainerd.Client) { func shutdownPlugin(p *v2.Plugin, c *controller, containerdClient libcontainerd.Client) {
pluginID := p.GetID() pluginID := p.GetID()
err := containerdClient.Signal(pluginID, int(syscall.SIGTERM)) err := containerdClient.Signal(pluginID, int(syscall.SIGTERM))
@ -59,7 +62,7 @@ func shutdownPlugin(p *v2.Plugin, containerdClient libcontainerd.Client) {
logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err)
} else { } else {
select { select {
case <-p.ExitChan: case <-c.exitChan:
logrus.Debug("Clean shutdown of plugin") logrus.Debug("Clean shutdown of plugin")
case <-time.After(time.Second * 10): case <-time.After(time.Second * 10):
logrus.Debug("Force shutdown plugin") logrus.Debug("Force shutdown plugin")
@ -70,15 +73,13 @@ func shutdownPlugin(p *v2.Plugin, containerdClient libcontainerd.Client) {
} }
} }
func (pm *Manager) disable(p *v2.Plugin) error { func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
if !p.IsEnabled() { if !p.IsEnabled() {
return fmt.Errorf("plugin %s is already disabled", p.Name()) return fmt.Errorf("plugin %s is already disabled", p.Name())
} }
p.Lock()
p.Restart = false
p.Unlock()
shutdownPlugin(p, pm.containerdClient) c.restart = false
shutdownPlugin(p, c, pm.containerdClient)
pm.pluginStore.SetState(p, false) pm.pluginStore.SetState(p, false)
return nil return nil
} }
@ -87,15 +88,17 @@ func (pm *Manager) disable(p *v2.Plugin) error {
func (pm *Manager) Shutdown() { func (pm *Manager) Shutdown() {
plugins := pm.pluginStore.GetAll() plugins := pm.pluginStore.GetAll()
for _, p := range plugins { for _, p := range plugins {
pm.mu.RLock()
c := pm.cMap[p]
pm.mu.RUnlock()
if pm.liveRestore && p.IsEnabled() { if pm.liveRestore && p.IsEnabled() {
logrus.Debug("Plugin active when liveRestore is set, skipping shutdown") logrus.Debug("Plugin active when liveRestore is set, skipping shutdown")
continue continue
} }
if pm.containerdClient != nil && p.IsEnabled() { if pm.containerdClient != nil && p.IsEnabled() {
p.Lock() c.restart = false
p.Restart = false shutdownPlugin(p, c, pm.containerdClient)
p.Unlock()
shutdownPlugin(p, pm.containerdClient)
} }
} }
} }

View File

@ -7,7 +7,7 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
func (pm *Manager) enable(p *v2.Plugin, force bool) error { func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
return fmt.Errorf("Not implemented") return fmt.Errorf("Not implemented")
} }
@ -15,7 +15,7 @@ func (pm *Manager) initSpec(p *v2.Plugin) (*specs.Spec, error) {
return nil, fmt.Errorf("Not implemented") return nil, fmt.Errorf("Not implemented")
} }
func (pm *Manager) disable(p *v2.Plugin) error { func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
return fmt.Errorf("Not implemented") return fmt.Errorf("Not implemented")
} }

View File

@ -9,7 +9,7 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
func (pm *Manager) enable(p *v2.Plugin, force bool) error { func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
return fmt.Errorf("Not implemented") return fmt.Errorf("Not implemented")
} }
@ -17,7 +17,7 @@ func (pm *Manager) initSpec(p *v2.Plugin) (*specs.Spec, error) {
return nil, fmt.Errorf("Not implemented") return nil, fmt.Errorf("Not implemented")
} }
func (pm *Manager) disable(p *v2.Plugin) error { func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
return fmt.Errorf("Not implemented") return fmt.Errorf("Not implemented")
} }

View File

@ -174,9 +174,7 @@ func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlug
} }
p, err = ps.GetByName(fullName) p, err = ps.GetByName(fullName)
if err == nil { if err == nil {
p.Lock() p.SetRefCount(mode + p.GetRefCount())
p.RefCount += mode
p.Unlock()
if p.IsEnabled() { if p.IsEnabled() {
return p.FilterByCap(capability) return p.FilterByCap(capability)
} }

View File

@ -17,15 +17,12 @@ import (
// Plugin represents an individual plugin. // Plugin represents an individual plugin.
type Plugin struct { type Plugin struct {
sync.RWMutex mu sync.RWMutex
PluginObj types.Plugin `json:"plugin"` PluginObj types.Plugin `json:"plugin"`
PClient *plugins.Client `json:"-"` pClient *plugins.Client
RuntimeSourcePath string `json:"-"` runtimeSourcePath string
RefCount int `json:"-"` refCount int
Restart bool `json:"-"` libRoot string
ExitChan chan bool `json:"-"`
LibRoot string `json:"-"`
TimeoutInSecs int `json:"-"`
} }
const defaultPluginRuntimeDestination = "/run/docker/plugins" const defaultPluginRuntimeDestination = "/run/docker/plugins"
@ -47,14 +44,39 @@ func newPluginObj(name, id, tag string) types.Plugin {
func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin { func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
return &Plugin{ return &Plugin{
PluginObj: newPluginObj(name, id, tag), PluginObj: newPluginObj(name, id, tag),
RuntimeSourcePath: filepath.Join(runRoot, id), runtimeSourcePath: filepath.Join(runRoot, id),
LibRoot: libRoot, libRoot: libRoot,
} }
} }
// Restore restores the plugin
func (p *Plugin) Restore(runRoot string) {
p.runtimeSourcePath = filepath.Join(runRoot, p.GetID())
}
// GetRuntimeSourcePath gets the Source (host) path of the plugin socket
// This path gets bind mounted into the plugin.
func (p *Plugin) GetRuntimeSourcePath() string {
p.mu.RLock()
defer p.mu.RUnlock()
return p.runtimeSourcePath
}
// Client returns the plugin client. // Client returns the plugin client.
func (p *Plugin) Client() *plugins.Client { func (p *Plugin) Client() *plugins.Client {
return p.PClient p.mu.RLock()
defer p.mu.RUnlock()
return p.pClient
}
// SetPClient set the plugin client.
func (p *Plugin) SetPClient(client *plugins.Client) {
p.mu.Lock()
defer p.mu.Unlock()
p.pClient = client
} }
// IsV1 returns true for V1 plugins and false otherwise. // IsV1 returns true for V1 plugins and false otherwise.
@ -85,12 +107,12 @@ func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
// RemoveFromDisk deletes the plugin's runtime files from disk. // RemoveFromDisk deletes the plugin's runtime files from disk.
func (p *Plugin) RemoveFromDisk() error { func (p *Plugin) RemoveFromDisk() error {
return os.RemoveAll(p.RuntimeSourcePath) return os.RemoveAll(p.runtimeSourcePath)
} }
// InitPlugin populates the plugin object from the plugin config file. // InitPlugin populates the plugin object from the plugin config file.
func (p *Plugin) InitPlugin() error { func (p *Plugin) InitPlugin() error {
dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json")) dt, err := os.Open(filepath.Join(p.libRoot, p.PluginObj.ID, "config.json"))
if err != nil { if err != nil {
return err return err
} }
@ -118,7 +140,7 @@ func (p *Plugin) InitPlugin() error {
} }
func (p *Plugin) writeSettings() error { func (p *Plugin) writeSettings() error {
f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json")) f, err := os.Create(filepath.Join(p.libRoot, p.PluginObj.ID, "plugin-settings.json"))
if err != nil { if err != nil {
return err return err
} }
@ -129,8 +151,8 @@ func (p *Plugin) writeSettings() error {
// Set is used to pass arguments to the plugin. // Set is used to pass arguments to the plugin.
func (p *Plugin) Set(args []string) error { func (p *Plugin) Set(args []string) error {
p.Lock() p.mu.Lock()
defer p.Unlock() defer p.mu.Unlock()
if p.PluginObj.Enabled { if p.PluginObj.Enabled {
return fmt.Errorf("cannot set on an active plugin, disable plugin before setting") return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
@ -218,36 +240,52 @@ next:
// IsEnabled returns the active state of the plugin. // IsEnabled returns the active state of the plugin.
func (p *Plugin) IsEnabled() bool { func (p *Plugin) IsEnabled() bool {
p.RLock() p.mu.RLock()
defer p.RUnlock() defer p.mu.RUnlock()
return p.PluginObj.Enabled return p.PluginObj.Enabled
} }
// GetID returns the plugin's ID. // GetID returns the plugin's ID.
func (p *Plugin) GetID() string { func (p *Plugin) GetID() string {
p.RLock() p.mu.RLock()
defer p.RUnlock() defer p.mu.RUnlock()
return p.PluginObj.ID return p.PluginObj.ID
} }
// GetSocket returns the plugin socket. // GetSocket returns the plugin socket.
func (p *Plugin) GetSocket() string { func (p *Plugin) GetSocket() string {
p.RLock() p.mu.RLock()
defer p.RUnlock() defer p.mu.RUnlock()
return p.PluginObj.Config.Interface.Socket return p.PluginObj.Config.Interface.Socket
} }
// GetTypes returns the interface types of a plugin. // GetTypes returns the interface types of a plugin.
func (p *Plugin) GetTypes() []types.PluginInterfaceType { func (p *Plugin) GetTypes() []types.PluginInterfaceType {
p.RLock() p.mu.RLock()
defer p.RUnlock() defer p.mu.RUnlock()
return p.PluginObj.Config.Interface.Types return p.PluginObj.Config.Interface.Types
} }
// GetRefCount returns the reference count.
func (p *Plugin) GetRefCount() int {
p.mu.RLock()
defer p.mu.RUnlock()
return p.refCount
}
// SetRefCount sets the reference count.
func (p *Plugin) SetRefCount(count int) {
p.mu.Lock()
defer p.mu.Unlock()
p.refCount = count
}
// InitSpec creates an OCI spec from the plugin's config. // InitSpec creates an OCI spec from the plugin's config.
func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
@ -262,7 +300,7 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
} }
mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
Source: &p.RuntimeSourcePath, Source: &p.runtimeSourcePath,
Destination: defaultPluginRuntimeDestination, Destination: defaultPluginRuntimeDestination,
Type: "bind", Type: "bind",
Options: []string{"rbind", "rshared"}, Options: []string{"rbind", "rshared"},