github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/remoteapplication.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "sort" 10 "time" 11 12 "github.com/juju/charm/v12" 13 "github.com/juju/collections/set" 14 "github.com/juju/errors" 15 "github.com/juju/mgo/v3" 16 "github.com/juju/mgo/v3/bson" 17 "github.com/juju/mgo/v3/txn" 18 "github.com/juju/names/v5" 19 jujutxn "github.com/juju/txn/v3" 20 "gopkg.in/macaroon.v2" 21 22 "github.com/juju/juju/core/crossmodel" 23 "github.com/juju/juju/core/status" 24 "github.com/juju/juju/environs" 25 ) 26 27 // RemoteApplication represents the state of an application hosted 28 // in an external (remote) model. 29 type RemoteApplication struct { 30 st *State 31 doc remoteApplicationDoc 32 } 33 34 // remoteApplicationDoc represents the internal state of a remote application in MongoDB. 35 type remoteApplicationDoc struct { 36 DocID string `bson:"_id"` 37 Name string `bson:"name"` 38 OfferUUID string `bson:"offer-uuid"` 39 URL string `bson:"url,omitempty"` 40 SourceControllerUUID string `bson:"source-controller-uuid"` 41 SourceModelUUID string `bson:"source-model-uuid"` 42 Endpoints []remoteEndpointDoc `bson:"endpoints"` 43 Spaces []remoteSpaceDoc `bson:"spaces"` 44 Bindings map[string]string `bson:"bindings"` 45 Life Life `bson:"life"` 46 RelationCount int `bson:"relationcount"` 47 IsConsumerProxy bool `bson:"is-consumer-proxy"` 48 Version int `bson:"version"` 49 Macaroon string `bson:"macaroon,omitempty"` 50 } 51 52 // remoteEndpointDoc represents the internal state of a remote application endpoint in MongoDB. 53 type remoteEndpointDoc struct { 54 Name string `bson:"name"` 55 Role charm.RelationRole `bson:"role"` 56 Interface string `bson:"interface"` 57 Limit int `bson:"limit"` 58 Scope charm.RelationScope `bson:"scope"` 59 } 60 61 type attributeMap map[string]interface{} 62 63 // remoteSpaceDoc represents the internal state of a space in another 64 // model in the DB. 65 type remoteSpaceDoc struct { 66 CloudType string `bson:"cloud-type"` 67 Name string `bson:"name"` 68 ProviderId string `bson:"provider-id"` 69 ProviderAttributes attributeMap `bson:"provider-attributes"` 70 Subnets []remoteSubnetDoc `bson:"subnets"` 71 } 72 73 // RemoteSpace represents a space in another model that endpoints are 74 // bound to. 75 type RemoteSpace struct { 76 CloudType string 77 Name string 78 ProviderId string 79 ProviderAttributes attributeMap 80 Subnets []RemoteSubnet 81 } 82 83 // remoteSubnetDoc represents a subnet in another model in the DB. 84 type remoteSubnetDoc struct { 85 CIDR string `bson:"cidr"` 86 ProviderId string `bson:"provider-id"` 87 VLANTag int `bson:"vlan-tag"` 88 AvailabilityZones []string `bson:"availability-zones"` 89 ProviderSpaceId string `bson:"provider-space-id"` 90 ProviderNetworkId string `bson:"provider-network-id"` 91 } 92 93 // RemoteSubnet represents a subnet in another model. 94 type RemoteSubnet struct { 95 CIDR string 96 ProviderId string 97 VLANTag int 98 AvailabilityZones []string 99 ProviderSpaceId string 100 ProviderNetworkId string 101 } 102 103 func newRemoteApplication(st *State, doc *remoteApplicationDoc) *RemoteApplication { 104 app := &RemoteApplication{ 105 st: st, 106 doc: *doc, 107 } 108 return app 109 } 110 111 // remoteApplicationGlobalKey returns the global database key for the 112 // remote application with the given name. 113 // 114 // This seems like an aggressively cryptic prefix, but apparently the 115 // all-watcher requires that global keys have single letter prefixes 116 // and r and a were taken. 117 // TODO(babbageclunk): check whether this is still the case. 118 func remoteApplicationGlobalKey(appName string) string { 119 return "c#" + appName 120 } 121 122 // globalKey returns the global database key for the remote application. 123 func (a *RemoteApplication) globalKey() string { 124 return remoteApplicationGlobalKey(a.doc.Name) 125 } 126 127 // IsRemote returns true for a remote application. 128 func (a *RemoteApplication) IsRemote() bool { 129 return true 130 } 131 132 // SourceModel returns the tag of the model to which the application belongs. 133 func (a *RemoteApplication) SourceModel() names.ModelTag { 134 return names.NewModelTag(a.doc.SourceModelUUID) 135 } 136 137 // SourceController returns the UUID of the controller hosting the application. 138 func (a *RemoteApplication) SourceController() string { 139 return a.doc.SourceControllerUUID 140 } 141 142 // IsConsumerProxy returns the application is created 143 // from a registration operation by a consuming model. 144 func (a *RemoteApplication) IsConsumerProxy() bool { 145 return a.doc.IsConsumerProxy 146 } 147 148 // ConsumeVersion is incremented each time a new consumer proxy 149 // is created for an offer. 150 func (a *RemoteApplication) ConsumeVersion() int { 151 return a.doc.Version 152 } 153 154 // Name returns the application name. 155 func (a *RemoteApplication) Name() string { 156 return a.doc.Name 157 } 158 159 // OfferUUID returns the offer UUID. 160 func (a *RemoteApplication) OfferUUID() string { 161 return a.doc.OfferUUID 162 } 163 164 // URL returns the remote application URL, and a boolean indicating whether or not 165 // a URL is known for the remote application. A URL will only be available for the 166 // consumer of an offered application. 167 func (a *RemoteApplication) URL() (string, bool) { 168 return a.doc.URL, a.doc.URL != "" 169 } 170 171 // Token returns the token for the remote application, provided by the remote 172 // model to identify the application in future communications. 173 func (a *RemoteApplication) Token() (string, error) { 174 r := a.st.RemoteEntities() 175 return r.GetToken(a.Tag()) 176 } 177 178 // Tag returns a name identifying the application. 179 func (a *RemoteApplication) Tag() names.Tag { 180 return names.NewApplicationTag(a.Name()) 181 } 182 183 // Life returns whether the application is Alive, Dying or Dead. 184 func (a *RemoteApplication) Life() Life { 185 return a.doc.Life 186 } 187 188 // StatusHistory returns a slice of at most filter.Size StatusInfo items 189 // or items as old as filter.Date or items newer than now - filter.Delta time 190 // representing past statuses for this remote application. 191 func (a *RemoteApplication) StatusHistory(filter status.StatusHistoryFilter) ([]status.StatusInfo, error) { 192 args := &statusHistoryArgs{ 193 db: a.st.db(), 194 globalKey: a.globalKey(), 195 filter: filter, 196 clock: a.st.clock(), 197 } 198 return statusHistory(args) 199 } 200 201 // Spaces returns the remote spaces this application is connected to. 202 func (a *RemoteApplication) Spaces() []RemoteSpace { 203 var result []RemoteSpace 204 for _, space := range a.doc.Spaces { 205 result = append(result, remoteSpaceFromDoc(space)) 206 } 207 return result 208 } 209 210 // Bindings returns the endpoint->space bindings for the application. 211 func (a *RemoteApplication) Bindings() map[string]string { 212 result := make(map[string]string) 213 for epName, spName := range a.doc.Bindings { 214 result[epName] = spName 215 } 216 return result 217 } 218 219 // SpaceForEndpoint returns the remote space an endpoint is bound to, 220 // if one is found. 221 func (a *RemoteApplication) SpaceForEndpoint(endpointName string) (RemoteSpace, bool) { 222 spaceName, ok := a.doc.Bindings[endpointName] 223 if !ok { 224 return RemoteSpace{}, false 225 } 226 for _, space := range a.doc.Spaces { 227 if space.Name == spaceName { 228 return remoteSpaceFromDoc(space), true 229 } 230 } 231 return RemoteSpace{}, false 232 } 233 234 func remoteSpaceFromDoc(space remoteSpaceDoc) RemoteSpace { 235 result := RemoteSpace{ 236 CloudType: space.CloudType, 237 Name: space.Name, 238 ProviderId: space.ProviderId, 239 ProviderAttributes: copyAttributes(space.ProviderAttributes), 240 } 241 for _, subnet := range space.Subnets { 242 result.Subnets = append(result.Subnets, remoteSubnetFromDoc(subnet)) 243 } 244 return result 245 } 246 247 func remoteSubnetFromDoc(subnet remoteSubnetDoc) RemoteSubnet { 248 return RemoteSubnet{ 249 CIDR: subnet.CIDR, 250 ProviderId: subnet.ProviderId, 251 VLANTag: subnet.VLANTag, 252 AvailabilityZones: copyStrings(subnet.AvailabilityZones), 253 ProviderSpaceId: subnet.ProviderSpaceId, 254 ProviderNetworkId: subnet.ProviderNetworkId, 255 } 256 } 257 258 func copyStrings(values []string) []string { 259 if values == nil { 260 return nil 261 } 262 result := make([]string, len(values)) 263 copy(result, values) 264 return result 265 } 266 267 func copyAttributes(values attributeMap) attributeMap { 268 if values == nil { 269 return nil 270 } 271 result := make(attributeMap) 272 for key, value := range values { 273 result[key] = value 274 } 275 return result 276 } 277 278 // DestroyOperation returns a model operation to destroy remote application. 279 func (a *RemoteApplication) DestroyOperation(force bool) *DestroyRemoteApplicationOperation { 280 return &DestroyRemoteApplicationOperation{ 281 app: &RemoteApplication{st: a.st, doc: a.doc}, 282 ForcedOperation: ForcedOperation{Force: force}, 283 } 284 } 285 286 // DestroyRemoteApplicationOperation is a model operation to destroy a remote application. 287 type DestroyRemoteApplicationOperation struct { 288 // ForcedOperation stores needed information to force this operation. 289 ForcedOperation 290 291 // app holds the remote application to destroy. 292 app *RemoteApplication 293 } 294 295 // Build is part of the ModelOperation interface. 296 func (op *DestroyRemoteApplicationOperation) Build(attempt int) ([]txn.Op, error) { 297 if attempt > 0 { 298 if err := op.app.Refresh(); errors.IsNotFound(err) { 299 return nil, jujutxn.ErrNoOperations 300 } else if err != nil { 301 return nil, err 302 } 303 } 304 // When 'force' is set on the operation, this call will return needed operations 305 // and accumulate all operational errors encountered in the operation. 306 // If the 'force' is not set, any error will be fatal and no operations will be returned. 307 switch ops, err := op.destroyOps(); err { 308 case errRefresh: 309 case errAlreadyDying: 310 return nil, jujutxn.ErrNoOperations 311 case nil: 312 return ops, nil 313 default: 314 if op.Force { 315 logger.Warningf("force destroy saas application %v despite error %v", op.app, err) 316 return ops, nil 317 } 318 return nil, err 319 } 320 return nil, jujutxn.ErrNoOperations 321 } 322 323 // Done is part of the ModelOperation interface. 324 func (op *DestroyRemoteApplicationOperation) Done(err error) error { 325 // NOTE(tsm): if you change the business logic here, check 326 // that RemoveOfferOperation is modified to suit 327 if err != nil { 328 if !op.Force { 329 return errors.Annotatef(err, "cannot destroy saas application %q", op.app) 330 } 331 op.AddError(errors.Errorf("force destroy of saas application %v failed but proceeded despite encountering ERROR %v", op.app, err)) 332 } 333 if err := op.eraseHistory(); err != nil { 334 if !op.Force { 335 logger.Errorf("cannot delete history for saas application %q: %v", op.app, err) 336 } 337 op.AddError(errors.Errorf("force erase saas application %q history proceeded despite encountering ERROR %v", op.app, err)) 338 } 339 if err := op.deleteSecretReferences(); err != nil { 340 logger.Errorf("cannot delete secret references for saas application %q: %v", op.app, err) 341 } 342 return nil 343 } 344 345 func (op *DestroyRemoteApplicationOperation) eraseHistory() error { 346 var stop <-chan struct{} // stop not used here yet. 347 if err := eraseStatusHistory(stop, op.app.st, op.app.globalKey()); err != nil { 348 one := errors.Annotate(err, "saas application") 349 if op.FatalError(one) { 350 return one 351 } 352 } 353 return nil 354 } 355 356 func (op *DestroyRemoteApplicationOperation) deleteSecretReferences() error { 357 if err := op.app.st.removeRemoteSecretConsumer(op.app.Name()); err != nil { 358 return errors.Annotatef(err, "deleting secret consumer records for %q", op.app.Name()) 359 } 360 return nil 361 } 362 363 // DestroyWithForce in addition to doing what Destroy() does, 364 // when force is passed in as 'true', forces th destruction of remote application, 365 // ignoring errors. 366 func (a *RemoteApplication) DestroyWithForce(force bool, maxWait time.Duration) (opErrs []error, err error) { 367 defer func() { 368 if err == nil { 369 a.doc.Life = Dying 370 } 371 }() 372 op := a.DestroyOperation(force) 373 op.MaxWait = maxWait 374 err = a.st.ApplyOperation(op) 375 return op.Errors, err 376 } 377 378 // Destroy ensures that this remote application reference and all its relations 379 // will be removed at some point; if no relation involving the 380 // application has any units in scope, they are all removed immediately. 381 func (a *RemoteApplication) Destroy() error { 382 errs, err := a.DestroyWithForce(false, time.Duration(0)) 383 if len(errs) != 0 { 384 logger.Warningf("operational errors destroying saas application %v: %v", a.Name(), errs) 385 } 386 return err 387 } 388 389 // destroyOps returns the operations required to destroy the application. If it 390 // returns errRefresh, the application should be refreshed and the destruction 391 // operations recalculated. 392 // When 'force' is set, this call will return needed operations 393 // and accumulate all operational errors encountered in the operation. 394 // If the 'force' is not set, any error will be fatal and no operations will be returned. 395 func (op *DestroyRemoteApplicationOperation) destroyOps() (ops []txn.Op, err error) { 396 if op.app.doc.Life == Dying { 397 if !op.Force { 398 return nil, errAlreadyDying 399 } 400 } 401 haveRels := true 402 rels, err := op.app.Relations() 403 if op.FatalError(err) { 404 return nil, errors.Trace(err) 405 } 406 if err != nil { 407 haveRels = false 408 } 409 410 // We'll need status below when processing relations. 411 statusInfo, statusErr := op.app.Status() 412 if op.FatalError(statusErr) && !errors.IsNotFound(statusErr) { 413 return nil, statusErr 414 } 415 // If the application is already terminated and dead, the removal 416 // can be short circuited. 417 forceTerminate := op.Force || statusInfo.Status == status.Terminated 418 419 if !forceTerminate && haveRels && len(rels) != op.app.doc.RelationCount { 420 // This is just an early bail out. The relations obtained may still 421 // be wrong, but that situation will be caught by a combination of 422 // asserts on relationcount and on each known relation, below. 423 return nil, errRefresh 424 } 425 426 op.ForcedOperation.Force = forceTerminate 427 removeCount := 0 428 if haveRels { 429 failRels := false 430 for _, rel := range rels { 431 // If the remote app has been terminated, we may have been offline 432 // and not noticed so need to clean up any exiting relation units. 433 destroyRelUnitOps, err := destroyCrossModelRelationUnitsOps(&op.ForcedOperation, op.app, rel, true) 434 if err != nil && err != jujutxn.ErrNoOperations { 435 return nil, errors.Trace(err) 436 } 437 ops = append(ops, destroyRelUnitOps...) 438 // When 'force' is set, this call will return both needed operations 439 // as well as all operational errors encountered. 440 // If the 'force' is not set, any error will be fatal and no operations will be returned. 441 relOps, isRemove, err := rel.destroyOps(op.app.doc.Name, &op.ForcedOperation) 442 if err == errAlreadyDying { 443 relOps = []txn.Op{{ 444 C: relationsC, 445 Id: rel.doc.DocID, 446 Assert: bson.D{{"life", Dying}}, 447 }} 448 } else if err != nil { 449 op.AddError(err) 450 failRels = true 451 continue 452 } 453 if isRemove { 454 removeCount++ 455 } 456 ops = append(ops, relOps...) 457 } 458 if !op.Force && failRels { 459 return nil, errors.Trace(op.LastError()) 460 } 461 } 462 // If all of the application's known relations will be 463 // removed, the application can also be removed. 464 if forceTerminate || op.app.doc.RelationCount == removeCount { 465 var hasLastRefs bson.D 466 if !forceTerminate { 467 hasLastRefs = bson.D{{"life", Alive}, {"relationcount", removeCount}} 468 } 469 removeOps, err := op.app.removeOps(hasLastRefs) 470 if err != nil { 471 return nil, errors.Trace(err) 472 } 473 ops = append(ops, removeOps...) 474 return ops, nil 475 } 476 // In all other cases, application removal will be handled as a consequence 477 // of the removal of the relation referencing it. If any relations have 478 // been removed, they'll be caught by the operations collected above; 479 // but if any has been added, we need to abort and add a destroy op for 480 // that relation too. 481 // In combination, it's enough to check for count equality: 482 // an add/remove will not touch the count, but will be caught by 483 // virtue of being a remove. 484 notLastRefs := bson.D{ 485 {"life", Alive}, 486 {"relationcount", op.app.doc.RelationCount}, 487 } 488 update := bson.D{{"$set", bson.D{{"life", Dying}}}} 489 if removeCount != 0 { 490 decref := bson.D{{"$inc", bson.D{{"relationcount", -removeCount}}}} 491 update = append(update, decref...) 492 } 493 ops = append(ops, txn.Op{ 494 C: remoteApplicationsC, 495 Id: op.app.doc.DocID, 496 Assert: notLastRefs, 497 Update: update, 498 }) 499 return ops, nil 500 } 501 502 // removeOps returns the operations required to remove the application. Supplied 503 // asserts will be included in the operation on the application document. 504 func (a *RemoteApplication) removeOps(asserts bson.D) ([]txn.Op, error) { 505 r := a.st.RemoteEntities() 506 ops := []txn.Op{ 507 { 508 C: remoteApplicationsC, 509 Id: a.doc.DocID, 510 Assert: asserts, 511 Remove: true, 512 }, 513 removeStatusOp(a.st, a.globalKey()), 514 } 515 tokenOps := r.removeRemoteEntityOps(a.Tag()) 516 ops = append(ops, tokenOps...) 517 518 secretConsumerPermissionsOps, err := a.st.removeConsumerSecretPermissionOps(a.Tag()) 519 if err != nil { 520 return nil, errors.Annotatef(err, "deleting secret consumer records for %q", a.Name()) 521 } 522 ops = append(ops, secretConsumerPermissionsOps...) 523 524 // If this is the last consumed app off an external controller, 525 // also remove the external controller record. 526 if a.doc.SourceControllerUUID != "" { 527 decRefOp, isFinal, err := decExternalControllersRefOp(a.st, a.doc.SourceControllerUUID) 528 if err != nil { 529 return nil, errors.Trace(err) 530 } 531 ops = append(ops, decRefOp) 532 if isFinal { 533 ops = append(ops, txn.Op{ 534 C: externalControllersC, 535 Id: a.doc.SourceControllerUUID, 536 Remove: true, 537 }) 538 } 539 } 540 return ops, nil 541 } 542 543 // Status returns the status of the remote application. 544 func (a *RemoteApplication) Status() (status.StatusInfo, error) { 545 return getStatus(a.st.db(), a.globalKey(), fmt.Sprintf("saas application %q", a.doc.Name)) 546 } 547 548 // SetStatus sets the status for the application. 549 func (a *RemoteApplication) SetStatus(info status.StatusInfo) error { 550 // We only care about status for alive apps; we want to 551 // avoid stray updates from the other model. 552 if a.Life() != Alive { 553 return nil 554 } 555 if !info.Status.KnownWorkloadStatus() { 556 return errors.Errorf("cannot set invalid status %q", info.Status) 557 } 558 559 return setStatus(a.st.db(), setStatusParams{ 560 badge: fmt.Sprintf("saas application %q", a.doc.Name), 561 globalKey: a.globalKey(), 562 status: info.Status, 563 message: info.Message, 564 rawData: info.Data, 565 updated: timeOrNow(info.Since, a.st.clock()), 566 }) 567 } 568 569 // TerminateOperation returns a ModelOperation that will terminate this 570 // remote application when applied, ensuring that all units have left 571 // scope as well. 572 func (a *RemoteApplication) TerminateOperation(message string) ModelOperation { 573 return &terminateRemoteApplicationOperation{ 574 app: a, 575 doc: statusDoc{ 576 Status: status.Terminated, 577 StatusInfo: message, 578 Updated: a.st.clock().Now().UnixNano(), 579 }, 580 } 581 } 582 583 type terminateRemoteApplicationOperation struct { 584 app *RemoteApplication 585 doc statusDoc 586 } 587 588 // Build is part of ModelOperation. 589 func (op *terminateRemoteApplicationOperation) Build(attempt int) ([]txn.Op, error) { 590 if attempt > 0 { 591 err := op.app.Refresh() 592 if err != nil && !errors.IsNotFound(err) { 593 return nil, errors.Trace(err) 594 } 595 if err != nil || op.app.Life() == Dead { 596 return nil, jujutxn.ErrNoOperations 597 } 598 } 599 ops, err := statusSetOps(op.app.st.db(), op.doc, op.app.globalKey()) 600 if err != nil { 601 return nil, errors.Annotate(err, "setting status") 602 } 603 // Strictly speaking, we should transition through Dying state. 604 ops = append(ops, txn.Op{ 605 C: remoteApplicationsC, 606 Id: op.app.doc.DocID, 607 Assert: notDeadDoc, 608 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 609 }) 610 name := op.app.Name() 611 logger.Debugf("leaving scope on all %q relation units", name) 612 rels, err := op.app.Relations() 613 if err != nil { 614 return nil, errors.Annotatef(err, "getting relations for %q", name) 615 } 616 // Termination happens when the offer has disappeared so we can force destroy any 617 // relations on the consuming side. 618 // Destroying each relation also forces remote units to leave scope. 619 for _, rel := range rels { 620 relOps, err := destroyCrossModelRelationUnitsOps(&ForcedOperation{Force: true}, op.app, rel, false) 621 if err != nil && err != jujutxn.ErrNoOperations { 622 return nil, errors.Annotatef(err, "removing relation %q", rel) 623 } 624 ops = append(ops, relOps...) 625 } 626 return ops, nil 627 } 628 629 // Done is part of ModelOperation. 630 func (op *terminateRemoteApplicationOperation) Done(err error) error { 631 if err != nil { 632 return errors.Annotatef(err, "terminating saas application %q", op.app.Name()) 633 } 634 _, _ = probablyUpdateStatusHistory(op.app.st.db(), op.app.globalKey(), op.doc) 635 // Set the life to Dead so that the lifecycle watcher will trigger to inform the 636 // relevant workers that this application is gone. 637 ops := []txn.Op{{ 638 C: remoteApplicationsC, 639 Id: op.app.doc.DocID, 640 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 641 }} 642 return op.app.st.db().RunTransaction(ops) 643 } 644 645 // Endpoints returns the application's currently available relation endpoints. 646 func (a *RemoteApplication) Endpoints() ([]Endpoint, error) { 647 return remoteEndpointDocsToEndpoints(a.Name(), a.doc.Endpoints), nil 648 } 649 650 func remoteEndpointDocsToEndpoints(applicationName string, docs []remoteEndpointDoc) []Endpoint { 651 eps := make([]Endpoint, len(docs)) 652 for i, ep := range docs { 653 eps[i] = Endpoint{ 654 ApplicationName: applicationName, 655 Relation: charm.Relation{ 656 Name: ep.Name, 657 Role: ep.Role, 658 Interface: ep.Interface, 659 Limit: ep.Limit, 660 Scope: ep.Scope, 661 }} 662 } 663 sort.Sort(epSlice(eps)) 664 return eps 665 } 666 667 // Endpoint returns the relation endpoint with the supplied name, if it exists. 668 func (a *RemoteApplication) Endpoint(relationName string) (Endpoint, error) { 669 eps, err := a.Endpoints() 670 if err != nil { 671 return Endpoint{}, err 672 } 673 for _, ep := range eps { 674 if ep.Name == relationName { 675 return ep, nil 676 } 677 } 678 return Endpoint{}, fmt.Errorf("saas application %q has no %q relation", a, relationName) 679 } 680 681 // AddEndpoints adds the specified endpoints to the remote application. 682 // If an endpoint with the same name already exists, an error is returned. 683 // If the endpoints change during the update, the operation is retried. 684 func (a *RemoteApplication) AddEndpoints(eps []charm.Relation) error { 685 newEps := make([]remoteEndpointDoc, len(eps)) 686 for i, ep := range eps { 687 newEps[i] = remoteEndpointDoc{ 688 Name: ep.Name, 689 Role: ep.Role, 690 Interface: ep.Interface, 691 Limit: ep.Limit, 692 Scope: ep.Scope, 693 } 694 } 695 696 model, err := a.st.Model() 697 if err != nil { 698 return errors.Trace(err) 699 } else if model.Life() != Alive { 700 return errors.Errorf("model is no longer alive") 701 } 702 703 checkCompatibleEndpoints := func(currentEndpoints []Endpoint) error { 704 // Ensure there are no current endpoints with the same name as 705 // any of those we want to update. 706 currentEndpointNames := set.NewStrings() 707 for _, ep := range currentEndpoints { 708 currentEndpointNames.Add(ep.Name) 709 } 710 for _, r := range eps { 711 if currentEndpointNames.Contains(r.Name) { 712 return errors.AlreadyExistsf("endpoint %v", r.Name) 713 } 714 } 715 return nil 716 } 717 718 currentEndpoints, err := a.Endpoints() 719 if err != nil { 720 return errors.Trace(err) 721 } 722 if err := checkCompatibleEndpoints(currentEndpoints); err != nil { 723 return err 724 } 725 applicationID := a.st.docID(a.Name()) 726 buildTxn := func(attempt int) ([]txn.Op, error) { 727 // If we've tried once already and failed, check that 728 // model may have been destroyed. 729 if attempt > 0 { 730 if err := checkModelActive(a.st); err != nil { 731 return nil, errors.Trace(err) 732 } 733 if err = a.Refresh(); err != nil { 734 return nil, errors.Trace(err) 735 } 736 currentEndpoints, err = a.Endpoints() 737 if err != nil { 738 return nil, errors.Trace(err) 739 } 740 if err := checkCompatibleEndpoints(currentEndpoints); err != nil { 741 return nil, err 742 } 743 } 744 ops := []txn.Op{ 745 model.assertActiveOp(), 746 { 747 C: remoteApplicationsC, 748 Id: applicationID, 749 Assert: bson.D{ 750 {"endpoints", bson.D{{ 751 "$not", bson.D{{ 752 "$elemMatch", bson.D{{ 753 "$in", newEps}}, 754 }}, 755 }}}, 756 }, 757 Update: bson.D{ 758 {"$addToSet", bson.D{{"endpoints", bson.D{{"$each", newEps}}}}}, 759 }, 760 }, 761 } 762 return ops, nil 763 } 764 if err := a.st.db().Run(buildTxn); err != nil { 765 return errors.Trace(err) 766 } 767 return a.Refresh() 768 } 769 770 // SetSourceController updates the source controller attribute. 771 func (a *RemoteApplication) SetSourceController(sourceControllerUUID string) error { 772 model, err := a.st.Model() 773 if err != nil { 774 return errors.Trace(err) 775 } else if model.Life() != Alive { 776 return errors.Errorf("model is no longer alive") 777 } 778 779 applicationID := a.st.docID(a.Name()) 780 buildTxn := func(attempt int) ([]txn.Op, error) { 781 // If we've tried once already and failed, check that 782 // model may have been destroyed. 783 if attempt > 0 { 784 if model.Life() == Dead { 785 return nil, errors.Errorf("model %q is %s", model.Name(), model.Life().String()) 786 } 787 if err = a.Refresh(); err != nil { 788 return nil, errors.Trace(err) 789 } 790 } 791 ops := []txn.Op{ 792 { 793 C: modelsC, 794 Id: model.UUID(), 795 Assert: notDeadDoc, 796 }, { 797 C: remoteApplicationsC, 798 Id: applicationID, 799 Assert: txn.DocExists, 800 Update: bson.D{ 801 {"$set", bson.D{{"source-controller-uuid", sourceControllerUUID}}}, 802 }, 803 }, 804 } 805 return ops, nil 806 } 807 if err := a.st.db().Run(buildTxn); err != nil { 808 return errors.Trace(err) 809 } 810 return a.Refresh() 811 } 812 813 func (a *RemoteApplication) Macaroon() (*macaroon.Macaroon, error) { 814 if a.doc.Macaroon == "" { 815 return nil, nil 816 } 817 var mac macaroon.Macaroon 818 err := json.Unmarshal([]byte(a.doc.Macaroon), &mac) 819 if err != nil { 820 return nil, errors.Trace(err) 821 } 822 return &mac, nil 823 } 824 825 // String returns the application name. 826 func (a *RemoteApplication) String() string { 827 return a.doc.Name 828 } 829 830 // Refresh refreshes the contents of the RemoteApplication from the underlying 831 // state. It returns an error that satisfies errors.IsNotFound if the 832 // application has been removed. 833 func (a *RemoteApplication) Refresh() error { 834 applications, closer := a.st.db().GetCollection(remoteApplicationsC) 835 defer closer() 836 837 err := applications.FindId(a.doc.DocID).One(&a.doc) 838 if err == mgo.ErrNotFound { 839 return errors.NotFoundf("saas application %q", a) 840 } 841 if err != nil { 842 return fmt.Errorf("cannot refresh application %q: %v", a, err) 843 } 844 return nil 845 } 846 847 // Relations returns a Relation for every relation the application is in. 848 func (a *RemoteApplication) Relations() (relations []*Relation, err error) { 849 return matchingRelations(a.st, a.doc.Name) 850 } 851 852 // AddRemoteApplicationParams contains the parameters for adding a remote application 853 // to the model. 854 type AddRemoteApplicationParams struct { 855 // Name is the name to give the remote application. This does not have to 856 // match the application name in the URL, or the name in the remote model. 857 Name string 858 859 // OfferUUID is the UUID of the offer. 860 OfferUUID string 861 862 // URL is either empty, or the URL that the remote application was offered 863 // with on the hosting model. 864 URL string 865 866 // ExternalControllerUUID, if set, is the UUID of the controller other 867 // than this one, which is hosting the offer. 868 ExternalControllerUUID string 869 870 // SourceModel is the tag of the model to which the remote application belongs. 871 SourceModel names.ModelTag 872 873 // Token is an opaque string that identifies the remote application in the 874 // source model. 875 Token string 876 877 // Endpoints describes the endpoints that the remote application implements. 878 Endpoints []charm.Relation 879 880 // Spaces describes the network spaces that the remote 881 // application's endpoints inhabit in the remote model. 882 Spaces []*environs.ProviderSpaceInfo 883 884 // Bindings maps each endpoint name to the remote space it is bound to. 885 Bindings map[string]string 886 887 // IsConsumerProxy is true when a remote application is created as a result 888 // of a registration operation from a remote model. 889 IsConsumerProxy bool 890 891 // ConsumeVersion is incremented each time a new consumer proxy 892 // is created for an offer. 893 ConsumeVersion int 894 895 // Macaroon is used for authentication on the offering side. 896 Macaroon *macaroon.Macaroon 897 } 898 899 // Validate returns an error if there's a problem with the 900 // parameters being used to create a remote application. 901 func (p AddRemoteApplicationParams) Validate() error { 902 if !names.IsValidApplication(p.Name) { 903 return errors.NotValidf("name %q", p.Name) 904 } 905 if p.URL != "" { 906 // URL may be empty, to represent remote applications corresponding 907 // to consumers of an offered application. 908 if _, err := crossmodel.ParseOfferURL(p.URL); err != nil { 909 return errors.Annotate(err, "validating offer URL") 910 } 911 } 912 if p.SourceModel == (names.ModelTag{}) { 913 return errors.NotValidf("empty source model tag") 914 } 915 spaceNames := set.NewStrings() 916 for _, space := range p.Spaces { 917 spaceNames.Add(string(space.Name)) 918 } 919 for endpoint, space := range p.Bindings { 920 if !spaceNames.Contains(space) { 921 return errors.NotValidf("endpoint %q bound to missing space %q", endpoint, space) 922 } 923 } 924 return nil 925 } 926 927 // AddRemoteApplication creates a new remote application record, 928 // having the supplied relation endpoints, with the supplied name, 929 // which must be unique across all applications, local and remote. 930 func (st *State) AddRemoteApplication(args AddRemoteApplicationParams) (_ *RemoteApplication, err error) { 931 defer errors.DeferredAnnotatef(&err, "cannot add saas application %q", args.Name) 932 933 // Sanity checks. 934 if err := args.Validate(); err != nil { 935 return nil, errors.Trace(err) 936 } 937 model, err := st.Model() 938 if err != nil { 939 return nil, errors.Trace(err) 940 } else if model.Life() != Alive { 941 return nil, errors.Errorf("model is no longer alive") 942 } 943 944 var macJSON string 945 if args.Macaroon != nil { 946 b, err := json.Marshal(args.Macaroon) 947 if err != nil { 948 return nil, errors.Trace(err) 949 } 950 macJSON = string(b) 951 } 952 applicationID := st.docID(args.Name) 953 // Create the application addition operations. 954 appDoc := &remoteApplicationDoc{ 955 DocID: applicationID, 956 Name: args.Name, 957 SourceControllerUUID: args.ExternalControllerUUID, 958 SourceModelUUID: args.SourceModel.Id(), 959 URL: args.URL, 960 Bindings: args.Bindings, 961 Life: Alive, 962 IsConsumerProxy: args.IsConsumerProxy, 963 Version: args.ConsumeVersion, 964 Macaroon: macJSON, 965 } 966 if !args.IsConsumerProxy { 967 if appDoc.Version, err = sequenceWithMin(st, args.OfferUUID, 1); err != nil { 968 return nil, errors.Trace(err) 969 } 970 appDoc.OfferUUID = args.OfferUUID 971 } 972 eps := make([]remoteEndpointDoc, len(args.Endpoints)) 973 for i, ep := range args.Endpoints { 974 eps[i] = remoteEndpointDoc{ 975 Name: ep.Name, 976 Role: ep.Role, 977 Interface: ep.Interface, 978 Limit: ep.Limit, 979 Scope: ep.Scope, 980 } 981 } 982 appDoc.Endpoints = eps 983 spaces := make([]remoteSpaceDoc, len(args.Spaces)) 984 for i, space := range args.Spaces { 985 spaces[i] = remoteSpaceDoc{ 986 CloudType: space.CloudType, 987 Name: string(space.Name), 988 ProviderId: string(space.ProviderId), 989 ProviderAttributes: space.ProviderAttributes, 990 } 991 subnets := make([]remoteSubnetDoc, len(space.Subnets)) 992 for i, subnet := range space.Subnets { 993 subnets[i] = remoteSubnetDoc{ 994 CIDR: subnet.CIDR, 995 ProviderId: string(subnet.ProviderId), 996 VLANTag: subnet.VLANTag, 997 AvailabilityZones: copyStrings(subnet.AvailabilityZones), 998 ProviderSpaceId: string(subnet.ProviderSpaceId), 999 ProviderNetworkId: string(subnet.ProviderNetworkId), 1000 } 1001 } 1002 spaces[i].Subnets = subnets 1003 } 1004 appDoc.Spaces = spaces 1005 app := newRemoteApplication(st, appDoc) 1006 1007 buildTxn := func(attempt int) ([]txn.Op, error) { 1008 // If we've tried once already and failed, check that 1009 // model may have been destroyed. 1010 if attempt > 0 { 1011 if err := checkModelActive(st); err != nil { 1012 return nil, errors.Trace(err) 1013 } 1014 // Ensure a local application with the same name doesn't exist. 1015 if localExists, err := isNotDead(st, applicationsC, args.Name); err != nil { 1016 return nil, errors.Trace(err) 1017 } else if localExists { 1018 return nil, errors.AlreadyExistsf("local application with same name") 1019 } 1020 // Ensure a remote application with the same name doesn't exist. 1021 if exists, err := isNotDead(st, remoteApplicationsC, args.Name); err != nil { 1022 return nil, errors.Trace(err) 1023 } else if exists { 1024 return nil, errors.AlreadyExistsf("saas application") 1025 } 1026 } 1027 ops := []txn.Op{ 1028 model.assertActiveOp(), 1029 { 1030 C: remoteApplicationsC, 1031 Id: appDoc.Name, 1032 Assert: txn.DocMissing, 1033 Insert: appDoc, 1034 }, { 1035 C: applicationsC, 1036 Id: appDoc.Name, 1037 Assert: txn.DocMissing, 1038 }, 1039 } 1040 if !args.IsConsumerProxy { 1041 statusDoc := statusDoc{ 1042 ModelUUID: st.ModelUUID(), 1043 Status: status.Unknown, 1044 Updated: st.clock().Now().UnixNano(), 1045 } 1046 ops = append(ops, createStatusOp(st, app.globalKey(), statusDoc)) 1047 } 1048 // If we know the token, import it. 1049 if args.Token != "" { 1050 importRemoteEntityOps := st.RemoteEntities().importRemoteEntityOps(app.Tag(), args.Token) 1051 ops = append(ops, importRemoteEntityOps...) 1052 } 1053 1054 if args.ExternalControllerUUID != "" { 1055 incRefOp, err := incExternalControllersRefOp(st, args.ExternalControllerUUID) 1056 if err != nil { 1057 return nil, errors.Trace(err) 1058 } 1059 ops = append(ops, incRefOp) 1060 } 1061 return ops, nil 1062 } 1063 if err = st.db().Run(buildTxn); err != nil { 1064 return nil, errors.Trace(err) 1065 } 1066 return app, nil 1067 } 1068 1069 // RemoteApplication returns a remote application state by name. 1070 func (st *State) RemoteApplication(name string) (_ *RemoteApplication, err error) { 1071 if !names.IsValidApplication(name) { 1072 return nil, errors.NotValidf("saas application name %q", name) 1073 } 1074 1075 applications, closer := st.db().GetCollection(remoteApplicationsC) 1076 defer closer() 1077 1078 appDoc := &remoteApplicationDoc{} 1079 err = applications.FindId(name).One(appDoc) 1080 if err == mgo.ErrNotFound { 1081 return nil, errors.NotFoundf("saas application %q", name) 1082 } 1083 if err != nil { 1084 return nil, errors.Annotatef(err, "cannot get saas application %q", name) 1085 } 1086 return newRemoteApplication(st, appDoc), nil 1087 } 1088 1089 // AllRemoteApplications returns all the remote applications used by the model. 1090 func (st *State) AllRemoteApplications() (applications []*RemoteApplication, err error) { 1091 applicationsCollection, closer := st.db().GetCollection(remoteApplicationsC) 1092 defer closer() 1093 1094 appDocs := []remoteApplicationDoc{} 1095 err = applicationsCollection.Find(bson.D{}).All(&appDocs) 1096 if err != nil { 1097 return nil, errors.Errorf("cannot get all saas applications") 1098 } 1099 for _, v := range appDocs { 1100 applications = append(applications, newRemoteApplication(st, &v)) 1101 } 1102 return applications, nil 1103 }