github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/dynamicplugins/registry.go (about) 1 // dynamicplugins is a package that manages dynamic plugins in Nomad. 2 // It exposes a registry that allows for plugins to be registered/deregistered 3 // and also allows subscribers to receive real time updates of these events. 4 package dynamicplugins 5 6 import ( 7 "container/list" 8 "context" 9 "errors" 10 "fmt" 11 "sync" 12 ) 13 14 const ( 15 PluginTypeCSIController = "csi-controller" 16 PluginTypeCSINode = "csi-node" 17 ) 18 19 // Registry is an interface that allows for the dynamic registration of plugins 20 // that are running as Nomad Tasks. 21 type Registry interface { 22 RegisterPlugin(info *PluginInfo) error 23 DeregisterPlugin(ptype, name, allocID string) error 24 25 ListPlugins(ptype string) []*PluginInfo 26 DispensePlugin(ptype, name string) (interface{}, error) 27 PluginForAlloc(ptype, name, allocID string) (*PluginInfo, error) 28 29 PluginsUpdatedCh(ctx context.Context, ptype string) <-chan *PluginUpdateEvent 30 31 Shutdown() 32 33 StubDispenserForType(ptype string, dispenser PluginDispenser) 34 } 35 36 // RegistryState is what we persist in the client state 37 // store. It contains a map of plugin types to maps of plugin name -> 38 // list of *PluginInfo, sorted by recency of registration 39 type RegistryState struct { 40 Plugins map[string]map[string]*list.List 41 } 42 43 type PluginDispenser func(info *PluginInfo) (interface{}, error) 44 45 // NewRegistry takes a map of `plugintype` to PluginDispenser functions 46 // that should be used to vend clients for plugins to be used. 47 func NewRegistry(state StateStorage, dispensers map[string]PluginDispenser) Registry { 48 49 registry := &dynamicRegistry{ 50 plugins: make(map[string]map[string]*list.List), 51 broadcasters: make(map[string]*pluginEventBroadcaster), 52 dispensers: dispensers, 53 state: state, 54 } 55 56 // populate the state and initial broadcasters if we have an 57 // existing state DB to restore 58 if state != nil { 59 storedState, err := state.GetDynamicPluginRegistryState() 60 if err == nil && storedState != nil { 61 registry.plugins = storedState.Plugins 62 for ptype := range registry.plugins { 63 registry.broadcasterForPluginType(ptype) 64 } 65 } 66 } 67 68 return registry 69 } 70 71 // StateStorage is used to persist the dynamic plugin registry's state 72 // across agent restarts. 73 type StateStorage interface { 74 // GetDynamicPluginRegistryState is used to restore the registry state 75 GetDynamicPluginRegistryState() (*RegistryState, error) 76 77 // PutDynamicPluginRegistryState is used to store the registry state 78 PutDynamicPluginRegistryState(state *RegistryState) error 79 } 80 81 // PluginInfo is the metadata that is stored by the registry for a given plugin. 82 type PluginInfo struct { 83 Name string 84 Type string 85 Version string 86 87 // ConnectionInfo should only be used externally during `RegisterPlugin` and 88 // may not be exposed in the future. 89 ConnectionInfo *PluginConnectionInfo 90 91 // AllocID tracks the allocation running the plugin 92 AllocID string 93 94 // Options is used for plugin registrations to pass further metadata along to 95 // other subsystems 96 Options map[string]string 97 } 98 99 // PluginConnectionInfo is the data required to connect to the plugin. 100 // note: We currently only support Unix Domain Sockets, but this may be expanded 101 // 102 // to support other connection modes in the future. 103 type PluginConnectionInfo struct { 104 // SocketPath is the path to the plugins api socket. 105 SocketPath string 106 } 107 108 // EventType is the enum of events that will be emitted by a Registry's 109 // PluginsUpdatedCh. 110 type EventType string 111 112 const ( 113 // EventTypeRegistered is emitted by the Registry when a new plugin has been 114 // registered. 115 EventTypeRegistered EventType = "registered" 116 // EventTypeDeregistered is emitted by the Registry when a plugin has been 117 // removed. 118 EventTypeDeregistered EventType = "deregistered" 119 ) 120 121 // PluginUpdateEvent is a struct that is sent over a PluginsUpdatedCh when 122 // plugins are added or removed from the registry. 123 type PluginUpdateEvent struct { 124 EventType EventType 125 Info *PluginInfo 126 } 127 128 type dynamicRegistry struct { 129 plugins map[string]map[string]*list.List 130 pluginsLock sync.RWMutex 131 132 broadcasters map[string]*pluginEventBroadcaster 133 broadcastersLock sync.Mutex 134 135 dispensers map[string]PluginDispenser 136 stubDispensers map[string]PluginDispenser 137 138 state StateStorage 139 } 140 141 // StubDispenserForType allows test functions to provide alternative plugin 142 // dispensers to simplify writing tests for higher level Nomad features. 143 // This function should not be called from production code. 144 func (d *dynamicRegistry) StubDispenserForType(ptype string, dispenser PluginDispenser) { 145 // delete from stubs 146 if dispenser == nil && d.stubDispensers != nil { 147 delete(d.stubDispensers, ptype) 148 if len(d.stubDispensers) == 0 { 149 d.stubDispensers = nil 150 } 151 152 return 153 } 154 155 // setup stubs 156 if d.stubDispensers == nil { 157 d.stubDispensers = make(map[string]PluginDispenser, 1) 158 } 159 160 d.stubDispensers[ptype] = dispenser 161 } 162 163 func (d *dynamicRegistry) RegisterPlugin(info *PluginInfo) error { 164 if info.Type == "" { 165 // This error shouldn't make it to a production cluster and is to aid 166 // developers during the development of new plugin types. 167 return errors.New("Plugin.Type must not be empty") 168 } 169 170 if info.ConnectionInfo == nil { 171 // This error shouldn't make it to a production cluster and is to aid 172 // developers during the development of new plugin types. 173 return errors.New("Plugin.ConnectionInfo must not be nil") 174 } 175 176 if info.Name == "" { 177 // This error shouldn't make it to a production cluster and is to aid 178 // developers during the development of new plugin types. 179 return errors.New("Plugin.Name must not be empty") 180 } 181 182 d.pluginsLock.Lock() 183 defer d.pluginsLock.Unlock() 184 185 pmap, ok := d.plugins[info.Type] 186 if !ok { 187 pmap = make(map[string]*list.List) 188 d.plugins[info.Type] = pmap 189 } 190 infos, ok := pmap[info.Name] 191 if !ok { 192 infos = list.New() 193 pmap[info.Name] = infos 194 } 195 196 // TODO(tgross): https://github.com/hashicorp/nomad/issues/11786 197 // If we're already registered, we should update the definition 198 // and send a broadcast of any update so the instanceManager can 199 // be restarted if there's been a change 200 var alreadyRegistered bool 201 for e := infos.Front(); e != nil; e = e.Next() { 202 if e.Value.(*PluginInfo).AllocID == info.AllocID { 203 alreadyRegistered = true 204 break 205 } 206 } 207 if !alreadyRegistered { 208 infos.PushFront(info) 209 broadcaster := d.broadcasterForPluginType(info.Type) 210 event := &PluginUpdateEvent{ 211 EventType: EventTypeRegistered, 212 Info: info, 213 } 214 broadcaster.broadcast(event) 215 } 216 217 return d.sync() 218 } 219 220 func (d *dynamicRegistry) broadcasterForPluginType(ptype string) *pluginEventBroadcaster { 221 d.broadcastersLock.Lock() 222 defer d.broadcastersLock.Unlock() 223 224 broadcaster, ok := d.broadcasters[ptype] 225 if !ok { 226 broadcaster = newPluginEventBroadcaster() 227 d.broadcasters[ptype] = broadcaster 228 } 229 230 return broadcaster 231 } 232 233 func (d *dynamicRegistry) DeregisterPlugin(ptype, name, allocID string) error { 234 d.pluginsLock.Lock() 235 defer d.pluginsLock.Unlock() 236 237 if ptype == "" { 238 // This error shouldn't make it to a production cluster and is to aid 239 // developers during the development of new plugin types. 240 return errors.New("must specify plugin type to deregister") 241 } 242 if name == "" { 243 // This error shouldn't make it to a production cluster and is to aid 244 // developers during the development of new plugin types. 245 return errors.New("must specify plugin name to deregister") 246 } 247 if allocID == "" { 248 return errors.New("must specify plugin allocation ID to deregister") 249 } 250 251 pmap, ok := d.plugins[ptype] 252 if !ok { 253 // If this occurs there's a bug in the registration handler. 254 return fmt.Errorf("no plugins registered for type: %s", ptype) 255 } 256 257 infos, ok := pmap[name] 258 if !ok { 259 // plugin already deregistered, don't send events or try re-deleting. 260 return nil 261 } 262 263 var info *PluginInfo 264 for e := infos.Front(); e != nil; e = e.Next() { 265 info = e.Value.(*PluginInfo) 266 if info.AllocID == allocID { 267 infos.Remove(e) 268 break 269 } 270 } 271 272 if info != nil { 273 broadcaster := d.broadcasterForPluginType(ptype) 274 event := &PluginUpdateEvent{ 275 EventType: EventTypeDeregistered, 276 Info: info, 277 } 278 broadcaster.broadcast(event) 279 } 280 281 return d.sync() 282 } 283 284 func (d *dynamicRegistry) ListPlugins(ptype string) []*PluginInfo { 285 d.pluginsLock.RLock() 286 defer d.pluginsLock.RUnlock() 287 288 pmap, ok := d.plugins[ptype] 289 if !ok { 290 return nil 291 } 292 293 plugins := make([]*PluginInfo, 0, len(pmap)) 294 295 for _, info := range pmap { 296 if info.Front() != nil { 297 plugins = append(plugins, info.Front().Value.(*PluginInfo)) 298 } 299 } 300 301 return plugins 302 } 303 304 func (d *dynamicRegistry) DispensePlugin(ptype string, name string) (interface{}, error) { 305 d.pluginsLock.Lock() 306 defer d.pluginsLock.Unlock() 307 308 if ptype == "" { 309 // This error shouldn't make it to a production cluster and is to aid 310 // developers during the development of new plugin types. 311 return nil, errors.New("must specify plugin type to dispense") 312 } 313 if name == "" { 314 // This error shouldn't make it to a production cluster and is to aid 315 // developers during the development of new plugin types. 316 return nil, errors.New("must specify plugin name to dispense") 317 } 318 319 dispenseFunc, ok := d.dispensers[ptype] 320 if !ok { 321 // This error shouldn't make it to a production cluster and is to aid 322 // developers during the development of new plugin types. 323 return nil, fmt.Errorf("no plugin dispenser found for type: %s", ptype) 324 } 325 326 // After initially loading the dispenser (to avoid masking missing setup in 327 // client/client.go), we then check to see if we have any stub dispensers for 328 // this plugin type. If we do, then replace the dispenser fn with the stub. 329 if d.stubDispensers != nil { 330 if stub, ok := d.stubDispensers[ptype]; ok { 331 dispenseFunc = stub 332 } 333 } 334 335 pmap, ok := d.plugins[ptype] 336 if !ok { 337 return nil, fmt.Errorf("no plugins registered for type: %s", ptype) 338 } 339 340 info, ok := pmap[name] 341 if !ok || info.Front() == nil { 342 return nil, fmt.Errorf("plugin %s for type %s not found", name, ptype) 343 } 344 345 return dispenseFunc(info.Front().Value.(*PluginInfo)) 346 } 347 348 func (d *dynamicRegistry) PluginForAlloc(ptype, name, allocID string) (*PluginInfo, error) { 349 d.pluginsLock.Lock() 350 defer d.pluginsLock.Unlock() 351 352 pmap, ok := d.plugins[ptype] 353 if !ok { 354 return nil, fmt.Errorf("no plugins registered for type: %s", ptype) 355 } 356 357 infos, ok := pmap[name] 358 if ok { 359 for e := infos.Front(); e != nil; e = e.Next() { 360 plugin := e.Value.(*PluginInfo) 361 if plugin.AllocID == allocID { 362 return plugin, nil 363 } 364 } 365 } 366 return nil, fmt.Errorf("no plugin for that allocation") 367 } 368 369 // PluginsUpdatedCh returns a channel over which plugin events for the requested 370 // plugin type will be emitted. These events are strongly ordered and will never 371 // be dropped. 372 // 373 // The receiving channel _must not_ be closed before the provided context is 374 // cancelled. 375 func (d *dynamicRegistry) PluginsUpdatedCh(ctx context.Context, ptype string) <-chan *PluginUpdateEvent { 376 b := d.broadcasterForPluginType(ptype) 377 ch := b.subscribe() 378 go func() { 379 select { 380 case <-b.shutdownCh: 381 return 382 case <-ctx.Done(): 383 b.unsubscribe(ch) 384 } 385 }() 386 387 return ch 388 } 389 390 func (d *dynamicRegistry) sync() error { 391 if d.state != nil { 392 storedState := &RegistryState{Plugins: d.plugins} 393 return d.state.PutDynamicPluginRegistryState(storedState) 394 } 395 return nil 396 } 397 398 func (d *dynamicRegistry) Shutdown() { 399 for _, b := range d.broadcasters { 400 b.shutdown() 401 } 402 } 403 404 type pluginEventBroadcaster struct { 405 stopCh chan struct{} 406 shutdownCh chan struct{} 407 publishCh chan *PluginUpdateEvent 408 409 subscriptions map[chan *PluginUpdateEvent]struct{} 410 subscriptionsLock sync.RWMutex 411 } 412 413 func newPluginEventBroadcaster() *pluginEventBroadcaster { 414 b := &pluginEventBroadcaster{ 415 stopCh: make(chan struct{}), 416 shutdownCh: make(chan struct{}), 417 publishCh: make(chan *PluginUpdateEvent, 1), 418 subscriptions: make(map[chan *PluginUpdateEvent]struct{}), 419 } 420 go b.run() 421 return b 422 } 423 424 func (p *pluginEventBroadcaster) run() { 425 for { 426 select { 427 case <-p.stopCh: 428 close(p.shutdownCh) 429 return 430 case msg := <-p.publishCh: 431 p.subscriptionsLock.RLock() 432 for msgCh := range p.subscriptions { 433 msgCh <- msg 434 } 435 p.subscriptionsLock.RUnlock() 436 } 437 } 438 } 439 440 func (p *pluginEventBroadcaster) shutdown() { 441 close(p.stopCh) 442 443 // Wait for loop to exit before closing subscriptions 444 <-p.shutdownCh 445 446 p.subscriptionsLock.Lock() 447 for sub := range p.subscriptions { 448 delete(p.subscriptions, sub) 449 close(sub) 450 } 451 p.subscriptionsLock.Unlock() 452 } 453 454 func (p *pluginEventBroadcaster) broadcast(e *PluginUpdateEvent) { 455 p.publishCh <- e 456 } 457 458 func (p *pluginEventBroadcaster) subscribe() chan *PluginUpdateEvent { 459 p.subscriptionsLock.Lock() 460 defer p.subscriptionsLock.Unlock() 461 462 ch := make(chan *PluginUpdateEvent, 1) 463 p.subscriptions[ch] = struct{}{} 464 return ch 465 } 466 467 func (p *pluginEventBroadcaster) unsubscribe(ch chan *PluginUpdateEvent) { 468 p.subscriptionsLock.Lock() 469 defer p.subscriptionsLock.Unlock() 470 471 _, ok := p.subscriptions[ch] 472 if ok { 473 delete(p.subscriptions, ch) 474 close(ch) 475 } 476 }