github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/stateconfigwatcher/manifold_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package stateconfigwatcher_test 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/names/v5" 11 "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils/v3/voyeur" 14 "github.com/juju/worker/v3" 15 "github.com/juju/worker/v3/dependency" 16 dt "github.com/juju/worker/v3/dependency/testing" 17 gc "gopkg.in/check.v1" 18 19 coreagent "github.com/juju/juju/agent" 20 "github.com/juju/juju/controller" 21 coretesting "github.com/juju/juju/testing" 22 "github.com/juju/juju/worker/stateconfigwatcher" 23 ) 24 25 type ManifoldSuite struct { 26 testing.IsolationSuite 27 agent *mockAgent 28 goodContext dependency.Context 29 agentConfigChanged *voyeur.Value 30 manifold dependency.Manifold 31 } 32 33 var _ = gc.Suite(&ManifoldSuite{}) 34 35 func (s *ManifoldSuite) SetUpTest(c *gc.C) { 36 s.IsolationSuite.SetUpTest(c) 37 38 s.agent = new(mockAgent) 39 s.agent.conf.setStateServingInfo(true) 40 s.agent.conf.tag = names.NewMachineTag("99") 41 42 s.goodContext = dt.StubContext(nil, map[string]interface{}{ 43 "agent": s.agent, 44 }) 45 46 s.agentConfigChanged = voyeur.NewValue(0) 47 s.manifold = stateconfigwatcher.Manifold(stateconfigwatcher.ManifoldConfig{ 48 AgentName: "agent", 49 AgentConfigChanged: s.agentConfigChanged, 50 }) 51 } 52 53 func (s *ManifoldSuite) TestInputs(c *gc.C) { 54 c.Assert(s.manifold.Inputs, jc.SameContents, []string{"agent"}) 55 } 56 57 func (s *ManifoldSuite) TestNoAgent(c *gc.C) { 58 context := dt.StubContext(nil, map[string]interface{}{ 59 "agent": dependency.ErrMissing, 60 }) 61 _, err := s.manifold.Start(context) 62 c.Assert(err, gc.Equals, dependency.ErrMissing) 63 } 64 65 func (s *ManifoldSuite) TestNilAgentConfigChanged(c *gc.C) { 66 manifold := stateconfigwatcher.Manifold(stateconfigwatcher.ManifoldConfig{ 67 AgentName: "agent", 68 }) 69 _, err := manifold.Start(s.goodContext) 70 c.Assert(err, gc.ErrorMatches, "nil AgentConfigChanged .+") 71 } 72 73 func (s *ManifoldSuite) TestNotMachineAgent(c *gc.C) { 74 s.agent.conf.tag = names.NewUnitTag("foo/0") 75 _, err := s.manifold.Start(s.goodContext) 76 c.Assert(err, gc.ErrorMatches, "manifold can only be used with a machine or controller agent") 77 } 78 79 func (s *ManifoldSuite) TestStart(c *gc.C) { 80 w, err := s.manifold.Start(s.goodContext) 81 c.Assert(err, jc.ErrorIsNil) 82 checkStop(c, w) 83 } 84 85 func (s *ManifoldSuite) TestOutputBadWorker(c *gc.C) { 86 var out bool 87 err := s.manifold.Output(dummyWorker{}, &out) 88 c.Check(err, gc.ErrorMatches, `in should be a \*stateconfigwatcher.stateConfigWatcher; .+`) 89 } 90 91 func (s *ManifoldSuite) TestOutputWrongType(c *gc.C) { 92 w, err := s.manifold.Start(s.goodContext) 93 c.Assert(err, jc.ErrorIsNil) 94 defer checkStop(c, w) 95 96 var out int 97 err = s.manifold.Output(w, &out) 98 c.Check(err, gc.ErrorMatches, `out should be \*bool; got .+`) 99 } 100 101 func (s *ManifoldSuite) TestOutputSuccessNotStateServer(c *gc.C) { 102 s.agent.conf.setStateServingInfo(false) 103 w, err := s.manifold.Start(s.goodContext) 104 c.Assert(err, jc.ErrorIsNil) 105 defer checkStop(c, w) 106 107 var out bool 108 err = s.manifold.Output(w, &out) 109 c.Check(err, jc.ErrorIsNil) 110 c.Check(out, jc.IsFalse) 111 } 112 113 func (s *ManifoldSuite) TestOutputSuccessStateServer(c *gc.C) { 114 s.agent.conf.setStateServingInfo(true) 115 w, err := s.manifold.Start(s.goodContext) 116 c.Assert(err, jc.ErrorIsNil) 117 defer checkStop(c, w) 118 119 var out bool 120 err = s.manifold.Output(w, &out) 121 c.Check(err, jc.ErrorIsNil) 122 c.Check(out, jc.IsTrue) 123 } 124 125 func (s *ManifoldSuite) TestBounceOnChange(c *gc.C) { 126 s.agent.conf.setStateServingInfo(false) 127 w, err := s.manifold.Start(s.goodContext) 128 c.Assert(err, jc.ErrorIsNil) 129 checkNotExiting(c, w) 130 131 checkOutput := func(expected bool) { 132 var out bool 133 err = s.manifold.Output(w, &out) 134 c.Assert(err, jc.ErrorIsNil) 135 c.Check(out, gc.Equals, expected) 136 } 137 138 // Not a state server yet, initial output should be False. 139 checkOutput(false) 140 141 // Changing the config without changing the state server status - 142 // worker should keep running and output should remain false. 143 s.agentConfigChanged.Set(0) 144 checkNotExiting(c, w) 145 checkOutput(false) 146 147 // Now change the config to include state serving info, worker 148 // should bounce. 149 s.agent.conf.setStateServingInfo(true) 150 s.agentConfigChanged.Set(0) 151 checkExitsWithError(c, w, dependency.ErrBounce) 152 153 // Restart the worker, the output should now be true. 154 w, err = s.manifold.Start(s.goodContext) 155 c.Assert(err, jc.ErrorIsNil) 156 checkNotExiting(c, w) 157 checkOutput(true) 158 159 // Changing the config again without changing the state serving 160 // info shouldn't cause the agent to exit. 161 s.agentConfigChanged.Set(0) 162 checkNotExiting(c, w) 163 checkOutput(true) 164 165 // Now remove the state serving info, the agent should bounce. 166 s.agent.conf.setStateServingInfo(false) 167 s.agentConfigChanged.Set(0) 168 checkExitsWithError(c, w, dependency.ErrBounce) 169 } 170 171 func (s *ManifoldSuite) TestClosedVoyeur(c *gc.C) { 172 w, err := s.manifold.Start(s.goodContext) 173 c.Assert(err, jc.ErrorIsNil) 174 checkNotExiting(c, w) 175 176 s.agentConfigChanged.Close() 177 178 c.Check(waitForExit(c, w), gc.ErrorMatches, "config changed value closed") 179 } 180 181 func checkStop(c *gc.C, w worker.Worker) { 182 err := worker.Stop(w) 183 c.Check(err, jc.ErrorIsNil) 184 } 185 186 func checkNotExiting(c *gc.C, w worker.Worker) { 187 exited := make(chan bool) 188 go func() { 189 w.Wait() 190 close(exited) 191 }() 192 193 select { 194 case <-exited: 195 c.Fatal("worker exited unexpectedly") 196 case <-time.After(coretesting.ShortWait): 197 // Worker didn't exit (good) 198 } 199 } 200 201 func checkExitsWithError(c *gc.C, w worker.Worker, expectedErr error) { 202 c.Check(waitForExit(c, w), gc.Equals, expectedErr) 203 } 204 205 func waitForExit(c *gc.C, w worker.Worker) error { 206 errCh := make(chan error) 207 go func() { 208 errCh <- w.Wait() 209 }() 210 select { 211 case err := <-errCh: 212 return err 213 case <-time.After(coretesting.LongWait): 214 c.Fatal("timed out waiting for worker to exit") 215 } 216 panic("can't get here") 217 } 218 219 type mockAgent struct { 220 coreagent.Agent 221 conf mockConfig 222 } 223 224 func (ma *mockAgent) CurrentConfig() coreagent.Config { 225 return &ma.conf 226 } 227 228 type mockConfig struct { 229 coreagent.ConfigSetter 230 tag names.Tag 231 mu sync.Mutex 232 ssInfoIsSet bool 233 } 234 235 func (mc *mockConfig) Tag() names.Tag { 236 return mc.tag 237 } 238 239 func (mc *mockConfig) setStateServingInfo(isSet bool) { 240 mc.mu.Lock() 241 defer mc.mu.Unlock() 242 mc.ssInfoIsSet = isSet 243 } 244 245 func (mc *mockConfig) StateServingInfo() (controller.StateServingInfo, bool) { 246 mc.mu.Lock() 247 defer mc.mu.Unlock() 248 return controller.StateServingInfo{}, mc.ssInfoIsSet 249 } 250 251 type dummyWorker struct { 252 worker.Worker 253 }