github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/pubsub" 14 "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/names.v2" 18 "gopkg.in/juju/worker.v1" 19 "gopkg.in/juju/worker.v1/workertest" 20 21 "github.com/juju/juju/api" 22 "github.com/juju/juju/apiserver/params" 23 "github.com/juju/juju/pubsub/apiserver" 24 "github.com/juju/juju/pubsub/centralhub" 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) 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 <-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 <-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) TestSameMessagesForwarded(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 = 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) TestLocalMessagesNotForwarded(c *gc.C) { 297 s.newHAWorker(c) 298 299 var last <-chan struct{} 300 for i := 0; i < 10; i++ { 301 done, err := s.hub.Publish("local.message", map[string]interface{}{ 302 "foo": "bar", 303 "local-only": true, 304 }) 305 c.Assert(err, jc.ErrorIsNil) 306 last = done 307 } 308 select { 309 case <-last: 310 c.Logf("message processing complete") 311 case <-time.After(coretesting.LongWait): 312 c.Fatal("messages not handled") 313 } 314 315 c.Assert(s.remotes.remotes, gc.HasLen, 2) 316 remote3 := s.remotes.remotes["machine-3"] 317 remote5 := s.remotes.remotes["machine-5"] 318 319 c.Assert(remote3.messages, gc.HasLen, 0) 320 c.Assert(remote5.messages, gc.HasLen, 0) 321 } 322 323 func (s *SubscriberSuite) TestOtherOriginMessagesNotForwarded(c *gc.C) { 324 s.newHAWorker(c) 325 326 var last <-chan struct{} 327 for i := 0; i < 10; i++ { 328 done, err := s.hub.Publish("not.ours", map[string]interface{}{ 329 "foo": "bar", 330 "origin": "other", 331 }) 332 c.Assert(err, jc.ErrorIsNil) 333 last = done 334 } 335 select { 336 case <-last: 337 c.Logf("message processing complete") 338 case <-time.After(coretesting.LongWait): 339 c.Fatal("messages not handled") 340 } 341 342 c.Assert(s.remotes.remotes, gc.HasLen, 2) 343 remote3 := s.remotes.remotes["machine-3"] 344 remote5 := s.remotes.remotes["machine-5"] 345 346 c.Assert(remote3.messages, gc.HasLen, 0) 347 c.Assert(remote5.messages, gc.HasLen, 0) 348 } 349 350 func (s *SubscriberSuite) TestIntrospectionReport(c *gc.C) { 351 w := s.newHAWorker(c) 352 353 r, ok := w.(psworker.Reporter) 354 c.Assert(ok, jc.IsTrue) 355 c.Assert(r.IntrospectionReport(), gc.Equals, ""+ 356 "Source: machine-42\n"+ 357 "\n"+ 358 "Target: machine-3\n"+ 359 " Status: connected\n"+ 360 " Addresses: [10.1.2.3]\n"+ 361 "\n"+ 362 "Target: machine-5\n"+ 363 " Status: connected\n"+ 364 " Addresses: [10.1.2.5]\n") 365 } 366 367 func (s *SubscriberSuite) TestReport(c *gc.C) { 368 w := s.newHAWorker(c) 369 370 r, ok := w.(psworker.Reporter) 371 c.Assert(ok, jc.IsTrue) 372 c.Assert(r.Report(), jc.DeepEquals, map[string]interface{}{ 373 "source": "machine-42", 374 "targets": map[string]interface{}{ 375 "machine-3": map[string]interface{}{ 376 "status": "connected", 377 "addresses": []string{"10.1.2.3"}, 378 }, 379 "machine-5": map[string]interface{}{ 380 "status": "connected", 381 "addresses": []string{"10.1.2.5"}, 382 }, 383 }}) 384 } 385 386 func (s *SubscriberSuite) TestRequestsDetailsOnceSubscribed(c *gc.C) { 387 subscribed := make(chan apiserver.DetailsRequest) 388 s.config.Hub.Subscribe(apiserver.DetailsRequestTopic, 389 func(_ string, req apiserver.DetailsRequest, err error) { 390 c.Check(err, jc.ErrorIsNil) 391 subscribed <- req 392 }, 393 ) 394 395 s.newHAWorker(c) 396 397 select { 398 case req := <-subscribed: 399 c.Assert(req, gc.Equals, apiserver.DetailsRequest{Requester: "pubsub-forwarder", LocalOnly: true}) 400 case <-time.After(coretesting.LongWait): 401 c.Fatalf("timed out waiting for details request") 402 } 403 } 404 405 var logger = loggo.GetLogger("workertest") 406 407 type fakeRemoteTracker struct { 408 remotes map[string]*fakeRemote 409 } 410 411 func (f *fakeRemoteTracker) new(config psworker.RemoteServerConfig) (psworker.RemoteServer, error) { 412 remote := &fakeRemote{config: config} 413 f.remotes[config.Target] = remote 414 return remote, nil 415 } 416 417 type fakeRemote struct { 418 psworker.RemoteServer 419 config psworker.RemoteServerConfig 420 messages []*params.PubSubMessage 421 } 422 423 func (f *fakeRemote) Report() map[string]interface{} { 424 return map[string]interface{}{ 425 "status": "connected", 426 "addresses": f.config.APIInfo.Addrs, 427 } 428 } 429 430 func (f *fakeRemote) IntrospectionReport() string { 431 return fmt.Sprintf(""+ 432 " Status: connected\n"+ 433 " Addresses: %v\n", 434 f.config.APIInfo.Addrs) 435 } 436 437 func (f *fakeRemote) Publish(message *params.PubSubMessage) { 438 logger.Debugf("fakeRemote.Publish %s to %s", message.Topic, f.config.Target) 439 f.messages = append(f.messages, message) 440 } 441 func (f *fakeRemote) UpdateAddresses(addresses []string) { 442 f.config.APIInfo.Addrs = addresses 443 } 444 func (*fakeRemote) Kill() {} 445 func (*fakeRemote) Wait() error { return nil }