github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/plugin/manager.go (about)

     1  package plugin
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"regexp"
    11  	"runtime"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/docker/distribution/reference"
    17  	"github.com/docker/docker/api/types"
    18  	"github.com/docker/docker/image"
    19  	"github.com/docker/docker/layer"
    20  	"github.com/docker/docker/pkg/authorization"
    21  	"github.com/docker/docker/pkg/ioutils"
    22  	"github.com/docker/docker/pkg/mount"
    23  	"github.com/docker/docker/pkg/pubsub"
    24  	"github.com/docker/docker/pkg/system"
    25  	"github.com/docker/docker/plugin/v2"
    26  	"github.com/docker/docker/registry"
    27  	"github.com/opencontainers/go-digest"
    28  	specs "github.com/opencontainers/runtime-spec/specs-go"
    29  	"github.com/pkg/errors"
    30  	"github.com/sirupsen/logrus"
    31  )
    32  
    33  const configFileName = "config.json"
    34  const rootFSFileName = "rootfs"
    35  
    36  var validFullID = regexp.MustCompile(`^([a-f0-9]{64})$`)
    37  
    38  // Executor is the interface that the plugin manager uses to interact with for starting/stopping plugins
    39  type Executor interface {
    40  	Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error
    41  	Restore(id string, stdout, stderr io.WriteCloser) error
    42  	IsRunning(id string) (bool, error)
    43  	Signal(id string, signal int) error
    44  }
    45  
    46  func (pm *Manager) restorePlugin(p *v2.Plugin) error {
    47  	if p.IsEnabled() {
    48  		return pm.restore(p)
    49  	}
    50  	return nil
    51  }
    52  
    53  type eventLogger func(id, name, action string)
    54  
    55  // ManagerConfig defines configuration needed to start new manager.
    56  type ManagerConfig struct {
    57  	Store              *Store // remove
    58  	RegistryService    registry.Service
    59  	LiveRestoreEnabled bool // TODO: remove
    60  	LogPluginEvent     eventLogger
    61  	Root               string
    62  	ExecRoot           string
    63  	CreateExecutor     ExecutorCreator
    64  	AuthzMiddleware    *authorization.Middleware
    65  }
    66  
    67  // ExecutorCreator is used in the manager config to pass in an `Executor`
    68  type ExecutorCreator func(*Manager) (Executor, error)
    69  
    70  // Manager controls the plugin subsystem.
    71  type Manager struct {
    72  	config    ManagerConfig
    73  	mu        sync.RWMutex // protects cMap
    74  	muGC      sync.RWMutex // protects blobstore deletions
    75  	cMap      map[*v2.Plugin]*controller
    76  	blobStore *basicBlobStore
    77  	publisher *pubsub.Publisher
    78  	executor  Executor
    79  }
    80  
    81  // controller represents the manager's control on a plugin.
    82  type controller struct {
    83  	restart       bool
    84  	exitChan      chan bool
    85  	timeoutInSecs int
    86  }
    87  
    88  // pluginRegistryService ensures that all resolved repositories
    89  // are of the plugin class.
    90  type pluginRegistryService struct {
    91  	registry.Service
    92  }
    93  
    94  func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) {
    95  	repoInfo, err = s.Service.ResolveRepository(name)
    96  	if repoInfo != nil {
    97  		repoInfo.Class = "plugin"
    98  	}
    99  	return
   100  }
   101  
   102  // NewManager returns a new plugin manager.
   103  func NewManager(config ManagerConfig) (*Manager, error) {
   104  	if config.RegistryService != nil {
   105  		config.RegistryService = pluginRegistryService{config.RegistryService}
   106  	}
   107  	manager := &Manager{
   108  		config: config,
   109  	}
   110  	for _, dirName := range []string{manager.config.Root, manager.config.ExecRoot, manager.tmpDir()} {
   111  		if err := os.MkdirAll(dirName, 0700); err != nil {
   112  			return nil, errors.Wrapf(err, "failed to mkdir %v", dirName)
   113  		}
   114  	}
   115  
   116  	if err := setupRoot(manager.config.Root); err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var err error
   121  	manager.executor, err = config.CreateExecutor(manager)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	manager.blobStore, err = newBasicBlobStore(filepath.Join(manager.config.Root, "storage/blobs"))
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	manager.cMap = make(map[*v2.Plugin]*controller)
   132  	if err := manager.reload(); err != nil {
   133  		return nil, errors.Wrap(err, "failed to restore plugins")
   134  	}
   135  
   136  	manager.publisher = pubsub.NewPublisher(0, 0)
   137  	return manager, nil
   138  }
   139  
   140  func (pm *Manager) tmpDir() string {
   141  	return filepath.Join(pm.config.Root, "tmp")
   142  }
   143  
   144  // HandleExitEvent is called when the executor receives the exit event
   145  // In the future we may change this, but for now all we care about is the exit event.
   146  func (pm *Manager) HandleExitEvent(id string) error {
   147  	p, err := pm.config.Store.GetV2Plugin(id)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	os.RemoveAll(filepath.Join(pm.config.ExecRoot, id))
   153  
   154  	if p.PropagatedMount != "" {
   155  		if err := mount.Unmount(p.PropagatedMount); err != nil {
   156  			logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
   157  		}
   158  		propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
   159  		if err := mount.Unmount(propRoot); err != nil {
   160  			logrus.Warn("Could not unmount %s: %v", propRoot, err)
   161  		}
   162  	}
   163  
   164  	pm.mu.RLock()
   165  	c := pm.cMap[p]
   166  	if c.exitChan != nil {
   167  		close(c.exitChan)
   168  	}
   169  	restart := c.restart
   170  	pm.mu.RUnlock()
   171  
   172  	if restart {
   173  		pm.enable(p, c, true)
   174  	}
   175  	return nil
   176  }
   177  
   178  func handleLoadError(err error, id string) {
   179  	if err == nil {
   180  		return
   181  	}
   182  	logger := logrus.WithError(err).WithField("id", id)
   183  	if os.IsNotExist(errors.Cause(err)) {
   184  		// Likely some error while removing on an older version of docker
   185  		logger.Warn("missing plugin config, skipping: this may be caused due to a failed remove and requires manual cleanup.")
   186  		return
   187  	}
   188  	logger.Error("error loading plugin, skipping")
   189  }
   190  
   191  func (pm *Manager) reload() error { // todo: restore
   192  	dir, err := ioutil.ReadDir(pm.config.Root)
   193  	if err != nil {
   194  		return errors.Wrapf(err, "failed to read %v", pm.config.Root)
   195  	}
   196  	plugins := make(map[string]*v2.Plugin)
   197  	for _, v := range dir {
   198  		if validFullID.MatchString(v.Name()) {
   199  			p, err := pm.loadPlugin(v.Name())
   200  			if err != nil {
   201  				handleLoadError(err, v.Name())
   202  				continue
   203  			}
   204  			plugins[p.GetID()] = p
   205  		} else {
   206  			if validFullID.MatchString(strings.TrimSuffix(v.Name(), "-removing")) {
   207  				// There was likely some error while removing this plugin, let's try to remove again here
   208  				if err := system.EnsureRemoveAll(v.Name()); err != nil {
   209  					logrus.WithError(err).WithField("id", v.Name()).Warn("error while attempting to clean up previously removed plugin")
   210  				}
   211  			}
   212  		}
   213  	}
   214  
   215  	pm.config.Store.SetAll(plugins)
   216  
   217  	var wg sync.WaitGroup
   218  	wg.Add(len(plugins))
   219  	for _, p := range plugins {
   220  		c := &controller{} // todo: remove this
   221  		pm.cMap[p] = c
   222  		go func(p *v2.Plugin) {
   223  			defer wg.Done()
   224  			if err := pm.restorePlugin(p); err != nil {
   225  				logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err)
   226  				return
   227  			}
   228  
   229  			if p.Rootfs != "" {
   230  				p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs")
   231  			}
   232  
   233  			// We should only enable rootfs propagation for certain plugin types that need it.
   234  			for _, typ := range p.PluginObj.Config.Interface.Types {
   235  				if (typ.Capability == "volumedriver" || typ.Capability == "graphdriver") && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
   236  					if p.PluginObj.Config.PropagatedMount != "" {
   237  						propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
   238  
   239  						// check if we need to migrate an older propagated mount from before
   240  						// these mounts were stored outside the plugin rootfs
   241  						if _, err := os.Stat(propRoot); os.IsNotExist(err) {
   242  							if _, err := os.Stat(p.PropagatedMount); err == nil {
   243  								// make sure nothing is mounted here
   244  								// don't care about errors
   245  								mount.Unmount(p.PropagatedMount)
   246  								if err := os.Rename(p.PropagatedMount, propRoot); err != nil {
   247  									logrus.WithError(err).WithField("dir", propRoot).Error("error migrating propagated mount storage")
   248  								}
   249  								if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
   250  									logrus.WithError(err).WithField("dir", p.PropagatedMount).Error("error migrating propagated mount storage")
   251  								}
   252  							}
   253  						}
   254  
   255  						if err := os.MkdirAll(propRoot, 0755); err != nil {
   256  							logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err)
   257  						}
   258  						// TODO: sanitize PropagatedMount and prevent breakout
   259  						p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount)
   260  						if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
   261  							logrus.Errorf("failed to create PropagatedMount directory at %s: %v", p.PropagatedMount, err)
   262  							return
   263  						}
   264  					}
   265  				}
   266  			}
   267  
   268  			pm.save(p)
   269  			requiresManualRestore := !pm.config.LiveRestoreEnabled && p.IsEnabled()
   270  
   271  			if requiresManualRestore {
   272  				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
   273  				if err := pm.enable(p, c, true); err != nil {
   274  					logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err)
   275  				}
   276  			}
   277  		}(p)
   278  	}
   279  	wg.Wait()
   280  	return nil
   281  }
   282  
   283  // Get looks up the requested plugin in the store.
   284  func (pm *Manager) Get(idOrName string) (*v2.Plugin, error) {
   285  	return pm.config.Store.GetV2Plugin(idOrName)
   286  }
   287  
   288  func (pm *Manager) loadPlugin(id string) (*v2.Plugin, error) {
   289  	p := filepath.Join(pm.config.Root, id, configFileName)
   290  	dt, err := ioutil.ReadFile(p)
   291  	if err != nil {
   292  		return nil, errors.Wrapf(err, "error reading %v", p)
   293  	}
   294  	var plugin v2.Plugin
   295  	if err := json.Unmarshal(dt, &plugin); err != nil {
   296  		return nil, errors.Wrapf(err, "error decoding %v", p)
   297  	}
   298  	return &plugin, nil
   299  }
   300  
   301  func (pm *Manager) save(p *v2.Plugin) error {
   302  	pluginJSON, err := json.Marshal(p)
   303  	if err != nil {
   304  		return errors.Wrap(err, "failed to marshal plugin json")
   305  	}
   306  	if err := ioutils.AtomicWriteFile(filepath.Join(pm.config.Root, p.GetID(), configFileName), pluginJSON, 0600); err != nil {
   307  		return errors.Wrap(err, "failed to write atomically plugin json")
   308  	}
   309  	return nil
   310  }
   311  
   312  // GC cleans up unreferenced blobs. This is recommended to run in a goroutine
   313  func (pm *Manager) GC() {
   314  	pm.muGC.Lock()
   315  	defer pm.muGC.Unlock()
   316  
   317  	whitelist := make(map[digest.Digest]struct{})
   318  	for _, p := range pm.config.Store.GetAll() {
   319  		whitelist[p.Config] = struct{}{}
   320  		for _, b := range p.Blobsums {
   321  			whitelist[b] = struct{}{}
   322  		}
   323  	}
   324  
   325  	pm.blobStore.gc(whitelist)
   326  }
   327  
   328  type logHook struct{ id string }
   329  
   330  func (logHook) Levels() []logrus.Level {
   331  	return logrus.AllLevels
   332  }
   333  
   334  func (l logHook) Fire(entry *logrus.Entry) error {
   335  	entry.Data = logrus.Fields{"plugin": l.id}
   336  	return nil
   337  }
   338  
   339  func makeLoggerStreams(id string) (stdout, stderr io.WriteCloser) {
   340  	logger := logrus.New()
   341  	logger.Hooks.Add(logHook{id})
   342  	return logger.WriterLevel(logrus.InfoLevel), logger.WriterLevel(logrus.ErrorLevel)
   343  }
   344  
   345  func validatePrivileges(requiredPrivileges, privileges types.PluginPrivileges) error {
   346  	if !isEqual(requiredPrivileges, privileges, isEqualPrivilege) {
   347  		return errors.New("incorrect privileges")
   348  	}
   349  
   350  	return nil
   351  }
   352  
   353  func isEqual(arrOne, arrOther types.PluginPrivileges, compare func(x, y types.PluginPrivilege) bool) bool {
   354  	if len(arrOne) != len(arrOther) {
   355  		return false
   356  	}
   357  
   358  	sort.Sort(arrOne)
   359  	sort.Sort(arrOther)
   360  
   361  	for i := 1; i < arrOne.Len(); i++ {
   362  		if !compare(arrOne[i], arrOther[i]) {
   363  			return false
   364  		}
   365  	}
   366  
   367  	return true
   368  }
   369  
   370  func isEqualPrivilege(a, b types.PluginPrivilege) bool {
   371  	if a.Name != b.Name {
   372  		return false
   373  	}
   374  
   375  	return reflect.DeepEqual(a.Value, b.Value)
   376  }
   377  
   378  func configToRootFS(c []byte) (*image.RootFS, layer.OS, error) {
   379  	// TODO @jhowardmsft LCOW - Will need to revisit this. For now, calculate the operating system.
   380  	os := layer.OS(runtime.GOOS)
   381  	if system.LCOWSupported() {
   382  		os = "linux"
   383  	}
   384  	var pluginConfig types.PluginConfig
   385  	if err := json.Unmarshal(c, &pluginConfig); err != nil {
   386  		return nil, "", err
   387  	}
   388  	// validation for empty rootfs is in distribution code
   389  	if pluginConfig.Rootfs == nil {
   390  		return nil, os, nil
   391  	}
   392  
   393  	return rootFSFromPlugin(pluginConfig.Rootfs), os, nil
   394  }
   395  
   396  func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {
   397  	rootFS := image.RootFS{
   398  		Type:    pluginfs.Type,
   399  		DiffIDs: make([]layer.DiffID, len(pluginfs.DiffIds)),
   400  	}
   401  	for i := range pluginfs.DiffIds {
   402  		rootFS.DiffIDs[i] = layer.DiffID(pluginfs.DiffIds[i])
   403  	}
   404  
   405  	return &rootFS
   406  }