github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "github.com/juju/errors" 8 "gopkg.in/juju/names.v2" 9 "gopkg.in/juju/worker.v1" 10 "gopkg.in/juju/worker.v1/catacomb" 11 "gopkg.in/macaroon.v2-unstable" 12 13 "github.com/juju/juju/apiserver/params" 14 "github.com/juju/juju/core/status" 15 "github.com/juju/juju/core/watcher" 16 ) 17 18 // remoteApplicationWorker listens for localChanges to relations 19 // involving a remote application, and publishes change to 20 // local relation units to the remote model. It also watches for 21 // changes originating from the offering model and consumes those 22 // in the local model. 23 type remoteApplicationWorker struct { 24 catacomb catacomb.Catacomb 25 26 // These attribute are relevant to dealing with a specific 27 // remote application proxy. 28 offerUUID string 29 applicationName string // name of the remote application proxy in the local model 30 localModelUUID string // uuid of the model hosting the local application 31 remoteModelUUID string // uuid of the model hosting the remote offer 32 isConsumerProxy bool 33 localRelationChanges chan params.RemoteRelationChangeEvent 34 remoteRelationChanges chan params.RemoteRelationChangeEvent 35 36 // offerMacaroon is used to confirm that permission has been granted to consume 37 // the remote application to which this worker pertains. 38 offerMacaroon *macaroon.Macaroon 39 40 // localModelFacade interacts with the local (consuming) model. 41 localModelFacade RemoteRelationsFacade 42 // remoteModelFacade interacts with the remote (offering) model. 43 remoteModelFacade RemoteModelRelationsFacadeCloser 44 45 newRemoteModelRelationsFacadeFunc newRemoteRelationsFacadeFunc 46 } 47 48 // relation holds attributes relevant to a particular 49 // relation between a local app and a remote offer. 50 type relation struct { 51 relationId int 52 suspended bool 53 localRuw *relationUnitsWorker 54 remoteRuw *relationUnitsWorker 55 remoteRrw *remoteRelationsWorker 56 57 applicationToken string // token for app in local model 58 relationToken string // token for relation in local model 59 localEndpoint params.RemoteEndpoint 60 remoteEndpointName string 61 macaroon *macaroon.Macaroon 62 } 63 64 // Kill is defined on worker.Worker 65 func (w *remoteApplicationWorker) Kill() { 66 w.catacomb.Kill(nil) 67 } 68 69 // Wait is defined on worker.Worker 70 func (w *remoteApplicationWorker) Wait() error { 71 err := w.catacomb.Wait() 72 if err != nil { 73 logger.Errorf("error in remote application worker for %v: %v", w.applicationName, err) 74 } 75 return err 76 } 77 78 func (w *remoteApplicationWorker) checkOfferPermissionDenied(err error, appToken, relationToken string) { 79 // If consume permission has been revoked for the offer, set the 80 // status of the local remote application entity. 81 if params.ErrCode(err) == params.CodeDischargeRequired { 82 if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Error, err.Error()); err != nil { 83 logger.Errorf( 84 "updating remote application %v status from remote model %v: %v", 85 w.applicationName, w.remoteModelUUID, err) 86 } 87 logger.Debugf("discharge required error: app token: %v rel token: %v", appToken, relationToken) 88 // If we know a specific relation, update that too. 89 if relationToken != "" { 90 suspended := true 91 event := params.RemoteRelationChangeEvent{ 92 RelationToken: relationToken, 93 ApplicationToken: appToken, 94 Suspended: &suspended, 95 SuspendedReason: "offer permission revoked", 96 } 97 if err := w.localModelFacade.ConsumeRemoteRelationChange(event); err != nil { 98 logger.Errorf("updating relation status: %v", err) 99 } 100 } 101 } 102 } 103 104 func (w *remoteApplicationWorker) remoteOfferRemoved() error { 105 logger.Debugf("remote offer for %s has been removed", w.applicationName) 106 if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Terminated, "offer has been removed"); err != nil { 107 return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID) 108 } 109 return nil 110 } 111 112 func (w *remoteApplicationWorker) loop() (err error) { 113 // Watch for changes to any remote relations to this application. 114 relationsWatcher, err := w.localModelFacade.WatchRemoteApplicationRelations(w.applicationName) 115 if errors.IsNotFound(err) { 116 return nil 117 } else if err != nil { 118 return errors.Annotatef(err, "watching relations for remote application %q", w.applicationName) 119 } 120 if err := w.catacomb.Add(relationsWatcher); err != nil { 121 return errors.Trace(err) 122 } 123 124 // On the consuming side, watch for status changes to the offer. 125 var offerStatusChanges watcher.OfferStatusChannel 126 if !w.isConsumerProxy { 127 // Get the connection info for the remote controller. 128 apiInfo, err := w.localModelFacade.ControllerAPIInfoForModel(w.remoteModelUUID) 129 if err != nil { 130 return errors.Trace(err) 131 } 132 logger.Debugf("remote controller api addresses: %v", apiInfo.Addrs) 133 134 w.remoteModelFacade, err = w.newRemoteModelRelationsFacadeFunc(apiInfo) 135 if err != nil { 136 return errors.Annotate(err, "opening facade to remote model") 137 } 138 139 defer func() { 140 w.remoteModelFacade.Close() 141 }() 142 143 arg := params.OfferArg{ 144 OfferUUID: w.offerUUID, 145 } 146 if w.offerMacaroon != nil { 147 arg.Macaroons = macaroon.Slice{w.offerMacaroon} 148 } 149 150 offerStatusWatcher, err := w.remoteModelFacade.WatchOfferStatus(arg) 151 if err != nil { 152 w.checkOfferPermissionDenied(err, "", "") 153 if params.IsCodeNotFound(err) { 154 return w.remoteOfferRemoved() 155 } 156 return errors.Annotate(err, "watching status for offer") 157 } 158 if err := w.catacomb.Add(offerStatusWatcher); err != nil { 159 return errors.Trace(err) 160 } 161 offerStatusChanges = offerStatusWatcher.Changes() 162 } 163 164 relations := make(map[string]*relation) 165 for { 166 select { 167 case <-w.catacomb.Dying(): 168 return w.catacomb.ErrDying() 169 case change, ok := <-relationsWatcher.Changes(): 170 logger.Debugf("relations changed: %#v, %v", change, ok) 171 if !ok { 172 // We are dying. 173 return w.catacomb.ErrDying() 174 } 175 results, err := w.localModelFacade.Relations(change) 176 if err != nil { 177 return errors.Annotate(err, "querying relations") 178 } 179 for i, result := range results { 180 key := change[i] 181 if err := w.relationChanged(key, result, relations); err != nil { 182 if params.IsCodeNotFound(err) { 183 return w.remoteOfferRemoved() 184 } 185 return errors.Annotatef(err, "handling change for relation %q", key) 186 } 187 } 188 case change := <-w.localRelationChanges: 189 logger.Debugf("local relation units changed -> publishing: %#v", change) 190 if err := w.remoteModelFacade.PublishRelationChange(change); err != nil { 191 w.checkOfferPermissionDenied(err, change.ApplicationToken, change.RelationToken) 192 if params.IsCodeNotFound(err) || params.IsCodeCannotEnterScope(err) { 193 return w.remoteOfferRemoved() 194 } 195 return errors.Annotatef(err, "publishing relation change %+v to remote model %v", change, w.remoteModelUUID) 196 } 197 case change := <-w.remoteRelationChanges: 198 logger.Debugf("remote relation units changed -> consuming: %#v", change) 199 if err := w.localModelFacade.ConsumeRemoteRelationChange(change); err != nil { 200 return errors.Annotatef(err, "consuming relation change %+v from remote model %v", change, w.remoteModelUUID) 201 } 202 case changes := <-offerStatusChanges: 203 logger.Debugf("offer status changed: %#v", changes) 204 for _, change := range changes { 205 if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, change.Status.Status, change.Status.Message); err != nil { 206 return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID) 207 } 208 } 209 } 210 } 211 } 212 213 func (w *remoteApplicationWorker) processRelationDying(key string, r *relation, forceCleanup bool) error { 214 logger.Debugf("relation %v dying (%v)", key, forceCleanup) 215 // On the consuming side, inform the remote side the relation is dying 216 // (but only if we are killing the relation due to it dying, not because 217 // it is suspended). 218 if !w.isConsumerProxy { 219 change := params.RemoteRelationChangeEvent{ 220 RelationToken: r.relationToken, 221 Life: params.Dying, 222 ApplicationToken: r.applicationToken, 223 Macaroons: macaroon.Slice{r.macaroon}, 224 } 225 // forceCleanup will be true if the worker has restarted and because the relation had 226 // already been removed, we won't get any more unit departed events. 227 if forceCleanup { 228 change.ForceCleanup = &forceCleanup 229 } 230 if err := w.remoteModelFacade.PublishRelationChange(change); err != nil { 231 w.checkOfferPermissionDenied(err, r.applicationToken, r.relationToken) 232 if params.IsCodeNotFound(err) { 233 logger.Debugf("relation %v dying but offer already removed", key) 234 return nil 235 } 236 return errors.Annotatef(err, "publishing relation dying %+v to remote model %v", change, w.remoteModelUUID) 237 } 238 } 239 return nil 240 } 241 242 func (w *remoteApplicationWorker) processRelationSuspended(key string, relations map[string]*relation) error { 243 logger.Debugf("relation %v suspended", key) 244 relation, ok := relations[key] 245 if !ok { 246 return nil 247 } 248 249 // For suspended relations on the consuming side 250 // we want to keep the remote lifecycle watcher 251 // so we know when the relation is resumed. 252 if w.isConsumerProxy { 253 if err := worker.Stop(relation.remoteRrw); err != nil { 254 logger.Warningf("stopping remote relations worker for %v: %v", key, err) 255 } 256 relation.remoteRuw = nil 257 delete(relations, key) 258 } 259 260 if relation.localRuw != nil { 261 if err := worker.Stop(relation.localRuw); err != nil { 262 logger.Warningf("stopping local relation unit worker for %v: %v", key, err) 263 } 264 relation.localRuw = nil 265 } 266 return nil 267 } 268 269 func (w *remoteApplicationWorker) processRelationRemoved(key string, relations map[string]*relation) error { 270 logger.Debugf("relation %v removed", key) 271 relation, ok := relations[key] 272 if !ok { 273 return nil 274 } 275 276 if err := worker.Stop(relation.remoteRrw); err != nil { 277 logger.Warningf("stopping remote relations worker for %v: %v", key, err) 278 } 279 relation.remoteRuw = nil 280 delete(relations, key) 281 282 // For the unit watchers, check to see if these are nil before stopping. 283 // They will be nil if the relation was suspended and then we kill it for real. 284 if relation.localRuw != nil { 285 if err := worker.Stop(relation.localRuw); err != nil { 286 logger.Warningf("stopping local relation unit worker for %v: %v", key, err) 287 } 288 relation.localRuw = nil 289 } 290 291 logger.Debugf("remote relation %v removed from remote model", key) 292 return nil 293 } 294 295 func (w *remoteApplicationWorker) relationChanged( 296 key string, result params.RemoteRelationResult, relations map[string]*relation, 297 ) error { 298 logger.Debugf("relation %q changed: %+v", key, result) 299 if result.Error != nil { 300 if params.IsCodeNotFound(result.Error) { 301 return w.processRelationRemoved(key, relations) 302 } 303 return result.Error 304 } 305 remoteRelation := result.Result 306 307 // If we have previously started the watcher and the 308 // relation is now suspended, stop the watcher. 309 if r := relations[key]; r != nil { 310 wasSuspended := r.suspended 311 r.suspended = remoteRelation.Suspended 312 relations[key] = r 313 if remoteRelation.Suspended { 314 return w.processRelationSuspended(key, relations) 315 } 316 if !wasSuspended && remoteRelation.Life == params.Alive { 317 // Nothing to do, we have previously started the watcher. 318 return nil 319 } 320 } 321 322 if w.isConsumerProxy { 323 // Nothing else to do on the offering side. 324 return nil 325 } 326 return w.processConsumingRelation(key, relations, remoteRelation) 327 } 328 329 // startUnitsWorkers starts 2 workers to watch for unit settings or departed changes; 330 // one worker is for the local model, the other for the remote model. 331 func (w *remoteApplicationWorker) startUnitsWorkers( 332 relationTag names.RelationTag, 333 applicationToken, relationToken, remoteAppToken string, 334 applicationName string, 335 mac *macaroon.Macaroon, 336 ) (*relationUnitsWorker, *relationUnitsWorker, error) { 337 // Start a watcher to track changes to the units in the relation in the local model. 338 localRelationUnitsWatcher, err := w.localModelFacade.WatchLocalRelationUnits(relationTag.Id()) 339 if err != nil { 340 return nil, nil, errors.Annotatef(err, "watching local side of relation %v", relationTag.Id()) 341 } 342 343 // localUnitSettingsFunc converts relations units watcher results from the local model 344 // into settings params using an api call to the local model. 345 localUnitSettingsFunc := func(changedUnitNames []string) ([]params.SettingsResult, error) { 346 relationUnits := make([]params.RelationUnit, len(changedUnitNames)) 347 for i, changedName := range changedUnitNames { 348 relationUnits[i] = params.RelationUnit{ 349 Relation: relationTag.String(), 350 Unit: names.NewUnitTag(changedName).String(), 351 } 352 } 353 return w.localModelFacade.RelationUnitSettings(relationUnits) 354 } 355 localUnitsWorker, err := newRelationUnitsWorker( 356 relationTag, 357 applicationToken, 358 mac, 359 relationToken, 360 localRelationUnitsWatcher, 361 w.localRelationChanges, 362 localUnitSettingsFunc, 363 ) 364 if err != nil { 365 return nil, nil, errors.Trace(err) 366 } 367 if err := w.catacomb.Add(localUnitsWorker); err != nil { 368 return nil, nil, errors.Trace(err) 369 } 370 371 // Start a watcher to track changes to the units in the relation in the remote model. 372 remoteRelationUnitsWatcher, err := w.remoteModelFacade.WatchRelationUnits(params.RemoteEntityArg{ 373 Token: relationToken, 374 Macaroons: macaroon.Slice{mac}, 375 }) 376 if err != nil { 377 w.checkOfferPermissionDenied(err, remoteAppToken, relationToken) 378 return nil, nil, errors.Annotatef( 379 err, "watching remote side of application %v and relation %v", 380 applicationName, relationTag.Id()) 381 } 382 383 // remoteUnitSettingsFunc converts relations units watcher results from the remote model 384 // into settings params using an api call to the remote model. 385 remoteUnitSettingsFunc := func(changedUnitNames []string) ([]params.SettingsResult, error) { 386 relationUnits := make([]params.RemoteRelationUnit, len(changedUnitNames)) 387 for i, changedName := range changedUnitNames { 388 relationUnits[i] = params.RemoteRelationUnit{ 389 RelationToken: relationToken, 390 Unit: names.NewUnitTag(changedName).String(), 391 Macaroons: macaroon.Slice{mac}, 392 } 393 } 394 return w.remoteModelFacade.RelationUnitSettings(relationUnits) 395 } 396 remoteUnitsWorker, err := newRelationUnitsWorker( 397 relationTag, 398 remoteAppToken, 399 mac, 400 relationToken, 401 remoteRelationUnitsWatcher, 402 w.remoteRelationChanges, 403 remoteUnitSettingsFunc, 404 ) 405 if err != nil { 406 return nil, nil, errors.Trace(err) 407 } 408 if err := w.catacomb.Add(remoteUnitsWorker); err != nil { 409 return nil, nil, errors.Trace(err) 410 } 411 return localUnitsWorker, remoteUnitsWorker, nil 412 } 413 414 // processConsumingRelation starts the sub-workers necessary to listen and publish 415 // local unit settings changes, and watch and consume remote unit settings changes. 416 // Ths will be called when a new relation is created or when a relation resumes 417 // after being suspended. 418 func (w *remoteApplicationWorker) processConsumingRelation( 419 key string, 420 relations map[string]*relation, 421 remoteRelation *params.RemoteRelation, 422 ) error { 423 424 // We have not seen the relation before, make 425 // sure it is registered on the offering side. 426 // Or relation was suspended and is now resumed so re-register. 427 applicationTag := names.NewApplicationTag(remoteRelation.ApplicationName) 428 relationTag := names.NewRelationTag(key) 429 applicationToken, remoteAppToken, relationToken, mac, err := w.registerRemoteRelation( 430 applicationTag, relationTag, w.offerUUID, 431 remoteRelation.Endpoint, remoteRelation.RemoteEndpointName) 432 if err != nil { 433 w.checkOfferPermissionDenied(err, "", "") 434 return errors.Annotatef(err, "registering application %v and relation %v", remoteRelation.ApplicationName, relationTag.Id()) 435 } 436 437 // Have we seen the relation before. 438 r, relationKnown := relations[key] 439 if !relationKnown { 440 // Totally new so start the lifecycle watcher. 441 remoteRelationsWatcher, err := w.remoteModelFacade.WatchRelationSuspendedStatus(params.RemoteEntityArg{ 442 Token: relationToken, 443 Macaroons: macaroon.Slice{mac}, 444 }) 445 if err != nil { 446 w.checkOfferPermissionDenied(err, remoteAppToken, relationToken) 447 return errors.Annotatef(err, "watching remote side of relation %v", remoteRelation.Key) 448 } 449 450 remoteRelationsWorker, err := newRemoteRelationsWorker( 451 relationTag, 452 remoteAppToken, 453 relationToken, 454 remoteRelationsWatcher, 455 w.remoteRelationChanges, 456 ) 457 if err != nil { 458 return errors.Trace(err) 459 } 460 if err := w.catacomb.Add(remoteRelationsWorker); err != nil { 461 return errors.Trace(err) 462 } 463 r = &relation{ 464 relationId: remoteRelation.Id, 465 suspended: remoteRelation.Suspended, 466 remoteRrw: remoteRelationsWorker, 467 macaroon: mac, 468 localEndpoint: remoteRelation.Endpoint, 469 remoteEndpointName: remoteRelation.RemoteEndpointName, 470 applicationToken: applicationToken, 471 relationToken: relationToken, 472 } 473 relations[key] = r 474 } 475 476 if r.localRuw == nil && !remoteRelation.Suspended { 477 // Also start the units watchers (local and remote). 478 localUnitsWorker, remoteUnitsWorker, err := w.startUnitsWorkers( 479 relationTag, applicationToken, relationToken, remoteAppToken, remoteRelation.ApplicationName, mac) 480 if err != nil { 481 return errors.Annotate(err, "starting relation units workers") 482 } 483 r.localRuw = localUnitsWorker 484 r.remoteRuw = remoteUnitsWorker 485 } 486 487 // If the relation is dying, stop the watcher. 488 if remoteRelation.Life != params.Alive { 489 return w.processRelationDying(key, r, !relationKnown) 490 } 491 492 return nil 493 } 494 495 func (w *remoteApplicationWorker) registerRemoteRelation( 496 applicationTag, relationTag names.Tag, offerUUID string, 497 localEndpointInfo params.RemoteEndpoint, remoteEndpointName string, 498 ) (applicationToken, offeringAppToken, relationToken string, _ *macaroon.Macaroon, _ error) { 499 logger.Debugf("register remote relation %v to local application %v", relationTag.Id(), applicationTag.Id()) 500 501 fail := func(err error) (string, string, string, *macaroon.Macaroon, error) { 502 return "", "", "", nil, err 503 } 504 505 // Ensure the relation is exported first up. 506 results, err := w.localModelFacade.ExportEntities([]names.Tag{applicationTag, relationTag}) 507 if err != nil { 508 return fail(errors.Annotatef(err, "exporting relation %v and application %v", relationTag, applicationTag)) 509 } 510 if results[0].Error != nil && !params.IsCodeAlreadyExists(results[0].Error) { 511 return fail(errors.Annotatef(err, "exporting application %v", applicationTag)) 512 } 513 applicationToken = results[0].Token 514 if results[1].Error != nil && !params.IsCodeAlreadyExists(results[1].Error) { 515 return fail(errors.Annotatef(err, "exporting relation %v", relationTag)) 516 } 517 relationToken = results[1].Token 518 519 // This data goes to the remote model so we map local info 520 // from this model to the remote arg values and visa versa. 521 arg := params.RegisterRemoteRelationArg{ 522 ApplicationToken: applicationToken, 523 SourceModelTag: names.NewModelTag(w.localModelUUID).String(), 524 RelationToken: relationToken, 525 OfferUUID: offerUUID, 526 RemoteEndpoint: localEndpointInfo, 527 LocalEndpointName: remoteEndpointName, 528 } 529 if w.offerMacaroon != nil { 530 arg.Macaroons = macaroon.Slice{w.offerMacaroon} 531 } 532 remoteRelation, err := w.remoteModelFacade.RegisterRemoteRelations(arg) 533 if err != nil { 534 return fail(errors.Trace(err)) 535 } 536 // remoteAppIds is a slice but there's only one item 537 // as we currently only register one remote application 538 if err := remoteRelation[0].Error; err != nil { 539 return fail(errors.Annotatef(err, "registering relation %v", relationTag)) 540 } 541 // Import the application id from the offering model. 542 registerResult := *remoteRelation[0].Result 543 offeringAppToken = registerResult.Token 544 // We have a new macaroon attenuated to the relation. 545 // Save for the firewaller. 546 if err := w.localModelFacade.SaveMacaroon(relationTag, registerResult.Macaroon); err != nil { 547 return fail(errors.Annotatef( 548 err, "saving macaroon for %v", relationTag)) 549 } 550 551 appTag := names.NewApplicationTag(w.applicationName) 552 logger.Debugf("import remote application token %v for %v", offeringAppToken, w.applicationName) 553 err = w.localModelFacade.ImportRemoteEntity(appTag, offeringAppToken) 554 if err != nil && !params.IsCodeAlreadyExists(err) { 555 return fail(errors.Annotatef( 556 err, "importing remote application %v to local model", w.applicationName)) 557 } 558 return applicationToken, offeringAppToken, relationToken, registerResult.Macaroon, nil 559 }