github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/daemon/cluster/controllers/plugin/controller.go (about)

     1  package plugin
     2  
     3  import (
     4  	"io"
     5  	"io/ioutil"
     6  	"net/http"
     7  
     8  	"github.com/docker/distribution/reference"
     9  	"github.com/docker/docker/api/errdefs"
    10  	enginetypes "github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/swarm/runtime"
    12  	"github.com/docker/docker/plugin"
    13  	"github.com/docker/docker/plugin/v2"
    14  	"github.com/docker/swarmkit/api"
    15  	"github.com/gogo/protobuf/proto"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  // Controller is the controller for the plugin backend.
    22  // Plugins are managed as a singleton object with a desired state (different from containers).
    23  // With the the plugin controller instead of having a strict create->start->stop->remove
    24  // task lifecycle like containers, we manage the desired state of the plugin and let
    25  // the plugin manager do what it already does and monitor the plugin.
    26  // We'll also end up with many tasks all pointing to the same plugin ID.
    27  //
    28  // TODO(@cpuguy83): registry auth is intentionally not supported until we work out
    29  // the right way to pass registry crednetials via secrets.
    30  type Controller struct {
    31  	backend Backend
    32  	spec    runtime.PluginSpec
    33  	logger  *logrus.Entry
    34  
    35  	pluginID  string
    36  	serviceID string
    37  	taskID    string
    38  
    39  	// hook used to signal tests that `Wait()` is actually ready and waiting
    40  	signalWaitReady func()
    41  }
    42  
    43  // Backend is the interface for interacting with the plugin manager
    44  // Controller actions are passed to the configured backend to do the real work.
    45  type Backend interface {
    46  	Disable(name string, config *enginetypes.PluginDisableConfig) error
    47  	Enable(name string, config *enginetypes.PluginEnableConfig) error
    48  	Remove(name string, config *enginetypes.PluginRmConfig) error
    49  	Pull(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, privileges enginetypes.PluginPrivileges, outStream io.Writer, opts ...plugin.CreateOpt) error
    50  	Upgrade(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, privileges enginetypes.PluginPrivileges, outStream io.Writer) error
    51  	Get(name string) (*v2.Plugin, error)
    52  	SubscribeEvents(buffer int, events ...plugin.Event) (eventCh <-chan interface{}, cancel func())
    53  }
    54  
    55  // NewController returns a new cluster plugin controller
    56  func NewController(backend Backend, t *api.Task) (*Controller, error) {
    57  	spec, err := readSpec(t)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return &Controller{
    62  		backend:   backend,
    63  		spec:      spec,
    64  		serviceID: t.ServiceID,
    65  		logger: logrus.WithFields(logrus.Fields{
    66  			"controller": "plugin",
    67  			"task":       t.ID,
    68  			"plugin":     spec.Name,
    69  		})}, nil
    70  }
    71  
    72  func readSpec(t *api.Task) (runtime.PluginSpec, error) {
    73  	var cfg runtime.PluginSpec
    74  
    75  	generic := t.Spec.GetGeneric()
    76  	if err := proto.Unmarshal(generic.Payload.Value, &cfg); err != nil {
    77  		return cfg, errors.Wrap(err, "error reading plugin spec")
    78  	}
    79  	return cfg, nil
    80  }
    81  
    82  // Update is the update phase from swarmkit
    83  func (p *Controller) Update(ctx context.Context, t *api.Task) error {
    84  	p.logger.Debug("Update")
    85  	return nil
    86  }
    87  
    88  // Prepare is the prepare phase from swarmkit
    89  func (p *Controller) Prepare(ctx context.Context) (err error) {
    90  	p.logger.Debug("Prepare")
    91  
    92  	remote, err := reference.ParseNormalizedNamed(p.spec.Remote)
    93  	if err != nil {
    94  		return errors.Wrapf(err, "error parsing remote reference %q", p.spec.Remote)
    95  	}
    96  
    97  	if p.spec.Name == "" {
    98  		p.spec.Name = remote.String()
    99  	}
   100  
   101  	var authConfig enginetypes.AuthConfig
   102  	privs := convertPrivileges(p.spec.Privileges)
   103  
   104  	pl, err := p.backend.Get(p.spec.Name)
   105  
   106  	defer func() {
   107  		if pl != nil && err == nil {
   108  			pl.Acquire()
   109  		}
   110  	}()
   111  
   112  	if err == nil && pl != nil {
   113  		if pl.SwarmServiceID != p.serviceID {
   114  			return errors.Errorf("plugin already exists: %s", p.spec.Name)
   115  		}
   116  		if pl.IsEnabled() {
   117  			if err := p.backend.Disable(pl.GetID(), &enginetypes.PluginDisableConfig{ForceDisable: true}); err != nil {
   118  				p.logger.WithError(err).Debug("could not disable plugin before running upgrade")
   119  			}
   120  		}
   121  		p.pluginID = pl.GetID()
   122  		return p.backend.Upgrade(ctx, remote, p.spec.Name, nil, &authConfig, privs, ioutil.Discard)
   123  	}
   124  
   125  	if err := p.backend.Pull(ctx, remote, p.spec.Name, nil, &authConfig, privs, ioutil.Discard, plugin.WithSwarmService(p.serviceID)); err != nil {
   126  		return err
   127  	}
   128  	pl, err = p.backend.Get(p.spec.Name)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	p.pluginID = pl.GetID()
   133  
   134  	return nil
   135  }
   136  
   137  // Start is the start phase from swarmkit
   138  func (p *Controller) Start(ctx context.Context) error {
   139  	p.logger.Debug("Start")
   140  
   141  	pl, err := p.backend.Get(p.pluginID)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	if p.spec.Disabled {
   147  		if pl.IsEnabled() {
   148  			return p.backend.Disable(p.pluginID, &enginetypes.PluginDisableConfig{ForceDisable: false})
   149  		}
   150  		return nil
   151  	}
   152  	if !pl.IsEnabled() {
   153  		return p.backend.Enable(p.pluginID, &enginetypes.PluginEnableConfig{Timeout: 30})
   154  	}
   155  	return nil
   156  }
   157  
   158  // Wait causes the task to wait until returned
   159  func (p *Controller) Wait(ctx context.Context) error {
   160  	p.logger.Debug("Wait")
   161  
   162  	pl, err := p.backend.Get(p.pluginID)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	events, cancel := p.backend.SubscribeEvents(1, plugin.EventDisable{Plugin: pl.PluginObj}, plugin.EventRemove{Plugin: pl.PluginObj}, plugin.EventEnable{Plugin: pl.PluginObj})
   168  	defer cancel()
   169  
   170  	if p.signalWaitReady != nil {
   171  		p.signalWaitReady()
   172  	}
   173  
   174  	if !p.spec.Disabled != pl.IsEnabled() {
   175  		return errors.New("mismatched plugin state")
   176  	}
   177  
   178  	for {
   179  		select {
   180  		case <-ctx.Done():
   181  			return ctx.Err()
   182  		case e := <-events:
   183  			p.logger.Debugf("got event %#T", e)
   184  
   185  			switch e.(type) {
   186  			case plugin.EventEnable:
   187  				if p.spec.Disabled {
   188  					return errors.New("plugin enabled")
   189  				}
   190  			case plugin.EventRemove:
   191  				return errors.New("plugin removed")
   192  			case plugin.EventDisable:
   193  				if !p.spec.Disabled {
   194  					return errors.New("plugin disabled")
   195  				}
   196  			}
   197  		}
   198  	}
   199  }
   200  
   201  func isNotFound(err error) bool {
   202  	return errdefs.IsNotFound(err)
   203  }
   204  
   205  // Shutdown is the shutdown phase from swarmkit
   206  func (p *Controller) Shutdown(ctx context.Context) error {
   207  	p.logger.Debug("Shutdown")
   208  	return nil
   209  }
   210  
   211  // Terminate is the terminate phase from swarmkit
   212  func (p *Controller) Terminate(ctx context.Context) error {
   213  	p.logger.Debug("Terminate")
   214  	return nil
   215  }
   216  
   217  // Remove is the remove phase from swarmkit
   218  func (p *Controller) Remove(ctx context.Context) error {
   219  	p.logger.Debug("Remove")
   220  
   221  	pl, err := p.backend.Get(p.pluginID)
   222  	if err != nil {
   223  		if isNotFound(err) {
   224  			return nil
   225  		}
   226  		return err
   227  	}
   228  
   229  	pl.Release()
   230  	if pl.GetRefCount() > 0 {
   231  		p.logger.Debug("skipping remove due to ref count")
   232  		return nil
   233  	}
   234  
   235  	// This may error because we have exactly 1 plugin, but potentially multiple
   236  	// tasks which are calling remove.
   237  	err = p.backend.Remove(p.pluginID, &enginetypes.PluginRmConfig{ForceRemove: true})
   238  	if isNotFound(err) {
   239  		return nil
   240  	}
   241  	return err
   242  }
   243  
   244  // Close is the close phase from swarmkit
   245  func (p *Controller) Close() error {
   246  	p.logger.Debug("Close")
   247  	return nil
   248  }
   249  
   250  func convertPrivileges(ls []*runtime.PluginPrivilege) enginetypes.PluginPrivileges {
   251  	var out enginetypes.PluginPrivileges
   252  	for _, p := range ls {
   253  		pp := enginetypes.PluginPrivilege{
   254  			Name:        p.Name,
   255  			Description: p.Description,
   256  			Value:       p.Value,
   257  		}
   258  		out = append(out, pp)
   259  	}
   260  	return out
   261  }