github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/caasunitprovisioner/provisioner.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasunitprovisioner 5 6 import ( 7 "fmt" 8 "sort" 9 "time" 10 11 "github.com/juju/charm/v12" 12 "github.com/juju/clock" 13 "github.com/juju/collections/set" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/names/v5" 17 18 "github.com/juju/juju/apiserver/common" 19 charmscommon "github.com/juju/juju/apiserver/common/charms" 20 "github.com/juju/juju/apiserver/common/storagecommon" 21 apiservererrors "github.com/juju/juju/apiserver/errors" 22 "github.com/juju/juju/apiserver/facade" 23 "github.com/juju/juju/apiserver/facades/client/application" 24 "github.com/juju/juju/apiserver/facades/controller/caasoperatorprovisioner" 25 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 26 "github.com/juju/juju/cloudconfig/podcfg" 27 "github.com/juju/juju/controller" 28 "github.com/juju/juju/core/status" 29 "github.com/juju/juju/docker" 30 "github.com/juju/juju/environs/config" 31 "github.com/juju/juju/environs/tags" 32 "github.com/juju/juju/rpc/params" 33 "github.com/juju/juju/state" 34 stateerrors "github.com/juju/juju/state/errors" 35 "github.com/juju/juju/state/watcher" 36 "github.com/juju/juju/storage" 37 "github.com/juju/juju/storage/poolmanager" 38 ) 39 40 var logger = loggo.GetLogger("juju.apiserver.controller.caasunitprovisioner") 41 42 type Facade struct { 43 *common.LifeGetter 44 entityWatcher *common.AgentEntityWatcher 45 charmInfoAPI *charmscommon.CharmInfoAPI 46 appCharmInfoAPI *charmscommon.ApplicationCharmInfoAPI 47 48 resources facade.Resources 49 state CAASUnitProvisionerState 50 storage StorageBackend 51 storagePoolManager poolmanager.PoolManager 52 registry storage.ProviderRegistry 53 devices DeviceBackend 54 clock clock.Clock 55 } 56 57 // NewFacade returns a new CAAS unit provisioner Facade facade. 58 func NewFacade( 59 resources facade.Resources, 60 authorizer facade.Authorizer, 61 st CAASUnitProvisionerState, 62 sb StorageBackend, 63 db DeviceBackend, 64 storagePoolManager poolmanager.PoolManager, 65 registry storage.ProviderRegistry, 66 charmInfoAPI *charmscommon.CharmInfoAPI, 67 appCharmInfoAPI *charmscommon.ApplicationCharmInfoAPI, 68 clock clock.Clock, 69 ) (*Facade, error) { 70 if !authorizer.AuthController() { 71 return nil, apiservererrors.ErrPerm 72 } 73 accessApplication := common.AuthFuncForTagKind(names.ApplicationTagKind) 74 return &Facade{ 75 entityWatcher: common.NewAgentEntityWatcher(st, resources, accessApplication), 76 charmInfoAPI: charmInfoAPI, 77 appCharmInfoAPI: appCharmInfoAPI, 78 LifeGetter: common.NewLifeGetter( 79 st, common.AuthAny( 80 common.AuthFuncForTagKind(names.ApplicationTagKind), 81 common.AuthFuncForTagKind(names.UnitTagKind), 82 ), 83 ), 84 resources: resources, 85 state: st, 86 storage: sb, 87 devices: db, 88 storagePoolManager: storagePoolManager, 89 registry: registry, 90 clock: clock, 91 }, nil 92 } 93 94 // WatchApplications starts a StringsWatcher to watch applications 95 // deployed to this model. 96 func (f *Facade) WatchApplications() (params.StringsWatchResult, error) { 97 watch := f.state.WatchApplications() 98 if changes, ok := <-watch.Changes(); ok { 99 return params.StringsWatchResult{ 100 StringsWatcherId: f.resources.Register(watch), 101 Changes: changes, 102 }, nil 103 } 104 return params.StringsWatchResult{}, watcher.EnsureErr(watch) 105 } 106 107 // CharmInfo returns information about the requested charm. 108 func (f *Facade) CharmInfo(args params.CharmURL) (params.Charm, error) { 109 return f.charmInfoAPI.CharmInfo(args) 110 } 111 112 // ApplicationCharmInfo returns information about an application's charm. 113 func (f *Facade) ApplicationCharmInfo(args params.Entity) (params.Charm, error) { 114 return f.appCharmInfoAPI.ApplicationCharmInfo(args) 115 } 116 117 // Watch starts a NotifyWatcher for each entity given. 118 func (f *Facade) Watch(args params.Entities) (params.NotifyWatchResults, error) { 119 return f.entityWatcher.Watch(args) 120 } 121 122 // WatchApplicationsScale starts a NotifyWatcher to watch changes 123 // to the applications' scale. 124 func (f *Facade) WatchApplicationsScale(args params.Entities) (params.NotifyWatchResults, error) { 125 results := params.NotifyWatchResults{ 126 Results: make([]params.NotifyWatchResult, len(args.Entities)), 127 } 128 for i, arg := range args.Entities { 129 id, err := f.watchApplicationScale(arg.Tag) 130 if err != nil { 131 results.Results[i].Error = apiservererrors.ServerError(err) 132 continue 133 } 134 results.Results[i].NotifyWatcherId = id 135 } 136 return results, nil 137 } 138 139 func (f *Facade) watchApplicationScale(tagString string) (string, error) { 140 tag, err := names.ParseApplicationTag(tagString) 141 if err != nil { 142 return "", errors.Trace(err) 143 } 144 app, err := f.state.Application(tag.Id()) 145 if err != nil { 146 return "", errors.Trace(err) 147 } 148 w := app.WatchScale() 149 if _, ok := <-w.Changes(); ok { 150 return f.resources.Register(w), nil 151 } 152 return "", watcher.EnsureErr(w) 153 } 154 155 // WatchPodSpec starts a NotifyWatcher to watch changes to the 156 // pod spec for specified units in this model. 157 func (f *Facade) WatchPodSpec(args params.Entities) (params.NotifyWatchResults, error) { 158 model, err := f.state.Model() 159 if err != nil { 160 return params.NotifyWatchResults{}, errors.Trace(err) 161 } 162 results := params.NotifyWatchResults{ 163 Results: make([]params.NotifyWatchResult, len(args.Entities)), 164 } 165 for i, arg := range args.Entities { 166 id, err := f.watchPodSpec(model, arg.Tag) 167 if err != nil { 168 results.Results[i].Error = apiservererrors.ServerError(err) 169 continue 170 } 171 results.Results[i].NotifyWatcherId = id 172 } 173 return results, nil 174 } 175 176 func (f *Facade) watchPodSpec(model Model, tagString string) (string, error) { 177 tag, err := names.ParseApplicationTag(tagString) 178 if err != nil { 179 return "", errors.Trace(err) 180 } 181 w, err := model.WatchPodSpec(tag) 182 if err != nil { 183 return "", errors.Trace(err) 184 } 185 if _, ok := <-w.Changes(); ok { 186 return f.resources.Register(w), nil 187 } 188 return "", watcher.EnsureErr(w) 189 } 190 191 // ApplicationsScale returns the scaling info for specified applications in this model. 192 func (f *Facade) ApplicationsScale(args params.Entities) (params.IntResults, error) { 193 results := params.IntResults{ 194 Results: make([]params.IntResult, len(args.Entities)), 195 } 196 for i, arg := range args.Entities { 197 scale, err := f.applicationScale(arg.Tag) 198 if err != nil { 199 results.Results[i].Error = apiservererrors.ServerError(err) 200 continue 201 } 202 results.Results[i].Result = scale 203 } 204 logger.Debugf("application scale result: %#v", results) 205 return results, nil 206 } 207 208 func (f *Facade) applicationScale(tagString string) (int, error) { 209 appTag, err := names.ParseApplicationTag(tagString) 210 if err != nil { 211 return 0, errors.Trace(err) 212 } 213 app, err := f.state.Application(appTag.Id()) 214 if err != nil { 215 return 0, errors.Trace(err) 216 } 217 return app.GetScale(), nil 218 } 219 220 // ApplicationsTrust returns the trust status for specified applications in this model. 221 func (f *Facade) ApplicationsTrust(args params.Entities) (params.BoolResults, error) { 222 results := params.BoolResults{ 223 Results: make([]params.BoolResult, len(args.Entities)), 224 } 225 for i, arg := range args.Entities { 226 trust, err := f.applicationTrust(arg.Tag) 227 if err != nil { 228 results.Results[i].Error = apiservererrors.ServerError(err) 229 continue 230 } 231 results.Results[i].Result = trust 232 } 233 logger.Debugf("application trust result: %#v", results) 234 return results, nil 235 } 236 237 func (f *Facade) applicationTrust(tagString string) (bool, error) { 238 appTag, err := names.ParseApplicationTag(tagString) 239 if err != nil { 240 return false, errors.Trace(err) 241 } 242 app, err := f.state.Application(appTag.Id()) 243 if err != nil { 244 return false, errors.Trace(err) 245 } 246 cfg, err := app.ApplicationConfig() 247 if err != nil { 248 return false, errors.Trace(err) 249 } 250 return cfg.GetBool(application.TrustConfigOptionName, false), nil 251 } 252 253 // WatchApplicationsTrustHash starts a StringsWatcher to watch changes 254 // to the applications' trust status. 255 func (f *Facade) WatchApplicationsTrustHash(args params.Entities) (params.StringsWatchResults, error) { 256 results := params.StringsWatchResults{ 257 Results: make([]params.StringsWatchResult, len(args.Entities)), 258 } 259 for i, arg := range args.Entities { 260 id, err := f.watchApplicationTrustHash(arg.Tag) 261 if err != nil { 262 results.Results[i].Error = apiservererrors.ServerError(err) 263 continue 264 } 265 results.Results[i].StringsWatcherId = id 266 } 267 return results, nil 268 } 269 270 func (f *Facade) watchApplicationTrustHash(tagString string) (string, error) { 271 tag, err := names.ParseApplicationTag(tagString) 272 if err != nil { 273 return "", errors.Trace(err) 274 } 275 app, err := f.state.Application(tag.Id()) 276 if err != nil { 277 return "", errors.Trace(err) 278 } 279 // This is currently implemented by just watching the 280 // app config settings which is where the trust value 281 // is stored. A similar pattern is used for model config 282 // watchers pending better filtering on watchers. 283 w := app.WatchConfigSettingsHash() 284 if _, ok := <-w.Changes(); ok { 285 return f.resources.Register(w), nil 286 } 287 return "", watcher.EnsureErr(w) 288 } 289 290 // DeploymentMode returns the deployment mode of the given applications' charms. 291 func (f *Facade) DeploymentMode(args params.Entities) (params.StringResults, error) { 292 results := params.StringResults{ 293 Results: make([]params.StringResult, len(args.Entities)), 294 } 295 for i, arg := range args.Entities { 296 mode, err := f.applicationDeploymentMode(arg.Tag) 297 if err != nil { 298 results.Results[i].Error = apiservererrors.ServerError(err) 299 continue 300 } 301 results.Results[i].Result = mode 302 } 303 return results, nil 304 } 305 306 func (f *Facade) applicationDeploymentMode(tagString string) (string, error) { 307 appTag, err := names.ParseApplicationTag(tagString) 308 if err != nil { 309 return "", errors.Trace(err) 310 } 311 app, err := f.state.Application(appTag.Id()) 312 if err != nil { 313 return "", errors.Trace(err) 314 } 315 ch, _, err := app.Charm() 316 if err != nil { 317 return "", errors.Trace(err) 318 } 319 var mode charm.DeploymentMode 320 if d := ch.Meta().Deployment; d != nil { 321 mode = d.DeploymentMode 322 } 323 if mode == "" { 324 mode = charm.ModeWorkload 325 } 326 return string(mode), nil 327 } 328 329 // ProvisioningInfo returns the provisioning info for specified applications in this model. 330 func (f *Facade) ProvisioningInfo(args params.Entities) (params.KubernetesProvisioningInfoResults, error) { 331 model, err := f.state.Model() 332 if err != nil { 333 return params.KubernetesProvisioningInfoResults{}, errors.Trace(err) 334 } 335 results := params.KubernetesProvisioningInfoResults{ 336 Results: make([]params.KubernetesProvisioningInfoResult, len(args.Entities)), 337 } 338 for i, arg := range args.Entities { 339 info, err := f.provisioningInfo(model, arg.Tag) 340 if err != nil { 341 results.Results[i].Error = apiservererrors.ServerError(err) 342 continue 343 } 344 results.Results[i].Result = info 345 } 346 return results, nil 347 } 348 349 func (f *Facade) provisioningInfo(model Model, tagString string) (*params.KubernetesProvisioningInfo, error) { 350 appTag, err := names.ParseApplicationTag(tagString) 351 if err != nil { 352 return nil, errors.Trace(err) 353 } 354 // First the pod spec. 355 podSpec, err := model.PodSpec(appTag) 356 if err != nil { 357 return nil, errors.Trace(err) 358 } 359 rawSpec, err := model.RawK8sSpec(appTag) 360 if err != nil { 361 return nil, errors.Trace(err) 362 } 363 if podSpec != "" && rawSpec != "" { 364 // This should never happen. 365 return nil, errors.New("both k8s spec and raw k8s spec were set") 366 } 367 368 // Now get any required storage. We need to provision storage 369 // at the same time as the pod as it can't be attached later. 370 371 // All units are currently homogeneous so we just 372 // need to get info for the first alive unit. 373 app, err := f.state.Application(appTag.Id()) 374 if err != nil { 375 return nil, errors.Trace(err) 376 } 377 modelConfig, err := model.ModelConfig() 378 if err != nil { 379 return nil, errors.Trace(err) 380 } 381 382 controllerCfg, err := f.state.ControllerConfig() 383 if err != nil { 384 return nil, errors.Trace(err) 385 } 386 vers, ok := modelConfig.AgentVersion() 387 if !ok { 388 return nil, errors.NewNotValid(nil, 389 fmt.Sprintf("agent version is missing in model config %q", modelConfig.Name()), 390 ) 391 } 392 registryPath, err := podcfg.GetJujuOCIImagePath(controllerCfg, vers) 393 if err != nil { 394 return nil, errors.Trace(err) 395 } 396 397 imageRepoDetails, err := docker.NewImageRepoDetails(controllerCfg.CAASImageRepo()) 398 if err != nil { 399 return nil, errors.Annotatef(err, "parsing %s", controller.CAASImageRepo) 400 } 401 imageRepo := params.NewDockerImageInfo(imageRepoDetails, registryPath) 402 logger.Tracef("imageRepo %v", imageRepo) 403 filesystemParams, err := f.applicationFilesystemParams(app, controllerCfg, modelConfig) 404 if err != nil { 405 return nil, errors.Trace(err) 406 } 407 408 devices, err := f.devicesParams(app) 409 if err != nil { 410 return nil, errors.Trace(err) 411 } 412 cons, err := app.Constraints() 413 if err != nil { 414 return nil, errors.Trace(err) 415 } 416 mergedCons, err := f.state.ResolveConstraints(cons) 417 if err != nil { 418 return nil, errors.Trace(err) 419 } 420 resourceTags := tags.ResourceTags( 421 names.NewModelTag(modelConfig.UUID()), 422 names.NewControllerTag(controllerCfg.ControllerUUID()), 423 modelConfig, 424 ) 425 426 ch, _, err := app.Charm() 427 if err != nil { 428 return nil, errors.Trace(err) 429 } 430 431 info := ¶ms.KubernetesProvisioningInfo{ 432 PodSpec: podSpec, 433 RawK8sSpec: rawSpec, 434 Filesystems: filesystemParams, 435 Devices: devices, 436 Constraints: mergedCons, 437 Tags: resourceTags, 438 CharmModifiedVersion: app.CharmModifiedVersion(), 439 ImageRepo: imageRepo, 440 } 441 deployInfo := ch.Meta().Deployment 442 if deployInfo != nil { 443 info.DeploymentInfo = ¶ms.KubernetesDeploymentInfo{ 444 DeploymentType: string(deployInfo.DeploymentType), 445 ServiceType: string(deployInfo.ServiceType), 446 } 447 } 448 return info, nil 449 } 450 451 func filesystemParams( 452 app Application, 453 cons state.StorageConstraints, 454 storageName string, 455 controllerUUID string, 456 modelConfig *config.Config, 457 poolManager poolmanager.PoolManager, 458 registry storage.ProviderRegistry, 459 ) (*params.KubernetesFilesystemParams, error) { 460 461 filesystemTags, err := storagecommon.StorageTags(nil, modelConfig.UUID(), controllerUUID, modelConfig) 462 if err != nil { 463 return nil, errors.Annotate(err, "computing storage tags") 464 } 465 filesystemTags[tags.JujuStorageOwner] = app.Name() 466 467 storageClassName, _ := modelConfig.AllAttrs()[k8sconstants.WorkloadStorageKey].(string) 468 if cons.Pool == "" && storageClassName == "" { 469 return nil, errors.Errorf("storage pool for %q must be specified since there's no model default storage class", storageName) 470 } 471 fsParams, err := caasoperatorprovisioner.CharmStorageParams(controllerUUID, storageClassName, modelConfig, cons.Pool, poolManager, registry) 472 if err != nil { 473 return nil, errors.Maskf(err, "getting filesystem storage parameters") 474 } 475 476 fsParams.Size = cons.Size 477 fsParams.StorageName = storageName 478 fsParams.Tags = filesystemTags 479 return fsParams, nil 480 } 481 482 // applicationFilesystemParams retrieves FilesystemParams for the filesystems 483 // that should be provisioned with, and attached to, pods of the application. 484 func (f *Facade) applicationFilesystemParams( 485 app Application, 486 controllerConfig controller.Config, 487 modelConfig *config.Config, 488 ) ([]params.KubernetesFilesystemParams, error) { 489 storageConstraints, err := app.StorageConstraints() 490 if err != nil { 491 return nil, errors.Trace(err) 492 } 493 494 ch, _, err := app.Charm() 495 if err != nil { 496 return nil, errors.Trace(err) 497 } 498 499 var allFilesystemParams []params.KubernetesFilesystemParams 500 // To always guarantee the same order, sort by names. 501 var sNames []string 502 for name := range storageConstraints { 503 sNames = append(sNames, name) 504 } 505 sort.Strings(sNames) 506 for _, name := range sNames { 507 cons := storageConstraints[name] 508 fsParams, err := filesystemParams( 509 app, cons, name, 510 controllerConfig.ControllerUUID(), 511 modelConfig, 512 f.storagePoolManager, f.registry, 513 ) 514 if err != nil { 515 return nil, errors.Annotatef(err, "getting filesystem %q parameters", name) 516 } 517 for i := 0; i < int(cons.Count); i++ { 518 charmStorage := ch.Meta().Storage[name] 519 id := fmt.Sprintf("%s/%v", name, i) 520 tag := names.NewStorageTag(id) 521 location, err := state.FilesystemMountPoint(charmStorage, tag, "ubuntu") 522 if err != nil { 523 return nil, errors.Trace(err) 524 } 525 filesystemAttachmentParams := params.KubernetesFilesystemAttachmentParams{ 526 Provider: fsParams.Provider, 527 MountPoint: location, 528 ReadOnly: charmStorage.ReadOnly, 529 } 530 fsParams.Attachment = &filesystemAttachmentParams 531 allFilesystemParams = append(allFilesystemParams, *fsParams) 532 } 533 } 534 return allFilesystemParams, nil 535 } 536 537 func (f *Facade) devicesParams(app Application) ([]params.KubernetesDeviceParams, error) { 538 devices, err := app.DeviceConstraints() 539 if err != nil { 540 return nil, errors.Trace(err) 541 } 542 logger.Debugf("getting device constraints from state: %#v", devices) 543 var devicesParams []params.KubernetesDeviceParams 544 for _, d := range devices { 545 devicesParams = append(devicesParams, params.KubernetesDeviceParams{ 546 Type: params.DeviceType(d.Type), 547 Count: d.Count, 548 Attributes: d.Attributes, 549 }) 550 } 551 return devicesParams, nil 552 } 553 554 // ApplicationsConfig returns the config for the specified applications. 555 func (f *Facade) ApplicationsConfig(args params.Entities) (params.ApplicationGetConfigResults, error) { 556 results := params.ApplicationGetConfigResults{ 557 Results: make([]params.ConfigResult, len(args.Entities)), 558 } 559 for i, arg := range args.Entities { 560 result, err := f.getApplicationConfig(arg.Tag) 561 results.Results[i].Config = result 562 results.Results[i].Error = apiservererrors.ServerError(err) 563 } 564 return results, nil 565 } 566 567 func (f *Facade) getApplicationConfig(tagString string) (map[string]interface{}, error) { 568 tag, err := names.ParseApplicationTag(tagString) 569 if err != nil { 570 return nil, errors.Trace(err) 571 } 572 app, err := f.state.Application(tag.Id()) 573 if err != nil { 574 return nil, errors.Trace(err) 575 } 576 return app.ApplicationConfig() 577 } 578 579 // UpdateApplicationsUnits updates the Juju data model to reflect the given 580 // units of the specified application. 581 func (f *Facade) UpdateApplicationsUnits(args params.UpdateApplicationUnitArgs) (params.UpdateApplicationUnitResults, error) { 582 result := params.UpdateApplicationUnitResults{ 583 Results: make([]params.UpdateApplicationUnitResult, len(args.Args)), 584 } 585 if len(args.Args) == 0 { 586 return result, nil 587 } 588 for i, appUpdate := range args.Args { 589 appTag, err := names.ParseApplicationTag(appUpdate.ApplicationTag) 590 if err != nil { 591 result.Results[i].Error = apiservererrors.ServerError(err) 592 continue 593 } 594 app, err := f.state.Application(appTag.Id()) 595 if err != nil { 596 result.Results[i].Error = apiservererrors.ServerError(err) 597 continue 598 } 599 appStatus := appUpdate.Status 600 if appStatus.Status != "" && appStatus.Status != status.Unknown { 601 now := f.clock.Now() 602 err = app.SetOperatorStatus(status.StatusInfo{ 603 Status: appStatus.Status, 604 Message: appStatus.Info, 605 Data: appStatus.Data, 606 Since: &now, 607 }) 608 if err != nil { 609 result.Results[i].Error = apiservererrors.ServerError(err) 610 continue 611 } 612 } 613 appUnitInfo, err := f.updateUnitsFromCloud(app, appUpdate.Scale, appUpdate.Generation, appUpdate.Units) 614 if err != nil { 615 // Mask any not found errors as the worker (caller) treats them specially 616 // and they are not relevant here. 617 result.Results[i].Error = apiservererrors.ServerError(errors.Mask(err)) 618 } 619 620 // Errors from SetScale will also include unit info. 621 if appUnitInfo != nil { 622 result.Results[i].Info = ¶ms.UpdateApplicationUnitsInfo{ 623 Units: appUnitInfo, 624 } 625 } 626 } 627 return result, nil 628 } 629 630 // updateStatus constructs the agent and cloud container status values. 631 func (f *Facade) updateStatus(params params.ApplicationUnitParams) ( 632 agentStatus *status.StatusInfo, 633 cloudContainerStatus *status.StatusInfo, 634 ) { 635 var containerStatus status.Status 636 switch status.Status(params.Status) { 637 case status.Unknown: 638 // The container runtime can spam us with unimportant 639 // status updates, so ignore any irrelevant ones. 640 return nil, nil 641 case status.Allocating: 642 // The container runtime has decided to restart the pod. 643 agentStatus = &status.StatusInfo{ 644 Status: status.Allocating, 645 Message: params.Info, 646 } 647 containerStatus = status.Waiting 648 case status.Running: 649 // A pod has finished starting so the workload is now active. 650 agentStatus = &status.StatusInfo{ 651 Status: status.Idle, 652 } 653 containerStatus = status.Running 654 case status.Error: 655 agentStatus = &status.StatusInfo{ 656 Status: status.Error, 657 Message: params.Info, 658 Data: params.Data, 659 } 660 containerStatus = status.Error 661 case status.Blocked: 662 containerStatus = status.Blocked 663 agentStatus = &status.StatusInfo{ 664 Status: status.Idle, 665 } 666 } 667 cloudContainerStatus = &status.StatusInfo{ 668 Status: containerStatus, 669 Message: params.Info, 670 Data: params.Data, 671 } 672 return agentStatus, cloudContainerStatus 673 } 674 675 // updateUnitsFromCloud takes a slice of unit information provided by an external 676 // source (typically a cloud update event) and merges that with the existing unit 677 // data model in state. The passed in units are the complete set for the cloud, so 678 // any existing units in state with provider ids which aren't in the set will be removed. 679 func (f *Facade) updateUnitsFromCloud(app Application, scale *int, 680 generation *int64, unitUpdates []params.ApplicationUnitParams) ([]params.ApplicationUnitInfo, error) { 681 logger.Debugf("unit updates: %#v", unitUpdates) 682 if scale != nil { 683 logger.Debugf("application scale: %v", *scale) 684 if *scale > 0 && len(unitUpdates) == 0 { 685 // no ops for empty units because we can not determine if it's stateful or not in this case. 686 logger.Debugf("ignoring empty k8s event for %q", app.Tag().String()) 687 return nil, nil 688 } 689 } 690 // Set up the initial data structures. 691 existingStateUnits, err := app.AllUnits() 692 if err != nil { 693 return nil, errors.Trace(err) 694 } 695 stateUnitsById := make(map[string]stateUnit) 696 cloudPodsById := make(map[string]params.ApplicationUnitParams) 697 698 // Record all unit provider ids known to exist in the cloud. 699 for _, u := range unitUpdates { 700 cloudPodsById[u.ProviderId] = u 701 } 702 703 stateUnitExistsInCloud := func(providerId string) bool { 704 if providerId == "" { 705 return false 706 } 707 _, ok := cloudPodsById[providerId] 708 return ok 709 } 710 711 unitInfo := &updateStateUnitParams{ 712 stateUnitsInCloud: make(map[string]Unit), 713 deletedRemoved: true, 714 } 715 var ( 716 // aliveStateIds holds the provider ids of alive units in state. 717 aliveStateIds = set.NewStrings() 718 719 // extraStateIds holds the provider ids of units in state which 720 // no longer exist in the cloud. 721 extraStateIds = set.NewStrings() 722 ) 723 724 // Loop over any existing state units and record those which do not yet have 725 // provider ids, and those which have been removed or updated. 726 for _, u := range existingStateUnits { 727 var providerId string 728 info, err := u.ContainerInfo() 729 if err != nil && !errors.IsNotFound(err) { 730 return nil, errors.Trace(err) 731 } 732 if err == nil { 733 providerId = info.ProviderId() 734 } 735 736 unitAlive := u.Life() == state.Alive 737 if !unitAlive { 738 continue 739 } 740 if providerId == "" { 741 logger.Debugf("unit %q is not associated with any pod", u.Name()) 742 unitInfo.unassociatedUnits = append(unitInfo.unassociatedUnits, u) 743 continue 744 } 745 746 stateUnitsById[providerId] = stateUnit{Unit: u} 747 stateUnitInCloud := stateUnitExistsInCloud(providerId) 748 aliveStateIds.Add(providerId) 749 if stateUnitInCloud { 750 logger.Debugf("unit %q (%v) has changed in the cloud", u.Name(), providerId) 751 unitInfo.stateUnitsInCloud[u.UnitTag().String()] = u 752 } else { 753 logger.Debugf("unit %q (%v) has removed in the cloud", u.Name(), providerId) 754 extraStateIds.Add(providerId) 755 } 756 } 757 758 // Do it in sorted order so it's deterministic for tests. 759 var ids []string 760 for id := range cloudPodsById { 761 ids = append(ids, id) 762 } 763 sort.Strings(ids) 764 765 // Sort extra ids also to guarantee order. 766 var extraIds []string 767 for id := range extraStateIds { 768 extraIds = append(extraIds, id) 769 } 770 sort.Strings(extraIds) 771 unassociatedUnitCount := len(unitInfo.unassociatedUnits) 772 extraUnitsInStateCount := 0 773 if scale != nil { 774 extraUnitsInStateCount = len(stateUnitsById) + unassociatedUnitCount - *scale 775 } 776 777 for _, id := range ids { 778 u := cloudPodsById[id] 779 unitInfo.deletedRemoved = !u.Stateful 780 if aliveStateIds.Contains(id) { 781 u.UnitTag = stateUnitsById[id].UnitTag().String() 782 unitInfo.existingCloudPods = append(unitInfo.existingCloudPods, u) 783 continue 784 } 785 786 // First attempt to add any new cloud pod not yet represented in state 787 // to a unit which does not yet have a provider id. 788 if unassociatedUnitCount > 0 { 789 unassociatedUnitCount-- 790 unitInfo.addedCloudPods = append(unitInfo.addedCloudPods, u) 791 continue 792 } 793 794 // A new pod was added to the cloud but does not yet have a unit in state. 795 unitInfo.addedCloudPods = append(unitInfo.addedCloudPods, u) 796 } 797 798 // If there are any extra provider ids left over after allocating all the cloud pods, 799 // then consider those state units as terminated. 800 logger.Debugf("alive state ids %v", aliveStateIds.Values()) 801 logger.Debugf("extra state ids %v", extraStateIds.Values()) 802 logger.Debugf("extra units in state: %v", extraUnitsInStateCount) 803 for _, providerId := range extraIds { 804 u := stateUnitsById[providerId] 805 logger.Debugf("unit %q (%v) has been removed from the cloud", u.Name(), providerId) 806 // If the unit in state is surplus to the application scale, remove it from state also. 807 // We retain units in state that are not surplus to cloud requirements as they will 808 // be regenerated by the cloud and we want to keep a stable unit name. 809 u.delete = unitInfo.deletedRemoved && scale != nil 810 if !u.delete && extraUnitsInStateCount > 0 { 811 logger.Debugf("deleting %v because it exceeds the scale of %v", u.Name(), scale) 812 u.delete = true 813 extraUnitsInStateCount-- 814 } 815 unitInfo.removedUnits = append(unitInfo.removedUnits, u) 816 } 817 818 if err := f.updateStateUnits(app, unitInfo); err != nil { 819 return nil, errors.Trace(err) 820 } 821 822 var providerIds []string 823 for _, u := range unitUpdates { 824 providerIds = append(providerIds, u.ProviderId) 825 } 826 m, err := f.state.Model() 827 if err != nil { 828 return nil, errors.Trace(err) 829 } 830 containers, err := m.Containers(providerIds...) 831 if err != nil { 832 return nil, errors.Trace(err) 833 } 834 var appUnitInfo []params.ApplicationUnitInfo 835 for _, c := range containers { 836 appUnitInfo = append(appUnitInfo, params.ApplicationUnitInfo{ 837 ProviderId: c.ProviderId(), 838 UnitTag: names.NewUnitTag(c.Unit()).String(), 839 }) 840 } 841 842 if scale == nil { 843 return appUnitInfo, nil 844 } 845 // Update the scale last now that the state 846 // model accurately reflects the cluster pods. 847 currentScale := app.GetScale() 848 var gen int64 849 if generation != nil { 850 gen = *generation 851 } 852 if currentScale != *scale { 853 return appUnitInfo, app.SetScale(*scale, gen, false) 854 } 855 return appUnitInfo, nil 856 } 857 858 type stateUnit struct { 859 Unit 860 delete bool 861 } 862 863 type updateStateUnitParams struct { 864 stateUnitsInCloud map[string]Unit 865 addedCloudPods []params.ApplicationUnitParams 866 existingCloudPods []params.ApplicationUnitParams 867 removedUnits []stateUnit 868 unassociatedUnits []Unit 869 deletedRemoved bool 870 } 871 872 type filesystemInfo struct { 873 unitTag names.UnitTag 874 providerId string 875 mountPoint string 876 readOnly bool 877 size uint64 878 filesystemId string 879 } 880 881 type volumeInfo struct { 882 unitTag names.UnitTag 883 providerId string 884 readOnly bool 885 persistent bool 886 size uint64 887 volumeId string 888 } 889 890 func (f *Facade) updateStateUnits(app Application, unitInfo *updateStateUnitParams) error { 891 892 if app.Life() != state.Alive { 893 // We ignore any updates for dying applications. 894 logger.Debugf("ignoring unit updates for dying application: %v", app.Name()) 895 return nil 896 } 897 898 logger.Tracef("added cloud units: %+v", unitInfo.addedCloudPods) 899 logger.Tracef("existing cloud units: %+v", unitInfo.existingCloudPods) 900 logger.Tracef("removed units: %+v", unitInfo.removedUnits) 901 logger.Tracef("unassociated units: %+v", unitInfo.unassociatedUnits) 902 903 // Now we have the added, removed, updated units all sorted, 904 // generate the state update operations. 905 var unitUpdate state.UpdateUnitsOperation 906 907 filesystemUpdates := make(map[string]filesystemInfo) 908 filesystemStatus := make(map[string]status.StatusInfo) 909 volumeUpdates := make(map[string]volumeInfo) 910 volumeStatus := make(map[string]status.StatusInfo) 911 912 for _, u := range unitInfo.removedUnits { 913 // If a unit is removed from the cloud, all filesystems are considered detached. 914 unitStorage, err := f.storage.UnitStorageAttachments(u.UnitTag()) 915 if err != nil { 916 return errors.Trace(err) 917 } 918 for _, sa := range unitStorage { 919 fs, err := f.storage.StorageInstanceFilesystem(sa.StorageInstance()) 920 if err != nil { 921 return errors.Trace(err) 922 } 923 filesystemStatus[fs.FilesystemTag().String()] = status.StatusInfo{Status: status.Detached} 924 } 925 926 if u.delete { 927 unitUpdate.Deletes = append(unitUpdate.Deletes, u.DestroyOperation()) 928 } 929 // We'll set the status as Terminated. This will either be transient, as will 930 // occur when a pod is restarted external to Juju, or permanent if the pod has 931 // been deleted external to Juju. In the latter case, juju remove-unit will be 932 // need to clean things up on the Juju side. 933 cloudContainerStatus := &status.StatusInfo{ 934 Status: status.Terminated, 935 Message: "unit stopped by the cloud", 936 } 937 agentStatus := &status.StatusInfo{ 938 Status: status.Idle, 939 } 940 updateProps := state.UnitUpdateProperties{ 941 CloudContainerStatus: cloudContainerStatus, 942 AgentStatus: agentStatus, 943 } 944 unitUpdate.Updates = append(unitUpdate.Updates, 945 u.UpdateOperation(updateProps)) 946 } 947 948 processUnitParams := func(unitParams params.ApplicationUnitParams) *state.UnitUpdateProperties { 949 agentStatus, cloudContainerStatus := f.updateStatus(unitParams) 950 return &state.UnitUpdateProperties{ 951 ProviderId: &unitParams.ProviderId, 952 Address: &unitParams.Address, 953 Ports: &unitParams.Ports, 954 AgentStatus: agentStatus, 955 CloudContainerStatus: cloudContainerStatus, 956 } 957 } 958 959 processFilesystemParams := func(processedFilesystemIds set.Strings, unitTag names.UnitTag, unitParams params.ApplicationUnitParams) error { 960 // Once a unit is available in the cluster, we consider 961 // its filesystem(s) to be attached since the unit is 962 // not considered ready until this happens. 963 filesystemInfoByName := make(map[string][]params.KubernetesFilesystemInfo) 964 for _, fsInfo := range unitParams.FilesystemInfo { 965 infos := filesystemInfoByName[fsInfo.StorageName] 966 infos = append(infos, fsInfo) 967 filesystemInfoByName[fsInfo.StorageName] = infos 968 } 969 970 for storageName, infos := range filesystemInfoByName { 971 logger.Debugf("updating storage %v for %v", storageName, unitTag) 972 if len(infos) == 0 { 973 continue 974 } 975 976 unitStorage, err := f.storage.UnitStorageAttachments(unitTag) 977 if err != nil { 978 return errors.Trace(err) 979 } 980 981 // Loop over all the storage for the unit and skip storage not 982 // relevant for storageName. 983 // TODO(caas) - Add storage bankend API to get all unit storage instances for a named storage. 984 for _, sa := range unitStorage { 985 si, err := f.storage.StorageInstance(sa.StorageInstance()) 986 if errors.IsNotFound(err) { 987 logger.Warningf("ignoring non-existent storage instance %v for unit %v", sa.StorageInstance(), unitTag.Id()) 988 continue 989 } 990 if err != nil { 991 return errors.Trace(err) 992 } 993 if si.StorageName() != storageName { 994 continue 995 } 996 fs, err := f.storage.StorageInstanceFilesystem(sa.StorageInstance()) 997 if err != nil { 998 return errors.Trace(err) 999 } 1000 fsInfo := infos[0] 1001 processedFilesystemIds.Add(fsInfo.FilesystemId) 1002 1003 // k8s reports provisioned info even when the volume is not ready. 1004 // Only update state when volume is created so Juju doesn't think 1005 // the volume is active when it's not. 1006 if fsInfo.Status != status.Pending.String() { 1007 filesystemUpdates[fs.FilesystemTag().String()] = filesystemInfo{ 1008 unitTag: unitTag, 1009 providerId: unitParams.ProviderId, 1010 mountPoint: fsInfo.MountPoint, 1011 readOnly: fsInfo.ReadOnly, 1012 size: fsInfo.Size, 1013 filesystemId: fsInfo.FilesystemId, 1014 } 1015 } 1016 filesystemStatus[fs.FilesystemTag().String()] = status.StatusInfo{ 1017 Status: status.Status(fsInfo.Status), 1018 Message: fsInfo.Info, 1019 Data: fsInfo.Data, 1020 } 1021 1022 // If the filesystem has a backing volume, get that info also. 1023 if _, err := fs.Volume(); err == nil { 1024 vol, err := f.storage.StorageInstanceVolume(sa.StorageInstance()) 1025 if err != nil { 1026 return errors.Trace(err) 1027 } 1028 if fsInfo.Volume.Status != status.Pending.String() { 1029 volumeUpdates[vol.VolumeTag().String()] = volumeInfo{ 1030 unitTag: unitTag, 1031 providerId: unitParams.ProviderId, 1032 size: fsInfo.Volume.Size, 1033 volumeId: fsInfo.Volume.VolumeId, 1034 persistent: fsInfo.Volume.Persistent, 1035 readOnly: fsInfo.ReadOnly, 1036 } 1037 } 1038 volumeStatus[vol.VolumeTag().String()] = status.StatusInfo{ 1039 Status: status.Status(fsInfo.Volume.Status), 1040 Message: fsInfo.Volume.Info, 1041 Data: fsInfo.Volume.Data, 1042 } 1043 } 1044 1045 infos = infos[1:] 1046 if len(infos) == 0 { 1047 break 1048 } 1049 } 1050 } 1051 return nil 1052 } 1053 1054 var unitParamsWithFilesystemInfo []params.ApplicationUnitParams 1055 1056 for _, unitParams := range unitInfo.existingCloudPods { 1057 u, ok := unitInfo.stateUnitsInCloud[unitParams.UnitTag] 1058 if !ok { 1059 logger.Warningf("unexpected unit parameters %+v not in state", unitParams) 1060 continue 1061 } 1062 updateProps := processUnitParams(unitParams) 1063 if len(unitParams.FilesystemInfo) > 0 { 1064 unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams) 1065 } 1066 unitUpdate.Updates = append(unitUpdate.Updates, 1067 u.UpdateOperation(*updateProps)) 1068 } 1069 1070 // For newly added units in the cloud, either update state units which 1071 // exist but which do not yet have provider ids (recording the provider 1072 // id as well), or add a brand new unit. 1073 idx := 0 1074 for _, unitParams := range unitInfo.addedCloudPods { 1075 if idx < len(unitInfo.unassociatedUnits) { 1076 u := unitInfo.unassociatedUnits[idx] 1077 updateProps := processUnitParams(unitParams) 1078 unitUpdate.Updates = append(unitUpdate.Updates, 1079 u.UpdateOperation(*updateProps)) 1080 idx++ 1081 if len(unitParams.FilesystemInfo) > 0 { 1082 unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams) 1083 } 1084 continue 1085 } 1086 1087 // Process units added directly in the cloud instead of via Juju. 1088 updateProps := processUnitParams(unitParams) 1089 if len(unitParams.FilesystemInfo) > 0 { 1090 unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams) 1091 } 1092 unitUpdate.Adds = append(unitUpdate.Adds, 1093 app.AddOperation(*updateProps)) 1094 } 1095 err := app.UpdateUnits(&unitUpdate) 1096 // We ignore any updates for dying applications. 1097 if stateerrors.IsNotAlive(err) { 1098 return nil 1099 } else if err != nil { 1100 return errors.Trace(err) 1101 } 1102 1103 // Now update filesystem info - attachment data and status. 1104 // For units added to the cloud directly, we first need to lookup the 1105 // newly created unit tag from Juju using the cloud provider ids. 1106 var providerIds []string 1107 for _, unitParams := range unitParamsWithFilesystemInfo { 1108 if unitParams.UnitTag == "" { 1109 providerIds = append(providerIds, unitParams.ProviderId) 1110 } 1111 } 1112 m, err := f.state.Model() 1113 if err != nil { 1114 return errors.Trace(err) 1115 } 1116 var providerIdToUnit = make(map[string]names.UnitTag) 1117 containers, err := m.Containers(providerIds...) 1118 if err != nil { 1119 return errors.Trace(err) 1120 } 1121 for _, c := range containers { 1122 providerIdToUnit[c.ProviderId()] = names.NewUnitTag(c.Unit()) 1123 } 1124 1125 processedFilesystemIds := set.NewStrings() 1126 for _, unitParams := range unitParamsWithFilesystemInfo { 1127 var ( 1128 unitTag names.UnitTag 1129 ok bool 1130 ) 1131 // For units added to the cloud directly, we first need to lookup the 1132 // newly created unit tag from Juju using the cloud provider ids. 1133 if unitParams.UnitTag == "" { 1134 unitTag, ok = providerIdToUnit[unitParams.ProviderId] 1135 if !ok { 1136 logger.Warningf("cannot update filesystem data for unknown pod %q", unitParams.ProviderId) 1137 continue 1138 } 1139 } else { 1140 unitTag, _ = names.ParseUnitTag(unitParams.UnitTag) 1141 } 1142 if err := processFilesystemParams(processedFilesystemIds, unitTag, unitParams); err != nil { 1143 return errors.Annotatef(err, "processing filesystem info for unit %q", unitTag.Id()) 1144 } 1145 } 1146 1147 // If pods are recreated on the Kubernetes side, new units are created on the Juju 1148 // side and so any previously attached filesystems become orphaned and need to 1149 // be cleaned up. 1150 appName := app.Name() 1151 if err := f.cleanupOrphanedFilesystems(processedFilesystemIds); err != nil { 1152 return errors.Annotatef(err, "deleting orphaned filesystems for %v", appName) 1153 } 1154 1155 // First do the volume updates as volumes need to be attached before the filesystem updates. 1156 if err := f.updateVolumeInfo(volumeUpdates, volumeStatus); err != nil { 1157 return errors.Annotatef(err, "updating volume information for %v", appName) 1158 } 1159 1160 err = f.updateFilesystemInfo(filesystemUpdates, filesystemStatus) 1161 return errors.Annotatef(err, "updating filesystem information for %v", appName) 1162 } 1163 1164 func (f *Facade) cleanupOrphanedFilesystems(processedFilesystemIds set.Strings) error { 1165 // TODO(caas) - record unit id on the filesystem so we can query by unit 1166 allFilesystems, err := f.storage.AllFilesystems() 1167 if err != nil { 1168 return errors.Trace(err) 1169 } 1170 for _, fs := range allFilesystems { 1171 fsInfo, err := fs.Info() 1172 if errors.IsNotProvisioned(err) { 1173 continue 1174 } 1175 if err != nil { 1176 return errors.Trace(err) 1177 } 1178 if !processedFilesystemIds.Contains(fsInfo.FilesystemId) { 1179 continue 1180 } 1181 1182 storageTag, err := fs.Storage() 1183 if err != nil && !errors.IsNotFound(err) { 1184 return errors.Trace(err) 1185 } 1186 if err != nil { 1187 continue 1188 } 1189 1190 si, err := f.storage.StorageInstance(storageTag) 1191 if err != nil && !errors.IsNotFound(err) { 1192 return errors.Trace(err) 1193 } 1194 if err != nil { 1195 continue 1196 } 1197 _, ok := si.Owner() 1198 if ok { 1199 continue 1200 } 1201 1202 logger.Debugf("found orphaned filesystem %v", fs.FilesystemTag()) 1203 // TODO (anastasiamac 2019-04-04) We can now force storage removal 1204 // but for now, while we have not an arg passed in, just hardcode. 1205 err = f.storage.DestroyStorageInstance(storageTag, false, false, time.Duration(0)) 1206 if err != nil && !errors.IsNotFound(err) { 1207 return errors.Trace(err) 1208 } 1209 err = f.storage.DestroyFilesystem(fs.FilesystemTag(), false) 1210 if err != nil && !errors.IsNotFound(err) { 1211 return errors.Trace(err) 1212 } 1213 } 1214 return nil 1215 } 1216 1217 func (f *Facade) updateVolumeInfo(volumeUpdates map[string]volumeInfo, volumeStatus map[string]status.StatusInfo) error { 1218 // Do it in sorted order so it's deterministic for tests. 1219 var volTags []string 1220 for tag := range volumeUpdates { 1221 volTags = append(volTags, tag) 1222 } 1223 sort.Strings(volTags) 1224 1225 logger.Debugf("updating volume data: %+v", volumeUpdates) 1226 for _, tagString := range volTags { 1227 volTag, _ := names.ParseVolumeTag(tagString) 1228 volData := volumeUpdates[tagString] 1229 1230 vol, err := f.storage.Volume(volTag) 1231 if err != nil { 1232 return errors.Trace(err) 1233 } 1234 // If we have already recorded the provisioning info, 1235 // it's an error to try and do it again. 1236 _, err = vol.Info() 1237 if err != nil && !errors.IsNotProvisioned(err) { 1238 return errors.Trace(err) 1239 } 1240 if err != nil { 1241 // Provisioning info not set yet. 1242 err = f.storage.SetVolumeInfo(volTag, state.VolumeInfo{ 1243 Size: volData.size, 1244 VolumeId: volData.volumeId, 1245 Persistent: volData.persistent, 1246 }) 1247 if err != nil { 1248 return errors.Trace(err) 1249 } 1250 } 1251 1252 err = f.storage.SetVolumeAttachmentInfo(volData.unitTag, volTag, state.VolumeAttachmentInfo{ 1253 ReadOnly: volData.readOnly, 1254 }) 1255 if err != nil { 1256 return errors.Trace(err) 1257 } 1258 } 1259 1260 // Do it in sorted order so it's deterministic for tests. 1261 volTags = []string{} 1262 for tag := range volumeStatus { 1263 volTags = append(volTags, tag) 1264 } 1265 sort.Strings(volTags) 1266 1267 logger.Debugf("updating volume status: %+v", volumeStatus) 1268 for _, tagString := range volTags { 1269 volTag, _ := names.ParseVolumeTag(tagString) 1270 volStatus := volumeStatus[tagString] 1271 vol, err := f.storage.Volume(volTag) 1272 if err != nil { 1273 return errors.Trace(err) 1274 } 1275 now := f.clock.Now() 1276 err = vol.SetStatus(status.StatusInfo{ 1277 Status: volStatus.Status, 1278 Message: volStatus.Message, 1279 Data: volStatus.Data, 1280 Since: &now, 1281 }) 1282 if err != nil { 1283 return errors.Trace(err) 1284 } 1285 } 1286 1287 return nil 1288 } 1289 1290 func (f *Facade) updateFilesystemInfo(filesystemUpdates map[string]filesystemInfo, filesystemStatus map[string]status.StatusInfo) error { 1291 // Do it in sorted order so it's deterministic for tests. 1292 var fsTags []string 1293 for tag := range filesystemUpdates { 1294 fsTags = append(fsTags, tag) 1295 } 1296 sort.Strings(fsTags) 1297 1298 logger.Debugf("updating filesystem data: %+v", filesystemUpdates) 1299 for _, tagString := range fsTags { 1300 fsTag, _ := names.ParseFilesystemTag(tagString) 1301 fsData := filesystemUpdates[tagString] 1302 1303 fs, err := f.storage.Filesystem(fsTag) 1304 if err != nil { 1305 return errors.Trace(err) 1306 } 1307 // If we have already recorded the provisioning info, 1308 // it's an error to try and do it again. 1309 _, err = fs.Info() 1310 if err != nil && !errors.IsNotProvisioned(err) { 1311 return errors.Trace(err) 1312 } 1313 if err != nil { 1314 // Provisioning info not set yet. 1315 err = f.storage.SetFilesystemInfo(fsTag, state.FilesystemInfo{ 1316 Size: fsData.size, 1317 FilesystemId: fsData.filesystemId, 1318 }) 1319 if err != nil { 1320 return errors.Trace(err) 1321 } 1322 } 1323 1324 err = f.storage.SetFilesystemAttachmentInfo(fsData.unitTag, fsTag, state.FilesystemAttachmentInfo{ 1325 MountPoint: fsData.mountPoint, 1326 ReadOnly: fsData.readOnly, 1327 }) 1328 if err != nil { 1329 return errors.Trace(err) 1330 } 1331 } 1332 1333 // Do it in sorted order so it's deterministic for tests. 1334 fsTags = []string{} 1335 for tag := range filesystemStatus { 1336 fsTags = append(fsTags, tag) 1337 } 1338 sort.Strings(fsTags) 1339 1340 logger.Debugf("updating filesystem status: %+v", filesystemStatus) 1341 for _, tagString := range fsTags { 1342 fsTag, _ := names.ParseFilesystemTag(tagString) 1343 fsStatus := filesystemStatus[tagString] 1344 fs, err := f.storage.Filesystem(fsTag) 1345 if err != nil { 1346 return errors.Trace(err) 1347 } 1348 now := f.clock.Now() 1349 err = fs.SetStatus(status.StatusInfo{ 1350 Status: fsStatus.Status, 1351 Message: fsStatus.Message, 1352 Data: fsStatus.Data, 1353 Since: &now, 1354 }) 1355 if err != nil { 1356 return errors.Trace(err) 1357 } 1358 } 1359 1360 return nil 1361 } 1362 1363 // ClearApplicationsResources clears the flags which indicate 1364 // applications still have resources in the cluster. 1365 func (f *Facade) ClearApplicationsResources(args params.Entities) (params.ErrorResults, error) { 1366 result := params.ErrorResults{ 1367 Results: make([]params.ErrorResult, len(args.Entities)), 1368 } 1369 if len(args.Entities) == 0 { 1370 return result, nil 1371 } 1372 for i, entity := range args.Entities { 1373 appTag, err := names.ParseApplicationTag(entity.Tag) 1374 if err != nil { 1375 result.Results[i].Error = apiservererrors.ServerError(err) 1376 continue 1377 } 1378 app, err := f.state.Application(appTag.Id()) 1379 if err != nil { 1380 result.Results[i].Error = apiservererrors.ServerError(err) 1381 continue 1382 } 1383 err = app.ClearResources() 1384 if err != nil { 1385 result.Results[i].Error = apiservererrors.ServerError(err) 1386 } 1387 } 1388 return result, nil 1389 } 1390 1391 // UpdateApplicationsService updates the Juju data model to reflect the given 1392 // service details of the specified application. 1393 func (f *Facade) UpdateApplicationsService(args params.UpdateApplicationServiceArgs) (params.ErrorResults, error) { 1394 result := params.ErrorResults{ 1395 Results: make([]params.ErrorResult, len(args.Args)), 1396 } 1397 if len(args.Args) == 0 { 1398 return result, nil 1399 } 1400 for i, appUpdate := range args.Args { 1401 appTag, err := names.ParseApplicationTag(appUpdate.ApplicationTag) 1402 if err != nil { 1403 result.Results[i].Error = apiservererrors.ServerError(err) 1404 continue 1405 } 1406 app, err := f.state.Application(appTag.Id()) 1407 if err != nil { 1408 result.Results[i].Error = apiservererrors.ServerError(err) 1409 continue 1410 } 1411 1412 sAddrs, err := params.ToProviderAddresses(appUpdate.Addresses...).ToSpaceAddresses(f.state) 1413 if err != nil { 1414 result.Results[i].Error = apiservererrors.ServerError(err) 1415 continue 1416 } 1417 1418 if err := app.UpdateCloudService(appUpdate.ProviderId, sAddrs); err != nil { 1419 result.Results[i].Error = apiservererrors.ServerError(err) 1420 } 1421 if appUpdate.Scale != nil { 1422 var generation int64 1423 if appUpdate.Generation != nil { 1424 generation = *appUpdate.Generation 1425 } 1426 if err := app.SetScale(*appUpdate.Scale, generation, false); err != nil { 1427 result.Results[i].Error = apiservererrors.ServerError(err) 1428 } 1429 } 1430 } 1431 return result, nil 1432 } 1433 1434 // SetOperatorStatus updates the operator status for each given application. 1435 func (f *Facade) SetOperatorStatus(args params.SetStatus) (params.ErrorResults, error) { 1436 result := params.ErrorResults{ 1437 Results: make([]params.ErrorResult, len(args.Entities)), 1438 } 1439 for i, arg := range args.Entities { 1440 appTag, err := names.ParseApplicationTag(arg.Tag) 1441 if err != nil { 1442 result.Results[i].Error = apiservererrors.ServerError(err) 1443 continue 1444 } 1445 app, err := f.state.Application(appTag.Id()) 1446 if err != nil { 1447 result.Results[i].Error = apiservererrors.ServerError(err) 1448 continue 1449 } 1450 now := f.clock.Now() 1451 s := status.StatusInfo{ 1452 Status: status.Status(arg.Status), 1453 Message: arg.Info, 1454 Data: arg.Data, 1455 Since: &now, 1456 } 1457 if err := app.SetOperatorStatus(s); err != nil { 1458 result.Results[i].Error = apiservererrors.ServerError(err) 1459 } 1460 } 1461 return result, nil 1462 }