github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/presence/presence_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package presence_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock/testclock" 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/names/v5" 13 "github.com/juju/pubsub/v2" 14 "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/worker/v3" 17 "github.com/juju/worker/v3/workertest" 18 "github.com/kr/pretty" 19 gc "gopkg.in/check.v1" 20 21 corepresence "github.com/juju/juju/core/presence" 22 "github.com/juju/juju/pubsub/apiserver" 23 "github.com/juju/juju/pubsub/centralhub" 24 "github.com/juju/juju/pubsub/forwarder" 25 coretesting "github.com/juju/juju/testing" 26 "github.com/juju/juju/worker/presence" 27 ) 28 29 type PresenceSuite struct { 30 testing.IsolationSuite 31 hub *pubsub.StructuredHub 32 clock *testclock.Clock 33 recorder corepresence.Recorder 34 config presence.WorkerConfig 35 } 36 37 var _ = gc.Suite(&PresenceSuite{}) 38 39 func (s *PresenceSuite) SetUpTest(c *gc.C) { 40 s.IsolationSuite.SetUpTest(c) 41 s.hub = centralhub.New(ourTag, centralhub.PubsubNoOpMetrics{}) 42 s.clock = testclock.NewClock(time.Time{}) 43 s.recorder = corepresence.New(s.clock) 44 s.recorder.Enable() 45 s.config = presence.WorkerConfig{ 46 Origin: ourServer, 47 Hub: s.hub, 48 Recorder: s.recorder, 49 Logger: loggo.GetLogger("test"), 50 } 51 loggo.ConfigureLoggers("<root>=trace") 52 } 53 54 func (s *PresenceSuite) worker(c *gc.C) worker.Worker { 55 w, err := presence.NewWorker(s.config) 56 c.Assert(err, jc.ErrorIsNil) 57 return w 58 } 59 60 func (s *PresenceSuite) TestWorkerConfigMissingOrigin(c *gc.C) { 61 s.config.Origin = "" 62 err := s.config.Validate() 63 c.Check(err, jc.Satisfies, errors.IsNotValid) 64 c.Check(err, gc.ErrorMatches, "missing origin not valid") 65 } 66 67 func (s *PresenceSuite) TestWorkerConfigMissingHub(c *gc.C) { 68 s.config.Hub = nil 69 err := s.config.Validate() 70 c.Check(err, jc.Satisfies, errors.IsNotValid) 71 c.Check(err, gc.ErrorMatches, "missing hub not valid") 72 } 73 74 func (s *PresenceSuite) TestWorkerConfigMissingRecorder(c *gc.C) { 75 s.config.Recorder = nil 76 err := s.config.Validate() 77 c.Check(err, jc.Satisfies, errors.IsNotValid) 78 c.Check(err, gc.ErrorMatches, "missing recorder not valid") 79 } 80 81 func (s *PresenceSuite) TestWorkerConfigMissingLogger(c *gc.C) { 82 s.config.Logger = nil 83 err := s.config.Validate() 84 c.Check(err, jc.Satisfies, errors.IsNotValid) 85 c.Check(err, gc.ErrorMatches, "missing logger not valid") 86 } 87 88 func (s *PresenceSuite) TestNewWorkerValidatesConfig(c *gc.C) { 89 w, err := presence.NewWorker(presence.WorkerConfig{}) 90 c.Check(err, gc.ErrorMatches, "missing origin not valid") 91 c.Check(w, gc.IsNil) 92 } 93 94 func (s *PresenceSuite) TestWorkerDies(c *gc.C) { 95 w := s.worker(c) 96 workertest.CleanKill(c, w) 97 } 98 99 func (s *PresenceSuite) TestReport(c *gc.C) { 100 w := s.worker(c) 101 defer workertest.CleanKill(c, w) 102 103 s.recorder.Connect("machine-0", "model-uuid", "agent", 1, false, "") 104 s.recorder.Connect("machine-0", "model-uuid", "agent", 2, false, "") 105 s.recorder.Connect("machine-0", "model-uuid", "agent", 3, false, "") 106 s.recorder.Connect("machine-1", "model-uuid", "agent", 4, false, "") 107 s.recorder.Connect("machine-1", "model-uuid", "agent", 5, false, "") 108 s.recorder.Connect("machine-2", "model-uuid", "agent", 6, false, "") 109 110 reporter, ok := w.(worker.Reporter) 111 c.Assert(ok, jc.IsTrue) 112 c.Assert(reporter.Report(), jc.DeepEquals, map[string]interface{}{ 113 "machine-0": 3, 114 "machine-1": 2, 115 "machine-2": 1, 116 }) 117 } 118 119 func (s *PresenceSuite) TestForwarderConnectToOther(c *gc.C) { 120 w := s.worker(c) 121 defer workertest.CleanKill(c, w) 122 123 done := make(chan struct{}) 124 125 unsub, err := s.hub.Subscribe(apiserver.PresenceRequestTopic, func(topic string, data apiserver.OriginTarget, err error) { 126 c.Logf("handler called for %q", topic) 127 c.Check(err, jc.ErrorIsNil) 128 c.Check(data.Target, gc.Equals, otherServer) 129 c.Check(data.Origin, gc.Equals, ourServer) 130 close(done) 131 }) 132 c.Assert(err, jc.ErrorIsNil) 133 defer unsub() 134 135 // When connections are established from us to them, we ask for their presence info. 136 _, err = s.hub.Publish( 137 forwarder.ConnectedTopic, 138 apiserver.OriginTarget{Origin: ourServer, Target: otherServer}) 139 c.Assert(err, jc.ErrorIsNil) 140 s.AssertDone(c, done) 141 } 142 143 func (s *PresenceSuite) TestForwarderConnectFromOther(c *gc.C) { 144 w := s.worker(c) 145 defer workertest.CleanKill(c, w) 146 147 done := make(chan struct{}) 148 149 unsub, err := s.hub.Subscribe(apiserver.PresenceRequestTopic, func(topic string, data apiserver.OriginTarget, err error) { 150 c.Logf("handler called for %q", topic) 151 c.Check(err, jc.ErrorIsNil) 152 c.Check(data.Target, gc.Equals, otherServer) 153 c.Check(data.Origin, gc.Equals, ourServer) 154 close(done) 155 }) 156 c.Assert(err, jc.ErrorIsNil) 157 defer unsub() 158 159 // When connections are established from them to us, we ask for their presence info. 160 _, err = s.hub.Publish( 161 forwarder.ConnectedTopic, 162 apiserver.OriginTarget{Origin: otherServer, Target: ourServer}) 163 c.Assert(err, jc.ErrorIsNil) 164 s.AssertDone(c, done) 165 } 166 167 func (s *PresenceSuite) TestForwarderConnectOtherIgnored(c *gc.C) { 168 w := s.worker(c) 169 defer workertest.CleanKill(c, w) 170 171 called := make(chan struct{}) 172 173 unsub, err := s.hub.Subscribe(apiserver.PresenceRequestTopic, func(topic string, data apiserver.OriginTarget, err error) { 174 c.Logf("handler called for %q", topic) 175 close(called) 176 }) 177 c.Assert(err, jc.ErrorIsNil) 178 defer unsub() 179 180 _, err = s.hub.Publish( 181 forwarder.ConnectedTopic, 182 apiserver.OriginTarget{Origin: otherServer, Target: "machine-8"}) 183 c.Assert(err, jc.ErrorIsNil) 184 s.AssertNotCalled(c, called) 185 } 186 187 func (s *PresenceSuite) TestForwarderDisconnectConnectFromOther(c *gc.C) { 188 w := s.worker(c) 189 defer workertest.CleanKill(c, w) 190 191 connect(s.recorder, agent1, agent2) 192 193 done, err := s.hub.Publish( 194 forwarder.DisconnectedTopic, 195 apiserver.OriginTarget{Origin: ourServer, Target: otherServer}) 196 c.Assert(err, jc.ErrorIsNil) 197 s.AssertDone(c, pubsub.Wait(done)) 198 s.AssertConnections(c, alive(agent1), missing(agent2)) 199 } 200 201 func (s *PresenceSuite) TestForwarderDisconnectOthersIgnored(c *gc.C) { 202 w := s.worker(c) 203 defer workertest.CleanKill(c, w) 204 205 connect(s.recorder, agent1, agent2) 206 207 done, err := s.hub.Publish( 208 forwarder.DisconnectedTopic, 209 apiserver.OriginTarget{Origin: "machine-7", Target: otherServer}) 210 c.Assert(err, jc.ErrorIsNil) 211 s.AssertDone(c, pubsub.Wait(done)) 212 s.AssertConnections(c, alive(agent1), alive(agent2)) 213 } 214 215 func (s *PresenceSuite) TestConnectTopic(c *gc.C) { 216 w := s.worker(c) 217 defer workertest.CleanKill(c, w) 218 219 done, err := s.hub.Publish( 220 apiserver.ConnectTopic, 221 apiserver.APIConnection{ 222 Origin: "machine-5", 223 ModelUUID: "model-uuid", 224 AgentTag: "agent-2", 225 ConnectionID: 42, 226 ControllerAgent: true, 227 UserData: "test", 228 }) 229 c.Assert(err, jc.ErrorIsNil) 230 s.AssertDone(c, pubsub.Wait(done)) 231 s.AssertConnections(c, corepresence.Value{ 232 Model: "model-uuid", 233 Server: "machine-5", 234 Agent: "agent-2", 235 ConnectionID: 42, 236 Status: corepresence.Alive, 237 ControllerAgent: true, 238 UserData: "test", 239 }) 240 } 241 242 func (s *PresenceSuite) TestDisconnectTopic(c *gc.C) { 243 w := s.worker(c) 244 defer workertest.CleanKill(c, w) 245 246 connect(s.recorder, agent1, agent2) 247 248 done, err := s.hub.Publish( 249 apiserver.DisconnectTopic, 250 apiserver.APIConnection{ 251 Origin: agent2.Server, 252 ConnectionID: agent2.ConnectionID, 253 }) 254 c.Assert(err, jc.ErrorIsNil) 255 s.AssertDone(c, pubsub.Wait(done)) 256 s.AssertConnections(c, alive(agent1)) 257 } 258 259 func (s *PresenceSuite) TestPresenceRequest(c *gc.C) { 260 w := s.worker(c) 261 defer workertest.CleanKill(c, w) 262 263 connect(s.recorder, agent1, agent2, agent3, agent4) 264 265 done := make(chan struct{}) 266 unsub, err := s.hub.Subscribe(apiserver.PresenceResponseTopic, func(topic string, data apiserver.PresenceResponse, err error) { 267 c.Logf("handler called for %q", topic) 268 c.Check(err, jc.ErrorIsNil) 269 c.Check(data.Origin, gc.Equals, ourServer) 270 271 c.Check(data.Connections, gc.HasLen, 2) 272 s.CheckConnection(c, data.Connections[0], agent1) 273 s.CheckConnection(c, data.Connections[1], agent3) 274 275 close(done) 276 }) 277 c.Assert(err, jc.ErrorIsNil) 278 defer unsub() 279 280 // When asked for our presence, we respond with the agents connected to us. 281 _, err = s.hub.Publish( 282 apiserver.PresenceRequestTopic, 283 apiserver.OriginTarget{Origin: otherServer, Target: ourServer}) 284 c.Assert(err, jc.ErrorIsNil) 285 s.AssertDone(c, done) 286 } 287 288 func (s *PresenceSuite) TestPresenceRequestOtherServer(c *gc.C) { 289 w := s.worker(c) 290 defer workertest.CleanKill(c, w) 291 292 called := make(chan struct{}) 293 unsub, err := s.hub.Subscribe(apiserver.PresenceResponseTopic, func(topic string, data apiserver.PresenceResponse, err error) { 294 c.Logf("handler called for %q", topic) 295 close(called) 296 }) 297 c.Assert(err, jc.ErrorIsNil) 298 defer unsub() 299 300 // When presence requests come in for other servers, we ignore them. 301 _, err = s.hub.Publish( 302 apiserver.PresenceRequestTopic, 303 apiserver.OriginTarget{Origin: otherServer, Target: "another"}) 304 c.Assert(err, jc.ErrorIsNil) 305 s.AssertNotCalled(c, called) 306 } 307 308 func (s *PresenceSuite) TestPresenceResponse(c *gc.C) { 309 w := s.worker(c) 310 defer workertest.CleanKill(c, w) 311 312 connect(s.recorder, agent1, agent2, agent3, agent4) 313 s.recorder.ServerDown(otherServer) 314 315 // When connections information comes from other servers, we update our recorder. 316 done, err := s.hub.Publish( 317 apiserver.PresenceResponseTopic, 318 apiserver.PresenceResponse{ 319 Origin: otherServer, 320 Connections: []apiserver.APIConnection{ 321 apiConn(agent2), apiConn(agent4), 322 }, 323 }) 324 c.Assert(err, jc.ErrorIsNil) 325 s.AssertDone(c, pubsub.Wait(done)) 326 327 s.AssertConnections(c, alive(agent1), alive(agent2), alive(agent3), alive(agent4)) 328 } 329 330 func (s *PresenceSuite) AssertDone(c *gc.C, called <-chan struct{}) { 331 select { 332 case <-called: 333 case <-time.After(coretesting.LongWait): 334 c.Fatal("event not handled") 335 } 336 } 337 338 func (s *PresenceSuite) AssertNotCalled(c *gc.C, called <-chan struct{}) { 339 select { 340 case <-called: 341 c.Fatal("event called unexpectedly") 342 case <-time.After(coretesting.ShortWait): 343 } 344 } 345 346 func (s *PresenceSuite) AssertConnections(c *gc.C, values ...corepresence.Value) { 347 connections := s.recorder.Connections() 348 c.Log(pretty.Sprint(connections)) 349 c.Assert(connections.Values(), jc.SameContents, values) 350 } 351 352 func (s *PresenceSuite) CheckConnection(c *gc.C, conn apiserver.APIConnection, agent corepresence.Value) { 353 c.Check(conn.AgentTag, gc.Equals, agent.Agent) 354 c.Check(conn.ControllerAgent, gc.Equals, agent.ControllerAgent) 355 c.Check(conn.ModelUUID, gc.Equals, agent.Model) 356 c.Check(conn.ConnectionID, gc.Equals, agent.ConnectionID) 357 c.Check(conn.Origin, gc.Equals, agent.Server) 358 c.Check(conn.UserData, gc.Equals, agent.UserData) 359 } 360 361 func apiConn(value corepresence.Value) apiserver.APIConnection { 362 return apiserver.APIConnection{ 363 AgentTag: value.Agent, 364 ControllerAgent: value.ControllerAgent, 365 ModelUUID: value.Model, 366 ConnectionID: value.ConnectionID, 367 Origin: value.Server, 368 UserData: value.UserData, 369 } 370 } 371 372 func alive(v corepresence.Value) corepresence.Value { 373 v.Status = corepresence.Alive 374 return v 375 } 376 377 func missing(v corepresence.Value) corepresence.Value { 378 v.Status = corepresence.Missing 379 return v 380 } 381 382 func connect(r corepresence.Recorder, values ...corepresence.Value) { 383 for _, info := range values { 384 r.Connect(info.Server, info.Model, info.Agent, info.ConnectionID, info.ControllerAgent, info.UserData) 385 } 386 } 387 388 const modelUUID = "model-uuid" 389 390 var ( 391 ourTag = names.NewMachineTag("1") 392 ourServer = ourTag.String() 393 otherServer = "machine-2" 394 agent1 = corepresence.Value{ 395 Model: modelUUID, 396 Server: ourServer, 397 Agent: "machine-0", 398 ConnectionID: 1237, 399 UserData: "foo", 400 } 401 agent2 = corepresence.Value{ 402 Model: modelUUID, 403 Server: otherServer, 404 Agent: "machine-1", 405 ConnectionID: 1238, 406 UserData: "bar", 407 } 408 agent3 = corepresence.Value{ 409 Model: modelUUID, 410 Server: ourServer, 411 Agent: "unit-ubuntu-0", 412 ConnectionID: 1239, 413 UserData: "baz", 414 } 415 agent4 = corepresence.Value{ 416 Model: modelUUID, 417 Server: otherServer, 418 Agent: "unit-ubuntu-1", 419 ConnectionID: 1240, 420 UserData: "splat", 421 } 422 )