github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/cache/controller.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cache 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/pubsub" 12 "gopkg.in/tomb.v2" 13 ) 14 15 // We use a package level logger here because the cache is only 16 // ever in machine agents, so will never need to be in an alternative 17 // logging context. 18 19 // ControllerConfig is a simple config value struct for the controller. 20 type ControllerConfig struct { 21 // Changes from the event source come over this channel. 22 // The changes channel must be non-nil. 23 Changes <-chan interface{} 24 25 // Notify is a callback function used primarily for testing, and is 26 // called by the controller main processing loop after processing a change. 27 // The change processed is passed in as the arg to notify. 28 Notify func(interface{}) 29 } 30 31 // Validate ensures the controller has the right values to be created. 32 func (c *ControllerConfig) Validate() error { 33 if c.Changes == nil { 34 return errors.NotValidf("nil Changes") 35 } 36 return nil 37 } 38 39 // Controller is the primary cached object. 40 type Controller struct { 41 config ControllerConfig 42 tomb tomb.Tomb 43 mu sync.Mutex 44 models map[string]*Model 45 hub *pubsub.SimpleHub 46 metrics *ControllerGauges 47 } 48 49 // NewController creates a new cached controller intance. 50 // The changes channel is what is used to supply the cache with the changes 51 // in order for the cache to be kept up to date. 52 func NewController(config ControllerConfig) (*Controller, error) { 53 if err := config.Validate(); err != nil { 54 return nil, errors.Trace(err) 55 } 56 c := &Controller{ 57 config: config, 58 models: make(map[string]*Model), 59 hub: pubsub.NewSimpleHub(&pubsub.SimpleHubConfig{ 60 // TODO: (thumper) add a get child method to loggers. 61 Logger: loggo.GetLogger("juju.core.cache.hub"), 62 }), 63 metrics: createControllerGauges(), 64 } 65 c.tomb.Go(c.loop) 66 return c, nil 67 } 68 69 func (c *Controller) loop() error { 70 for { 71 select { 72 case <-c.tomb.Dying(): 73 return nil 74 case change := <-c.config.Changes: 75 switch ch := change.(type) { 76 case ModelChange: 77 c.updateModel(ch) 78 case RemoveModel: 79 c.removeModel(ch) 80 case ApplicationChange: 81 c.updateApplication(ch) 82 case RemoveApplication: 83 c.removeApplication(ch) 84 } 85 if c.config.Notify != nil { 86 c.config.Notify(change) 87 } 88 } 89 } 90 } 91 92 // Report returns information that is used in the dependency engine report. 93 func (c *Controller) Report() map[string]interface{} { 94 result := make(map[string]interface{}) 95 96 c.mu.Lock() 97 for uuid, model := range c.models { 98 result[uuid] = model.Report() 99 } 100 c.mu.Unlock() 101 102 return result 103 } 104 105 // ModelUUIDs returns the UUIDs of the models in the cache. 106 func (c *Controller) ModelUUIDs() []string { 107 c.mu.Lock() 108 109 result := make([]string, 0, len(c.models)) 110 for uuid := range c.models { 111 result = append(result, uuid) 112 } 113 114 c.mu.Unlock() 115 return result 116 } 117 118 // Kill is part of the worker.Worker interface. 119 func (c *Controller) Kill() { 120 c.tomb.Kill(nil) 121 } 122 123 // Wait is part of the worker.Worker interface. 124 func (c *Controller) Wait() error { 125 return c.tomb.Wait() 126 } 127 128 // Model returns the model for the specified UUID. 129 // If the model isn't found, a NotFoundError is returned. 130 func (c *Controller) Model(uuid string) (*Model, error) { 131 c.mu.Lock() 132 defer c.mu.Unlock() 133 134 model, found := c.models[uuid] 135 if !found { 136 return nil, errors.NotFoundf("model %q", uuid) 137 } 138 return model, nil 139 } 140 141 // updateModel will add or update the model details as 142 // described in the ModelChange. 143 func (c *Controller) updateModel(ch ModelChange) { 144 c.mu.Lock() 145 146 model, found := c.models[ch.ModelUUID] 147 if !found { 148 model = newModel(c.metrics, c.hub) 149 c.models[ch.ModelUUID] = model 150 } 151 model.setDetails(ch) 152 153 c.mu.Unlock() 154 } 155 156 // removeModel removes the model from the cache. 157 func (c *Controller) removeModel(ch RemoveModel) { 158 c.mu.Lock() 159 delete(c.models, ch.ModelUUID) 160 c.mu.Unlock() 161 } 162 163 // updateApplication adds or updates the application in the specified model. 164 func (c *Controller) updateApplication(ch ApplicationChange) { 165 c.mu.Lock() 166 167 // While it is likely that we will receive a change update for the model 168 // before we get an update for the application for that model, but the 169 // cache needs to be resilient enough to make sure that we can handle 170 // the situation where this is not the case. 171 model, found := c.models[ch.ModelUUID] 172 if !found { 173 model = newModel(c.metrics, c.hub) 174 c.models[ch.ModelUUID] = model 175 } 176 model.updateApplication(ch) 177 178 c.mu.Unlock() 179 } 180 181 // removeApplication removes the application for the cached model. 182 // If the cache does not have the model loaded for the application yet, 183 // then it will not have the application cached. 184 func (c *Controller) removeApplication(ch RemoveApplication) { 185 c.mu.Lock() 186 187 model, found := c.models[ch.ModelUUID] 188 if found { 189 model.removeApplication(ch) 190 } 191 192 c.mu.Unlock() 193 }