github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/shared.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 14 "github.com/juju/juju/apiserver/facade" 15 jujucontroller "github.com/juju/juju/controller" 16 "github.com/juju/juju/core/cache" 17 coredatabase "github.com/juju/juju/core/database" 18 "github.com/juju/juju/core/lease" 19 "github.com/juju/juju/core/multiwatcher" 20 "github.com/juju/juju/core/presence" 21 "github.com/juju/juju/pubsub/controller" 22 "github.com/juju/juju/state" 23 ) 24 25 // SharedHub represents the methods of the pubsub.StructuredHub 26 // that are used. The context uses an interface to allow mocking 27 // of the hub. 28 type SharedHub interface { 29 Publish(topic string, data interface{}) (func(), error) 30 Subscribe(topic string, handler interface{}) (func(), error) 31 } 32 33 // sharedServerContext contains a number of components that are unchangeable in the API server. 34 // These components need to be exposed through the facade.Context. Instead of having the methods 35 // of newAPIHandler and newAPIRoot take ever increasing numbers of parameters, they will instead 36 // have a pointer to the sharedServerContext. 37 // 38 // All attributes in the context should be goroutine aware themselves, like the state pool, hub, and 39 // presence, or protected and only accessed through methods on this context object. 40 type sharedServerContext struct { 41 statePool *state.StatePool 42 controller *cache.Controller 43 multiwatcherFactory multiwatcher.Factory 44 centralHub SharedHub 45 presence presence.Recorder 46 leaseManager lease.Manager 47 logger loggo.Logger 48 cancel <-chan struct{} 49 charmhubHTTPClient facade.HTTPClient 50 dbGetter coredatabase.DBGetter 51 52 configMutex sync.RWMutex 53 controllerConfig jujucontroller.Config 54 features set.Strings 55 56 unsubscribe func() 57 } 58 59 type sharedServerConfig struct { 60 statePool *state.StatePool 61 controller *cache.Controller 62 multiwatcherFactory multiwatcher.Factory 63 centralHub SharedHub 64 presence presence.Recorder 65 leaseManager lease.Manager 66 controllerConfig jujucontroller.Config 67 logger loggo.Logger 68 charmhubHTTPClient facade.HTTPClient 69 dbGetter coredatabase.DBGetter 70 } 71 72 func (c *sharedServerConfig) validate() error { 73 if c.statePool == nil { 74 return errors.NotValidf("nil statePool") 75 } 76 if c.controller == nil { 77 return errors.NotValidf("nil controller") 78 } 79 if c.multiwatcherFactory == nil { 80 return errors.NotValidf("nil multiwatcherFactory") 81 } 82 if c.centralHub == nil { 83 return errors.NotValidf("nil centralHub") 84 } 85 if c.presence == nil { 86 return errors.NotValidf("nil presence") 87 } 88 if c.leaseManager == nil { 89 return errors.NotValidf("nil leaseManager") 90 } 91 if c.controllerConfig == nil { 92 return errors.NotValidf("nil controllerConfig") 93 } 94 if c.dbGetter == nil { 95 return errors.NotValidf("nil dbGetter") 96 } 97 return nil 98 } 99 100 func newSharedServerContext(config sharedServerConfig) (*sharedServerContext, error) { 101 if err := config.validate(); err != nil { 102 return nil, errors.Trace(err) 103 } 104 ctx := &sharedServerContext{ 105 statePool: config.statePool, 106 controller: config.controller, 107 multiwatcherFactory: config.multiwatcherFactory, 108 centralHub: config.centralHub, 109 presence: config.presence, 110 leaseManager: config.leaseManager, 111 logger: config.logger, 112 controllerConfig: config.controllerConfig, 113 charmhubHTTPClient: config.charmhubHTTPClient, 114 dbGetter: config.dbGetter, 115 } 116 ctx.features = config.controllerConfig.Features() 117 // We are able to get the current controller config before subscribing to changes 118 // because the changes are only ever published in response to an API call, and 119 // this function is called in the newServer call to create the API server, 120 // and we know that we can't make any API calls until the server has started. 121 unsubscribe, err := ctx.centralHub.Subscribe(controller.ConfigChanged, ctx.onConfigChanged) 122 if err != nil { 123 ctx.logger.Criticalf("programming error in subscribe function: %v", err) 124 return nil, errors.Trace(err) 125 } 126 ctx.unsubscribe = unsubscribe 127 return ctx, nil 128 } 129 130 func (c *sharedServerContext) Close() { 131 c.unsubscribe() 132 } 133 134 func (c *sharedServerContext) onConfigChanged(topic string, data controller.ConfigChangedMessage, err error) { 135 if err != nil { 136 c.logger.Criticalf("programming error in %s message data: %v", topic, err) 137 return 138 } 139 140 features := data.Config.Features() 141 142 c.configMutex.Lock() 143 c.controllerConfig = data.Config 144 removed := c.features.Difference(features) 145 added := features.Difference(c.features) 146 c.features = features 147 values := features.SortedValues() 148 c.configMutex.Unlock() 149 150 if removed.Size() != 0 || added.Size() != 0 { 151 c.logger.Infof("updating features to %v", values) 152 } 153 } 154 155 func (c *sharedServerContext) featureEnabled(flag string) bool { 156 c.configMutex.RLock() 157 defer c.configMutex.RUnlock() 158 return c.features.Contains(flag) 159 } 160 161 func (c *sharedServerContext) maxDebugLogDuration() time.Duration { 162 c.configMutex.RLock() 163 defer c.configMutex.RUnlock() 164 return c.controllerConfig.MaxDebugLogDuration() 165 }