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