github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/remoterelations/remoterelations_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package remoterelations_test 5 6 import ( 7 "reflect" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 jujutesting "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/juju/names.v2" 16 "gopkg.in/juju/worker.v1" 17 "gopkg.in/juju/worker.v1/workertest" 18 "gopkg.in/macaroon.v2-unstable" 19 20 "github.com/juju/juju/api" 21 apitesting "github.com/juju/juju/api/testing" 22 "github.com/juju/juju/apiserver/common" 23 "github.com/juju/juju/apiserver/params" 24 apiservertesting "github.com/juju/juju/apiserver/testing" 25 "github.com/juju/juju/core/life" 26 "github.com/juju/juju/core/status" 27 "github.com/juju/juju/core/watcher" 28 coretesting "github.com/juju/juju/testing" 29 "github.com/juju/juju/worker/remoterelations" 30 ) 31 32 var _ = gc.Suite(&remoteRelationsSuite{}) 33 34 type remoteRelationsSuite struct { 35 coretesting.BaseSuite 36 37 resources *common.Resources 38 authorizer *apiservertesting.FakeAuthorizer 39 relationsFacade *mockRelationsFacade 40 remoteRelationsFacade *mockRemoteRelationsFacade 41 config remoterelations.Config 42 stub *jujutesting.Stub 43 } 44 45 func (s *remoteRelationsSuite) SetUpTest(c *gc.C) { 46 s.BaseSuite.SetUpTest(c) 47 48 s.stub = new(jujutesting.Stub) 49 s.relationsFacade = newMockRelationsFacade(s.stub) 50 s.remoteRelationsFacade = newMockRemoteRelationsFacade(s.stub) 51 s.config = remoterelations.Config{ 52 ModelUUID: "local-model-uuid", 53 RelationsFacade: s.relationsFacade, 54 NewRemoteModelFacadeFunc: func(*api.Info) (remoterelations.RemoteModelRelationsFacadeCloser, error) { 55 return s.remoteRelationsFacade, nil 56 }, 57 Clock: testclock.NewClock(time.Time{}), 58 } 59 } 60 61 func (s *remoteRelationsSuite) waitForWorkerStubCalls(c *gc.C, expected []jujutesting.StubCall) { 62 waitForStubCalls(c, s.stub, expected) 63 } 64 65 func waitForStubCalls(c *gc.C, stub *jujutesting.Stub, expected []jujutesting.StubCall) { 66 var calls []jujutesting.StubCall 67 for a := coretesting.LongAttempt.Start(); a.Next(); { 68 calls = stub.Calls() 69 if reflect.DeepEqual(calls, expected) { 70 return 71 } 72 } 73 c.Fatalf("failed to see expected calls.\nexpected: %v\nobserved: %v", expected, calls) 74 } 75 76 func (s *remoteRelationsSuite) assertRemoteApplicationWorkers(c *gc.C) worker.Worker { 77 // Checks that the main worker loop responds to remote application events 78 // by starting relevant relation watchers. 79 s.relationsFacade.remoteApplications["db2"] = newMockRemoteApplication("db2", "db2url") 80 s.relationsFacade.remoteApplications["mysql"] = newMockRemoteApplication("mysql", "mysqlurl") 81 s.relationsFacade.controllerInfo["remote-model-uuid"] = &api.Info{ 82 Addrs: []string{"1.2.3.4:1234"}, CACert: coretesting.CACert} 83 84 w, err := remoterelations.New(s.config) 85 c.Assert(err, jc.ErrorIsNil) 86 expected := []jujutesting.StubCall{ 87 {"WatchRemoteApplications", nil}, 88 } 89 s.waitForWorkerStubCalls(c, expected) 90 s.stub.ResetCalls() 91 92 mac, err := apitesting.NewMacaroon("test") 93 c.Assert(err, jc.ErrorIsNil) 94 s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"db2"} 95 expected = []jujutesting.StubCall{ 96 {"RemoteApplications", []interface{}{[]string{"db2"}}}, 97 {"WatchRemoteApplicationRelations", []interface{}{"db2"}}, 98 {"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}}, 99 {"WatchOfferStatus", []interface{}{"offer-db2-uuid", macaroon.Slice{mac}}}, 100 } 101 s.waitForWorkerStubCalls(c, expected) 102 s.stub.ResetCalls() 103 104 s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"mysql"} 105 expected = []jujutesting.StubCall{ 106 {"RemoteApplications", []interface{}{[]string{"mysql"}}}, 107 {"WatchRemoteApplicationRelations", []interface{}{"mysql"}}, 108 {"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}}, 109 {"WatchOfferStatus", []interface{}{"offer-mysql-uuid", macaroon.Slice{mac}}}, 110 } 111 s.waitForWorkerStubCalls(c, expected) 112 s.stub.ResetCalls() 113 114 applicationNames := []string{"db2", "mysql"} 115 for _, app := range applicationNames { 116 w, ok := s.relationsFacade.remoteApplicationRelationsWatcher(app) 117 c.Check(ok, jc.IsTrue) 118 waitForStubCalls(c, &w.Stub, []jujutesting.StubCall{ 119 {"Changes", nil}, 120 }) 121 } 122 return w 123 } 124 125 func (s *remoteRelationsSuite) TestRemoteApplicationWorkers(c *gc.C) { 126 w := s.assertRemoteApplicationWorkers(c) 127 workertest.CleanKill(c, w) 128 129 // Check that relation watchers are stopped with the worker. 130 applicationNames := []string{"db2", "mysql"} 131 for _, app := range applicationNames { 132 w, ok := s.relationsFacade.remoteApplicationRelationsWatcher(app) 133 c.Check(ok, jc.IsTrue) 134 c.Check(w.killed(), jc.IsTrue) 135 } 136 } 137 138 func (s *remoteRelationsSuite) TestRemoteApplicationRemoved(c *gc.C) { 139 // Checks that when a remote application is removed, the relation 140 // worker is killed. 141 w := s.assertRemoteApplicationWorkers(c) 142 defer workertest.CleanKill(c, w) 143 s.stub.ResetCalls() 144 145 relWatcher, _ := s.relationsFacade.removeApplication("mysql") 146 s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"mysql"} 147 for a := coretesting.LongAttempt.Start(); a.Next(); { 148 _, ok := s.relationsFacade.remoteApplicationRelationsWatcher("mysql") 149 if !ok { 150 break 151 } 152 } 153 c.Check(relWatcher.killed(), jc.IsTrue) 154 expected := []jujutesting.StubCall{ 155 {"RemoteApplications", []interface{}{[]string{"mysql"}}}, 156 {"Close", nil}, 157 } 158 s.waitForWorkerStubCalls(c, expected) 159 } 160 161 func (s *remoteRelationsSuite) TestRemoteNotFoundTerminatesOnWatching(c *gc.C) { 162 s.relationsFacade.remoteApplications["db2"] = newMockRemoteApplication("db2", "db2url") 163 s.relationsFacade.remoteApplications["mysql"] = newMockRemoteApplication("mysql", "mysqlurl") 164 s.relationsFacade.controllerInfo["remote-model-uuid"] = &api.Info{ 165 Addrs: []string{"1.2.3.4:1234"}, CACert: coretesting.CACert} 166 167 w, err := remoterelations.New(s.config) 168 c.Assert(err, jc.ErrorIsNil) 169 defer workertest.CleanKill(c, w) 170 171 expected := []jujutesting.StubCall{ 172 {"WatchRemoteApplications", nil}, 173 } 174 s.waitForWorkerStubCalls(c, expected) 175 s.stub.ResetCalls() 176 177 s.stub.SetErrors(nil, nil, nil, params.Error{Code: params.CodeNotFound}) 178 179 mac, err := apitesting.NewMacaroon("test") 180 c.Assert(err, jc.ErrorIsNil) 181 s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"db2"} 182 expected = []jujutesting.StubCall{ 183 {"RemoteApplications", []interface{}{[]string{"db2"}}}, 184 {"WatchRemoteApplicationRelations", []interface{}{"db2"}}, 185 {"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}}, 186 {"WatchOfferStatus", []interface{}{"offer-db2-uuid", macaroon.Slice{mac}}}, 187 {"SetRemoteApplicationStatus", []interface{}{"db2", "terminated", "offer has been removed"}}, 188 {"Close", nil}, 189 } 190 s.waitForWorkerStubCalls(c, expected) 191 } 192 193 func (s *remoteRelationsSuite) TestOfferStatusChange(c *gc.C) { 194 w := s.assertRemoteApplicationWorkers(c) 195 defer workertest.CleanKill(c, w) 196 s.stub.ResetCalls() 197 198 statusWatcher := s.remoteRelationsFacade.offersStatusWatchers["offer-mysql-uuid"] 199 statusWatcher.changes <- []watcher.OfferStatusChange{{ 200 Name: "mysql", 201 Status: status.StatusInfo{ 202 Status: status.Active, 203 Message: "started", 204 }, 205 }} 206 207 expected := []jujutesting.StubCall{ 208 {"SetRemoteApplicationStatus", []interface{}{"mysql", "active", "started"}}, 209 } 210 s.waitForWorkerStubCalls(c, expected) 211 } 212 213 func (s *remoteRelationsSuite) TestRemoteNotFoundTerminatesOnChange(c *gc.C) { 214 s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123) 215 w := s.assertRemoteApplicationWorkers(c) 216 defer workertest.CleanKill(c, w) 217 218 s.stub.ResetCalls() 219 s.stub.SetErrors(nil, nil, params.Error{Code: params.CodeNotFound}) 220 221 s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{ 222 localApplicationName: "django", 223 localEndpoint: params.RemoteEndpoint{ 224 Name: "db2", 225 Role: "requires", 226 Interface: "db2", 227 }, 228 remoteEndpointName: "data", 229 } 230 231 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 232 relWatcher.changes <- []string{"db2:db django:db"} 233 234 mac, err := apitesting.NewMacaroon("test") 235 c.Assert(err, jc.ErrorIsNil) 236 relTag := names.NewRelationTag("db2:db django:db") 237 expected := []jujutesting.StubCall{ 238 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 239 {"ExportEntities", []interface{}{ 240 []names.Tag{names.NewApplicationTag("django"), relTag}}}, 241 {"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{ 242 ApplicationToken: "token-django", 243 SourceModelTag: "model-local-model-uuid", 244 RelationToken: "token-db2:db django:db", 245 RemoteEndpoint: params.RemoteEndpoint{ 246 Name: "db2", 247 Role: "requires", 248 Interface: "db2", 249 }, 250 OfferUUID: "offer-db2-uuid", 251 LocalEndpointName: "data", 252 Macaroons: macaroon.Slice{mac}, 253 }}}}, 254 {"SetRemoteApplicationStatus", []interface{}{"db2", "terminated", "offer has been removed"}}, 255 {"Close", nil}, 256 } 257 s.waitForWorkerStubCalls(c, expected) 258 } 259 260 func (s *remoteRelationsSuite) assertRemoteRelationsWorkers(c *gc.C) worker.Worker { 261 s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123) 262 w := s.assertRemoteApplicationWorkers(c) 263 s.stub.ResetCalls() 264 265 s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{ 266 localApplicationName: "django", 267 localEndpoint: params.RemoteEndpoint{ 268 Name: "db2", 269 Role: "requires", 270 Interface: "db2", 271 }, 272 remoteEndpointName: "data", 273 } 274 275 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 276 relWatcher.changes <- []string{"db2:db django:db"} 277 278 mac, err := apitesting.NewMacaroon("test") 279 c.Assert(err, jc.ErrorIsNil) 280 apiMac, err := apitesting.NewMacaroon("apimac") 281 c.Assert(err, jc.ErrorIsNil) 282 relTag := names.NewRelationTag("db2:db django:db") 283 expected := []jujutesting.StubCall{ 284 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 285 {"ExportEntities", []interface{}{ 286 []names.Tag{names.NewApplicationTag("django"), relTag}}}, 287 {"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{ 288 ApplicationToken: "token-django", 289 SourceModelTag: "model-local-model-uuid", 290 RelationToken: "token-db2:db django:db", 291 RemoteEndpoint: params.RemoteEndpoint{ 292 Name: "db2", 293 Role: "requires", 294 Interface: "db2", 295 }, 296 OfferUUID: "offer-db2-uuid", 297 LocalEndpointName: "data", 298 Macaroons: macaroon.Slice{mac}, 299 }}}}, 300 {"SaveMacaroon", []interface{}{relTag, apiMac}}, 301 {"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}}, 302 {"WatchRelationSuspendedStatus", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}}, 303 {"WatchLocalRelationUnits", []interface{}{"db2:db django:db"}}, 304 {"WatchRelationUnits", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}}, 305 } 306 s.waitForWorkerStubCalls(c, expected) 307 308 unitWatcher, ok := s.relationsFacade.relationsUnitsWatcher("db2:db django:db") 309 c.Check(ok, jc.IsTrue) 310 waitForStubCalls(c, &unitWatcher.Stub, []jujutesting.StubCall{ 311 {"Changes", nil}, 312 }) 313 unitWatcher, ok = s.remoteRelationsFacade.relationsUnitsWatcher("token-db2:db django:db") 314 c.Check(ok, jc.IsTrue) 315 waitForStubCalls(c, &unitWatcher.Stub, []jujutesting.StubCall{ 316 {"Changes", nil}, 317 }) 318 relationStatusWatcher, ok := s.remoteRelationsFacade.relationsStatusWatcher("token-db2:db django:db") 319 c.Check(ok, jc.IsTrue) 320 waitForStubCalls(c, &relationStatusWatcher.Stub, []jujutesting.StubCall{ 321 {"Changes", nil}, 322 }) 323 return w 324 } 325 326 func (s *remoteRelationsSuite) TestRemoteRelationsWorkers(c *gc.C) { 327 w := s.assertRemoteRelationsWorkers(c) 328 workertest.CleanKill(c, w) 329 330 // Check that relation unit watchers are stopped with the worker. 331 relWatcher, ok := s.relationsFacade.relationsUnitsWatchers["db2:db django:db"] 332 c.Check(ok, jc.IsTrue) 333 c.Check(relWatcher.killed(), jc.IsTrue) 334 335 relWatcher, ok = s.remoteRelationsFacade.relationsUnitsWatchers["token-db2:db django:db"] 336 c.Check(ok, jc.IsTrue) 337 c.Check(relWatcher.killed(), jc.IsTrue) 338 } 339 340 func (s *remoteRelationsSuite) TestRemoteRelationsRevoked(c *gc.C) { 341 // The consume permission is revoked after an offer is consumed. 342 // Subsequent api calls against that offer will fail and record an 343 // error in the local model. 344 s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123) 345 w := s.assertRemoteApplicationWorkers(c) 346 defer workertest.CleanKill(c, w) 347 s.stub.ResetCalls() 348 s.stub.SetErrors(nil, nil, ¶ms.Error{ 349 Code: params.CodeDischargeRequired, 350 Message: "message", 351 }) 352 353 s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{ 354 localApplicationName: "django", 355 localEndpoint: params.RemoteEndpoint{ 356 Name: "db2", 357 Role: "requires", 358 Interface: "db2", 359 }, 360 remoteEndpointName: "data", 361 } 362 363 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 364 relWatcher.changes <- []string{"db2:db django:db"} 365 366 mac, err := apitesting.NewMacaroon("test") 367 c.Assert(err, jc.ErrorIsNil) 368 relTag := names.NewRelationTag("db2:db django:db") 369 expected := []jujutesting.StubCall{ 370 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 371 {"ExportEntities", []interface{}{ 372 []names.Tag{names.NewApplicationTag("django"), relTag}}}, 373 {"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{ 374 ApplicationToken: "token-django", 375 SourceModelTag: "model-local-model-uuid", 376 RelationToken: "token-db2:db django:db", 377 RemoteEndpoint: params.RemoteEndpoint{ 378 Name: "db2", 379 Role: "requires", 380 Interface: "db2", 381 }, 382 OfferUUID: "offer-db2-uuid", 383 LocalEndpointName: "data", 384 Macaroons: macaroon.Slice{mac}, 385 }}}}, 386 {"SetRemoteApplicationStatus", []interface{}{"db2", "error", "message"}}, 387 {"Close", nil}, 388 } 389 s.waitForWorkerStubCalls(c, expected) 390 } 391 392 func (s *remoteRelationsSuite) TestRemoteRelationsDying(c *gc.C) { 393 // Checks that when a remote relation dies, the relation units 394 // workers are killed. 395 w := s.assertRemoteRelationsWorkers(c) 396 defer workertest.CleanKill(c, w) 397 s.stub.ResetCalls() 398 399 unitsWatcher, _ := s.relationsFacade.updateRelationLife("db2:db django:db", params.Dying) 400 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 401 relWatcher.changes <- []string{"db2:db django:db"} 402 for a := coretesting.LongAttempt.Start(); a.Next(); { 403 _, ok := s.relationsFacade.relationsUnitsWatcher("db2:db django:db") 404 if ok { 405 continue 406 } 407 _, ok = s.remoteRelationsFacade.relationsUnitsWatcher("token-db2:db django:db") 408 if !ok { 409 break 410 } 411 _, ok = s.remoteRelationsFacade.relationsStatusWatcher("token-db2:db django:db") 412 if !ok { 413 break 414 } 415 } 416 // We keep the relation units watcher alive when the relation 417 // goes to Dying; they're only stopped when the relation is 418 // finally removed. 419 c.Assert(unitsWatcher.killed(), jc.IsFalse) 420 apiMac, err := apitesting.NewMacaroon("apimac") 421 c.Assert(err, jc.ErrorIsNil) 422 mac, err := apitesting.NewMacaroon("test") 423 c.Assert(err, jc.ErrorIsNil) 424 relTag := names.NewRelationTag("db2:db django:db") 425 expected := []jujutesting.StubCall{ 426 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 427 {"ExportEntities", []interface{}{ 428 []names.Tag{names.NewApplicationTag("django"), relTag}}}, 429 {"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{ 430 ApplicationToken: "token-django", 431 SourceModelTag: "model-local-model-uuid", 432 RelationToken: "token-db2:db django:db", 433 RemoteEndpoint: params.RemoteEndpoint{ 434 Name: "db2", 435 Role: "requires", 436 Interface: "db2", 437 }, 438 OfferUUID: "offer-db2-uuid", 439 LocalEndpointName: "data", 440 Macaroons: macaroon.Slice{mac}, 441 }}}}, 442 {"SaveMacaroon", []interface{}{relTag, apiMac}}, 443 {"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}}, 444 {"PublishRelationChange", []interface{}{ 445 params.RemoteRelationChangeEvent{ 446 Life: params.Dying, 447 ApplicationToken: "token-django", 448 RelationToken: "token-db2:db django:db", 449 Macaroons: macaroon.Slice{apiMac}, 450 }, 451 }}, 452 } 453 s.waitForWorkerStubCalls(c, expected) 454 } 455 456 func (s *remoteRelationsSuite) TestLocalRelationsRemoved(c *gc.C) { 457 // Checks that when a remote relation goes away, the relation units 458 // worker is killed. 459 w := s.assertRemoteRelationsWorkers(c) 460 defer workertest.CleanKill(c, w) 461 s.stub.ResetCalls() 462 463 unitsWatcher, _ := s.relationsFacade.removeRelation("db2:db django:db") 464 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 465 relWatcher.changes <- []string{"db2:db django:db"} 466 for a := coretesting.LongAttempt.Start(); a.Next(); { 467 _, ok := s.relationsFacade.relationsUnitsWatcher("db2:db django:db") 468 if !ok { 469 break 470 } 471 } 472 c.Assert(unitsWatcher.killed(), jc.IsTrue) 473 expected := []jujutesting.StubCall{ 474 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 475 } 476 s.waitForWorkerStubCalls(c, expected) 477 } 478 479 func (s *remoteRelationsSuite) TestLocalRelationsChangedNotifies(c *gc.C) { 480 w := s.assertRemoteRelationsWorkers(c) 481 defer workertest.CleanKill(c, w) 482 s.stub.ResetCalls() 483 484 unitsWatcher, _ := s.relationsFacade.relationsUnitsWatcher("db2:db django:db") 485 unitsWatcher.changes <- watcher.RelationUnitsChange{ 486 Changed: map[string]watcher.UnitSettings{"unit/1": {Version: 2}}, 487 Departed: []string{"unit/2"}, 488 } 489 490 mac, err := apitesting.NewMacaroon("apimac") 491 c.Assert(err, jc.ErrorIsNil) 492 expected := []jujutesting.StubCall{ 493 {"RelationUnitSettings", []interface{}{ 494 []params.RelationUnit{{ 495 Relation: "relation-db2.db#django.db", 496 Unit: "unit-unit-1"}}}}, 497 {"PublishRelationChange", []interface{}{ 498 params.RemoteRelationChangeEvent{ 499 ApplicationToken: "token-django", 500 RelationToken: "token-db2:db django:db", 501 ChangedUnits: []params.RemoteRelationUnitChange{{ 502 UnitId: 1, 503 Settings: map[string]interface{}{"foo": "bar"}, 504 }}, 505 DepartedUnits: []int{2}, 506 Macaroons: macaroon.Slice{mac}, 507 }, 508 }}, 509 } 510 s.waitForWorkerStubCalls(c, expected) 511 } 512 513 func (s *remoteRelationsSuite) TestRemoteNotFoundTerminatesOnPublish(c *gc.C) { 514 w := s.assertRemoteRelationsWorkers(c) 515 defer workertest.CleanKill(c, w) 516 s.stub.ResetCalls() 517 518 s.stub.SetErrors(nil, params.Error{Code: params.CodeNotFound}) 519 520 unitsWatcher, _ := s.relationsFacade.relationsUnitsWatcher("db2:db django:db") 521 unitsWatcher.changes <- watcher.RelationUnitsChange{ 522 Changed: map[string]watcher.UnitSettings{"unit/1": {Version: 2}}, 523 Departed: []string{"unit/2"}, 524 } 525 526 mac, err := apitesting.NewMacaroon("apimac") 527 c.Assert(err, jc.ErrorIsNil) 528 expected := []jujutesting.StubCall{ 529 {"RelationUnitSettings", []interface{}{ 530 []params.RelationUnit{{ 531 Relation: "relation-db2.db#django.db", 532 Unit: "unit-unit-1"}}}}, 533 {"PublishRelationChange", []interface{}{ 534 params.RemoteRelationChangeEvent{ 535 ApplicationToken: "token-django", 536 RelationToken: "token-db2:db django:db", 537 ChangedUnits: []params.RemoteRelationUnitChange{{ 538 UnitId: 1, 539 Settings: map[string]interface{}{"foo": "bar"}, 540 }}, 541 DepartedUnits: []int{2}, 542 Macaroons: macaroon.Slice{mac}, 543 }, 544 }}, 545 {"SetRemoteApplicationStatus", []interface{}{"db2", "terminated", "offer has been removed"}}, 546 {"Close", nil}, 547 } 548 s.waitForWorkerStubCalls(c, expected) 549 } 550 551 func (s *remoteRelationsSuite) TestRemoteRelationsChangedConsumes(c *gc.C) { 552 w := s.assertRemoteRelationsWorkers(c) 553 defer workertest.CleanKill(c, w) 554 s.stub.ResetCalls() 555 556 unitsWatcher, _ := s.remoteRelationsFacade.relationsUnitsWatcher("token-db2:db django:db") 557 unitsWatcher.changes <- watcher.RelationUnitsChange{ 558 Changed: map[string]watcher.UnitSettings{"unit/1": {Version: 2}}, 559 Departed: []string{"unit/2"}, 560 } 561 562 mac, err := apitesting.NewMacaroon("apimac") 563 c.Assert(err, jc.ErrorIsNil) 564 expected := []jujutesting.StubCall{ 565 {"RelationUnitSettings", []interface{}{ 566 []params.RemoteRelationUnit{{ 567 RelationToken: "token-db2:db django:db", 568 Unit: "unit-unit-1", 569 Macaroons: macaroon.Slice{mac}}}}}, 570 {"ConsumeRemoteRelationChange", []interface{}{ 571 params.RemoteRelationChangeEvent{ 572 ApplicationToken: "token-offer-db2-uuid", 573 RelationToken: "token-db2:db django:db", 574 ChangedUnits: []params.RemoteRelationUnitChange{{ 575 UnitId: 1, 576 Settings: map[string]interface{}{"foo": "bar"}, 577 }}, 578 DepartedUnits: []int{2}, 579 Macaroons: macaroon.Slice{mac}, 580 }, 581 }}, 582 } 583 s.waitForWorkerStubCalls(c, expected) 584 } 585 586 func (s *remoteRelationsSuite) TestRemoteRelationsDyingConsumes(c *gc.C) { 587 w := s.assertRemoteRelationsWorkers(c) 588 defer workertest.CleanKill(c, w) 589 s.stub.ResetCalls() 590 591 statusWatcher, _ := s.remoteRelationsFacade.relationsStatusWatcher("token-db2:db django:db") 592 statusWatcher.changes <- []watcher.RelationStatusChange{{ 593 Life: life.Dying, 594 }} 595 596 suspended := false 597 expected := []jujutesting.StubCall{ 598 {"ConsumeRemoteRelationChange", []interface{}{ 599 params.RemoteRelationChangeEvent{ 600 Life: params.Dying, 601 ApplicationToken: "token-offer-db2-uuid", 602 RelationToken: "token-db2:db django:db", 603 Suspended: &suspended, 604 }, 605 }}, 606 } 607 s.waitForWorkerStubCalls(c, expected) 608 } 609 610 func (s *remoteRelationsSuite) TestRemoteRelationsChangedError(c *gc.C) { 611 s.assertRemoteRelationsChangedError(c, false) 612 } 613 614 func (s *remoteRelationsSuite) TestRemoteDyingRelationsChangedError(c *gc.C) { 615 s.assertRemoteRelationsChangedError(c, true) 616 } 617 618 func (s *remoteRelationsSuite) assertRemoteRelationsChangedError(c *gc.C, dying bool) { 619 w := s.assertRemoteRelationsWorkers(c) 620 defer workertest.CleanKill(c, w) 621 s.stub.ResetCalls() 622 623 s.stub.SetErrors(errors.New("failed")) 624 unitsWatcher, _ := s.relationsFacade.relationsUnitsWatcher("db2:db django:db") 625 unitsWatcher.changes <- watcher.RelationUnitsChange{ 626 Departed: []string{"unit/1"}, 627 } 628 629 // The error causes relation change publication to fail. 630 apiMac, err := apitesting.NewMacaroon("apimac") 631 c.Assert(err, jc.ErrorIsNil) 632 expected := []jujutesting.StubCall{ 633 {"PublishRelationChange", []interface{}{ 634 params.RemoteRelationChangeEvent{ 635 ApplicationToken: "token-django", 636 RelationToken: "token-db2:db django:db", 637 DepartedUnits: []int{1}, 638 Macaroons: macaroon.Slice{apiMac}, 639 }, 640 }}, 641 {"Close", nil}, 642 } 643 644 s.waitForWorkerStubCalls(c, expected) 645 // An error in one of the units watchers does not kill the parent worker. 646 workertest.CheckAlive(c, w) 647 648 // Allow the worker to resume. 649 s.stub.SetErrors(nil) 650 s.stub.ResetCalls() 651 s.config.Clock.(*testclock.Clock).WaitAdvance(50*time.Second, coretesting.LongWait, 1) 652 // Not resumed yet. 653 c.Assert(s.stub.Calls(), gc.HasLen, 0) 654 s.config.Clock.(*testclock.Clock).WaitAdvance(10*time.Second, coretesting.LongWait, 1) 655 656 mac, err := apitesting.NewMacaroon("test") 657 c.Assert(err, jc.ErrorIsNil) 658 expected = []jujutesting.StubCall{ 659 {"WatchRemoteApplicationRelations", []interface{}{"db2"}}, 660 {"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}}, 661 {"WatchOfferStatus", []interface{}{"offer-db2-uuid", macaroon.Slice{mac}}}, 662 } 663 s.waitForWorkerStubCalls(c, expected) 664 s.stub.ResetCalls() 665 666 relTag := names.NewRelationTag("db2:db django:db") 667 expected = []jujutesting.StubCall{ 668 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 669 {"ExportEntities", []interface{}{ 670 []names.Tag{names.NewApplicationTag("django"), relTag}}}, 671 {"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{ 672 ApplicationToken: "token-django", 673 SourceModelTag: "model-local-model-uuid", 674 RelationToken: "token-db2:db django:db", 675 RemoteEndpoint: params.RemoteEndpoint{ 676 Name: "db2", 677 Role: "requires", 678 Interface: "db2", 679 }, 680 OfferUUID: "offer-db2-uuid", 681 LocalEndpointName: "data", 682 Macaroons: macaroon.Slice{mac}, 683 }}}}, 684 {"SaveMacaroon", []interface{}{relTag, apiMac}}, 685 {"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}}, 686 {"WatchRelationSuspendedStatus", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}}, 687 {"WatchLocalRelationUnits", []interface{}{"db2:db django:db"}}, 688 {"WatchRelationUnits", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}}, 689 } 690 691 // If a relation is dying and there's been an error, when processing resumes 692 // a cleanup is forced on the remote side. 693 if dying { 694 s.relationsFacade.updateRelationLife("db2:db django:db", params.Dying) 695 forceCleanup := true 696 expected = append(expected, jujutesting.StubCall{ 697 FuncName: "PublishRelationChange", 698 Args: []interface{}{ 699 params.RemoteRelationChangeEvent{ 700 ApplicationToken: "token-django", 701 RelationToken: "token-db2:db django:db", 702 Life: params.Dying, 703 Macaroons: macaroon.Slice{apiMac}, 704 ForceCleanup: &forceCleanup, 705 }, 706 }}, 707 ) 708 } 709 710 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 711 relWatcher.changes <- []string{"db2:db django:db"} 712 713 // After the worker resumes, normal processing happens. 714 s.waitForWorkerStubCalls(c, expected) 715 } 716 717 func (s *remoteRelationsSuite) TestRegisteredApplicationNotRegistered(c *gc.C) { 718 s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123) 719 db2app := newMockRemoteApplication("db2", "db2url") 720 db2app.registered = true 721 s.relationsFacade.remoteApplications["db2"] = db2app 722 applicationNames := []string{"db2"} 723 s.relationsFacade.remoteApplicationsWatcher.changes <- applicationNames 724 725 w, err := remoterelations.New(s.config) 726 c.Assert(err, jc.ErrorIsNil) 727 defer workertest.CleanKill(c, w) 728 729 expected := []jujutesting.StubCall{ 730 {"WatchRemoteApplications", nil}, 731 {"RemoteApplications", []interface{}{[]string{"db2"}}}, 732 {"WatchRemoteApplicationRelations", []interface{}{"db2"}}, 733 } 734 s.waitForWorkerStubCalls(c, expected) 735 s.stub.ResetCalls() 736 737 s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{ 738 localApplicationName: "django", 739 localEndpoint: params.RemoteEndpoint{ 740 Name: "db2", 741 Role: "requires", 742 Interface: "db2", 743 }, 744 remoteEndpointName: "data", 745 } 746 747 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 748 relWatcher.changes <- []string{"db2:db django:db"} 749 750 expected = []jujutesting.StubCall{ 751 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 752 } 753 s.waitForWorkerStubCalls(c, expected) 754 } 755 756 func (s *remoteRelationsSuite) TestRemoteRelationSuspended(c *gc.C) { 757 w := s.assertRemoteRelationsWorkers(c) 758 defer workertest.CleanKill(c, w) 759 s.stub.ResetCalls() 760 761 // First suspend the relation. 762 s.relationsFacade.relations["db2:db django:db"].SetSuspended(true) 763 relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") 764 relWatcher.changes <- []string{"db2:db django:db"} 765 766 expected := []jujutesting.StubCall{ 767 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 768 } 769 s.waitForWorkerStubCalls(c, expected) 770 s.stub.ResetCalls() 771 772 // Now resume the relation. 773 s.relationsFacade.relations["db2:db django:db"].SetSuspended(false) 774 relWatcher.changes <- []string{"db2:db django:db"} 775 776 mac, err := apitesting.NewMacaroon("test") 777 c.Assert(err, jc.ErrorIsNil) 778 apiMac, err := apitesting.NewMacaroon("apimac") 779 relTag := names.NewRelationTag("db2:db django:db") 780 // When resuming, it's similar to setting things up for a new relation 781 // except that the call to create te life/status listener is missing. 782 expected = []jujutesting.StubCall{ 783 {"Relations", []interface{}{[]string{"db2:db django:db"}}}, 784 {"ExportEntities", []interface{}{ 785 []names.Tag{names.NewApplicationTag("django"), relTag}}}, 786 {"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{ 787 ApplicationToken: "token-django", 788 SourceModelTag: "model-local-model-uuid", 789 RelationToken: "token-db2:db django:db", 790 RemoteEndpoint: params.RemoteEndpoint{ 791 Name: "db2", 792 Role: "requires", 793 Interface: "db2", 794 }, 795 OfferUUID: "offer-db2-uuid", 796 LocalEndpointName: "data", 797 Macaroons: macaroon.Slice{mac}, 798 }}}}, 799 {"SaveMacaroon", []interface{}{relTag, apiMac}}, 800 {"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}}, 801 {"WatchLocalRelationUnits", []interface{}{"db2:db django:db"}}, 802 {"WatchRelationUnits", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}}, 803 } 804 s.waitForWorkerStubCalls(c, expected) 805 }