f3711704 |
package plugin
import ( |
3d86b0c7 |
"encoding/json" |
1b41b7a4 |
"net" |
3d86b0c7 |
"os" |
f3711704 |
"path/filepath" |
863ab9ab |
"time" |
f3711704 |
|
3d86b0c7 |
"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon/initlayer" |
d453fe35 |
"github.com/docker/docker/errdefs" |
7a7357da |
"github.com/docker/docker/pkg/containerfs" |
09cd96c5 |
"github.com/docker/docker/pkg/idtools" |
c54b717c |
"github.com/docker/docker/pkg/mount" |
f3711704 |
"github.com/docker/docker/pkg/plugins" |
3d86b0c7 |
"github.com/docker/docker/pkg/stringid" |
27a55fba |
"github.com/docker/docker/plugin/v2" |
7a855799 |
"github.com/opencontainers/go-digest" |
3d86b0c7 |
"github.com/pkg/errors" |
1009e6a4 |
"github.com/sirupsen/logrus" |
069fdc8a |
"golang.org/x/sys/unix" |
f3711704 |
)
|
ddae20c0 |
func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) (err error) { |
3d86b0c7 |
p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs") |
27a55fba |
if p.IsEnabled() && !force { |
ebcb7d6b |
return errors.Wrap(enabledError(p.Name()), "plugin already enabled") |
b867f6c6 |
} |
3d86b0c7 |
spec, err := p.InitSpec(pm.config.ExecRoot) |
f3711704 |
if err != nil {
return err
} |
b35490a8 |
c.restart = true
c.exitChan = make(chan bool)
pm.mu.Lock()
pm.cMap[p] = c
pm.mu.Unlock()
|
e8307b86 |
var propRoot string |
c54b717c |
if p.PropagatedMount != "" { |
e8307b86 |
propRoot = filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
|
ddae20c0 |
if err = os.MkdirAll(propRoot, 0755); err != nil { |
e8307b86 |
logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err)
}
|
ddae20c0 |
if err = mount.MakeRShared(propRoot); err != nil { |
e8307b86 |
return errors.Wrap(err, "error setting up propagated mount dir")
}
|
ddae20c0 |
if err = mount.Mount(propRoot, p.PropagatedMount, "none", "rbind"); err != nil { |
e8307b86 |
return errors.Wrap(err, "error creating mount for propagated mount") |
c54b717c |
}
}
|
7a7357da |
rootFS := containerfs.NewLocalContainerFS(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName))
if err := initlayer.Setup(rootFS, idtools.IDPair{0, 0}); err != nil { |
26d0bac8 |
return errors.WithStack(err) |
3d86b0c7 |
}
|
c85e8622 |
stdout, stderr := makeLoggerStreams(p.GetID())
if err := pm.executor.Create(p.GetID(), *spec, stdout, stderr); err != nil { |
cef443bd |
if p.PropagatedMount != "" {
if err := mount.Unmount(p.PropagatedMount); err != nil {
logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
} |
e8307b86 |
if err := mount.Unmount(propRoot); err != nil {
logrus.Warnf("Could not unmount %s: %v", propRoot, err)
} |
cef443bd |
} |
f3711704 |
}
|
cb663317 |
return pm.pluginPostStart(p, c)
}
func (pm *Manager) pluginPostStart(p *v2.Plugin, c *controller) error { |
1b41b7a4 |
sockAddr := filepath.Join(pm.config.ExecRoot, p.GetID(), p.GetSocket()) |
8dd100a2 |
client, err := plugins.NewClientWithTimeout("unix://"+sockAddr, nil, time.Duration(c.timeoutInSecs)*time.Second) |
f3711704 |
if err != nil { |
b35490a8 |
c.restart = false |
c85e8622 |
shutdownPlugin(p, c, pm.executor) |
26d0bac8 |
return errors.WithStack(err) |
f3711704 |
}
|
b35490a8 |
p.SetPClient(client) |
1b41b7a4 |
|
891f9acb |
// Initial sleep before net Dial to allow plugin to listen on socket.
time.Sleep(500 * time.Millisecond) |
1b41b7a4 |
maxRetries := 3
var retries int
for { |
891f9acb |
// net dial into the unix socket to see if someone's listening.
conn, err := net.Dial("unix", sockAddr)
if err == nil {
conn.Close()
break
}
|
1b41b7a4 |
time.Sleep(3 * time.Second)
retries++
if retries > maxRetries {
logrus.Debugf("error net dialing plugin: %v", err)
c.restart = false |
bbbf64f7 |
// While restoring plugins, we need to explicitly set the state to disabled
pm.config.Store.SetState(p, false) |
c85e8622 |
shutdownPlugin(p, c, pm.executor) |
1b41b7a4 |
return err
}
} |
3d86b0c7 |
pm.config.Store.SetState(p, true)
pm.config.Store.CallHandler(p)
return pm.save(p) |
f3711704 |
}
|
27a55fba |
func (pm *Manager) restore(p *v2.Plugin) error { |
c85e8622 |
stdout, stderr := makeLoggerStreams(p.GetID())
if err := pm.executor.Restore(p.GetID(), stdout, stderr); err != nil { |
cb663317 |
return err
}
|
3d86b0c7 |
if pm.config.LiveRestoreEnabled { |
cb663317 |
c := &controller{} |
c85e8622 |
if isRunning, _ := pm.executor.IsRunning(p.GetID()); !isRunning { |
cb663317 |
// plugin is not running, so follow normal startup procedure
return pm.enable(p, c, true)
}
c.exitChan = make(chan bool)
c.restart = true
pm.mu.Lock()
pm.cMap[p] = c
pm.mu.Unlock()
return pm.pluginPostStart(p, c)
}
return nil |
dfd91873 |
}
|
c85e8622 |
func shutdownPlugin(p *v2.Plugin, c *controller, executor Executor) { |
766cc9b4 |
pluginID := p.GetID()
|
c85e8622 |
err := executor.Signal(pluginID, int(unix.SIGTERM)) |
766cc9b4 |
if err != nil {
logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err)
} else {
select { |
b35490a8 |
case <-c.exitChan: |
766cc9b4 |
logrus.Debug("Clean shutdown of plugin")
case <-time.After(time.Second * 10):
logrus.Debug("Force shutdown plugin") |
c85e8622 |
if err := executor.Signal(pluginID, int(unix.SIGKILL)); err != nil { |
766cc9b4 |
logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err)
} |
ddae20c0 |
select {
case <-c.exitChan:
logrus.Debug("SIGKILL plugin shutdown")
case <-time.After(time.Second * 10):
logrus.Debug("Force shutdown plugin FAILED")
} |
766cc9b4 |
}
}
}
|
0c2821d6 |
func setupRoot(root string) error {
if err := mount.MakePrivate(root); err != nil {
return errors.Wrap(err, "error setting plugin manager root to private")
}
return nil
}
|
b35490a8 |
func (pm *Manager) disable(p *v2.Plugin, c *controller) error { |
27a55fba |
if !p.IsEnabled() { |
ebcb7d6b |
return errors.Wrap(errDisabled(p.Name()), "plugin is already disabled") |
b867f6c6 |
} |
766cc9b4 |
|
b35490a8 |
c.restart = false |
c85e8622 |
shutdownPlugin(p, c, pm.executor) |
3d86b0c7 |
pm.config.Store.SetState(p, false)
return pm.save(p) |
f3711704 |
} |
863ab9ab |
// Shutdown stops all plugins and called during daemon shutdown.
func (pm *Manager) Shutdown() { |
3d86b0c7 |
plugins := pm.config.Store.GetAll() |
27a55fba |
for _, p := range plugins { |
b35490a8 |
pm.mu.RLock()
c := pm.cMap[p]
pm.mu.RUnlock()
|
3d86b0c7 |
if pm.config.LiveRestoreEnabled && p.IsEnabled() { |
27a55fba |
logrus.Debug("Plugin active when liveRestore is set, skipping shutdown") |
4a44cf1d |
continue
} |
c85e8622 |
if pm.executor != nil && p.IsEnabled() { |
b35490a8 |
c.restart = false |
c85e8622 |
shutdownPlugin(p, c, pm.executor) |
863ab9ab |
}
} |
0c2821d6 |
mount.Unmount(pm.config.Root) |
863ab9ab |
} |
3d86b0c7 |
|
03c69497 |
func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) {
config, err := pm.setupNewPlugin(configDigest, blobsums, privileges)
if err != nil {
return err
}
pdir := filepath.Join(pm.config.Root, p.PluginObj.ID)
orig := filepath.Join(pdir, "rootfs") |
83f44d23 |
// Make sure nothing is mounted
// This could happen if the plugin was disabled with `-f` with active mounts.
// If there is anything in `orig` is still mounted, this should error out. |
11cf394e |
if err := mount.RecursiveUnmount(orig); err != nil { |
87a12421 |
return errdefs.System(err) |
83f44d23 |
}
|
03c69497 |
backup := orig + "-old"
if err := os.Rename(orig, backup); err != nil { |
87a12421 |
return errors.Wrap(errdefs.System(err), "error backing up plugin data before upgrade") |
3d86b0c7 |
}
|
03c69497 |
defer func() {
if err != nil {
if rmErr := os.RemoveAll(orig); rmErr != nil && !os.IsNotExist(rmErr) {
logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade")
return
} |
5d25195f |
if mvErr := os.Rename(backup, orig); mvErr != nil {
err = errors.Wrap(mvErr, "error restoring old plugin root on upgrade failure") |
03c69497 |
}
if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) {
logrus.WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir)
}
} else {
if rmErr := os.RemoveAll(backup); rmErr != nil && !os.IsNotExist(rmErr) {
logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade")
}
p.Config = configDigest
p.Blobsums = blobsums
}
}()
if err := os.Rename(tmpRootFSDir, orig); err != nil { |
87a12421 |
return errors.Wrap(errdefs.System(err), "error upgrading") |
03c69497 |
}
p.PluginObj.Config = config
err = pm.save(p)
return errors.Wrap(err, "error saving upgraded plugin config")
}
func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) { |
3d86b0c7 |
configRC, err := pm.blobStore.Get(configDigest)
if err != nil { |
03c69497 |
return types.PluginConfig{}, err |
3d86b0c7 |
}
defer configRC.Close()
var config types.PluginConfig
dec := json.NewDecoder(configRC)
if err := dec.Decode(&config); err != nil { |
03c69497 |
return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config") |
3d86b0c7 |
}
if dec.More() { |
03c69497 |
return types.PluginConfig{}, errors.New("invalid config json") |
3d86b0c7 |
}
|
ebcb7d6b |
requiredPrivileges := computePrivileges(config) |
3d86b0c7 |
if err != nil { |
03c69497 |
return types.PluginConfig{}, err |
3d86b0c7 |
}
if privileges != nil {
if err := validatePrivileges(requiredPrivileges, *privileges); err != nil { |
03c69497 |
return types.PluginConfig{}, err |
3d86b0c7 |
}
}
|
03c69497 |
return config, nil
}
// createPlugin creates a new plugin. take lock before calling. |
72c3bcf2 |
func (pm *Manager) createPlugin(name string, configDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges, opts ...CreateOpt) (p *v2.Plugin, err error) { |
03c69497 |
if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store |
87a12421 |
return nil, errdefs.InvalidParameter(err) |
03c69497 |
}
config, err := pm.setupNewPlugin(configDigest, blobsums, privileges)
if err != nil {
return nil, err
}
|
3d86b0c7 |
p = &v2.Plugin{
PluginObj: types.Plugin{
Name: name,
ID: stringid.GenerateRandomID(),
Config: config,
},
Config: configDigest,
Blobsums: blobsums,
}
p.InitEmptySettings() |
72c3bcf2 |
for _, o := range opts {
o(p)
} |
3d86b0c7 |
pdir := filepath.Join(pm.config.Root, p.PluginObj.ID)
if err := os.MkdirAll(pdir, 0700); err != nil {
return nil, errors.Wrapf(err, "failed to mkdir %v", pdir)
}
defer func() {
if err != nil {
os.RemoveAll(pdir)
}
}()
if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil {
return nil, errors.Wrap(err, "failed to rename rootfs")
}
if err := pm.save(p); err != nil {
return nil, err
}
pm.config.Store.Add(p) // todo: remove
return p, nil
} |