github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/meterstatus/manifold_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package meterstatus_test 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/names/v5" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 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 "github.com/juju/juju/agent" 20 msapi "github.com/juju/juju/api/agent/meterstatus" 21 "github.com/juju/juju/api/base" 22 "github.com/juju/juju/core/machinelock" 23 "github.com/juju/juju/core/watcher" 24 coretesting "github.com/juju/juju/testing" 25 "github.com/juju/juju/worker/meterstatus" 26 "github.com/juju/juju/worker/uniter/runner" 27 ) 28 29 type ManifoldSuite struct { 30 coretesting.BaseSuite 31 32 stub *testing.Stub 33 34 dataDir string 35 36 manifoldConfig meterstatus.ManifoldConfig 37 manifold dependency.Manifold 38 resources dt.StubResources 39 } 40 41 var _ = gc.Suite(&ManifoldSuite{}) 42 43 func (s *ManifoldSuite) SetUpTest(c *gc.C) { 44 s.BaseSuite.SetUpTest(c) 45 s.stub = &testing.Stub{} 46 47 s.manifoldConfig = meterstatus.ManifoldConfig{ 48 AgentName: "agent-name", 49 APICallerName: "apicaller-name", 50 MachineLock: &fakemachinelock{}, 51 Clock: testclock.NewClock(time.Now()), 52 NewHookRunner: meterstatus.NewHookRunner, 53 NewMeterStatusAPIClient: msapi.NewClient, 54 55 NewConnectedStatusWorker: meterstatus.NewConnectedStatusWorker, 56 NewIsolatedStatusWorker: meterstatus.NewIsolatedStatusWorker, 57 } 58 s.manifold = meterstatus.Manifold(s.manifoldConfig) 59 s.dataDir = c.MkDir() 60 61 s.resources = dt.StubResources{ 62 "agent-name": dt.NewStubResource(&dummyAgent{dataDir: s.dataDir}), 63 "apicaller-name": dt.NewStubResource(&dummyAPICaller{}), 64 } 65 } 66 67 // TestInputs ensures the collect manifold has the expected defined inputs. 68 func (s *ManifoldSuite) TestInputs(c *gc.C) { 69 c.Check(s.manifold.Inputs, jc.DeepEquals, []string{ 70 "agent-name", "apicaller-name", 71 }) 72 } 73 74 // TestStartMissingDeps ensures that the manifold correctly handles a missing 75 // resource dependency. 76 func (s *ManifoldSuite) TestStartMissingDeps(c *gc.C) { 77 for _, missingDep := range []string{ 78 "agent-name", 79 } { 80 testResources := dt.StubResources{} 81 for k, v := range s.resources { 82 if k == missingDep { 83 testResources[k] = dt.StubResource{Error: dependency.ErrMissing} 84 } else { 85 testResources[k] = v 86 } 87 } 88 worker, err := s.manifold.Start(testResources.Context()) 89 c.Check(worker, gc.IsNil) 90 c.Check(err, gc.Equals, dependency.ErrMissing) 91 } 92 } 93 94 type PatchedManifoldSuite struct { 95 coretesting.BaseSuite 96 msClient *stubMeterStatusClient 97 manifoldConfig meterstatus.ManifoldConfig 98 stub *testing.Stub 99 resources dt.StubResources 100 } 101 102 func (s *PatchedManifoldSuite) SetUpTest(c *gc.C) { 103 s.BaseSuite.SetUpTest(c) 104 105 s.stub = &testing.Stub{} 106 s.msClient = &stubMeterStatusClient{stub: s.stub, changes: make(chan struct{})} 107 newMSClient := func(_ base.APICaller, _ names.UnitTag) msapi.MeterStatusClient { 108 return s.msClient 109 } 110 newHookRunner := func(_ meterstatus.HookRunnerConfig) meterstatus.HookRunner { 111 return &stubRunner{stub: s.stub} 112 } 113 114 s.manifoldConfig = meterstatus.ManifoldConfig{ 115 AgentName: "agent-name", 116 APICallerName: "apicaller-name", 117 MachineLock: &fakemachinelock{}, 118 NewHookRunner: newHookRunner, 119 NewMeterStatusAPIClient: newMSClient, 120 } 121 } 122 123 // TestStatusWorkerStarts ensures that the manifold correctly sets up the connected worker. 124 func (s *PatchedManifoldSuite) TestStatusWorkerStarts(c *gc.C) { 125 var called bool 126 s.manifoldConfig.NewConnectedStatusWorker = func(cfg meterstatus.ConnectedConfig) (worker.Worker, error) { 127 called = true 128 return meterstatus.NewConnectedStatusWorker(cfg) 129 } 130 manifold := meterstatus.Manifold(s.manifoldConfig) 131 worker, err := manifold.Start(s.resources.Context()) 132 c.Assert(called, jc.IsTrue) 133 c.Assert(err, jc.ErrorIsNil) 134 c.Assert(worker, gc.NotNil) 135 worker.Kill() 136 err = worker.Wait() 137 c.Assert(err, jc.ErrorIsNil) 138 s.stub.CheckCallNames(c, "MeterStatus", "RunHook", "WatchMeterStatus") 139 } 140 141 // TestInactiveWorker ensures that the manifold correctly sets up the isolated worker. 142 func (s *PatchedManifoldSuite) TestIsolatedWorker(c *gc.C) { 143 delete(s.resources, "apicaller-name") 144 var called bool 145 s.manifoldConfig.NewIsolatedStatusWorker = func(cfg meterstatus.IsolatedConfig) (worker.Worker, error) { 146 called = true 147 return meterstatus.NewIsolatedStatusWorker(cfg) 148 } 149 manifold := meterstatus.Manifold(s.manifoldConfig) 150 worker, err := manifold.Start(s.resources.Context()) 151 c.Assert(called, jc.IsTrue) 152 c.Assert(err, jc.ErrorIsNil) 153 c.Assert(worker, gc.NotNil) 154 worker.Kill() 155 err = worker.Wait() 156 c.Assert(err, jc.ErrorIsNil) 157 s.stub.CheckCallNames(c, "MeterStatus", "RunHook", "WatchMeterStatus") 158 } 159 160 type dummyAgent struct { 161 agent.Agent 162 dataDir string 163 } 164 165 func (a dummyAgent) CurrentConfig() agent.Config { 166 return &dummyAgentConfig{dataDir: a.dataDir} 167 } 168 169 type dummyAgentConfig struct { 170 agent.Config 171 dataDir string 172 } 173 174 // Tag implements agent.AgentConfig. 175 func (ac dummyAgentConfig) Tag() names.Tag { 176 return names.NewUnitTag("u/0") 177 } 178 179 // DataDir implements agent.AgentConfig. 180 func (ac dummyAgentConfig) DataDir() string { 181 return ac.dataDir 182 } 183 184 type dummyAPICaller struct { 185 base.APICaller 186 } 187 188 func (dummyAPICaller) BestFacadeVersion(facade string) int { 189 return 42 190 } 191 192 type stubMeterStatusClient struct { 193 sync.RWMutex 194 stub *testing.Stub 195 changes chan struct{} 196 code string 197 } 198 199 func newStubMeterStatusClient(stub *testing.Stub) *stubMeterStatusClient { 200 changes := make(chan struct{}) 201 return &stubMeterStatusClient{stub: stub, changes: changes} 202 } 203 204 func (s *stubMeterStatusClient) SignalStatus(codes ...string) { 205 if len(codes) == 0 { 206 codes = []string{s.code} 207 } 208 for _, code := range codes { 209 s.SetStatus(code) 210 select { 211 case s.changes <- struct{}{}: 212 case <-time.After(coretesting.LongWait): 213 panic("timed out signaling meter status change") 214 } 215 } 216 } 217 218 func (s *stubMeterStatusClient) SetStatus(code string) { 219 s.Lock() 220 defer s.Unlock() 221 s.code = code 222 } 223 224 func (s *stubMeterStatusClient) MeterStatus() (string, string, error) { 225 s.RLock() 226 defer s.RUnlock() 227 s.stub.MethodCall(s, "MeterStatus") 228 if s.code == "" { 229 return "GREEN", "", nil 230 } else { 231 return s.code, "", nil 232 } 233 234 } 235 236 func (s *stubMeterStatusClient) WatchMeterStatus() (watcher.NotifyWatcher, error) { 237 s.stub.MethodCall(s, "WatchMeterStatus") 238 return s, nil 239 } 240 241 func (s *stubMeterStatusClient) Changes() watcher.NotifyChannel { 242 return s.changes 243 } 244 245 func (s *stubMeterStatusClient) Kill() { 246 } 247 248 func (s *stubMeterStatusClient) Wait() error { 249 return nil 250 } 251 252 type stubRunner struct { 253 runner.Runner 254 stub *testing.Stub 255 ran chan struct{} 256 } 257 258 func (r *stubRunner) RunHook(code, info string, abort <-chan struct{}) { 259 r.stub.MethodCall(r, "RunHook", code, info) 260 if r.ran != nil { 261 select { 262 case r.ran <- struct{}{}: 263 case <-time.After(coretesting.LongWait): 264 panic("timed out signaling hook run") 265 } 266 } 267 } 268 269 type fakemachinelock struct { 270 mu sync.Mutex 271 } 272 273 func (f *fakemachinelock) Acquire(spec machinelock.Spec) (func(), error) { 274 f.mu.Lock() 275 return func() { 276 f.mu.Unlock() 277 }, nil 278 } 279 func (f *fakemachinelock) Report(opts ...machinelock.ReportOption) (string, error) { 280 return "", nil 281 }