github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/cluster/controllers/plugin/controller.go (about)

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