github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/pubsub/subscriber_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package pubsub_test 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names/v5" 14 "github.com/juju/pubsub/v2" 15 "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/worker/v3" 18 "github.com/juju/worker/v3/workertest" 19 gc "gopkg.in/check.v1" 20 21 "github.com/juju/juju/api" 22 "github.com/juju/juju/pubsub/apiserver" 23 "github.com/juju/juju/pubsub/centralhub" 24 "github.com/juju/juju/rpc/params" 25 coretesting "github.com/juju/juju/testing" 26 psworker "github.com/juju/juju/worker/pubsub" 27 ) 28 29 type WorkerConfigSuite struct { 30 } 31 32 var _ = gc.Suite(&WorkerConfigSuite{}) 33 34 func (*WorkerConfigSuite) TestValidate(c *gc.C) { 35 logger := loggo.GetLogger("juju.worker.pubsub") 36 for i, test := range []struct { 37 cfg psworker.WorkerConfig 38 errMatch string 39 }{ 40 { 41 errMatch: "missing origin not valid", 42 }, { 43 cfg: psworker.WorkerConfig{ 44 Origin: "origin", 45 }, 46 errMatch: "missing clock not valid", 47 }, { 48 cfg: psworker.WorkerConfig{ 49 Origin: "origin", 50 Clock: testclock.NewClock(time.Now()), 51 }, 52 errMatch: "missing hub not valid", 53 }, { 54 cfg: psworker.WorkerConfig{ 55 Origin: "origin", 56 Clock: testclock.NewClock(time.Now()), 57 Hub: pubsub.NewStructuredHub(nil), 58 }, 59 errMatch: "missing logger not valid", 60 }, { 61 cfg: psworker.WorkerConfig{ 62 Origin: "origin", 63 Clock: testclock.NewClock(time.Now()), 64 Hub: pubsub.NewStructuredHub(nil), 65 Logger: logger, 66 }, 67 errMatch: "missing api info not valid", 68 }, { 69 cfg: psworker.WorkerConfig{ 70 Origin: "origin", 71 Clock: testclock.NewClock(time.Now()), 72 Hub: pubsub.NewStructuredHub(nil), 73 Logger: logger, 74 APIInfo: &api.Info{ 75 Addrs: []string{"localhost"}, 76 }, 77 }, 78 errMatch: "missing new writer not valid", 79 }, { 80 cfg: psworker.WorkerConfig{ 81 Origin: "origin", 82 Clock: testclock.NewClock(time.Now()), 83 Hub: pubsub.NewStructuredHub(nil), 84 Logger: logger, 85 APIInfo: &api.Info{ 86 Addrs: []string{"localhost"}, 87 }, 88 NewWriter: func(*api.Info) (psworker.MessageWriter, error) { 89 return &messageWriter{}, nil 90 }, 91 }, 92 errMatch: "missing new remote not valid", 93 }, { 94 cfg: psworker.WorkerConfig{ 95 Origin: "origin", 96 Clock: testclock.NewClock(time.Now()), 97 Hub: pubsub.NewStructuredHub(nil), 98 Logger: logger, 99 APIInfo: &api.Info{ 100 Addrs: []string{"localhost"}, 101 }, 102 NewWriter: func(*api.Info) (psworker.MessageWriter, error) { 103 return &messageWriter{}, nil 104 }, 105 NewRemote: func(psworker.RemoteServerConfig) (psworker.RemoteServer, error) { 106 return &fakeRemote{}, nil 107 }, 108 }, 109 }, 110 } { 111 c.Logf("test %d", i) 112 err := test.cfg.Validate() 113 if test.errMatch != "" { 114 c.Check(err, gc.ErrorMatches, test.errMatch) 115 c.Check(err, jc.Satisfies, errors.IsNotValid) 116 } else { 117 c.Check(err, jc.ErrorIsNil) 118 } 119 } 120 } 121 122 type SubscriberSuite struct { 123 testing.IsolationSuite 124 config psworker.WorkerConfig 125 clock *testclock.Clock 126 hub *pubsub.StructuredHub 127 origin string 128 remotes *fakeRemoteTracker 129 } 130 131 var _ = gc.Suite(&SubscriberSuite{}) 132 133 func (s *SubscriberSuite) SetUpTest(c *gc.C) { 134 s.IsolationSuite.SetUpTest(c) 135 logger := loggo.GetLogger("juju.worker.pubsub") 136 logger.SetLogLevel(loggo.TRACE) 137 // loggo.GetLogger("pubsub").SetLogLevel(loggo.TRACE) 138 tag := names.NewMachineTag("42") 139 s.clock = testclock.NewClock(time.Now()) 140 s.hub = centralhub.New(tag, centralhub.PubsubNoOpMetrics{}) 141 s.origin = tag.String() 142 s.remotes = &fakeRemoteTracker{ 143 remotes: make(map[string]*fakeRemote), 144 } 145 s.config = psworker.WorkerConfig{ 146 Origin: s.origin, 147 Clock: s.clock, 148 Hub: s.hub, 149 Logger: logger, 150 APIInfo: &api.Info{ 151 Addrs: []string{"localhost"}, 152 CACert: "fake as", 153 Tag: tag, 154 }, 155 NewWriter: func(*api.Info) (psworker.MessageWriter, error) { 156 return &messageWriter{}, nil 157 }, 158 NewRemote: s.remotes.new, 159 } 160 } 161 162 func (s *SubscriberSuite) TestBadConfig(c *gc.C) { 163 s.config.Clock = nil 164 w, err := psworker.NewWorker(s.config) 165 c.Assert(err, gc.ErrorMatches, "missing clock not valid") 166 c.Assert(w, gc.IsNil) 167 } 168 169 func (s *SubscriberSuite) TestCleanShutdown(c *gc.C) { 170 w, err := psworker.NewWorker(s.config) 171 c.Assert(err, jc.ErrorIsNil) 172 workertest.CleanKill(c, w) 173 } 174 175 func (s *SubscriberSuite) TestNoInitialRemotes(c *gc.C) { 176 w, err := psworker.NewWorker(s.config) 177 c.Assert(err, jc.ErrorIsNil) 178 defer workertest.CleanKill(c, w) 179 180 c.Assert(s.remotes.remotes, gc.HasLen, 0) 181 } 182 183 func (s *SubscriberSuite) enableHA(c *gc.C) { 184 done, err := s.hub.Publish(apiserver.DetailsTopic, apiserver.Details{ 185 Servers: map[string]apiserver.APIServer{ 186 "3": { 187 ID: "3", 188 Addresses: []string{"10.1.2.3"}, 189 }, 190 "5": { 191 ID: "5", 192 Addresses: []string{"10.1.2.5"}, 193 }, 194 "42": { 195 ID: "42", 196 Addresses: []string{"10.1.2.42"}, 197 }, 198 }, 199 LocalOnly: true, 200 }) 201 c.Assert(err, jc.ErrorIsNil) 202 203 select { 204 case <-pubsub.Wait(done): 205 case <-time.After(coretesting.LongWait): 206 c.Fatal("message handling not completed") 207 } 208 } 209 210 func (s *SubscriberSuite) newHAWorker(c *gc.C) worker.Worker { 211 w, err := psworker.NewWorker(s.config) 212 c.Assert(err, jc.ErrorIsNil) 213 s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, w) }) 214 s.enableHA(c) 215 return w 216 } 217 218 func (s *SubscriberSuite) TestEnableHA(c *gc.C) { 219 s.newHAWorker(c) 220 221 c.Assert(s.remotes.remotes, gc.HasLen, 2) 222 remote3 := s.remotes.remotes["machine-3"] 223 c.Assert(remote3.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.1.2.3"}) 224 remote5 := s.remotes.remotes["machine-5"] 225 c.Assert(remote5.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.1.2.5"}) 226 } 227 228 func (s *SubscriberSuite) TestEnableHAInternalAddress(c *gc.C) { 229 w, err := psworker.NewWorker(s.config) 230 c.Assert(err, jc.ErrorIsNil) 231 s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, w) }) 232 done, err := s.hub.Publish(apiserver.DetailsTopic, apiserver.Details{ 233 Servers: map[string]apiserver.APIServer{ 234 "3": { 235 ID: "3", 236 Addresses: []string{"10.1.2.3"}, 237 InternalAddress: "10.5.4.3", 238 }, 239 "5": { 240 ID: "5", 241 Addresses: []string{"10.1.2.5"}, 242 InternalAddress: "10.5.4.4", 243 }, 244 "42": { 245 ID: "42", 246 Addresses: []string{"10.1.2.42"}, 247 InternalAddress: "10.5.4.5", 248 }, 249 }, 250 LocalOnly: true, 251 }) 252 c.Assert(err, jc.ErrorIsNil) 253 254 select { 255 case <-pubsub.Wait(done): 256 case <-time.After(coretesting.LongWait): 257 c.Fatal("message handling not completed") 258 } 259 c.Assert(s.remotes.remotes, gc.HasLen, 2) 260 remote3 := s.remotes.remotes["machine-3"] 261 c.Assert(remote3.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.5.4.3"}) 262 remote5 := s.remotes.remotes["machine-5"] 263 c.Assert(remote5.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.5.4.4"}) 264 } 265 266 func (s *SubscriberSuite) TestSameMessagesForwardedForMachine(c *gc.C) { 267 s.newHAWorker(c) 268 269 var expected []*params.PubSubMessage 270 var last <-chan struct{} 271 for i := 0; i < 10; i++ { 272 message := ¶ms.PubSubMessage{ 273 Topic: fmt.Sprintf("topic.%d", i), 274 Data: map[string]interface{}{"origin": "machine-42"}, 275 } 276 expected = append(expected, message) 277 done, err := s.hub.Publish(message.Topic, nil) 278 c.Assert(err, jc.ErrorIsNil) 279 last = pubsub.Wait(done) 280 } 281 select { 282 case <-last: 283 c.Logf("message processing complete") 284 case <-time.After(coretesting.LongWait): 285 c.Fatal("messages not handled") 286 } 287 288 c.Assert(s.remotes.remotes, gc.HasLen, 2) 289 remote3 := s.remotes.remotes["machine-3"] 290 remote5 := s.remotes.remotes["machine-5"] 291 292 c.Assert(remote3.messages, jc.DeepEquals, expected) 293 c.Assert(remote5.messages, jc.DeepEquals, expected) 294 } 295 296 func (s *SubscriberSuite) TestSameMessagesForwardedForController(c *gc.C) { 297 tag := names.NewControllerAgentTag("42") 298 s.origin = tag.String() 299 s.hub = centralhub.New(tag, centralhub.PubsubNoOpMetrics{}) 300 s.config.Origin = s.origin 301 s.config.Hub = s.hub 302 s.config.APIInfo.Tag = tag 303 304 s.newHAWorker(c) 305 306 var expected []*params.PubSubMessage 307 var last <-chan struct{} 308 for i := 0; i < 10; i++ { 309 message := ¶ms.PubSubMessage{ 310 Topic: fmt.Sprintf("topic.%d", i), 311 Data: map[string]interface{}{"origin": "controller-42"}, 312 } 313 expected = append(expected, message) 314 done, err := s.hub.Publish(message.Topic, nil) 315 c.Assert(err, jc.ErrorIsNil) 316 last = pubsub.Wait(done) 317 } 318 select { 319 case <-last: 320 c.Logf("message processing complete") 321 case <-time.After(coretesting.LongWait): 322 c.Fatal("messages not handled") 323 } 324 325 c.Assert(s.remotes.remotes, gc.HasLen, 2) 326 remote3 := s.remotes.remotes["controller-3"] 327 remote5 := s.remotes.remotes["controller-5"] 328 329 c.Assert(remote3.messages, jc.DeepEquals, expected) 330 c.Assert(remote5.messages, jc.DeepEquals, expected) 331 } 332 333 func (s *SubscriberSuite) TestLocalMessagesNotForwarded(c *gc.C) { 334 s.newHAWorker(c) 335 336 var last <-chan struct{} 337 for i := 0; i < 10; i++ { 338 done, err := s.hub.Publish("local.message", map[string]interface{}{ 339 "foo": "bar", 340 "local-only": true, 341 }) 342 c.Assert(err, jc.ErrorIsNil) 343 last = pubsub.Wait(done) 344 } 345 select { 346 case <-last: 347 c.Logf("message processing complete") 348 case <-time.After(coretesting.LongWait): 349 c.Fatal("messages not handled") 350 } 351 352 c.Assert(s.remotes.remotes, gc.HasLen, 2) 353 remote3 := s.remotes.remotes["machine-3"] 354 remote5 := s.remotes.remotes["machine-5"] 355 356 c.Assert(remote3.messages, gc.HasLen, 0) 357 c.Assert(remote5.messages, gc.HasLen, 0) 358 } 359 360 func (s *SubscriberSuite) TestOtherOriginMessagesNotForwarded(c *gc.C) { 361 s.newHAWorker(c) 362 363 var last <-chan struct{} 364 for i := 0; i < 10; i++ { 365 done, err := s.hub.Publish("not.ours", map[string]interface{}{ 366 "foo": "bar", 367 "origin": "other", 368 }) 369 c.Assert(err, jc.ErrorIsNil) 370 last = pubsub.Wait(done) 371 } 372 select { 373 case <-last: 374 c.Logf("message processing complete") 375 case <-time.After(coretesting.LongWait): 376 c.Fatal("messages not handled") 377 } 378 379 c.Assert(s.remotes.remotes, gc.HasLen, 2) 380 remote3 := s.remotes.remotes["machine-3"] 381 remote5 := s.remotes.remotes["machine-5"] 382 383 c.Assert(remote3.messages, gc.HasLen, 0) 384 c.Assert(remote5.messages, gc.HasLen, 0) 385 } 386 387 func (s *SubscriberSuite) TestIntrospectionReport(c *gc.C) { 388 w := s.newHAWorker(c) 389 390 r, ok := w.(psworker.Reporter) 391 c.Assert(ok, jc.IsTrue) 392 c.Assert(r.IntrospectionReport(), gc.Equals, ""+ 393 "Source: machine-42\n"+ 394 "\n"+ 395 "Target: machine-3\n"+ 396 " Status: connected\n"+ 397 " Addresses: [10.1.2.3]\n"+ 398 "\n"+ 399 "Target: machine-5\n"+ 400 " Status: connected\n"+ 401 " Addresses: [10.1.2.5]\n") 402 } 403 404 func (s *SubscriberSuite) TestReport(c *gc.C) { 405 w := s.newHAWorker(c) 406 407 r, ok := w.(psworker.Reporter) 408 c.Assert(ok, jc.IsTrue) 409 c.Assert(r.Report(), jc.DeepEquals, map[string]interface{}{ 410 "source": "machine-42", 411 "targets": map[string]interface{}{ 412 "machine-3": map[string]interface{}{ 413 "status": "connected", 414 "addresses": []string{"10.1.2.3"}, 415 }, 416 "machine-5": map[string]interface{}{ 417 "status": "connected", 418 "addresses": []string{"10.1.2.5"}, 419 }, 420 }}) 421 } 422 423 func (s *SubscriberSuite) TestRequestsDetailsOnceSubscribed(c *gc.C) { 424 subscribed := make(chan apiserver.DetailsRequest) 425 s.config.Hub.Subscribe(apiserver.DetailsRequestTopic, 426 func(_ string, req apiserver.DetailsRequest, err error) { 427 c.Check(err, jc.ErrorIsNil) 428 subscribed <- req 429 }, 430 ) 431 432 s.newHAWorker(c) 433 434 select { 435 case req := <-subscribed: 436 c.Assert(req, gc.Equals, apiserver.DetailsRequest{Requester: "pubsub-forwarder", LocalOnly: true}) 437 case <-time.After(coretesting.LongWait): 438 c.Fatalf("timed out waiting for details request") 439 } 440 } 441 442 var logger = loggo.GetLogger("workertest") 443 444 type fakeRemoteTracker struct { 445 remotes map[string]*fakeRemote 446 } 447 448 func (f *fakeRemoteTracker) new(config psworker.RemoteServerConfig) (psworker.RemoteServer, error) { 449 remote := &fakeRemote{config: config} 450 f.remotes[config.Target] = remote 451 return remote, nil 452 } 453 454 type fakeRemote struct { 455 psworker.RemoteServer 456 config psworker.RemoteServerConfig 457 messages []*params.PubSubMessage 458 } 459 460 func (f *fakeRemote) Report() map[string]interface{} { 461 return map[string]interface{}{ 462 "status": "connected", 463 "addresses": f.config.APIInfo.Addrs, 464 } 465 } 466 467 func (f *fakeRemote) IntrospectionReport() string { 468 return fmt.Sprintf(""+ 469 " Status: connected\n"+ 470 " Addresses: %v\n", 471 f.config.APIInfo.Addrs) 472 } 473 474 func (f *fakeRemote) Publish(message *params.PubSubMessage) { 475 logger.Debugf("fakeRemote.Publish %s to %s", message.Topic, f.config.Target) 476 f.messages = append(f.messages, message) 477 } 478 func (f *fakeRemote) UpdateAddresses(addresses []string) { 479 f.config.APIInfo.Addrs = addresses 480 } 481 func (*fakeRemote) Kill() {} 482 func (*fakeRemote) Wait() error { return nil }