github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/state/manifold_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 jujutesting "github.com/juju/testing" 11 jc "github.com/juju/testing/checkers" 12 "github.com/prometheus/client_golang/prometheus" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/worker.v1" 15 "gopkg.in/juju/worker.v1/dependency" 16 dt "gopkg.in/juju/worker.v1/dependency/testing" 17 "gopkg.in/juju/worker.v1/workertest" 18 19 coreagent "github.com/juju/juju/agent" 20 "github.com/juju/juju/state" 21 statetesting "github.com/juju/juju/state/testing" 22 coretesting "github.com/juju/juju/testing" 23 workerstate "github.com/juju/juju/worker/state" 24 ) 25 26 type ManifoldSuite struct { 27 statetesting.StateSuite 28 manifold dependency.Manifold 29 openStateCalled bool 30 openStateErr error 31 config workerstate.ManifoldConfig 32 agent *mockAgent 33 resources dt.StubResources 34 setStatePoolCalls []*state.StatePool 35 } 36 37 var _ = gc.Suite(&ManifoldSuite{}) 38 39 func (s *ManifoldSuite) SetUpTest(c *gc.C) { 40 s.StateSuite.SetUpTest(c) 41 42 s.openStateCalled = false 43 s.openStateErr = nil 44 s.setStatePoolCalls = nil 45 46 s.config = workerstate.ManifoldConfig{ 47 AgentName: "agent", 48 StateConfigWatcherName: "state-config-watcher", 49 OpenStatePool: s.fakeOpenState, 50 PingInterval: 10 * time.Millisecond, 51 PrometheusRegisterer: prometheus.NewRegistry(), 52 SetStatePool: func(pool *state.StatePool) { 53 s.setStatePoolCalls = append(s.setStatePoolCalls, pool) 54 }, 55 } 56 s.manifold = workerstate.Manifold(s.config) 57 s.resources = dt.StubResources{ 58 "agent": dt.NewStubResource(new(mockAgent)), 59 "state-config-watcher": dt.NewStubResource(true), 60 } 61 } 62 63 func (s *ManifoldSuite) fakeOpenState(coreagent.Config) (*state.StatePool, error) { 64 s.openStateCalled = true 65 if s.openStateErr != nil { 66 return nil, s.openStateErr 67 } 68 // Here's one we prepared earlier... 69 return s.StatePool, nil 70 } 71 72 func (s *ManifoldSuite) TestInputs(c *gc.C) { 73 c.Assert(s.manifold.Inputs, jc.SameContents, []string{ 74 "agent", 75 "state-config-watcher", 76 }) 77 } 78 79 func (s *ManifoldSuite) TestStartAgentMissing(c *gc.C) { 80 s.resources["agent"] = dt.StubResource{Error: dependency.ErrMissing} 81 w, err := s.startManifold(c) 82 c.Check(w, gc.IsNil) 83 c.Check(err, gc.Equals, dependency.ErrMissing) 84 } 85 86 func (s *ManifoldSuite) TestStateConfigWatcherMissing(c *gc.C) { 87 s.resources["state-config-watcher"] = dt.StubResource{Error: dependency.ErrMissing} 88 w, err := s.startManifold(c) 89 c.Check(w, gc.IsNil) 90 c.Check(err, gc.Equals, dependency.ErrMissing) 91 } 92 93 func (s *ManifoldSuite) TestStartOpenStateNil(c *gc.C) { 94 s.config.OpenStatePool = nil 95 s.startManifoldInvalidConfig(c, s.config, "nil OpenStatePool not valid") 96 } 97 98 func (s *ManifoldSuite) TestStartPrometheusRegistererNil(c *gc.C) { 99 s.config.PrometheusRegisterer = nil 100 s.startManifoldInvalidConfig(c, s.config, "nil PrometheusRegisterer not valid") 101 } 102 103 func (s *ManifoldSuite) TestStartSetStatePoolNil(c *gc.C) { 104 s.config.SetStatePool = nil 105 s.startManifoldInvalidConfig(c, s.config, "nil SetStatePool not valid") 106 } 107 108 func (s *ManifoldSuite) startManifoldInvalidConfig(c *gc.C, config workerstate.ManifoldConfig, expect string) { 109 manifold := workerstate.Manifold(config) 110 w, err := manifold.Start(s.resources.Context()) 111 c.Check(w, gc.IsNil) 112 c.Check(err, gc.ErrorMatches, expect) 113 } 114 115 func (s *ManifoldSuite) TestStartNotStateServer(c *gc.C) { 116 s.resources["state-config-watcher"] = dt.NewStubResource(false) 117 w, err := s.startManifold(c) 118 c.Check(w, gc.IsNil) 119 c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing) 120 c.Check(err, gc.ErrorMatches, "no StateServingInfo in config: dependency not available") 121 } 122 123 func (s *ManifoldSuite) TestStartOpenStateFails(c *gc.C) { 124 s.openStateErr = errors.New("boom") 125 w, err := s.startManifold(c) 126 c.Check(w, gc.IsNil) 127 c.Check(err, gc.ErrorMatches, "boom") 128 } 129 130 func (s *ManifoldSuite) TestStartSuccess(c *gc.C) { 131 w := s.mustStartManifold(c) 132 c.Check(s.openStateCalled, jc.IsTrue) 133 checkNotExiting(c, w) 134 workertest.CleanKill(c, w) 135 } 136 137 func (s *ManifoldSuite) TestStatePinging(c *gc.C) { 138 w := s.mustStartManifold(c) 139 checkNotExiting(c, w) 140 141 // Kill the mongod to cause pings to fail. 142 jujutesting.MgoServer.Destroy() 143 144 checkExitsWithError(c, w, "state ping failed: .+") 145 } 146 147 func (s *ManifoldSuite) TestOutputBadWorker(c *gc.C) { 148 var st *state.State 149 err := s.manifold.Output(dummyWorker{}, &st) 150 c.Check(st, gc.IsNil) 151 c.Check(err, gc.ErrorMatches, `in should be a \*state.stateWorker; .+`) 152 } 153 154 func (s *ManifoldSuite) TestOutputWrongType(c *gc.C) { 155 w := s.mustStartManifold(c) 156 157 var wrong int 158 err := s.manifold.Output(w, &wrong) 159 c.Check(wrong, gc.Equals, 0) 160 c.Check(err, gc.ErrorMatches, `out should be \*StateTracker; got .+`) 161 } 162 163 func (s *ManifoldSuite) TestOutputSuccess(c *gc.C) { 164 w := s.mustStartManifold(c) 165 166 var stTracker workerstate.StateTracker 167 err := s.manifold.Output(w, &stTracker) 168 c.Assert(err, jc.ErrorIsNil) 169 170 pool, err := stTracker.Use() 171 c.Assert(err, jc.ErrorIsNil) 172 c.Check(pool.SystemState(), gc.Equals, s.State) 173 c.Assert(stTracker.Done(), jc.ErrorIsNil) 174 175 // Ensure State is closed when the worker is done. 176 workertest.CleanKill(c, w) 177 assertStatePoolClosed(c, s.StatePool) 178 } 179 180 func (s *ManifoldSuite) TestStateStillInUse(c *gc.C) { 181 w := s.mustStartManifold(c) 182 183 var stTracker workerstate.StateTracker 184 err := s.manifold.Output(w, &stTracker) 185 c.Assert(err, jc.ErrorIsNil) 186 187 pool, err := stTracker.Use() 188 c.Assert(err, jc.ErrorIsNil) 189 190 // Close the worker while the State is still in use. 191 workertest.CleanKill(c, w) 192 assertStatePoolNotClosed(c, pool) 193 194 // Now signal that the State is no longer needed. 195 c.Assert(stTracker.Done(), jc.ErrorIsNil) 196 assertStatePoolClosed(c, pool) 197 } 198 199 func (s *ManifoldSuite) TestDeadStateRemoved(c *gc.C) { 200 // Create an additional state *before* we start 201 // the worker, so the worker's lifecycle watcher 202 // is guaranteed to observe it in both the Alive 203 // state and the Dead state. 204 newSt := s.Factory.MakeModel(c, nil) 205 defer newSt.Close() 206 model, err := newSt.Model() 207 c.Assert(err, jc.ErrorIsNil) 208 209 w := s.mustStartManifold(c) 210 defer workertest.CleanKill(c, w) 211 212 var stTracker workerstate.StateTracker 213 err = s.manifold.Output(w, &stTracker) 214 c.Assert(err, jc.ErrorIsNil) 215 pool, err := stTracker.Use() 216 c.Assert(err, jc.ErrorIsNil) 217 defer stTracker.Done() 218 219 // Get a reference to the state pool entry, so we can 220 // prevent it from being fully removed from the pool. 221 st, err := pool.Get(newSt.ModelUUID()) 222 c.Assert(err, jc.ErrorIsNil) 223 defer st.Release() 224 225 // Progress the model to Dead. 226 c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil) 227 c.Assert(model.Refresh(), jc.ErrorIsNil) 228 c.Assert(model.Life(), gc.Equals, state.Dying) 229 c.Assert(newSt.RemoveDyingModel(), jc.ErrorIsNil) 230 c.Assert(model.Refresh(), jc.Satisfies, errors.IsNotFound) 231 s.State.StartSync() 232 233 for a := coretesting.LongAttempt.Start(); a.Next(); { 234 st, err := pool.Get(newSt.ModelUUID()) 235 if errors.IsNotFound(err) { 236 c.Assert(err, gc.ErrorMatches, "model .* has been removed") 237 return 238 } 239 c.Assert(err, jc.ErrorIsNil) 240 st.Release() 241 } 242 c.Fatal("timed out waiting for model state to be removed from pool") 243 } 244 245 func (s *ManifoldSuite) mustStartManifold(c *gc.C) worker.Worker { 246 w, err := s.startManifold(c) 247 c.Assert(err, jc.ErrorIsNil) 248 return w 249 } 250 251 func (s *ManifoldSuite) startManifold(c *gc.C) (worker.Worker, error) { 252 w, err := s.manifold.Start(s.resources.Context()) 253 if w != nil { 254 s.AddCleanup(func(*gc.C) { worker.Stop(w) }) 255 } 256 return w, err 257 } 258 259 func checkNotExiting(c *gc.C, w worker.Worker) { 260 exited := make(chan bool) 261 go func() { 262 w.Wait() 263 close(exited) 264 }() 265 266 select { 267 case <-exited: 268 c.Fatal("worker exited unexpectedly") 269 case <-time.After(coretesting.ShortWait): 270 // Worker didn't exit (good) 271 } 272 } 273 274 func checkExitsWithError(c *gc.C, w worker.Worker, expectedErr string) { 275 errCh := make(chan error) 276 go func() { 277 errCh <- w.Wait() 278 }() 279 select { 280 case err := <-errCh: 281 c.Check(err, gc.ErrorMatches, expectedErr) 282 case <-time.After(coretesting.LongWait): 283 c.Fatal("timed out waiting for worker to exit") 284 } 285 } 286 287 type mockAgent struct { 288 coreagent.Agent 289 } 290 291 func (ma *mockAgent) CurrentConfig() coreagent.Config { 292 return new(mockConfig) 293 } 294 295 type mockConfig struct { 296 coreagent.Config 297 } 298 299 type dummyWorker struct { 300 worker.Worker 301 }