github.com/olljanat/moby@v1.13.1/plugin/manager_linux.go (about)

     1  // +build linux
     2  
     3  package plugin
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/distribution/digest"
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/docker/docker/daemon/initlayer"
    17  	"github.com/docker/docker/libcontainerd"
    18  	"github.com/docker/docker/pkg/mount"
    19  	"github.com/docker/docker/pkg/plugins"
    20  	"github.com/docker/docker/pkg/stringid"
    21  	"github.com/docker/docker/plugin/v2"
    22  	specs "github.com/opencontainers/runtime-spec/specs-go"
    23  	"github.com/pkg/errors"
    24  )
    25  
    26  func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
    27  	p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs")
    28  	if p.IsEnabled() && !force {
    29  		return fmt.Errorf("plugin %s is already enabled", p.Name())
    30  	}
    31  	spec, err := p.InitSpec(pm.config.ExecRoot)
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	c.restart = true
    37  	c.exitChan = make(chan bool)
    38  
    39  	pm.mu.Lock()
    40  	pm.cMap[p] = c
    41  	pm.mu.Unlock()
    42  
    43  	var propRoot string
    44  	if p.PropagatedMount != "" {
    45  		propRoot = filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
    46  
    47  		if err := os.MkdirAll(propRoot, 0755); err != nil {
    48  			logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err)
    49  		}
    50  
    51  		if err := mount.MakeRShared(propRoot); err != nil {
    52  			return errors.Wrap(err, "error setting up propagated mount dir")
    53  		}
    54  
    55  		if err := mount.Mount(propRoot, p.PropagatedMount, "none", "rbind"); err != nil {
    56  			return errors.Wrap(err, "error creating mount for propagated mount")
    57  		}
    58  	}
    59  
    60  	if err := initlayer.Setup(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName), 0, 0); err != nil {
    61  		return errors.WithStack(err)
    62  	}
    63  
    64  	if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
    65  		if p.PropagatedMount != "" {
    66  			if err := mount.Unmount(p.PropagatedMount); err != nil {
    67  				logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
    68  			}
    69  			if err := mount.Unmount(propRoot); err != nil {
    70  				logrus.Warnf("Could not unmount %s: %v", propRoot, err)
    71  			}
    72  		}
    73  		return errors.WithStack(err)
    74  	}
    75  
    76  	return pm.pluginPostStart(p, c)
    77  }
    78  
    79  func (pm *Manager) pluginPostStart(p *v2.Plugin, c *controller) error {
    80  	client, err := plugins.NewClientWithTimeout("unix://"+filepath.Join(pm.config.ExecRoot, p.GetID(), p.GetSocket()), nil, c.timeoutInSecs)
    81  	if err != nil {
    82  		c.restart = false
    83  		shutdownPlugin(p, c, pm.containerdClient)
    84  		return errors.WithStack(err)
    85  	}
    86  
    87  	p.SetPClient(client)
    88  	pm.config.Store.SetState(p, true)
    89  	pm.config.Store.CallHandler(p)
    90  
    91  	return pm.save(p)
    92  }
    93  
    94  func (pm *Manager) restore(p *v2.Plugin) error {
    95  	if err := pm.containerdClient.Restore(p.GetID(), attachToLog(p.GetID())); err != nil {
    96  		return err
    97  	}
    98  
    99  	if pm.config.LiveRestoreEnabled {
   100  		c := &controller{}
   101  		if pids, _ := pm.containerdClient.GetPidsForContainer(p.GetID()); len(pids) == 0 {
   102  			// plugin is not running, so follow normal startup procedure
   103  			return pm.enable(p, c, true)
   104  		}
   105  
   106  		c.exitChan = make(chan bool)
   107  		c.restart = true
   108  		pm.mu.Lock()
   109  		pm.cMap[p] = c
   110  		pm.mu.Unlock()
   111  		return pm.pluginPostStart(p, c)
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func shutdownPlugin(p *v2.Plugin, c *controller, containerdClient libcontainerd.Client) {
   118  	pluginID := p.GetID()
   119  
   120  	err := containerdClient.Signal(pluginID, int(syscall.SIGTERM))
   121  	if err != nil {
   122  		logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err)
   123  	} else {
   124  		select {
   125  		case <-c.exitChan:
   126  			logrus.Debug("Clean shutdown of plugin")
   127  		case <-time.After(time.Second * 10):
   128  			logrus.Debug("Force shutdown plugin")
   129  			if err := containerdClient.Signal(pluginID, int(syscall.SIGKILL)); err != nil {
   130  				logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err)
   131  			}
   132  		}
   133  	}
   134  }
   135  
   136  func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
   137  	if !p.IsEnabled() {
   138  		return fmt.Errorf("plugin %s is already disabled", p.Name())
   139  	}
   140  
   141  	c.restart = false
   142  	shutdownPlugin(p, c, pm.containerdClient)
   143  	pm.config.Store.SetState(p, false)
   144  	return pm.save(p)
   145  }
   146  
   147  // Shutdown stops all plugins and called during daemon shutdown.
   148  func (pm *Manager) Shutdown() {
   149  	plugins := pm.config.Store.GetAll()
   150  	for _, p := range plugins {
   151  		pm.mu.RLock()
   152  		c := pm.cMap[p]
   153  		pm.mu.RUnlock()
   154  
   155  		if pm.config.LiveRestoreEnabled && p.IsEnabled() {
   156  			logrus.Debug("Plugin active when liveRestore is set, skipping shutdown")
   157  			continue
   158  		}
   159  		if pm.containerdClient != nil && p.IsEnabled() {
   160  			c.restart = false
   161  			shutdownPlugin(p, c, pm.containerdClient)
   162  		}
   163  	}
   164  }
   165  
   166  func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) {
   167  	config, err := pm.setupNewPlugin(configDigest, blobsums, privileges)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	pdir := filepath.Join(pm.config.Root, p.PluginObj.ID)
   173  	orig := filepath.Join(pdir, "rootfs")
   174  	backup := orig + "-old"
   175  	if err := os.Rename(orig, backup); err != nil {
   176  		return err
   177  	}
   178  
   179  	defer func() {
   180  		if err != nil {
   181  			if rmErr := os.RemoveAll(orig); rmErr != nil && !os.IsNotExist(rmErr) {
   182  				logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade")
   183  				return
   184  			}
   185  
   186  			if err := os.Rename(backup, orig); err != nil {
   187  				err = errors.Wrap(err, "error restoring old plugin root on upgrade failure")
   188  			}
   189  			if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) {
   190  				logrus.WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir)
   191  			}
   192  		} else {
   193  			if rmErr := os.RemoveAll(backup); rmErr != nil && !os.IsNotExist(rmErr) {
   194  				logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade")
   195  			}
   196  
   197  			p.Config = configDigest
   198  			p.Blobsums = blobsums
   199  		}
   200  	}()
   201  
   202  	if err := os.Rename(tmpRootFSDir, orig); err != nil {
   203  		return errors.Wrap(err, "error upgrading")
   204  	}
   205  
   206  	p.PluginObj.Config = config
   207  	err = pm.save(p)
   208  	return errors.Wrap(err, "error saving upgraded plugin config")
   209  }
   210  
   211  func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) {
   212  	configRC, err := pm.blobStore.Get(configDigest)
   213  	if err != nil {
   214  		return types.PluginConfig{}, err
   215  	}
   216  	defer configRC.Close()
   217  
   218  	var config types.PluginConfig
   219  	dec := json.NewDecoder(configRC)
   220  	if err := dec.Decode(&config); err != nil {
   221  		return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config")
   222  	}
   223  	if dec.More() {
   224  		return types.PluginConfig{}, errors.New("invalid config json")
   225  	}
   226  
   227  	requiredPrivileges, err := computePrivileges(config)
   228  	if err != nil {
   229  		return types.PluginConfig{}, err
   230  	}
   231  	if privileges != nil {
   232  		if err := validatePrivileges(requiredPrivileges, *privileges); err != nil {
   233  			return types.PluginConfig{}, err
   234  		}
   235  	}
   236  
   237  	return config, nil
   238  }
   239  
   240  // createPlugin creates a new plugin. take lock before calling.
   241  func (pm *Manager) createPlugin(name string, configDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges) (p *v2.Plugin, err error) {
   242  	if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store
   243  		return nil, err
   244  	}
   245  
   246  	config, err := pm.setupNewPlugin(configDigest, blobsums, privileges)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	p = &v2.Plugin{
   252  		PluginObj: types.Plugin{
   253  			Name:   name,
   254  			ID:     stringid.GenerateRandomID(),
   255  			Config: config,
   256  		},
   257  		Config:   configDigest,
   258  		Blobsums: blobsums,
   259  	}
   260  	p.InitEmptySettings()
   261  
   262  	pdir := filepath.Join(pm.config.Root, p.PluginObj.ID)
   263  	if err := os.MkdirAll(pdir, 0700); err != nil {
   264  		return nil, errors.Wrapf(err, "failed to mkdir %v", pdir)
   265  	}
   266  
   267  	defer func() {
   268  		if err != nil {
   269  			os.RemoveAll(pdir)
   270  		}
   271  	}()
   272  
   273  	if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil {
   274  		return nil, errors.Wrap(err, "failed to rename rootfs")
   275  	}
   276  
   277  	if err := pm.save(p); err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	pm.config.Store.Add(p) // todo: remove
   282  
   283  	return p, nil
   284  }