github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/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  	"strings"
    12  	"sync"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/docker/docker/image"
    17  	"github.com/docker/docker/layer"
    18  	"github.com/docker/docker/libcontainerd"
    19  	"github.com/docker/docker/pkg/ioutils"
    20  	"github.com/docker/docker/pkg/mount"
    21  	"github.com/docker/docker/plugin/v2"
    22  	"github.com/docker/docker/reference"
    23  	"github.com/docker/docker/registry"
    24  	"github.com/opencontainers/go-digest"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  const configFileName = "config.json"
    29  const rootFSFileName = "rootfs"
    30  
    31  var validFullID = regexp.MustCompile(`^([a-f0-9]{64})$`)
    32  
    33  func (pm *Manager) restorePlugin(p *v2.Plugin) error {
    34  	if p.IsEnabled() {
    35  		return pm.restore(p)
    36  	}
    37  	return nil
    38  }
    39  
    40  type eventLogger func(id, name, action string)
    41  
    42  // ManagerConfig defines configuration needed to start new manager.
    43  type ManagerConfig struct {
    44  	Store              *Store // remove
    45  	Executor           libcontainerd.Remote
    46  	RegistryService    registry.Service
    47  	LiveRestoreEnabled bool // TODO: remove
    48  	LogPluginEvent     eventLogger
    49  	Root               string
    50  	ExecRoot           string
    51  }
    52  
    53  // Manager controls the plugin subsystem.
    54  type Manager struct {
    55  	config           ManagerConfig
    56  	mu               sync.RWMutex // protects cMap
    57  	muGC             sync.RWMutex // protects blobstore deletions
    58  	cMap             map[*v2.Plugin]*controller
    59  	containerdClient libcontainerd.Client
    60  	blobStore        *basicBlobStore
    61  }
    62  
    63  // controller represents the manager's control on a plugin.
    64  type controller struct {
    65  	restart       bool
    66  	exitChan      chan bool
    67  	timeoutInSecs int
    68  }
    69  
    70  // pluginRegistryService ensures that all resolved repositories
    71  // are of the plugin class.
    72  type pluginRegistryService struct {
    73  	registry.Service
    74  }
    75  
    76  func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) {
    77  	repoInfo, err = s.Service.ResolveRepository(name)
    78  	if repoInfo != nil {
    79  		repoInfo.Class = "plugin"
    80  	}
    81  	return
    82  }
    83  
    84  // NewManager returns a new plugin manager.
    85  func NewManager(config ManagerConfig) (*Manager, error) {
    86  	if config.RegistryService != nil {
    87  		config.RegistryService = pluginRegistryService{config.RegistryService}
    88  	}
    89  	manager := &Manager{
    90  		config: config,
    91  	}
    92  	if err := os.MkdirAll(manager.config.Root, 0700); err != nil {
    93  		return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.Root)
    94  	}
    95  	if err := os.MkdirAll(manager.config.ExecRoot, 0700); err != nil {
    96  		return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.ExecRoot)
    97  	}
    98  	if err := os.MkdirAll(manager.tmpDir(), 0700); err != nil {
    99  		return nil, errors.Wrapf(err, "failed to mkdir %v", manager.tmpDir())
   100  	}
   101  	var err error
   102  	manager.containerdClient, err = config.Executor.Client(manager) // todo: move to another struct
   103  	if err != nil {
   104  		return nil, errors.Wrap(err, "failed to create containerd client")
   105  	}
   106  	manager.blobStore, err = newBasicBlobStore(filepath.Join(manager.config.Root, "storage/blobs"))
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	manager.cMap = make(map[*v2.Plugin]*controller)
   112  	if err := manager.reload(); err != nil {
   113  		return nil, errors.Wrap(err, "failed to restore plugins")
   114  	}
   115  	return manager, nil
   116  }
   117  
   118  func (pm *Manager) tmpDir() string {
   119  	return filepath.Join(pm.config.Root, "tmp")
   120  }
   121  
   122  // StateChanged updates plugin internals using libcontainerd events.
   123  func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
   124  	logrus.Debugf("plugin state changed %s %#v", id, e)
   125  
   126  	switch e.State {
   127  	case libcontainerd.StateExit:
   128  		p, err := pm.config.Store.GetV2Plugin(id)
   129  		if err != nil {
   130  			return err
   131  		}
   132  
   133  		pm.mu.RLock()
   134  		c := pm.cMap[p]
   135  
   136  		if c.exitChan != nil {
   137  			close(c.exitChan)
   138  		}
   139  		restart := c.restart
   140  		pm.mu.RUnlock()
   141  
   142  		os.RemoveAll(filepath.Join(pm.config.ExecRoot, id))
   143  
   144  		if p.PropagatedMount != "" {
   145  			if err := mount.Unmount(p.PropagatedMount); err != nil {
   146  				logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
   147  			}
   148  		}
   149  
   150  		if restart {
   151  			pm.enable(p, c, true)
   152  		}
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func (pm *Manager) reload() error { // todo: restore
   159  	dir, err := ioutil.ReadDir(pm.config.Root)
   160  	if err != nil {
   161  		return errors.Wrapf(err, "failed to read %v", pm.config.Root)
   162  	}
   163  	plugins := make(map[string]*v2.Plugin)
   164  	for _, v := range dir {
   165  		if validFullID.MatchString(v.Name()) {
   166  			p, err := pm.loadPlugin(v.Name())
   167  			if err != nil {
   168  				return err
   169  			}
   170  			plugins[p.GetID()] = p
   171  		}
   172  	}
   173  
   174  	pm.config.Store.SetAll(plugins)
   175  
   176  	var wg sync.WaitGroup
   177  	wg.Add(len(plugins))
   178  	for _, p := range plugins {
   179  		c := &controller{} // todo: remove this
   180  		pm.cMap[p] = c
   181  		go func(p *v2.Plugin) {
   182  			defer wg.Done()
   183  			if err := pm.restorePlugin(p); err != nil {
   184  				logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err)
   185  				return
   186  			}
   187  
   188  			if p.Rootfs != "" {
   189  				p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs")
   190  			}
   191  
   192  			// We should only enable rootfs propagation for certain plugin types that need it.
   193  			for _, typ := range p.PluginObj.Config.Interface.Types {
   194  				if (typ.Capability == "volumedriver" || typ.Capability == "graphdriver") && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
   195  					if p.PluginObj.Config.PropagatedMount != "" {
   196  						// TODO: sanitize PropagatedMount and prevent breakout
   197  						p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount)
   198  						if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
   199  							logrus.Errorf("failed to create PropagatedMount directory at %s: %v", p.PropagatedMount, err)
   200  							return
   201  						}
   202  					}
   203  				}
   204  			}
   205  
   206  			pm.save(p)
   207  			requiresManualRestore := !pm.config.LiveRestoreEnabled && p.IsEnabled()
   208  
   209  			if requiresManualRestore {
   210  				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
   211  				if err := pm.enable(p, c, true); err != nil {
   212  					logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err)
   213  				}
   214  			}
   215  		}(p)
   216  	}
   217  	wg.Wait()
   218  	return nil
   219  }
   220  
   221  func (pm *Manager) loadPlugin(id string) (*v2.Plugin, error) {
   222  	p := filepath.Join(pm.config.Root, id, configFileName)
   223  	dt, err := ioutil.ReadFile(p)
   224  	if err != nil {
   225  		return nil, errors.Wrapf(err, "error reading %v", p)
   226  	}
   227  	var plugin v2.Plugin
   228  	if err := json.Unmarshal(dt, &plugin); err != nil {
   229  		return nil, errors.Wrapf(err, "error decoding %v", p)
   230  	}
   231  	return &plugin, nil
   232  }
   233  
   234  func (pm *Manager) save(p *v2.Plugin) error {
   235  	pluginJSON, err := json.Marshal(p)
   236  	if err != nil {
   237  		return errors.Wrap(err, "failed to marshal plugin json")
   238  	}
   239  	if err := ioutils.AtomicWriteFile(filepath.Join(pm.config.Root, p.GetID(), configFileName), pluginJSON, 0600); err != nil {
   240  		return err
   241  	}
   242  	return nil
   243  }
   244  
   245  // GC cleans up unrefrenced blobs. This is recommended to run in a goroutine
   246  func (pm *Manager) GC() {
   247  	pm.muGC.Lock()
   248  	defer pm.muGC.Unlock()
   249  
   250  	whitelist := make(map[digest.Digest]struct{})
   251  	for _, p := range pm.config.Store.GetAll() {
   252  		whitelist[p.Config] = struct{}{}
   253  		for _, b := range p.Blobsums {
   254  			whitelist[b] = struct{}{}
   255  		}
   256  	}
   257  
   258  	pm.blobStore.gc(whitelist)
   259  }
   260  
   261  type logHook struct{ id string }
   262  
   263  func (logHook) Levels() []logrus.Level {
   264  	return logrus.AllLevels
   265  }
   266  
   267  func (l logHook) Fire(entry *logrus.Entry) error {
   268  	entry.Data = logrus.Fields{"plugin": l.id}
   269  	return nil
   270  }
   271  
   272  func attachToLog(id string) func(libcontainerd.IOPipe) error {
   273  	return func(iop libcontainerd.IOPipe) error {
   274  		iop.Stdin.Close()
   275  
   276  		logger := logrus.New()
   277  		logger.Hooks.Add(logHook{id})
   278  		// TODO: cache writer per id
   279  		w := logger.Writer()
   280  		go func() {
   281  			io.Copy(w, iop.Stdout)
   282  		}()
   283  		go func() {
   284  			// TODO: update logrus and use logger.WriterLevel
   285  			io.Copy(w, iop.Stderr)
   286  		}()
   287  		return nil
   288  	}
   289  }
   290  
   291  func validatePrivileges(requiredPrivileges, privileges types.PluginPrivileges) error {
   292  	// todo: make a better function that doesn't check order
   293  	if !reflect.DeepEqual(privileges, requiredPrivileges) {
   294  		return errors.New("incorrect privileges")
   295  	}
   296  	return nil
   297  }
   298  
   299  func configToRootFS(c []byte) (*image.RootFS, error) {
   300  	var pluginConfig types.PluginConfig
   301  	if err := json.Unmarshal(c, &pluginConfig); err != nil {
   302  		return nil, err
   303  	}
   304  	// validation for empty rootfs is in distribution code
   305  	if pluginConfig.Rootfs == nil {
   306  		return nil, nil
   307  	}
   308  
   309  	return rootFSFromPlugin(pluginConfig.Rootfs), nil
   310  }
   311  
   312  func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {
   313  	rootFS := image.RootFS{
   314  		Type:    pluginfs.Type,
   315  		DiffIDs: make([]layer.DiffID, len(pluginfs.DiffIds)),
   316  	}
   317  	for i := range pluginfs.DiffIds {
   318  		rootFS.DiffIDs[i] = layer.DiffID(pluginfs.DiffIds[i])
   319  	}
   320  
   321  	return &rootFS
   322  }