github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/globalclockupdater/manifold_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package globalclockupdater_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/juju/worker.v1" 16 "gopkg.in/juju/worker.v1/dependency" 17 dt "gopkg.in/juju/worker.v1/dependency/testing" 18 "gopkg.in/juju/worker.v1/workertest" 19 20 "github.com/juju/juju/core/globalclock" 21 "github.com/juju/juju/state" 22 coretesting "github.com/juju/juju/testing" 23 "github.com/juju/juju/worker/globalclockupdater" 24 ) 25 26 type ManifoldSuite struct { 27 testing.IsolationSuite 28 stub testing.Stub 29 config globalclockupdater.ManifoldConfig 30 stateTracker stubStateTracker 31 worker worker.Worker 32 logger loggo.Logger 33 } 34 35 var _ = gc.Suite(&ManifoldSuite{}) 36 37 func (s *ManifoldSuite) SetUpTest(c *gc.C) { 38 s.IsolationSuite.SetUpTest(c) 39 s.stub.ResetCalls() 40 s.logger = loggo.GetLogger("globalclockupdater_test") 41 s.config = globalclockupdater.ManifoldConfig{ 42 ClockName: "clock", 43 StateName: "state", 44 NewWorker: s.newWorker, 45 UpdateInterval: time.Second, 46 BackoffDelay: time.Second, 47 Logger: s.logger, 48 } 49 s.stateTracker = stubStateTracker{ 50 done: make(chan struct{}), 51 } 52 s.worker = worker.NewRunner(worker.RunnerParams{}) 53 s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, s.worker) }) 54 } 55 56 func (s *ManifoldSuite) newWorker(config globalclockupdater.Config) (worker.Worker, error) { 57 s.stub.AddCall("NewWorker", config) 58 if err := s.stub.NextErr(); err != nil { 59 return nil, err 60 } 61 return s.worker, nil 62 } 63 64 func (s *ManifoldSuite) TestInputs(c *gc.C) { 65 manifold := globalclockupdater.Manifold(s.config) 66 expectInputs := []string{"clock", "state"} 67 c.Check(manifold.Inputs, jc.SameContents, expectInputs) 68 } 69 70 func (s *ManifoldSuite) TestLeaseManagerInputs(c *gc.C) { 71 s.config.StateName = "" 72 s.config.LeaseManagerName = "lease-manager" 73 manifold := globalclockupdater.Manifold(s.config) 74 expectInputs := []string{"clock", "lease-manager"} 75 c.Check(manifold.Inputs, jc.SameContents, expectInputs) 76 } 77 78 func (s *ManifoldSuite) TestLeaseManagerAndRaftInputs(c *gc.C) { 79 s.config.StateName = "" 80 s.config.LeaseManagerName = "lease-manager" 81 s.config.RaftName = "raft" 82 manifold := globalclockupdater.Manifold(s.config) 83 expectInputs := []string{"clock", "lease-manager", "raft"} 84 c.Check(manifold.Inputs, jc.SameContents, expectInputs) 85 } 86 87 func (s *ManifoldSuite) TestStartValidateClockName(c *gc.C) { 88 s.config.ClockName = "" 89 s.testStartValidateConfig(c, "empty ClockName not valid") 90 } 91 92 func (s *ManifoldSuite) TestStartValidateStateName(c *gc.C) { 93 s.config.StateName = "" 94 s.testStartValidateConfig(c, "both StateName and LeaseManagerName empty not valid") 95 } 96 97 func (s *ManifoldSuite) TestStartValidateNotBoth(c *gc.C) { 98 s.config.LeaseManagerName = "lease-manager" 99 s.testStartValidateConfig(c, "only one of StateName and LeaseManagerName can be set") 100 } 101 102 func (s *ManifoldSuite) TestStartValidateNotRaftAndState(c *gc.C) { 103 s.config.RaftName = "raft" 104 s.testStartValidateConfig(c, "RaftName only valid with LeaseManagerName") 105 } 106 107 func (s *ManifoldSuite) TestStartValidateUpdateInterval(c *gc.C) { 108 s.config.UpdateInterval = 0 109 s.testStartValidateConfig(c, "non-positive UpdateInterval not valid") 110 } 111 112 func (s *ManifoldSuite) TestStartValidateBackoffDelay(c *gc.C) { 113 s.config.BackoffDelay = -1 114 s.testStartValidateConfig(c, "non-positive BackoffDelay not valid") 115 } 116 117 func (s *ManifoldSuite) testStartValidateConfig(c *gc.C, expect string) { 118 manifold := globalclockupdater.Manifold(s.config) 119 context := dt.StubContext(nil, map[string]interface{}{ 120 "clock": nil, 121 "state": nil, 122 }) 123 worker, err := manifold.Start(context) 124 c.Check(err, gc.ErrorMatches, expect) 125 c.Check(worker, gc.IsNil) 126 } 127 128 func (s *ManifoldSuite) TestStartMissingClock(c *gc.C) { 129 manifold := globalclockupdater.Manifold(s.config) 130 context := dt.StubContext(nil, map[string]interface{}{ 131 "clock": dependency.ErrMissing, 132 }) 133 134 worker, err := manifold.Start(context) 135 c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing) 136 c.Check(worker, gc.IsNil) 137 } 138 139 func (s *ManifoldSuite) TestStartMissingLeaseManager(c *gc.C) { 140 s.config.StateName = "" 141 s.config.LeaseManagerName = "lease-manager" 142 manifold := globalclockupdater.Manifold(s.config) 143 context := dt.StubContext(nil, map[string]interface{}{ 144 "clock": fakeClock{}, 145 "lease-manager": dependency.ErrMissing, 146 }) 147 148 worker, err := manifold.Start(context) 149 c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing) 150 c.Check(worker, gc.IsNil) 151 } 152 153 func (s *ManifoldSuite) TestStartMissingRaft(c *gc.C) { 154 updater := fakeUpdater{} 155 s.config.StateName = "" 156 s.config.LeaseManagerName = "lease-manager" 157 s.config.RaftName = "raft" 158 manifold := globalclockupdater.Manifold(s.config) 159 context := dt.StubContext(nil, map[string]interface{}{ 160 "clock": fakeClock{}, 161 "lease-manager": &updater, 162 "raft": dependency.ErrMissing, 163 }) 164 165 worker, err := manifold.Start(context) 166 c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing) 167 c.Check(worker, gc.IsNil) 168 } 169 170 func (s *ManifoldSuite) TestStartMissingState(c *gc.C) { 171 manifold := globalclockupdater.Manifold(s.config) 172 context := dt.StubContext(nil, map[string]interface{}{ 173 "clock": fakeClock{}, 174 "state": dependency.ErrMissing, 175 }) 176 177 worker, err := manifold.Start(context) 178 c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing) 179 c.Check(worker, gc.IsNil) 180 } 181 182 func (s *ManifoldSuite) TestStartStateTrackerError(c *gc.C) { 183 s.stateTracker.SetErrors(errors.New("phail")) 184 worker, err := s.startManifold(c) 185 c.Check(err, gc.ErrorMatches, "phail") 186 c.Check(worker, gc.IsNil) 187 } 188 189 func (s *ManifoldSuite) TestStartNewWorkerError(c *gc.C) { 190 s.stub.SetErrors(errors.New("phail")) 191 worker, err := s.startManifold(c) 192 c.Check(err, gc.ErrorMatches, "phail") 193 c.Check(worker, gc.IsNil) 194 s.stateTracker.CheckCallNames(c, "Use", "Done") 195 } 196 197 func (s *ManifoldSuite) TestStartNewWorkerSuccess(c *gc.C) { 198 worker, err := s.startManifold(c) 199 c.Check(err, jc.ErrorIsNil) 200 c.Check(worker, gc.Equals, s.worker) 201 202 s.stub.CheckCallNames(c, "NewWorker") 203 config := s.stub.Calls()[0].Args[0].(globalclockupdater.Config) 204 c.Assert(config.NewUpdater, gc.NotNil) 205 config.NewUpdater = nil 206 c.Assert(config, jc.DeepEquals, globalclockupdater.Config{ 207 LocalClock: fakeClock{}, 208 UpdateInterval: s.config.UpdateInterval, 209 BackoffDelay: s.config.BackoffDelay, 210 Logger: s.logger, 211 }) 212 } 213 214 func (s *ManifoldSuite) TestStartNewWorkerSuccessWithLeaseManager(c *gc.C) { 215 updater := fakeUpdater{} 216 s.config.StateName = "" 217 s.config.LeaseManagerName = "lease-manager" 218 s.config.RaftName = "raft" 219 worker, err := s.startManifoldWithContext(c, map[string]interface{}{ 220 "clock": fakeClock{}, 221 "lease-manager": &updater, 222 "raft": nil, 223 }) 224 c.Check(err, jc.ErrorIsNil) 225 c.Check(worker, gc.Equals, s.worker) 226 227 s.stub.CheckCallNames(c, "NewWorker") 228 config := s.stub.Calls()[0].Args[0].(globalclockupdater.Config) 229 c.Assert(config.NewUpdater, gc.NotNil) 230 actualUpdater, err := config.NewUpdater() 231 c.Assert(err, jc.ErrorIsNil) 232 c.Assert(actualUpdater, gc.Equals, &updater) 233 config.NewUpdater = nil 234 c.Assert(config, jc.DeepEquals, globalclockupdater.Config{ 235 LocalClock: fakeClock{}, 236 UpdateInterval: s.config.UpdateInterval, 237 BackoffDelay: s.config.BackoffDelay, 238 Logger: s.logger, 239 }) 240 } 241 242 func (s *ManifoldSuite) TestStoppingWorkerReleasesState(c *gc.C) { 243 worker, err := s.startManifold(c) 244 c.Check(err, jc.ErrorIsNil) 245 c.Check(worker, gc.Equals, s.worker) 246 247 s.stateTracker.CheckCallNames(c, "Use") 248 select { 249 case <-s.stateTracker.done: 250 c.Fatal("unexpected state release") 251 case <-time.After(coretesting.ShortWait): 252 } 253 254 // Stopping the worker should cause the state to 255 // eventually be released. 256 workertest.CleanKill(c, worker) 257 258 s.stateTracker.waitDone(c) 259 s.stateTracker.CheckCallNames(c, "Use", "Done") 260 } 261 262 func (s *ManifoldSuite) startManifold(c *gc.C) (worker.Worker, error) { 263 worker, err := s.startManifoldWithContext(c, map[string]interface{}{ 264 "clock": fakeClock{}, 265 "state": &s.stateTracker, 266 }) 267 if err != nil { 268 return nil, err 269 } 270 // Add a cleanup to wait for the worker to be done; this 271 // is necessary to avoid races. 272 s.AddCleanup(func(c *gc.C) { 273 workertest.DirtyKill(c, worker) 274 s.stateTracker.waitDone(c) 275 }) 276 return worker, err 277 } 278 279 func (s *ManifoldSuite) startManifoldWithContext(c *gc.C, data map[string]interface{}) (worker.Worker, error) { 280 manifold := globalclockupdater.Manifold(s.config) 281 context := dt.StubContext(nil, data) 282 worker, err := manifold.Start(context) 283 if err != nil { 284 return nil, err 285 } 286 return worker, nil 287 } 288 289 type fakeClock struct { 290 clock.Clock 291 } 292 293 type fakeUpdater struct { 294 globalclock.Updater 295 } 296 297 type stubStateTracker struct { 298 testing.Stub 299 pool state.StatePool 300 done chan struct{} 301 } 302 303 func (s *stubStateTracker) Use() (*state.StatePool, error) { 304 s.MethodCall(s, "Use") 305 return &s.pool, s.NextErr() 306 } 307 308 func (s *stubStateTracker) Done() error { 309 s.MethodCall(s, "Done") 310 err := s.NextErr() 311 // close must be the last read or write on stubStateTracker in Done 312 close(s.done) 313 return err 314 } 315 316 func (s *stubStateTracker) waitDone(c *gc.C) { 317 select { 318 case <-s.done: 319 case <-time.After(coretesting.LongWait): 320 c.Fatal("timed out waiting for state to be released") 321 } 322 } 323 324 func (s *stubStateTracker) Report() map[string]interface{} { 325 s.MethodCall(s, "Report") 326 return nil 327 }