1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #29191 from anusha-ragunathan/plugin_races

Make v2/Plugin accesses safe.
This commit is contained in:
Anusha Ragunathan 2016-12-07 17:52:44 -08:00 committed by GitHub
commit 9d898b872e
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"},