github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/remoterelations/remoteapplicationworker.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package remoterelations 5 6 import ( 7 "fmt" 8 "sync" 9 10 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" 11 "github.com/juju/errors" 12 "github.com/juju/names/v5" 13 "github.com/juju/worker/v3" 14 "github.com/juju/worker/v3/catacomb" 15 "gopkg.in/macaroon.v2" 16 17 "github.com/juju/juju/api" 18 "github.com/juju/juju/core/crossmodel" 19 "github.com/juju/juju/core/life" 20 "github.com/juju/juju/core/network" 21 "github.com/juju/juju/core/status" 22 "github.com/juju/juju/core/watcher" 23 "github.com/juju/juju/rpc/params" 24 ) 25 26 // remoteApplicationWorker listens for localChanges to relations 27 // involving a remote application, and publishes change to 28 // local relation units to the remote model. It also watches for 29 // changes originating from the offering model and consumes those 30 // in the local model. 31 type remoteApplicationWorker struct { 32 catacomb catacomb.Catacomb 33 34 mu sync.Mutex 35 36 // These attributes are relevant to dealing with a specific 37 // remote application proxy. 38 offerUUID string 39 applicationName string // name of the remote application proxy in the local model 40 localModelUUID string // uuid of the model hosting the local application 41 remoteModelUUID string // uuid of the model hosting the remote offer 42 isConsumerProxy bool 43 consumeVersion int 44 localRelationUnitChanges chan RelationUnitChangeEvent 45 remoteRelationUnitChanges chan RelationUnitChangeEvent 46 secretChangesWatcher watcher.SecretsRevisionWatcher 47 secretChanges watcher.SecretRevisionChannel 48 49 // relations is stored here for the engine report. 50 relations map[string]*relation 51 52 // offerMacaroon is used to confirm that permission has been granted to consume 53 // the remote application to which this worker pertains. 54 offerMacaroon *macaroon.Macaroon 55 56 // localModelFacade interacts with the local (consuming) model. 57 localModelFacade RemoteRelationsFacade 58 // remoteModelFacade interacts with the remote (offering) model. 59 remoteModelFacade RemoteModelRelationsFacadeCloser 60 61 newRemoteModelRelationsFacadeFunc newRemoteRelationsFacadeFunc 62 63 logger Logger 64 } 65 66 // relation holds attributes relevant to a particular 67 // relation between a local app and a remote offer. 68 type relation struct { 69 relationId int 70 localDead bool 71 suspended bool 72 localUnitCount int 73 localRuw *relationUnitsWorker 74 remoteRuw *relationUnitsWorker 75 remoteRrw *remoteRelationsWorker 76 77 applicationToken string // token for app in local model 78 relationToken string // token for relation in local model 79 localEndpoint params.RemoteEndpoint 80 remoteEndpointName string 81 macaroon *macaroon.Macaroon 82 } 83 84 // Kill is defined on worker.Worker 85 func (w *remoteApplicationWorker) Kill() { 86 w.catacomb.Kill(nil) 87 } 88 89 // Wait is defined on worker.Worker 90 func (w *remoteApplicationWorker) Wait() error { 91 err := w.catacomb.Wait() 92 if err != nil { 93 w.logger.Errorf("error in remote application worker for %v: %v", w.applicationName, err) 94 } 95 return err 96 } 97 98 func (w *remoteApplicationWorker) checkOfferPermissionDenied(err error, appToken, relationToken string) { 99 // If consume permission has been revoked for the offer, set the 100 // status of the local remote application entity. 101 if params.ErrCode(err) == params.CodeDischargeRequired { 102 if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Error, err.Error()); err != nil { 103 w.logger.Errorf( 104 "updating remote application %v status from remote model %v: %v", 105 w.applicationName, w.remoteModelUUID, err) 106 } 107 w.logger.Debugf("discharge required error: app token: %v rel token: %v", appToken, relationToken) 108 // If we know a specific relation, update that too. 109 if relationToken != "" { 110 suspended := true 111 event := params.RemoteRelationChangeEvent{ 112 RelationToken: relationToken, 113 ApplicationToken: appToken, 114 Suspended: &suspended, 115 SuspendedReason: "offer permission revoked", 116 } 117 if err := w.localModelFacade.ConsumeRemoteRelationChange(event); err != nil { 118 w.logger.Errorf("updating relation status: %v", err) 119 } 120 } 121 } 122 } 123 124 func (w *remoteApplicationWorker) remoteOfferRemoved() error { 125 w.logger.Debugf("remote offer for %s has been removed", w.applicationName) 126 if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Terminated, "offer has been removed"); err != nil { 127 return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID) 128 } 129 return nil 130 } 131 132 // isNotFound allows either type of not found error 133 // to be correctly handled. 134 // TODO(wallyworld) - remove when all api facades are fixed 135 func isNotFound(err error) bool { 136 if err == nil { 137 return false 138 } 139 return errors.IsNotFound(err) || params.IsCodeNotFound(err) 140 } 141 142 func (w *remoteApplicationWorker) loop() (err error) { 143 // Watch for changes to any local relations to the remote application. 144 relationsWatcher, err := w.localModelFacade.WatchRemoteApplicationRelations(w.applicationName) 145 if err != nil && isNotFound(err) { 146 return nil 147 } else if err != nil { 148 return errors.Annotatef(err, "watching relations for remote application %q", w.applicationName) 149 } 150 if err := w.catacomb.Add(relationsWatcher); err != nil { 151 return errors.Trace(err) 152 } 153 154 // On the consuming side, watch for status changes to the offer. 155 var ( 156 offerStatusWatcher watcher.OfferStatusWatcher 157 offerStatusChanges watcher.OfferStatusChannel 158 ) 159 if !w.isConsumerProxy { 160 if err := w.newRemoteRelationsFacadeWithRedirect(); err != nil { 161 msg := fmt.Sprintf("cannot connect to external controller: %v", err.Error()) 162 if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Error, msg); err != nil { 163 return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID) 164 } 165 return errors.Annotate(err, "cannot connect to external controller") 166 } 167 defer func() { 168 if err := w.remoteModelFacade.Close(); err != nil { 169 w.logger.Errorf("error closing remote-relations facade: %s", err) 170 } 171 }() 172 173 arg := params.OfferArg{ 174 OfferUUID: w.offerUUID, 175 } 176 if w.offerMacaroon != nil { 177 arg.Macaroons = macaroon.Slice{w.offerMacaroon} 178 arg.BakeryVersion = bakery.LatestVersion 179 } 180 181 offerStatusWatcher, err = w.remoteModelFacade.WatchOfferStatus(arg) 182 if err != nil { 183 w.checkOfferPermissionDenied(err, "", "") 184 if isNotFound(err) { 185 return w.remoteOfferRemoved() 186 } 187 return errors.Annotate(err, "watching status for offer") 188 } 189 if err := w.catacomb.Add(offerStatusWatcher); err != nil { 190 return errors.Trace(err) 191 } 192 offerStatusChanges = offerStatusWatcher.Changes() 193 } 194 195 w.mu.Lock() 196 w.relations = make(map[string]*relation) 197 w.mu.Unlock() 198 for { 199 select { 200 case <-w.catacomb.Dying(): 201 return w.catacomb.ErrDying() 202 case change, ok := <-relationsWatcher.Changes(): 203 w.logger.Debugf("relations changed: %#v, %v", &change, ok) 204 if !ok { 205 // We are dying. 206 return w.catacomb.ErrDying() 207 } 208 results, err := w.localModelFacade.Relations(change) 209 if err != nil { 210 return errors.Annotate(err, "querying relations") 211 } 212 for i, result := range results { 213 key := change[i] 214 if err := w.relationChanged(key, result); err != nil { 215 if isNotFound(err) { 216 // Relation has been deleted, so ensure relevant workers are stopped. 217 w.logger.Debugf("relation %q changed but has been removed", key) 218 err2 := w.localRelationChanged(key, nil) 219 if err2 != nil { 220 return errors.Annotatef(err2, "cleaning up removed local relation %q", key) 221 } 222 continue 223 } 224 return errors.Annotatef(err, "handling change for relation %q", key) 225 } 226 } 227 case change := <-w.localRelationUnitChanges: 228 w.logger.Debugf("local relation units changed -> publishing: %#v", &change) 229 // TODO(babbageclunk): add macaroons to event here instead 230 // of in the relation units worker. 231 if err := w.remoteModelFacade.PublishRelationChange(change.RemoteRelationChangeEvent); err != nil { 232 w.checkOfferPermissionDenied(err, change.ApplicationToken, change.RelationToken) 233 if isNotFound(err) || params.IsCodeCannotEnterScope(err) { 234 w.logger.Debugf("relation %v changed but remote side already removed", change.Tag.Id()) 235 continue 236 } 237 return errors.Annotatef(err, "publishing relation change %#v to remote model %v", &change, w.remoteModelUUID) 238 } 239 240 // TODO(juju4) - remove 241 // UnitCount has had omitempty removed, but we need to account for older controllers. 242 zero := 0 243 unitCount := change.UnitCount 244 if unitCount == nil { 245 unitCount = &zero 246 } 247 248 if err := w.localRelationChanged(change.Tag.Id(), unitCount); err != nil { 249 return errors.Annotatef(err, "processing local relation change for %v", change.Tag.Id()) 250 } 251 case change := <-w.remoteRelationUnitChanges: 252 w.logger.Debugf("remote relation units changed -> consuming: %#v", &change) 253 if err := w.localModelFacade.ConsumeRemoteRelationChange(change.RemoteRelationChangeEvent); err != nil { 254 if isNotFound(err) || params.IsCodeCannotEnterScope(err) { 255 w.logger.Debugf("relation %v changed but local side already removed", change.Tag.Id()) 256 continue 257 } 258 return errors.Annotatef(err, "consuming relation change %#v from remote model %v", &change, w.remoteModelUUID) 259 } 260 case changes := <-offerStatusChanges: 261 w.logger.Debugf("offer status changed: %#v", changes) 262 for _, change := range changes { 263 if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, change.Status.Status, change.Status.Message); err != nil { 264 return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID) 265 } 266 // If the offer is terminated the status watcher can be stopped immediately. 267 if change.Status.Status == status.Terminated { 268 offerStatusWatcher.Kill() 269 if err := offerStatusWatcher.Wait(); err != nil { 270 w.logger.Warningf("error stopping status watcher for saas application %s: %v", w.applicationName, err) 271 } 272 offerStatusChanges = nil 273 break 274 } 275 } 276 case changes := <-w.secretChanges: 277 err := w.localModelFacade.ConsumeRemoteSecretChanges(changes) 278 if err != nil { 279 if isNotFound(err) { 280 w.logger.Debugf("secrets %v changed but local side already removed", changes) 281 continue 282 } 283 return errors.Annotatef(err, "consuming secrets change %#v from remote model %v", changes, w.remoteModelUUID) 284 } 285 } 286 } 287 } 288 289 // newRemoteRelationsFacadeWithRedirect attempts to open an API connection to 290 // the remote model for the watcher's application. 291 // If a redirect error is returned, we attempt to open a connection to the new 292 // controller and update our local controller info for the model so that future 293 // API connections are to the new location. 294 func (w *remoteApplicationWorker) newRemoteRelationsFacadeWithRedirect() error { 295 apiInfo, err := w.localModelFacade.ControllerAPIInfoForModel(w.remoteModelUUID) 296 if err != nil { 297 return errors.Annotate(err, "cannot get controller api info for remote model") 298 } 299 w.logger.Debugf("remote controller API addresses: %v", apiInfo.Addrs) 300 301 w.remoteModelFacade, err = w.newRemoteModelRelationsFacadeFunc(apiInfo) 302 if redirectErr, ok := errors.Cause(err).(*api.RedirectError); ok { 303 apiInfo.Addrs = network.CollapseToHostPorts(redirectErr.Servers).Strings() 304 apiInfo.CACert = redirectErr.CACert 305 306 w.logger.Debugf("received redirect; new API addresses: %v", apiInfo.Addrs) 307 308 if w.remoteModelFacade, err = w.newRemoteModelRelationsFacadeFunc(apiInfo); err == nil { 309 // We successfully followed the redirect, 310 // so update the controller information for this model. 311 controllerInfo := crossmodel.ControllerInfo{ 312 ControllerTag: redirectErr.ControllerTag, 313 Alias: redirectErr.ControllerAlias, 314 Addrs: apiInfo.Addrs, 315 CACert: apiInfo.CACert, 316 } 317 318 if err = w.localModelFacade.UpdateControllerForModel(controllerInfo, w.remoteModelUUID); err != nil { 319 _ = w.remoteModelFacade.Close() 320 err = errors.Annotate(err, "updating external controller info") 321 } 322 } 323 } 324 325 return errors.Annotate(err, "opening facade to remote model") 326 } 327 328 func (w *remoteApplicationWorker) processRelationDying(key string, r *relation, forceCleanup bool) error { 329 w.logger.Debugf("relation %v dying (%v)", key, forceCleanup) 330 // On the consuming side, inform the remote side the relation is dying 331 // (but only if we are killing the relation due to it dying, not because 332 // it is suspended). 333 if !w.isConsumerProxy { 334 change := params.RemoteRelationChangeEvent{ 335 RelationToken: r.relationToken, 336 Life: life.Dying, 337 ApplicationToken: r.applicationToken, 338 Macaroons: macaroon.Slice{r.macaroon}, 339 BakeryVersion: bakery.LatestVersion, 340 } 341 // forceCleanup will be true if the worker has restarted and because the relation had 342 // already been removed, we won't get any more unit departed events. 343 if forceCleanup { 344 change.ForceCleanup = &forceCleanup 345 } 346 if err := w.remoteModelFacade.PublishRelationChange(change); err != nil { 347 w.checkOfferPermissionDenied(err, r.applicationToken, r.relationToken) 348 if isNotFound(err) { 349 w.logger.Debugf("relation %v dying but remote side already removed", key) 350 return nil 351 } 352 return errors.Annotatef(err, "publishing relation dying %#v to remote model %v", &change, w.remoteModelUUID) 353 } 354 } 355 return nil 356 } 357 358 func (w *remoteApplicationWorker) processRelationSuspended(key string, relLife life.Value, relations map[string]*relation) error { 359 w.logger.Debugf("(%v) relation %v suspended", relLife, key) 360 relation, ok := relations[key] 361 if !ok { 362 return nil 363 } 364 365 // Only stop the watchers for relation unit changes if relation is alive, 366 // as we need to always deal with units leaving scope etc if the relation is dying. 367 if relLife != life.Alive { 368 return nil 369 } 370 371 // On the offering side, if the relation is resumed, 372 // it will be treated like the relation has been joined 373 // for the first time; all workers will be restarted. 374 // The offering side has isConsumerProxy = true. 375 if w.isConsumerProxy { 376 delete(relations, key) 377 } 378 379 if relation.localRuw != nil { 380 if err := worker.Stop(relation.localRuw); err != nil { 381 w.logger.Warningf("stopping local relation unit worker for %v: %v", key, err) 382 } 383 relation.localRuw = nil 384 } 385 if relation.remoteRuw != nil { 386 if err := worker.Stop(relation.remoteRuw); err != nil { 387 w.logger.Warningf("stopping remote relation unit worker for %v: %v", key, err) 388 } 389 relation.remoteRuw = nil 390 } 391 return nil 392 } 393 394 // processLocalRelationRemoved is called when a change event arrives from the remote model 395 // but the relation in the local model has been removed. 396 func (w *remoteApplicationWorker) processLocalRelationRemoved(key string, relations map[string]*relation) error { 397 w.logger.Debugf("local relation %v removed", key) 398 relation, ok := relations[key] 399 if !ok { 400 return nil 401 } 402 403 // Stop the worker which watches remote status/life. 404 if relation.remoteRrw != nil { 405 if err := worker.Stop(relation.remoteRrw); err != nil { 406 w.logger.Warningf("stopping remote relations worker for %v: %v", key, err) 407 } 408 relation.remoteRrw = nil 409 relations[key] = relation 410 } 411 412 w.logger.Debugf("remote relation %v removed from local model", key) 413 return nil 414 } 415 416 // localRelationChanged processes changes to the relation 417 // as recorded in the local model; the primary function 418 // is to shut down workers when the relation is dead. 419 func (w *remoteApplicationWorker) localRelationChanged(key string, unitCountPtr *int) error { 420 unitCountMsg := " (removed)" 421 if unitCountPtr != nil { 422 unitCountMsg = fmt.Sprintf(", still has %d unit(s) in scope", *unitCountPtr) 423 } 424 w.logger.Debugf("local relation %v changed%s", key, unitCountMsg) 425 w.mu.Lock() 426 defer w.mu.Unlock() 427 428 relation, ok := w.relations[key] 429 if !ok { 430 w.logger.Debugf("local relation %v already gone", key) 431 return nil 432 } 433 w.logger.Debugf("relation %v in mem unit count is %d", key, relation.localUnitCount) 434 if unitCountPtr != nil { 435 relation.localUnitCount = *unitCountPtr 436 } 437 if !relation.localDead { 438 w.logger.Debugf("local relation %v not dead yet", key) 439 return nil 440 } 441 if relation.localUnitCount > 0 { 442 w.logger.Debugf("relation dead but still has %d units in scope", relation.localUnitCount) 443 return nil 444 } 445 return w.terminateLocalRelation(key) 446 } 447 448 func (w *remoteApplicationWorker) terminateLocalRelation(key string) error { 449 relation, ok := w.relations[key] 450 if !ok { 451 return nil 452 } 453 delete(w.relations, key) 454 w.logger.Debugf("local relation %v is terminated", key) 455 456 // For the unit watchers, check to see if these are nil before stopping. 457 // They will be nil if the relation was suspended and then we kill it for real. 458 if relation.localRuw != nil { 459 if err := worker.Stop(relation.localRuw); err != nil { 460 w.logger.Warningf("stopping local relation unit worker for %v: %v", key, err) 461 } 462 relation.localRuw = nil 463 } 464 if relation.remoteRuw != nil { 465 if err := worker.Stop(relation.remoteRuw); err != nil { 466 w.logger.Warningf("stopping remote relation unit worker for %v: %v", key, err) 467 } 468 relation.remoteRuw = nil 469 } 470 471 w.logger.Debugf("local relation %v removed from local model", key) 472 return nil 473 } 474 475 // relationChanged processes changes to the relation as recorded in the 476 // local model when a change event arrives from the remote model. 477 func (w *remoteApplicationWorker) relationChanged(key string, localRelation params.RemoteRelationResult) (err error) { 478 w.logger.Debugf("relation %q changed in local model: %#v", key, localRelation) 479 w.mu.Lock() 480 defer w.mu.Unlock() 481 482 defer func() { 483 if err == nil || !isNotFound(err) { 484 return 485 } 486 if err2 := w.processLocalRelationRemoved(key, w.relations); err2 != nil { 487 err = errors.Annotate(err2, "processing local relation removed") 488 } 489 if r := w.relations[key]; r != nil { 490 r.localDead = true 491 w.relations[key] = r 492 } 493 }() 494 if localRelation.Error != nil { 495 return localRelation.Error 496 } 497 localRelationInfo := localRelation.Result 498 499 // If we have previously started the watcher and the 500 // relation is now suspended, stop the watcher. 501 if r := w.relations[key]; r != nil { 502 wasSuspended := r.suspended 503 r.suspended = localRelationInfo.Suspended 504 w.relations[key] = r 505 if localRelationInfo.Suspended { 506 return w.processRelationSuspended(key, localRelationInfo.Life, w.relations) 507 } 508 if localRelationInfo.Life == life.Alive { 509 if r.localDead { 510 // A previous relation with the same name was removed but 511 // not cleaned up properly so do it now before starting up 512 // workers again. 513 w.logger.Debugf("still have zombie local relation %v", key) 514 if err := w.terminateLocalRelation(key); err != nil { 515 return errors.Annotatef(err, "terminating zombie local relation %v", key) 516 } 517 } else if !wasSuspended { 518 // Nothing to do, we have previously started the watcher. 519 return nil 520 } 521 } 522 } 523 524 if w.isConsumerProxy { 525 // Nothing else to do on the offering side. 526 return nil 527 } 528 return w.processConsumingRelation(key, localRelationInfo) 529 } 530 531 // startUnitsWorkers starts 2 workers to watch for unit settings or departed changes; 532 // one worker is for the local model, the other for the remote model. 533 func (w *remoteApplicationWorker) startUnitsWorkers( 534 relationTag names.RelationTag, 535 relationToken, remoteAppToken string, 536 applicationName string, 537 mac *macaroon.Macaroon, 538 ) (*relationUnitsWorker, *relationUnitsWorker, error) { 539 // Start a watcher to track changes to the units in the relation in the local model. 540 localRelationUnitsWatcher, err := w.localModelFacade.WatchLocalRelationChanges(relationTag.Id()) 541 if err != nil { 542 return nil, nil, errors.Annotatef(err, "watching local side of relation %v", relationTag.Id()) 543 } 544 545 localUnitsWorker, err := newRelationUnitsWorker( 546 relationTag, 547 mac, 548 localRelationUnitsWatcher, 549 w.localRelationUnitChanges, 550 w.logger, 551 "local", 552 ) 553 if err != nil { 554 return nil, nil, errors.Trace(err) 555 } 556 if err := w.catacomb.Add(localUnitsWorker); err != nil { 557 return nil, nil, errors.Trace(err) 558 } 559 560 // Start a watcher to track changes to the units in the relation in the remote model. 561 remoteRelationUnitsWatcher, err := w.remoteModelFacade.WatchRelationChanges( 562 relationToken, remoteAppToken, macaroon.Slice{mac}, 563 ) 564 if err != nil { 565 w.checkOfferPermissionDenied(err, remoteAppToken, relationToken) 566 return nil, nil, errors.Annotatef( 567 err, "watching remote side of application %v and relation %v", 568 applicationName, relationTag.Id()) 569 } 570 571 remoteUnitsWorker, err := newRelationUnitsWorker( 572 relationTag, 573 mac, 574 remoteRelationUnitsWatcher, 575 w.remoteRelationUnitChanges, 576 w.logger, 577 "remote", 578 ) 579 if err != nil { 580 return nil, nil, errors.Trace(err) 581 } 582 if err := w.catacomb.Add(remoteUnitsWorker); err != nil { 583 return nil, nil, errors.Trace(err) 584 } 585 return localUnitsWorker, remoteUnitsWorker, nil 586 } 587 588 // processConsumingRelation starts the sub-workers necessary to listen and publish 589 // local unit settings changes, and watch and consume remote unit settings changes. 590 // Ths will be called when a new relation is created or when a relation resumes 591 // after being suspended. 592 func (w *remoteApplicationWorker) processConsumingRelation( 593 key string, 594 remoteRelation *params.RemoteRelation, 595 ) error { 596 597 // We have not seen the relation before, make 598 // sure it is registered on the offering side. 599 // Or relation was suspended and is now resumed so re-register. 600 applicationTag := names.NewApplicationTag(remoteRelation.ApplicationName) 601 relationTag := names.NewRelationTag(key) 602 applicationToken, remoteAppToken, relationToken, mac, err := w.registerRemoteRelation( 603 applicationTag, relationTag, w.offerUUID, w.consumeVersion, 604 remoteRelation.Endpoint, remoteRelation.RemoteEndpointName) 605 if err != nil { 606 w.checkOfferPermissionDenied(err, "", "") 607 return errors.Annotatef(err, "registering application %v and relation %v", remoteRelation.ApplicationName, relationTag.Id()) 608 } 609 w.logger.Debugf("remote relation registered for %q: app token=%q, rel token=%q, remote app token=%q", key, applicationToken, relationToken, remoteAppToken) 610 611 // Have we seen the relation before. 612 r, relationKnown := w.relations[key] 613 if !relationKnown { 614 // Totally new so start the lifecycle watcher. 615 remoteRelationsWatcher, err := w.remoteModelFacade.WatchRelationSuspendedStatus(params.RemoteEntityArg{ 616 Token: relationToken, 617 Macaroons: macaroon.Slice{mac}, 618 BakeryVersion: bakery.LatestVersion, 619 }) 620 if err != nil { 621 w.checkOfferPermissionDenied(err, remoteAppToken, relationToken) 622 return errors.Annotatef(err, "watching remote side of relation %v", remoteRelation.Key) 623 } 624 625 remoteRelationsWorker, err := newRemoteRelationsWorker( 626 relationTag, 627 remoteAppToken, 628 relationToken, 629 remoteRelationsWatcher, 630 w.remoteRelationUnitChanges, 631 w.logger, 632 ) 633 if err != nil { 634 return errors.Trace(err) 635 } 636 if err := w.catacomb.Add(remoteRelationsWorker); err != nil { 637 return errors.Trace(err) 638 } 639 r = &relation{ 640 relationId: remoteRelation.Id, 641 suspended: remoteRelation.Suspended, 642 localUnitCount: remoteRelation.UnitCount, 643 remoteRrw: remoteRelationsWorker, 644 macaroon: mac, 645 localEndpoint: remoteRelation.Endpoint, 646 remoteEndpointName: remoteRelation.RemoteEndpointName, 647 applicationToken: applicationToken, 648 relationToken: relationToken, 649 } 650 w.relations[key] = r 651 } 652 653 if r.localRuw == nil && !remoteRelation.Suspended { 654 // Also start the units watchers (local and remote). 655 localUnitsWorker, remoteUnitsWorker, err := w.startUnitsWorkers( 656 relationTag, relationToken, remoteAppToken, remoteRelation.ApplicationName, mac) 657 if err != nil { 658 return errors.Annotate(err, "starting relation units workers") 659 } 660 r.localRuw = localUnitsWorker 661 r.remoteRuw = remoteUnitsWorker 662 } 663 664 if w.secretChangesWatcher == nil { 665 w.secretChangesWatcher, err = w.remoteModelFacade.WatchConsumedSecretsChanges(applicationToken, relationToken, w.offerMacaroon) 666 if err != nil && !errors.Is(err, errors.NotFound) && !errors.Is(err, errors.NotImplemented) { 667 w.checkOfferPermissionDenied(err, "", "") 668 return errors.Annotate(err, "watching consumed secret changes") 669 } 670 if err == nil { 671 if err := w.catacomb.Add(w.secretChangesWatcher); err != nil { 672 return errors.Trace(err) 673 } 674 w.secretChanges = w.secretChangesWatcher.Changes() 675 } 676 } 677 678 // If the relation is dying, stop the watcher. 679 if remoteRelation.Life != life.Alive { 680 return w.processRelationDying(key, r, !relationKnown) 681 } 682 683 return nil 684 } 685 686 func (w *remoteApplicationWorker) registerRemoteRelation( 687 applicationTag, relationTag names.Tag, offerUUID string, consumeVersion int, 688 localEndpointInfo params.RemoteEndpoint, remoteEndpointName string, 689 ) (applicationToken, offeringAppToken, relationToken string, _ *macaroon.Macaroon, _ error) { 690 w.logger.Debugf("register remote relation %v to local application %v", relationTag.Id(), applicationTag.Id()) 691 692 fail := func(err error) (string, string, string, *macaroon.Macaroon, error) { 693 return "", "", "", nil, err 694 } 695 696 // Ensure the relation is exported first up. 697 results, err := w.localModelFacade.ExportEntities([]names.Tag{applicationTag, relationTag}) 698 if err != nil { 699 return fail(errors.Annotatef(err, "exporting relation %v and application %v", relationTag, applicationTag)) 700 } 701 if results[0].Error != nil && !params.IsCodeAlreadyExists(results[0].Error) { 702 return fail(errors.Annotatef(err, "exporting application %v", applicationTag)) 703 } 704 applicationToken = results[0].Token 705 if results[1].Error != nil && !params.IsCodeAlreadyExists(results[1].Error) { 706 return fail(errors.Annotatef(err, "exporting relation %v", relationTag)) 707 } 708 relationToken = results[1].Token 709 710 // This data goes to the remote model so we map local info 711 // from this model to the remote arg values and visa versa. 712 arg := params.RegisterRemoteRelationArg{ 713 ApplicationToken: applicationToken, 714 SourceModelTag: names.NewModelTag(w.localModelUUID).String(), 715 RelationToken: relationToken, 716 OfferUUID: offerUUID, 717 RemoteEndpoint: localEndpointInfo, 718 LocalEndpointName: remoteEndpointName, 719 ConsumeVersion: consumeVersion, 720 } 721 if w.offerMacaroon != nil { 722 arg.Macaroons = macaroon.Slice{w.offerMacaroon} 723 arg.BakeryVersion = bakery.LatestVersion 724 } 725 remoteRelation, err := w.remoteModelFacade.RegisterRemoteRelations(arg) 726 if err != nil { 727 return fail(errors.Trace(err)) 728 } 729 // remoteAppIds is a slice but there's only one item 730 // as we currently only register one remote application 731 if err := remoteRelation[0].Error; err != nil { 732 return fail(errors.Annotatef(err, "registering relation %v", relationTag)) 733 } 734 // Import the application id from the offering model. 735 registerResult := *remoteRelation[0].Result 736 offeringAppToken = registerResult.Token 737 // We have a new macaroon attenuated to the relation. 738 // Save for the firewaller. 739 if err := w.localModelFacade.SaveMacaroon(relationTag, registerResult.Macaroon); err != nil { 740 return fail(errors.Annotatef( 741 err, "saving macaroon for %v", relationTag)) 742 } 743 744 appTag := names.NewApplicationTag(w.applicationName) 745 w.logger.Debugf("import remote application token %v for %v", offeringAppToken, w.applicationName) 746 err = w.localModelFacade.ImportRemoteEntity(appTag, offeringAppToken) 747 if err != nil && !params.IsCodeAlreadyExists(err) { 748 return fail(errors.Annotatef( 749 err, "importing remote application %v to local model", w.applicationName)) 750 } 751 return applicationToken, offeringAppToken, relationToken, registerResult.Macaroon, nil 752 } 753 754 // Report provides information for the engine report. 755 func (w *remoteApplicationWorker) Report() map[string]interface{} { 756 result := make(map[string]interface{}) 757 w.mu.Lock() 758 defer w.mu.Unlock() 759 760 relationsInfo := make(map[string]interface{}) 761 for rel, info := range w.relations { 762 report := map[string]interface{}{ 763 "relation-id": info.relationId, 764 "local-dead": info.localDead, 765 "suspended": info.suspended, 766 "application-token": info.applicationToken, 767 "relation-token": info.relationToken, 768 "local-endpoint": info.localEndpoint.Name, 769 "remote-endpoint": info.remoteEndpointName, 770 } 771 if info.remoteRrw != nil { 772 report["last-status-event"] = info.remoteRrw.Report() 773 } 774 if info.localRuw != nil { 775 report["last-local-change"] = info.localRuw.Report() 776 } 777 if info.remoteRuw != nil { 778 report["last-remote-change"] = info.remoteRuw.Report() 779 } 780 relationsInfo[rel] = report 781 } 782 if len(relationsInfo) > 0 { 783 result["relations"] = relationsInfo 784 } 785 result["remote-model-uuid"] = w.remoteModelUUID 786 if w.isConsumerProxy { 787 result["consumer-proxy"] = true 788 result["consume-version"] = w.consumeVersion 789 } else { 790 result["saas-application"] = true 791 result["offer-uuid"] = w.offerUUID 792 } 793 794 return result 795 }