github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/modelcache/worker_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelcache_test 5 6 import ( 7 "time" 8 9 "github.com/juju/juju/testing/factory" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jc "github.com/juju/testing/checkers" 14 "github.com/prometheus/client_golang/prometheus" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/worker.v1" 17 "gopkg.in/juju/worker.v1/workertest" 18 19 "github.com/juju/juju/core/cache" 20 "github.com/juju/juju/core/life" 21 "github.com/juju/juju/state" 22 statetesting "github.com/juju/juju/state/testing" 23 "github.com/juju/juju/testing" 24 "github.com/juju/juju/worker/modelcache" 25 ) 26 27 type WorkerSuite struct { 28 statetesting.StateSuite 29 logger loggo.Logger 30 config modelcache.Config 31 notify func(interface{}) 32 } 33 34 var _ = gc.Suite(&WorkerSuite{}) 35 36 func (s *WorkerSuite) SetUpTest(c *gc.C) { 37 s.StateSuite.SetUpTest(c) 38 s.notify = nil 39 s.logger = loggo.GetLogger("test") 40 s.logger.SetLogLevel(loggo.TRACE) 41 s.config = modelcache.Config{ 42 Logger: s.logger, 43 StatePool: s.StatePool, 44 PrometheusRegisterer: noopRegisterer{}, 45 Cleanup: func() {}, 46 } 47 } 48 49 func (s *WorkerSuite) TestConfigMissingLogger(c *gc.C) { 50 s.config.Logger = nil 51 err := s.config.Validate() 52 c.Check(err, jc.Satisfies, errors.IsNotValid) 53 c.Check(err, gc.ErrorMatches, "missing logger not valid") 54 } 55 56 func (s *WorkerSuite) TestConfigMissingStatePool(c *gc.C) { 57 s.config.StatePool = nil 58 err := s.config.Validate() 59 c.Check(err, jc.Satisfies, errors.IsNotValid) 60 c.Check(err, gc.ErrorMatches, "missing state pool not valid") 61 } 62 63 func (s *WorkerSuite) TestConfigMissingRegisterer(c *gc.C) { 64 s.config.PrometheusRegisterer = nil 65 err := s.config.Validate() 66 c.Check(err, jc.Satisfies, errors.IsNotValid) 67 c.Check(err, gc.ErrorMatches, "missing prometheus registerer not valid") 68 } 69 70 func (s *WorkerSuite) TestConfigMissingCleanup(c *gc.C) { 71 s.config.Cleanup = nil 72 err := s.config.Validate() 73 c.Check(err, jc.Satisfies, errors.IsNotValid) 74 c.Check(err, gc.ErrorMatches, "missing cleanup func not valid") 75 } 76 77 func (s *WorkerSuite) getController(c *gc.C, w worker.Worker) *cache.Controller { 78 var controller *cache.Controller 79 err := modelcache.ExtractCacheController(w, &controller) 80 c.Assert(err, jc.ErrorIsNil) 81 return controller 82 } 83 84 func (s *WorkerSuite) TestExtractCacheController(c *gc.C) { 85 var controller *cache.Controller 86 var empty worker.Worker 87 err := modelcache.ExtractCacheController(empty, &controller) 88 c.Assert(err.Error(), gc.Equals, "in should be a *modelcache.cacheWorker; got <nil>") 89 } 90 91 func (s *WorkerSuite) start(c *gc.C) worker.Worker { 92 config := s.config 93 config.Notify = s.notify 94 w, err := modelcache.NewWorker(config) 95 c.Assert(err, jc.ErrorIsNil) 96 s.AddCleanup(func(c *gc.C) { 97 workertest.CleanKill(c, w) 98 }) 99 return w 100 } 101 102 func (s *WorkerSuite) captureModelEvents(c *gc.C) <-chan interface{} { 103 events := make(chan interface{}) 104 s.notify = func(change interface{}) { 105 send := false 106 switch change.(type) { 107 case cache.ModelChange: 108 send = true 109 case cache.RemoveModel: 110 send = true 111 default: 112 // no-op 113 } 114 if send { 115 c.Logf("sending %#v", change) 116 select { 117 case events <- change: 118 case <-time.After(testing.LongWait): 119 c.Fatalf("change not processed by test") 120 } 121 } 122 } 123 return events 124 } 125 126 func (s *WorkerSuite) checkModel(c *gc.C, obtained interface{}, model *state.Model) { 127 change, ok := obtained.(cache.ModelChange) 128 c.Assert(ok, jc.IsTrue) 129 130 c.Check(change.ModelUUID, gc.Equals, model.UUID()) 131 c.Check(change.Name, gc.Equals, model.Name()) 132 c.Check(change.Life, gc.Equals, life.Value(model.Life().String())) 133 c.Check(change.Owner, gc.Equals, model.Owner().Name()) 134 cfg, err := model.Config() 135 c.Assert(err, jc.ErrorIsNil) 136 c.Check(change.Config, jc.DeepEquals, cfg.AllAttrs()) 137 status, err := model.Status() 138 c.Assert(err, jc.ErrorIsNil) 139 c.Check(change.Status, jc.DeepEquals, status) 140 } 141 142 func (s *WorkerSuite) nextChange(c *gc.C, changes <-chan interface{}) interface{} { 143 var obtained interface{} 144 select { 145 case obtained = <-changes: 146 case <-time.After(testing.LongWait): 147 c.Fatalf("no change") 148 } 149 return obtained 150 } 151 152 func (s *WorkerSuite) TestInitialModel(c *gc.C) { 153 changes := s.captureModelEvents(c) 154 s.start(c) 155 156 obtained := s.nextChange(c, changes) 157 expected, err := s.State.Model() 158 c.Assert(err, jc.ErrorIsNil) 159 s.checkModel(c, obtained, expected) 160 } 161 162 func (s *WorkerSuite) TestModelConfigChange(c *gc.C) { 163 changes := s.captureModelEvents(c) 164 w := s.start(c) 165 // discard initial event 166 s.nextChange(c, changes) 167 168 model, err := s.State.Model() 169 c.Assert(err, jc.ErrorIsNil) 170 171 c.Logf("\nupdating status\n\n") 172 173 // Add a different logging config value. 174 expected := "juju=INFO;missing=DEBUG;unit=DEBUG" 175 err = model.UpdateModelConfig(map[string]interface{}{ 176 "logging-config": expected, 177 }, nil) 178 c.Assert(err, jc.ErrorIsNil) 179 s.State.StartSync() 180 181 // Wait for the change. 182 s.nextChange(c, changes) 183 184 controller := s.getController(c, w) 185 cachedModel, err := controller.Model(s.State.ModelUUID()) 186 c.Assert(err, jc.ErrorIsNil) 187 188 c.Assert(cachedModel.Config()["logging-config"], gc.Equals, expected) 189 } 190 191 func (s *WorkerSuite) TestNewModel(c *gc.C) { 192 changes := s.captureModelEvents(c) 193 w := s.start(c) 194 // grab and discard the event for the initial model 195 s.nextChange(c, changes) 196 197 newState := s.Factory.MakeModel(c, nil) 198 s.State.StartSync() 199 defer newState.Close() 200 201 obtained := s.nextChange(c, changes) 202 expected, err := newState.Model() 203 c.Assert(err, jc.ErrorIsNil) 204 s.checkModel(c, obtained, expected) 205 206 controller := s.getController(c, w) 207 c.Assert(controller.ModelUUIDs(), gc.HasLen, 2) 208 } 209 210 func (s *WorkerSuite) TestRemovedModel(c *gc.C) { 211 changes := s.captureModelEvents(c) 212 w := s.start(c) 213 214 // grab and discard the event for the initial model 215 s.nextChange(c, changes) 216 217 st := s.Factory.MakeModel(c, nil) 218 s.State.StartSync() 219 defer st.Close() 220 221 // grab and discard the event for the new model 222 s.nextChange(c, changes) 223 224 model, err := st.Model() 225 c.Assert(err, jc.ErrorIsNil) 226 err = model.Destroy(state.DestroyModelParams{}) 227 c.Assert(err, jc.ErrorIsNil) 228 s.State.StartSync() 229 230 // grab and discard the event for the new model 231 obtained := s.nextChange(c, changes) 232 modelChange, ok := obtained.(cache.ModelChange) 233 c.Assert(ok, jc.IsTrue) 234 c.Assert(modelChange.Life, gc.Equals, life.Value("dying")) 235 236 err = st.ProcessDyingModel() 237 c.Assert(err, jc.ErrorIsNil) 238 s.State.StartSync() 239 240 err = st.RemoveDyingModel() 241 c.Assert(err, jc.ErrorIsNil) 242 s.State.StartSync() 243 244 obtained = s.nextChange(c, changes) 245 246 change, ok := obtained.(cache.RemoveModel) 247 c.Assert(ok, jc.IsTrue) 248 c.Check(change.ModelUUID, gc.Equals, model.UUID()) 249 250 // Controller just has the system state again. 251 controller := s.getController(c, w) 252 c.Assert(controller.ModelUUIDs(), jc.SameContents, []string{s.State.ModelUUID()}) 253 } 254 255 func (s *WorkerSuite) captureApplicationEvents(c *gc.C) <-chan interface{} { 256 events := make(chan interface{}) 257 s.notify = func(change interface{}) { 258 send := false 259 switch change.(type) { 260 case cache.ApplicationChange: 261 send = true 262 case cache.RemoveApplication: 263 send = true 264 default: 265 // no-op 266 } 267 if send { 268 c.Logf("sending %#v", change) 269 select { 270 case events <- change: 271 case <-time.After(testing.LongWait): 272 c.Fatalf("change not processed by test") 273 } 274 } 275 } 276 return events 277 } 278 279 func (s *WorkerSuite) TestAddApplication(c *gc.C) { 280 changes := s.captureApplicationEvents(c) 281 w := s.start(c) 282 283 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{}) 284 s.State.StartSync() 285 286 change := s.nextChange(c, changes) 287 obtained, ok := change.(cache.ApplicationChange) 288 c.Assert(ok, jc.IsTrue) 289 c.Check(obtained.Name, gc.Equals, app.Name()) 290 291 controller := s.getController(c, w) 292 modUUIDs := controller.ModelUUIDs() 293 c.Check(modUUIDs, gc.HasLen, 1) 294 295 mod, err := controller.Model(modUUIDs[0]) 296 c.Assert(err, jc.ErrorIsNil) 297 298 cachedApp, err := mod.Application(app.Name()) 299 c.Assert(err, jc.ErrorIsNil) 300 c.Check(cachedApp, gc.NotNil) 301 } 302 303 func (s *WorkerSuite) TestRemoveApplication(c *gc.C) { 304 changes := s.captureApplicationEvents(c) 305 w := s.start(c) 306 307 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{}) 308 s.State.StartSync() 309 _ = s.nextChange(c, changes) 310 311 controller := s.getController(c, w) 312 modUUID := controller.ModelUUIDs()[0] 313 314 c.Assert(app.Destroy(), jc.ErrorIsNil) 315 s.State.StartSync() 316 317 // We will either get our application event, 318 // or time-out after processing all the changes. 319 for { 320 change := s.nextChange(c, changes) 321 if _, ok := change.(cache.RemoveApplication); ok { 322 mod, err := controller.Model(modUUID) 323 c.Assert(err, jc.ErrorIsNil) 324 325 _, err = mod.Application(app.Name()) 326 c.Check(errors.IsNotFound(err), jc.IsTrue) 327 return 328 } 329 } 330 } 331 332 type noopRegisterer struct { 333 prometheus.Registerer 334 } 335 336 func (noopRegisterer) Register(prometheus.Collector) error { 337 return nil 338 } 339 340 func (noopRegisterer) Unregister(prometheus.Collector) bool { 341 return true 342 }