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