github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/cluster/controllers/plugin/controller.go (about) 1 package plugin // import "github.com/Prakhar-Agarwal-byte/moby/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/Prakhar-Agarwal-byte/moby/api/types" 11 "github.com/Prakhar-Agarwal-byte/moby/api/types/registry" 12 "github.com/Prakhar-Agarwal-byte/moby/api/types/swarm/runtime" 13 "github.com/Prakhar-Agarwal-byte/moby/errdefs" 14 "github.com/Prakhar-Agarwal-byte/moby/plugin" 15 v2 "github.com/Prakhar-Agarwal-byte/moby/plugin/v2" 16 "github.com/gogo/protobuf/proto" 17 "github.com/moby/swarmkit/v2/api" 18 "github.com/pkg/errors" 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 *log.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 *types.PluginDisableConfig) error 46 Enable(name string, config *types.PluginEnableConfig) error 47 Remove(name string, config *types.PluginRmConfig) error 48 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 49 Upgrade(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *registry.AuthConfig, privileges types.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: log.G(context.TODO()).WithFields(log.Fields{ 65 "controller": "plugin", 66 "task": t.ID, 67 "plugin": spec.Name, 68 }), 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 registry.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(), &types.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, io.Discard) 123 } 124 125 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 { 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, &types.PluginDisableConfig{ForceDisable: false}) 149 } 150 return nil 151 } 152 if !pl.IsEnabled() { 153 return p.backend.Enable(p.pluginID, &types.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, &types.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) types.PluginPrivileges { 251 var out types.PluginPrivileges 252 for _, p := range ls { 253 pp := types.PluginPrivilege{ 254 Name: p.Name, 255 Description: p.Description, 256 Value: p.Value, 257 } 258 out = append(out, pp) 259 } 260 return out 261 }