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