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 }