github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/plugin/manager_linux.go (about)

     1  package plugin // import "github.com/docker/docker/plugin"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"net"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/containerd/containerd/content"
    12  	"github.com/containerd/log"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/daemon/initlayer"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/docker/docker/pkg/idtools"
    17  	"github.com/docker/docker/pkg/plugins"
    18  	"github.com/docker/docker/pkg/stringid"
    19  	v2 "github.com/docker/docker/plugin/v2"
    20  	"github.com/moby/sys/mount"
    21  	"github.com/opencontainers/go-digest"
    22  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    23  	"github.com/pkg/errors"
    24  	"golang.org/x/sys/unix"
    25  )
    26  
    27  func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
    28  	p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs")
    29  	if p.IsEnabled() && !force {
    30  		return errors.Wrap(enabledError(p.Name()), "plugin already enabled")
    31  	}
    32  	spec, err := p.InitSpec(pm.config.ExecRoot)
    33  	if err != nil {
    34  		return err
    35  	}
    36  
    37  	c.restart = true
    38  	c.exitChan = make(chan bool)
    39  
    40  	pm.mu.Lock()
    41  	pm.cMap[p] = c
    42  	pm.mu.Unlock()
    43  
    44  	var propRoot string
    45  	if p.PluginObj.Config.PropagatedMount != "" {
    46  		propRoot = filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
    47  
    48  		if err := os.MkdirAll(propRoot, 0o755); err != nil {
    49  			log.G(context.TODO()).Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err)
    50  		}
    51  
    52  		if err := mount.MakeRShared(propRoot); err != nil {
    53  			return errors.Wrap(err, "error setting up propagated mount dir")
    54  		}
    55  	}
    56  
    57  	rootFS := filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName)
    58  	if err := initlayer.Setup(rootFS, idtools.Identity{UID: 0, GID: 0}); err != nil {
    59  		return errors.WithStack(err)
    60  	}
    61  
    62  	stdout, stderr := makeLoggerStreams(p.GetID())
    63  	if err := pm.executor.Create(p.GetID(), *spec, stdout, stderr); err != nil {
    64  		if p.PluginObj.Config.PropagatedMount != "" {
    65  			if err := mount.Unmount(propRoot); err != nil {
    66  				log.G(context.TODO()).WithField("plugin", p.Name()).WithError(err).Warn("Failed to unmount vplugin propagated mount root")
    67  			}
    68  		}
    69  		return errors.WithStack(err)
    70  	}
    71  	return pm.pluginPostStart(p, c)
    72  }
    73  
    74  func (pm *Manager) pluginPostStart(p *v2.Plugin, c *controller) error {
    75  	sockAddr := filepath.Join(pm.config.ExecRoot, p.GetID(), p.GetSocket())
    76  	p.SetTimeout(time.Duration(c.timeoutInSecs) * time.Second)
    77  	addr := &net.UnixAddr{Net: "unix", Name: sockAddr}
    78  	p.SetAddr(addr)
    79  
    80  	if p.Protocol() == plugins.ProtocolSchemeHTTPV1 {
    81  		client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, p.Timeout())
    82  		if err != nil {
    83  			c.restart = false
    84  			shutdownPlugin(p, c.exitChan, pm.executor)
    85  			return errors.WithStack(err)
    86  		}
    87  
    88  		p.SetPClient(client)
    89  	}
    90  
    91  	// Initial sleep before net Dial to allow plugin to listen on socket.
    92  	time.Sleep(500 * time.Millisecond)
    93  	maxRetries := 3
    94  	var retries int
    95  	for {
    96  		// net dial into the unix socket to see if someone's listening.
    97  		conn, err := net.Dial("unix", sockAddr)
    98  		if err == nil {
    99  			conn.Close()
   100  			break
   101  		}
   102  
   103  		time.Sleep(3 * time.Second)
   104  		retries++
   105  
   106  		if retries > maxRetries {
   107  			log.G(context.TODO()).Debugf("error net dialing plugin: %v", err)
   108  			c.restart = false
   109  			// While restoring plugins, we need to explicitly set the state to disabled
   110  			pm.config.Store.SetState(p, false)
   111  			shutdownPlugin(p, c.exitChan, pm.executor)
   112  			return err
   113  		}
   114  	}
   115  	pm.config.Store.SetState(p, true)
   116  	pm.config.Store.CallHandler(p)
   117  
   118  	return pm.save(p)
   119  }
   120  
   121  func (pm *Manager) restore(p *v2.Plugin, c *controller) error {
   122  	stdout, stderr := makeLoggerStreams(p.GetID())
   123  	alive, err := pm.executor.Restore(p.GetID(), stdout, stderr)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	if pm.config.LiveRestoreEnabled {
   129  		if !alive {
   130  			return pm.enable(p, c, true)
   131  		}
   132  
   133  		c.exitChan = make(chan bool)
   134  		c.restart = true
   135  		pm.mu.Lock()
   136  		pm.cMap[p] = c
   137  		pm.mu.Unlock()
   138  		return pm.pluginPostStart(p, c)
   139  	}
   140  
   141  	if alive {
   142  		// TODO(@cpuguy83): Should we always just re-attach to the running plugin instead of doing this?
   143  		c.restart = false
   144  		shutdownPlugin(p, c.exitChan, pm.executor)
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  const shutdownTimeout = 10 * time.Second
   151  
   152  func shutdownPlugin(p *v2.Plugin, ec chan bool, executor Executor) {
   153  	pluginID := p.GetID()
   154  
   155  	if err := executor.Signal(pluginID, unix.SIGTERM); err != nil {
   156  		log.G(context.TODO()).Errorf("Sending SIGTERM to plugin failed with error: %v", err)
   157  		return
   158  	}
   159  
   160  	timeout := time.NewTimer(shutdownTimeout)
   161  	defer timeout.Stop()
   162  
   163  	select {
   164  	case <-ec:
   165  		log.G(context.TODO()).Debug("Clean shutdown of plugin")
   166  	case <-timeout.C:
   167  		log.G(context.TODO()).Debug("Force shutdown plugin")
   168  		if err := executor.Signal(pluginID, unix.SIGKILL); err != nil {
   169  			log.G(context.TODO()).Errorf("Sending SIGKILL to plugin failed with error: %v", err)
   170  		}
   171  
   172  		timeout.Reset(shutdownTimeout)
   173  
   174  		select {
   175  		case <-ec:
   176  			log.G(context.TODO()).Debug("SIGKILL plugin shutdown")
   177  		case <-timeout.C:
   178  			log.G(context.TODO()).WithField("plugin", p.Name).Warn("Force shutdown plugin FAILED")
   179  		}
   180  	}
   181  }
   182  
   183  func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
   184  	if !p.IsEnabled() {
   185  		return errors.Wrap(errDisabled(p.Name()), "plugin is already disabled")
   186  	}
   187  
   188  	c.restart = false
   189  	shutdownPlugin(p, c.exitChan, pm.executor)
   190  	pm.config.Store.SetState(p, false)
   191  	return pm.save(p)
   192  }
   193  
   194  // Shutdown stops all plugins and called during daemon shutdown.
   195  func (pm *Manager) Shutdown() {
   196  	plugins := pm.config.Store.GetAll()
   197  	for _, p := range plugins {
   198  		pm.mu.RLock()
   199  		c := pm.cMap[p]
   200  		pm.mu.RUnlock()
   201  
   202  		if pm.config.LiveRestoreEnabled && p.IsEnabled() {
   203  			log.G(context.TODO()).Debug("Plugin active when liveRestore is set, skipping shutdown")
   204  			continue
   205  		}
   206  		if pm.executor != nil && p.IsEnabled() {
   207  			c.restart = false
   208  			shutdownPlugin(p, c.exitChan, pm.executor)
   209  		}
   210  	}
   211  	if err := mount.RecursiveUnmount(pm.config.Root); err != nil {
   212  		log.G(context.TODO()).WithError(err).Warn("error cleaning up plugin mounts")
   213  	}
   214  }
   215  
   216  func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest, manifestDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) {
   217  	config, err := pm.setupNewPlugin(configDigest, privileges)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	pdir := filepath.Join(pm.config.Root, p.PluginObj.ID)
   223  	orig := filepath.Join(pdir, "rootfs")
   224  
   225  	// Make sure nothing is mounted
   226  	// This could happen if the plugin was disabled with `-f` with active mounts.
   227  	// If there is anything in `orig` is still mounted, this should error out.
   228  	if err := mount.RecursiveUnmount(orig); err != nil {
   229  		return errdefs.System(err)
   230  	}
   231  
   232  	backup := orig + "-old"
   233  	if err := os.Rename(orig, backup); err != nil {
   234  		return errors.Wrap(errdefs.System(err), "error backing up plugin data before upgrade")
   235  	}
   236  
   237  	defer func() {
   238  		if err != nil {
   239  			if rmErr := os.RemoveAll(orig); rmErr != nil {
   240  				log.G(context.TODO()).WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade")
   241  				return
   242  			}
   243  			if mvErr := os.Rename(backup, orig); mvErr != nil {
   244  				err = errors.Wrap(mvErr, "error restoring old plugin root on upgrade failure")
   245  			}
   246  			if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) {
   247  				log.G(context.TODO()).WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir)
   248  			}
   249  		} else {
   250  			if rmErr := os.RemoveAll(backup); rmErr != nil {
   251  				log.G(context.TODO()).WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade")
   252  			}
   253  
   254  			p.Config = configDigest
   255  			p.Blobsums = blobsums
   256  		}
   257  	}()
   258  
   259  	if err := os.Rename(tmpRootFSDir, orig); err != nil {
   260  		return errors.Wrap(errdefs.System(err), "error upgrading")
   261  	}
   262  
   263  	p.PluginObj.Config = config
   264  	p.Manifest = manifestDigest
   265  	err = pm.save(p)
   266  	return errors.Wrap(err, "error saving upgraded plugin config")
   267  }
   268  
   269  func (pm *Manager) setupNewPlugin(configDigest digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) {
   270  	configRA, err := pm.blobStore.ReaderAt(context.TODO(), ocispec.Descriptor{Digest: configDigest})
   271  	if err != nil {
   272  		return types.PluginConfig{}, err
   273  	}
   274  	defer configRA.Close()
   275  
   276  	configR := content.NewReader(configRA)
   277  
   278  	var config types.PluginConfig
   279  	dec := json.NewDecoder(configR)
   280  	if err := dec.Decode(&config); err != nil {
   281  		return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config")
   282  	}
   283  	if dec.More() {
   284  		return types.PluginConfig{}, errors.New("invalid config json")
   285  	}
   286  
   287  	requiredPrivileges := computePrivileges(config)
   288  	if privileges != nil {
   289  		if err := validatePrivileges(requiredPrivileges, *privileges); err != nil {
   290  			return types.PluginConfig{}, err
   291  		}
   292  	}
   293  
   294  	return config, nil
   295  }
   296  
   297  // createPlugin creates a new plugin. take lock before calling.
   298  func (pm *Manager) createPlugin(name string, configDigest, manifestDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges, opts ...CreateOpt) (p *v2.Plugin, err error) {
   299  	if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store
   300  		return nil, errdefs.InvalidParameter(err)
   301  	}
   302  
   303  	config, err := pm.setupNewPlugin(configDigest, privileges)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  
   308  	p = &v2.Plugin{
   309  		PluginObj: types.Plugin{
   310  			Name:   name,
   311  			ID:     stringid.GenerateRandomID(),
   312  			Config: config,
   313  		},
   314  		Config:   configDigest,
   315  		Blobsums: blobsums,
   316  		Manifest: manifestDigest,
   317  	}
   318  	p.InitEmptySettings()
   319  	for _, o := range opts {
   320  		o(p)
   321  	}
   322  
   323  	pdir := filepath.Join(pm.config.Root, p.PluginObj.ID)
   324  	if err := os.MkdirAll(pdir, 0o700); err != nil {
   325  		return nil, errors.Wrapf(err, "failed to mkdir %v", pdir)
   326  	}
   327  
   328  	defer func() {
   329  		if err != nil {
   330  			os.RemoveAll(pdir)
   331  		}
   332  	}()
   333  
   334  	if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil {
   335  		return nil, errors.Wrap(err, "failed to rename rootfs")
   336  	}
   337  
   338  	if err := pm.save(p); err != nil {
   339  		return nil, err
   340  	}
   341  
   342  	pm.config.Store.Add(p) // todo: remove
   343  
   344  	return p, nil
   345  }
   346  
   347  func recursiveUnmount(target string) error {
   348  	return mount.RecursiveUnmount(target)
   349  }