github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/crossmodel/crossmodel.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package crossmodel 5 6 import ( 7 "fmt" 8 "net" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/names/v5" 13 14 "github.com/juju/juju/apiserver/authentication" 15 "github.com/juju/juju/apiserver/common" 16 apiservererrors "github.com/juju/juju/apiserver/errors" 17 "github.com/juju/juju/core/crossmodel" 18 "github.com/juju/juju/core/life" 19 corelogger "github.com/juju/juju/core/logger" 20 "github.com/juju/juju/core/migration" 21 "github.com/juju/juju/core/permission" 22 "github.com/juju/juju/core/status" 23 "github.com/juju/juju/network" 24 "github.com/juju/juju/rpc/params" 25 ) 26 27 var ( 28 logger = loggo.GetLoggerWithLabels("juju.apiserver.common.crossmodel", corelogger.CMR) 29 authlogger = loggo.GetLoggerWithLabels("juju.apiserver.common.crossmodelauth", corelogger.CMR_AUTH) 30 ) 31 32 // PublishRelationChange applies the relation change event to the specified backend. 33 func PublishRelationChange(auth authoriser, backend Backend, relationTag, applicationTag names.Tag, change params.RemoteRelationChangeEvent) error { 34 logger.Debugf("publish into model %v change for %v on %v: %#v", backend.ModelUUID(), relationTag, applicationTag, &change) 35 36 dyingOrDead := change.Life != "" && change.Life != life.Alive 37 // Ensure the relation exists. 38 rel, err := backend.KeyRelation(relationTag.Id()) 39 if errors.IsNotFound(err) { 40 if dyingOrDead { 41 return nil 42 } 43 } 44 if err != nil { 45 return errors.Trace(err) 46 } 47 48 if err := handleSuspendedRelation(auth, backend, change, rel, dyingOrDead); err != nil { 49 return errors.Trace(err) 50 } 51 52 // If the remote model has destroyed the relation, do it here also. 53 forceCleanUp := change.ForceCleanup != nil && *change.ForceCleanup 54 if dyingOrDead { 55 logger.Debugf("remote consuming side of %v died", relationTag) 56 if forceCleanUp && applicationTag != nil { 57 logger.Debugf("forcing cleanup of units for %v", applicationTag.Id()) 58 remoteUnits, err := rel.AllRemoteUnits(applicationTag.Id()) 59 if err != nil { 60 return errors.Trace(err) 61 } 62 logger.Debugf("got %v relation units to clean", len(remoteUnits)) 63 for _, ru := range remoteUnits { 64 if err := ru.LeaveScope(); err != nil { 65 return errors.Trace(err) 66 } 67 } 68 } 69 70 if forceCleanUp { 71 oppErrs, err := rel.DestroyWithForce(true, 0) 72 if len(oppErrs) > 0 { 73 logger.Warningf("errors forcing cleanup of %v: %v", rel.Tag().Id(), oppErrs) 74 } 75 // If we are forcing cleanup, we can exit early here. 76 return errors.Trace(err) 77 } 78 if err := rel.Destroy(); err != nil { 79 return errors.Trace(err) 80 } 81 } 82 83 // TODO(wallyworld) - deal with remote application being removed 84 if applicationTag == nil { 85 logger.Infof("no remote application found for %v", relationTag.Id()) 86 return nil 87 } 88 logger.Debugf("remote application for changed relation %v is %v in model %v", 89 relationTag.Id(), applicationTag.Id(), backend.ModelUUID()) 90 91 // Allow sending an empty non-nil map to clear all the settings. 92 if change.ApplicationSettings != nil { 93 logger.Debugf("remote application %v in %v settings changed to %v", 94 applicationTag.Id(), relationTag.Id(), change.ApplicationSettings) 95 err := rel.ReplaceApplicationSettings(applicationTag.Id(), change.ApplicationSettings) 96 if err != nil { 97 return errors.Trace(err) 98 } 99 } 100 101 if err := handleDepartedUnits(backend, change, applicationTag, rel); err != nil { 102 return errors.Trace(err) 103 } 104 105 return errors.Trace(handleChangedUnits(change, applicationTag, rel)) 106 } 107 108 type authoriser interface { 109 EntityHasPermission(entity names.Tag, operation permission.Access, target names.Tag) error 110 } 111 112 type offerBackend interface { 113 ApplicationOfferForUUID(offerUUID string) (*crossmodel.ApplicationOffer, error) 114 } 115 116 // CheckCanConsume checks consume permission for a user on an offer connection. 117 func CheckCanConsume(auth authoriser, backend offerBackend, controllerTag, modelTag names.Tag, oc OfferConnection) (bool, error) { 118 user := names.NewUserTag(oc.UserName()) 119 err := auth.EntityHasPermission(user, permission.SuperuserAccess, controllerTag) 120 if err != nil && !errors.Is(err, authentication.ErrorEntityMissingPermission) { 121 return false, errors.Trace(err) 122 } else if err == nil { 123 return true, nil 124 } 125 126 err = auth.EntityHasPermission(user, permission.AdminAccess, modelTag) 127 if err != nil && !errors.Is(err, authentication.ErrorEntityMissingPermission) { 128 return false, errors.Trace(err) 129 } else if err == nil { 130 return true, nil 131 } 132 err = auth.EntityHasPermission(user, permission.ConsumeAccess, names.NewApplicationOfferTag(oc.OfferUUID())) 133 return err == nil, err 134 } 135 136 func handleSuspendedRelation(auth authoriser, backend Backend, change params.RemoteRelationChangeEvent, rel Relation, dyingOrDead bool) error { 137 // Update the relation suspended status. 138 currentStatus := rel.Suspended() 139 if !dyingOrDead && change.Suspended != nil && currentStatus != *change.Suspended { 140 var ( 141 newStatus status.Status 142 message string 143 ) 144 if *change.Suspended { 145 newStatus = status.Suspending 146 message = change.SuspendedReason 147 if message == "" { 148 message = "suspending after update from remote model" 149 } 150 } else { 151 oc, err := backend.OfferConnectionForRelation(rel.Tag().Id()) 152 if err != nil && !errors.Is(err, errors.NotFound) { 153 return errors.Trace(err) 154 } 155 if err == nil { 156 ok, err := CheckCanConsume(auth, backend, backend.ControllerTag(), backend.ModelTag(), oc) 157 if err != nil { 158 return errors.Trace(err) 159 } 160 if !ok { 161 return apiservererrors.ErrPerm 162 } 163 } 164 } 165 if err := rel.SetSuspended(*change.Suspended, message); err != nil { 166 return errors.Trace(err) 167 } 168 if !*change.Suspended { 169 newStatus = status.Joining 170 message = "" 171 } 172 if err := rel.SetStatus(status.StatusInfo{ 173 Status: newStatus, 174 Message: message, 175 }); err != nil && !errors.IsNotValid(err) { 176 return errors.Trace(err) 177 } 178 } 179 return nil 180 } 181 182 func handleDepartedUnits(backend Backend, change params.RemoteRelationChangeEvent, applicationTag names.Tag, rel Relation) error { 183 for _, id := range change.DepartedUnits { 184 unitTag := names.NewUnitTag(fmt.Sprintf("%s/%v", applicationTag.Id(), id)) 185 logger.Debugf("unit %v has departed relation %v", unitTag.Id(), rel.Tag().Id()) 186 ru, err := rel.RemoteUnit(unitTag.Id()) 187 if err != nil { 188 return errors.Trace(err) 189 } 190 logger.Debugf("%s leaving scope", unitTag.Id()) 191 if err := ru.LeaveScope(); err != nil { 192 return errors.Trace(err) 193 } 194 if err := backend.RemoveSecretConsumer(unitTag); err != nil { 195 return errors.Trace(err) 196 } 197 } 198 return nil 199 } 200 201 func handleChangedUnits(change params.RemoteRelationChangeEvent, applicationTag names.Tag, rel Relation) error { 202 for _, change := range change.ChangedUnits { 203 unitTag := names.NewUnitTag(fmt.Sprintf("%s/%v", applicationTag.Id(), change.UnitId)) 204 logger.Debugf("changed unit tag for unit id %v is %v", change.UnitId, unitTag) 205 ru, err := rel.RemoteUnit(unitTag.Id()) 206 if err != nil { 207 return errors.Trace(err) 208 } 209 inScope, err := ru.InScope() 210 if err != nil { 211 return errors.Trace(err) 212 } 213 settings := make(map[string]interface{}) 214 for k, v := range change.Settings { 215 settings[k] = v 216 } 217 if !inScope { 218 logger.Debugf("%s entering scope (%v)", unitTag.Id(), settings) 219 err = ru.EnterScope(settings) 220 } else { 221 logger.Debugf("%s updated settings (%v)", unitTag.Id(), settings) 222 err = ru.ReplaceSettings(settings) 223 } 224 if err != nil { 225 return errors.Trace(err) 226 } 227 } 228 return nil 229 } 230 231 // GetOfferingRelationTokens returns the tokens for the relation and the offer 232 // of the passed in relation tag. 233 func GetOfferingRelationTokens(backend Backend, tag names.RelationTag) (string, string, error) { 234 offerUUID, err := backend.OfferUUIDForRelation(tag.Id()) 235 if err != nil { 236 return "", "", errors.Annotatef(err, "getting offer for relation %q", tag.Id()) 237 } 238 relationToken, err := backend.GetToken(tag) 239 if err != nil { 240 return "", "", errors.Annotatef(err, "getting token for relation %q", tag.Id()) 241 } 242 appToken, err := backend.GetToken(names.NewApplicationOfferTag(offerUUID)) 243 if err != nil { 244 return "", "", errors.Annotatef(err, "getting token for application offer %q", offerUUID) 245 } 246 return relationToken, appToken, nil 247 } 248 249 // GetConsumingRelationTokens returns the tokens for the relation and the local 250 // application of the passed in relation tag. 251 func GetConsumingRelationTokens(backend Backend, tag names.RelationTag) (string, string, error) { 252 relation, err := backend.KeyRelation(tag.Id()) 253 if err != nil { 254 return "", "", errors.Annotatef(err, "getting relation for %q", tag.Id()) 255 } 256 localAppName, err := getLocalApplicationName(backend, relation) 257 if err != nil { 258 return "", "", errors.Annotatef(err, "getting local application for relation %q", tag.Id()) 259 } 260 relationToken, err := backend.GetToken(tag) 261 if err != nil { 262 return "", "", errors.Annotatef(err, "getting consuming token for relation %q", tag.Id()) 263 } 264 appToken, err := backend.GetToken(names.NewApplicationTag(localAppName)) 265 if err != nil { 266 return "", "", errors.Annotatef(err, "getting consuming token for application %q", localAppName) 267 } 268 return relationToken, appToken, nil 269 } 270 271 func getLocalApplicationName(backend Backend, relation Relation) (string, error) { 272 for _, ep := range relation.Endpoints() { 273 _, err := backend.Application(ep.ApplicationName) 274 if errors.IsNotFound(err) { 275 // Not found, so it's the remote application. Try the next endpoint. 276 continue 277 } else if err != nil { 278 return "", errors.Trace(err) 279 } 280 return ep.ApplicationName, nil 281 } 282 return "", errors.NotFoundf("local application for %s", names.ReadableString(relation.Tag())) 283 } 284 285 // WatchRelationUnits returns a watcher for changes to the units on the specified relation. 286 func WatchRelationUnits(backend Backend, tag names.RelationTag) (common.RelationUnitsWatcher, error) { 287 relation, err := backend.KeyRelation(tag.Id()) 288 if err != nil { 289 return nil, errors.Annotatef(err, "getting relation for %q", tag.Id()) 290 } 291 localAppName, err := getLocalApplicationName(backend, relation) 292 if err != nil { 293 return nil, errors.Annotatef(err, "getting local application for relation %q", tag.Id()) 294 } 295 w, err := relation.WatchUnits(localAppName) 296 if err != nil { 297 return nil, errors.Annotatef(err, "watching units for %q", localAppName) 298 } 299 wrapped, err := common.RelationUnitsWatcherFromState(w) 300 if err != nil { 301 return nil, errors.Annotatef(err, "getting relation units watcher for %q", tag.Id()) 302 } 303 return wrapped, nil 304 } 305 306 // ExpandChange converts a params.RelationUnitsChange into a 307 // params.RemoteRelationChangeEvent by filling out the extra 308 // information from the passed backend. This takes relation and 309 // application token so that it can still return sensible results if 310 // the relation has been removed (just departing units). 311 func ExpandChange( 312 backend Backend, 313 relationToken string, 314 appToken string, 315 change params.RelationUnitsChange, 316 ) (params.RemoteRelationChangeEvent, error) { 317 var empty params.RemoteRelationChangeEvent 318 319 var departed []int 320 for _, unitName := range change.Departed { 321 num, err := names.UnitNumber(unitName) 322 if err != nil { 323 return empty, errors.Trace(err) 324 } 325 departed = append(departed, num) 326 } 327 328 relationTag, err := backend.GetRemoteEntity(relationToken) 329 if errors.IsNotFound(err) { 330 // This can happen when the last unit leaves scope on a dying 331 // relation and the relation is removed. In that case there 332 // aren't any application- or unit-level settings to send; we 333 // just send the departed units so they can leave scope on 334 // the other side of a cross-model relation. 335 return params.RemoteRelationChangeEvent{ 336 RelationToken: relationToken, 337 ApplicationToken: appToken, 338 DepartedUnits: departed, 339 }, nil 340 341 } else if err != nil { 342 return empty, errors.Trace(err) 343 } 344 345 relation, err := backend.KeyRelation(relationTag.Id()) 346 if err != nil { 347 return empty, errors.Trace(err) 348 } 349 localAppName, err := getLocalApplicationName(backend, relation) 350 if err != nil { 351 return empty, errors.Trace(err) 352 } 353 354 var appSettings map[string]interface{} 355 if len(change.AppChanged) > 0 { 356 appSettings, err = relation.ApplicationSettings(localAppName) 357 if err != nil { 358 return empty, errors.Trace(err) 359 } 360 } 361 362 var unitChanges []params.RemoteRelationUnitChange 363 for unitName := range change.Changed { 364 relUnit, err := relation.Unit(unitName) 365 if err != nil { 366 return empty, errors.Annotatef(err, "getting unit %q in %q", unitName, relationTag.Id()) 367 } 368 unitSettings, err := relUnit.Settings() 369 if err != nil { 370 return empty, errors.Annotatef(err, "getting settings for %q in %q", unitName, relationTag.Id()) 371 } 372 num, err := names.UnitNumber(unitName) 373 if err != nil { 374 return empty, errors.Trace(err) 375 } 376 unitChanges = append(unitChanges, params.RemoteRelationUnitChange{ 377 UnitId: num, 378 Settings: unitSettings, 379 }) 380 } 381 382 uc := relation.UnitCount() 383 result := params.RemoteRelationChangeEvent{ 384 RelationToken: relationToken, 385 ApplicationToken: appToken, 386 ApplicationSettings: appSettings, 387 ChangedUnits: unitChanges, 388 DepartedUnits: departed, 389 UnitCount: &uc, 390 } 391 392 return result, nil 393 } 394 395 // WrappedUnitsWatcher is a relation units watcher that remembers 396 // details about the relation it came from so changes can be expanded 397 // for sending outside this model. 398 type WrappedUnitsWatcher struct { 399 common.RelationUnitsWatcher 400 RelationToken string 401 ApplicationToken string 402 } 403 404 // RelationUnitSettings returns the unit settings for the specified relation unit. 405 func RelationUnitSettings(backend Backend, ru params.RelationUnit) (params.Settings, error) { 406 relationTag, err := names.ParseRelationTag(ru.Relation) 407 if err != nil { 408 return nil, errors.Trace(err) 409 } 410 rel, err := backend.KeyRelation(relationTag.Id()) 411 if err != nil { 412 return nil, errors.Trace(err) 413 } 414 unitTag, err := names.ParseUnitTag(ru.Unit) 415 if err != nil { 416 return nil, errors.Trace(err) 417 } 418 unit, err := rel.Unit(unitTag.Id()) 419 if err != nil { 420 return nil, errors.Trace(err) 421 } 422 settings, err := unit.Settings() 423 if err != nil { 424 return nil, errors.Trace(err) 425 } 426 paramsSettings := make(params.Settings) 427 for k, v := range settings { 428 vString, ok := v.(string) 429 if !ok { 430 return nil, errors.Errorf( 431 "invalid relation setting %q: expected string, got %T", k, v, 432 ) 433 } 434 paramsSettings[k] = vString 435 } 436 return paramsSettings, nil 437 } 438 439 // PublishIngressNetworkChange saves the specified ingress networks for a relation. 440 func PublishIngressNetworkChange(backend Backend, relationTag names.Tag, change params.IngressNetworksChangeEvent) error { 441 logger.Debugf("publish into model %v network change for %v: %#v", backend.ModelUUID(), relationTag, &change) 442 443 // Ensure the relation exists. 444 rel, err := backend.KeyRelation(relationTag.Id()) 445 if errors.IsNotFound(err) { 446 return nil 447 } 448 if err != nil { 449 return errors.Trace(err) 450 } 451 452 logger.Debugf("relation %v requires ingress networks %v", rel, change.Networks) 453 if err := validateIngressNetworks(backend, change.Networks); err != nil { 454 return errors.Trace(err) 455 } 456 457 _, err = backend.SaveIngressNetworks(rel.Tag().Id(), change.Networks) 458 return err 459 } 460 461 func validateIngressNetworks(backend Backend, networks []string) error { 462 if len(networks) == 0 { 463 return nil 464 } 465 466 // Check that the required ingress is allowed. 467 cfg, err := backend.ModelConfig() 468 if err != nil { 469 return errors.Trace(err) 470 } 471 472 var whitelistCIDRs, requestedCIDRs []*net.IPNet 473 if err := parseCIDRs(&whitelistCIDRs, cfg.SAASIngressAllow()); err != nil { 474 return errors.Trace(err) 475 } 476 if err := parseCIDRs(&requestedCIDRs, networks); err != nil { 477 return errors.Trace(err) 478 } 479 if len(whitelistCIDRs) > 0 { 480 for _, n := range requestedCIDRs { 481 if !network.SubnetInAnyRange(whitelistCIDRs, n) { 482 return ¶ms.Error{ 483 Code: params.CodeForbidden, 484 Message: fmt.Sprintf("subnet %v not in firewall whitelist", n), 485 } 486 } 487 } 488 } 489 return nil 490 } 491 492 func parseCIDRs(cidrs *[]*net.IPNet, values []string) error { 493 for _, cidrStr := range values { 494 if _, ipNet, err := net.ParseCIDR(cidrStr); err != nil { 495 return err 496 } else { 497 *cidrs = append(*cidrs, ipNet) 498 } 499 } 500 return nil 501 } 502 503 type relationGetter interface { 504 // KeyRelation returns the relation identified by the input key. 505 KeyRelation(string) (Relation, error) 506 // IsMigrationActive returns true if the current model is 507 // in the process of being migrated to another controller. 508 IsMigrationActive() (bool, error) 509 } 510 511 // GetRelationLifeSuspendedStatusChange returns a life/suspended status change 512 // struct for a specified relation key. 513 func GetRelationLifeSuspendedStatusChange( 514 st relationGetter, key string, 515 ) (*params.RelationLifeSuspendedStatusChange, error) { 516 rel, err := st.KeyRelation(key) 517 if errors.IsNotFound(err) { 518 // If the relation is not found we represent it as dead, 519 // but *only* if we are not currently migrating. 520 // If we are migrating, we do not want to inform remote watchers that 521 // the relation is dead before they have had a chance to be redirected 522 // to the new controller. 523 if migrating, mErr := st.IsMigrationActive(); mErr == nil && !migrating { 524 return ¶ms.RelationLifeSuspendedStatusChange{ 525 Key: key, 526 Life: life.Dead, 527 }, nil 528 } else if mErr != nil { 529 err = mErr 530 } 531 } 532 if err != nil { 533 return nil, errors.Trace(err) 534 } 535 return ¶ms.RelationLifeSuspendedStatusChange{ 536 Key: key, 537 Life: life.Value(rel.Life().String()), 538 Suspended: rel.Suspended(), 539 SuspendedReason: rel.SuspendedReason(), 540 }, nil 541 } 542 543 type offerGetter interface { 544 ApplicationOfferForUUID(string) (*crossmodel.ApplicationOffer, error) 545 Application(string) (Application, error) 546 547 // IsMigrationActive returns true if the current model is 548 // in the process of being migrated to another controller. 549 IsMigrationActive() (bool, error) 550 } 551 552 // GetOfferStatusChange returns a status change struct for the input offer name. 553 // If the offer or application are not found during a migration, a specific 554 // error to indicate the migration-in-progress is returned. 555 // This is interpreted upstream as a watcher error and propagated to the 556 // remote CMR consumer. 557 func GetOfferStatusChange(st offerGetter, offerUUID, offerName string) (*params.OfferStatusChange, error) { 558 migrating, err := st.IsMigrationActive() 559 if err != nil { 560 return nil, errors.Trace(err) 561 } 562 563 offer, err := st.ApplicationOfferForUUID(offerUUID) 564 if errors.IsNotFound(err) { 565 if migrating { 566 return nil, migration.ErrMigrating 567 } 568 return ¶ms.OfferStatusChange{ 569 OfferName: offerName, 570 Status: params.EntityStatus{ 571 Status: status.Terminated, 572 Info: "offer has been removed", 573 }, 574 }, nil 575 } else if err != nil { 576 return nil, errors.Trace(err) 577 } 578 579 app, err := st.Application(offer.ApplicationName) 580 if errors.IsNotFound(err) { 581 if migrating { 582 return nil, migration.ErrMigrating 583 } 584 return ¶ms.OfferStatusChange{ 585 OfferName: offerName, 586 Status: params.EntityStatus{ 587 Status: status.Terminated, 588 Info: "application has been removed", 589 }, 590 }, nil 591 } else if err != nil { 592 return nil, errors.Trace(err) 593 } 594 595 sts := status.StatusInfo{ 596 Status: status.Unknown, 597 } 598 599 if appStatus, err := app.Status(); err == nil { 600 // If the status is set to unset, then we need to query all the 601 // units of the application to work out the correct series. 602 if appStatus.Status == status.Unset { 603 derived, err := getDerivedUnitsStatus(app) 604 if err == nil { 605 sts = derived 606 } 607 } else { 608 sts = appStatus 609 } 610 } 611 612 return ¶ms.OfferStatusChange{ 613 OfferName: offerName, 614 Status: params.EntityStatus{ 615 Status: sts.Status, 616 Info: sts.Message, 617 Data: sts.Data, 618 Since: sts.Since, 619 }, 620 }, nil 621 } 622 623 func getDerivedUnitsStatus(app Application) (status.StatusInfo, error) { 624 units, err := app.AllUnits() 625 if err != nil { 626 return status.StatusInfo{}, errors.Trace(err) 627 } 628 629 statuses := make([]status.StatusInfo, len(units)) 630 for _, unit := range units { 631 st, err := unit.Status() 632 if err != nil { 633 return status.StatusInfo{}, errors.Trace(err) 634 } 635 636 statuses = append(statuses, st) 637 } 638 derived := status.DeriveStatus(statuses) 639 return derived, nil 640 }