github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "sort" 8 9 "github.com/juju/clock" 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/apiserver/common/storagecommon" 17 "github.com/juju/juju/apiserver/facade" 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/caas" 20 "github.com/juju/juju/controller" 21 "github.com/juju/juju/core/status" 22 "github.com/juju/juju/environs/config" 23 "github.com/juju/juju/environs/tags" 24 "github.com/juju/juju/state" 25 "github.com/juju/juju/state/stateenvirons" 26 "github.com/juju/juju/state/watcher" 27 "github.com/juju/juju/storage" 28 "github.com/juju/juju/storage/poolmanager" 29 ) 30 31 var logger = loggo.GetLogger("juju.apiserver.controller.caasunitprovisioner") 32 33 type Facade struct { 34 *common.LifeGetter 35 resources facade.Resources 36 state CAASUnitProvisionerState 37 storage StorageBackend 38 storageProviderRegistry storage.ProviderRegistry 39 storagePoolManager poolmanager.PoolManager 40 devices DeviceBackend 41 clock clock.Clock 42 } 43 44 // NewStateFacade provides the signature required for facade registration. 45 func NewStateFacade(ctx facade.Context) (*Facade, error) { 46 authorizer := ctx.Auth() 47 resources := ctx.Resources() 48 sb, err := state.NewStorageBackend(ctx.State()) 49 if err != nil { 50 return nil, errors.Trace(err) 51 } 52 db, err := state.NewDeviceBackend(ctx.State()) 53 if err != nil { 54 return nil, errors.Trace(err) 55 } 56 57 broker, err := stateenvirons.GetNewCAASBrokerFunc(caas.New)(ctx.State()) 58 if err != nil { 59 return nil, errors.Annotate(err, "getting caas client") 60 } 61 registry := stateenvirons.NewStorageProviderRegistry(broker) 62 pm := poolmanager.New(state.NewStateSettings(ctx.State()), registry) 63 64 return NewFacade( 65 resources, 66 authorizer, 67 stateShim{ctx.State()}, 68 sb, 69 db, 70 registry, 71 pm, 72 clock.WallClock, 73 ) 74 } 75 76 // NewFacade returns a new CAAS unit provisioner Facade facade. 77 func NewFacade( 78 resources facade.Resources, 79 authorizer facade.Authorizer, 80 st CAASUnitProvisionerState, 81 sb StorageBackend, 82 db DeviceBackend, 83 storageProviderRegistry storage.ProviderRegistry, 84 storagePoolManager poolmanager.PoolManager, 85 clock clock.Clock, 86 ) (*Facade, error) { 87 if !authorizer.AuthController() { 88 return nil, common.ErrPerm 89 } 90 return &Facade{ 91 LifeGetter: common.NewLifeGetter( 92 st, common.AuthAny( 93 common.AuthFuncForTagKind(names.ApplicationTagKind), 94 common.AuthFuncForTagKind(names.UnitTagKind), 95 ), 96 ), 97 resources: resources, 98 state: st, 99 storage: sb, 100 devices: db, 101 storageProviderRegistry: storageProviderRegistry, 102 storagePoolManager: storagePoolManager, 103 clock: clock, 104 }, nil 105 } 106 107 // WatchApplications starts a StringsWatcher to watch CAAS applications 108 // deployed to this model. 109 func (f *Facade) WatchApplications() (params.StringsWatchResult, error) { 110 watch := f.state.WatchApplications() 111 if changes, ok := <-watch.Changes(); ok { 112 return params.StringsWatchResult{ 113 StringsWatcherId: f.resources.Register(watch), 114 Changes: changes, 115 }, nil 116 } 117 return params.StringsWatchResult{}, watcher.EnsureErr(watch) 118 } 119 120 // WatchApplicationsScale starts a NotifyWatcher to watch changes 121 // to the applications' scale. 122 func (f *Facade) WatchApplicationsScale(args params.Entities) (params.NotifyWatchResults, error) { 123 results := params.NotifyWatchResults{ 124 Results: make([]params.NotifyWatchResult, len(args.Entities)), 125 } 126 for i, arg := range args.Entities { 127 id, err := f.watchApplicationScale(arg.Tag) 128 if err != nil { 129 results.Results[i].Error = common.ServerError(err) 130 continue 131 } 132 results.Results[i].NotifyWatcherId = id 133 } 134 return results, nil 135 } 136 137 func (f *Facade) watchApplicationScale(tagString string) (string, error) { 138 tag, err := names.ParseApplicationTag(tagString) 139 if err != nil { 140 return "", errors.Trace(err) 141 } 142 app, err := f.state.Application(tag.Id()) 143 if err != nil { 144 return "", errors.Trace(err) 145 } 146 w := app.WatchScale() 147 if _, ok := <-w.Changes(); ok { 148 return f.resources.Register(w), nil 149 } 150 return "", watcher.EnsureErr(w) 151 } 152 153 // WatchPodSpec starts a NotifyWatcher to watch changes to the 154 // pod spec for specified units in this model. 155 func (f *Facade) WatchPodSpec(args params.Entities) (params.NotifyWatchResults, error) { 156 model, err := f.state.Model() 157 if err != nil { 158 return params.NotifyWatchResults{}, errors.Trace(err) 159 } 160 results := params.NotifyWatchResults{ 161 Results: make([]params.NotifyWatchResult, len(args.Entities)), 162 } 163 for i, arg := range args.Entities { 164 id, err := f.watchPodSpec(model, arg.Tag) 165 if err != nil { 166 results.Results[i].Error = common.ServerError(err) 167 continue 168 } 169 results.Results[i].NotifyWatcherId = id 170 } 171 return results, nil 172 } 173 174 func (f *Facade) watchPodSpec(model Model, tagString string) (string, error) { 175 tag, err := names.ParseApplicationTag(tagString) 176 if err != nil { 177 return "", errors.Trace(err) 178 } 179 w, err := model.WatchPodSpec(tag) 180 if err != nil { 181 return "", errors.Trace(err) 182 } 183 if _, ok := <-w.Changes(); ok { 184 return f.resources.Register(w), nil 185 } 186 return "", watcher.EnsureErr(w) 187 } 188 189 // ApplicationsScale returns the scaling info for specified applications in this model. 190 func (f *Facade) ApplicationsScale(args params.Entities) (params.IntResults, error) { 191 results := params.IntResults{ 192 Results: make([]params.IntResult, len(args.Entities)), 193 } 194 for i, arg := range args.Entities { 195 scale, err := f.applicationScale(arg.Tag) 196 if err != nil { 197 results.Results[i].Error = common.ServerError(err) 198 continue 199 } 200 results.Results[i].Result = scale 201 } 202 logger.Debugf("provisioning info result: %#v", results) 203 return results, nil 204 } 205 206 func (f *Facade) applicationScale(tagString string) (int, error) { 207 appTag, err := names.ParseApplicationTag(tagString) 208 if err != nil { 209 return 0, errors.Trace(err) 210 } 211 app, err := f.state.Application(appTag.Id()) 212 if err != nil { 213 return 0, errors.Trace(err) 214 } 215 return app.GetScale(), nil 216 } 217 218 // ProvisioningInfo returns the provisioning info for specified applications in this model. 219 func (f *Facade) ProvisioningInfo(args params.Entities) (params.KubernetesProvisioningInfoResults, error) { 220 model, err := f.state.Model() 221 if err != nil { 222 return params.KubernetesProvisioningInfoResults{}, errors.Trace(err) 223 } 224 results := params.KubernetesProvisioningInfoResults{ 225 Results: make([]params.KubernetesProvisioningInfoResult, len(args.Entities)), 226 } 227 for i, arg := range args.Entities { 228 info, err := f.provisioningInfo(model, arg.Tag) 229 if err != nil { 230 results.Results[i].Error = common.ServerError(err) 231 continue 232 } 233 results.Results[i].Result = info 234 } 235 logger.Debugf("provisioning info result: %#v", results) 236 return results, nil 237 } 238 239 func (f *Facade) provisioningInfo(model Model, tagString string) (*params.KubernetesProvisioningInfo, error) { 240 appTag, err := names.ParseApplicationTag(tagString) 241 if err != nil { 242 return nil, errors.Trace(err) 243 } 244 // First the pod spec. 245 podSpec, err := model.PodSpec(appTag) 246 if err != nil { 247 return nil, errors.Trace(err) 248 } 249 250 // Now get any required storage. We need to provision storage 251 // at the same time as the pod as it can't be attached later. 252 253 // All units are currently homogeneous so we just 254 // need to get info for the first alive unit. 255 app, err := f.state.Application(appTag.Id()) 256 if err != nil { 257 return nil, errors.Trace(err) 258 } 259 units, err := app.AllUnits() 260 if err != nil { 261 return nil, errors.Trace(err) 262 } 263 // Can happen if scale is set to 0 in k8s, outside of Juju. 264 // In this case, there is no provisioning info to return. 265 if len(units) == 0 { 266 logger.Debugf("cannot provision application %q with no units", appTag.Id()) 267 return nil, nil 268 } 269 modelConfig, err := model.ModelConfig() 270 if err != nil { 271 return nil, errors.Trace(err) 272 } 273 274 // Find the first alive unit which will be used to get filesystem info. 275 var aliveUnit Unit 276 for _, u := range units { 277 if u.Life() == state.Alive { 278 aliveUnit = u 279 break 280 } 281 } 282 283 controllerCfg, err := f.state.ControllerConfig() 284 if err != nil { 285 return nil, errors.Trace(err) 286 } 287 288 var filesystemParams []params.KubernetesFilesystemParams 289 if aliveUnit != nil { 290 filesystemParams, err = f.applicationFilesystemParams(controllerCfg, modelConfig, aliveUnit.UnitTag()) 291 if err != nil { 292 return nil, errors.Trace(err) 293 } 294 295 // The juju-storage-owner tag is set to the unit. We use it as a label on the CAAS volume. 296 // Since we used an arbitrary unit to get the info, reset the tag to the application. 297 for _, fsp := range filesystemParams { 298 fsp.Tags[tags.JujuStorageOwner] = appTag.Id() 299 } 300 } 301 302 devices, err := f.devicesParams(app) 303 if err != nil { 304 return nil, errors.Trace(err) 305 } 306 cons, err := app.Constraints() 307 if err != nil { 308 return nil, errors.Trace(err) 309 } 310 resourceTags := tags.ResourceTags( 311 names.NewModelTag(modelConfig.UUID()), 312 names.NewControllerTag(controllerCfg.ControllerUUID()), 313 modelConfig, 314 ) 315 316 return ¶ms.KubernetesProvisioningInfo{ 317 PodSpec: podSpec, 318 Filesystems: filesystemParams, 319 Devices: devices, 320 Constraints: cons, 321 Placement: app.GetPlacement(), 322 Tags: resourceTags, 323 }, nil 324 } 325 326 func filesystemParams( 327 f state.Filesystem, 328 storageInstance state.StorageInstance, 329 modelUUID, controllerUUID string, 330 modelConfig *config.Config, 331 poolManager poolmanager.PoolManager, 332 registry storage.ProviderRegistry, 333 ) (params.KubernetesFilesystemParams, error) { 334 335 var pool string 336 var size uint64 337 if stateFilesystemParams, ok := f.Params(); ok { 338 pool = stateFilesystemParams.Pool 339 size = stateFilesystemParams.Size 340 } else { 341 filesystemInfo, err := f.Info() 342 if err != nil { 343 return params.KubernetesFilesystemParams{}, errors.Trace(err) 344 } 345 pool = filesystemInfo.Pool 346 size = filesystemInfo.Size 347 } 348 349 filesystemTags, err := storagecommon.StorageTags(storageInstance, modelUUID, controllerUUID, modelConfig) 350 if err != nil { 351 return params.KubernetesFilesystemParams{}, errors.Annotate(err, "computing storage tags") 352 } 353 354 providerType, cfg, err := storagecommon.StoragePoolConfig(pool, poolManager, registry) 355 if err != nil { 356 return params.KubernetesFilesystemParams{}, errors.Trace(err) 357 } 358 result := params.KubernetesFilesystemParams{ 359 Provider: string(providerType), 360 Attributes: cfg.Attrs(), 361 Tags: filesystemTags, 362 Size: size, 363 StorageName: storageInstance.StorageName(), 364 } 365 return result, nil 366 } 367 368 // applicationFilesystemParams retrieves FilesystemParams for the filesystems 369 // that should be provisioned with, and attached to, pods of the application. 370 func (f *Facade) applicationFilesystemParams( 371 controllerConfig controller.Config, 372 modelConfig *config.Config, 373 unitTag names.UnitTag, 374 ) ([]params.KubernetesFilesystemParams, error) { 375 attachments, err := f.storage.UnitStorageAttachments(unitTag) 376 if err != nil { 377 return nil, errors.Trace(err) 378 } 379 if len(attachments) == 0 { 380 return nil, nil 381 } 382 383 allFilesystemParams := make([]params.KubernetesFilesystemParams, 0, len(attachments)) 384 for _, attachment := range attachments { 385 si, err := f.storage.StorageInstance(attachment.StorageInstance()) 386 if err != nil { 387 return nil, errors.Trace(err) 388 } 389 fs, err := f.storage.StorageInstanceFilesystem(si.StorageTag()) 390 if err != nil { 391 return nil, errors.Trace(err) 392 } 393 filesystemParams, err := filesystemParams( 394 fs, si, modelConfig.UUID(), controllerConfig.ControllerUUID(), 395 modelConfig, f.storagePoolManager, f.storageProviderRegistry, 396 ) 397 if err != nil { 398 return nil, errors.Annotatef(err, "getting filesystem %q parameters", fs.Tag().Id()) 399 } 400 filesystemAttachment, err := f.storage.FilesystemAttachment(unitTag, fs.FilesystemTag()) 401 if err != nil { 402 return nil, errors.Annotatef(err, "getting filesystem %q attachment info", fs.Tag().Id()) 403 } 404 var location string 405 var readOnly bool 406 if filesystemAttachmentParams, ok := filesystemAttachment.Params(); ok { 407 location = filesystemAttachmentParams.Location 408 readOnly = filesystemAttachmentParams.ReadOnly 409 } else { 410 // All units are the same so even if the attachment exists 411 // for the unit used to gather info, we still need to read 412 // the relevant attachment params for the application as a whole. 413 filesystemAttachmentInfo, err := filesystemAttachment.Info() 414 if err != nil { 415 return nil, errors.Trace(err) 416 } 417 location = filesystemAttachmentInfo.MountPoint 418 readOnly = filesystemAttachmentInfo.ReadOnly 419 } 420 filesystemAttachmentParams := params.KubernetesFilesystemAttachmentParams{ 421 Provider: filesystemParams.Provider, 422 MountPoint: location, 423 ReadOnly: readOnly, 424 } 425 filesystemParams.Attachment = &filesystemAttachmentParams 426 allFilesystemParams = append(allFilesystemParams, filesystemParams) 427 } 428 return allFilesystemParams, nil 429 } 430 431 func (f *Facade) devicesParams(app Application) ([]params.KubernetesDeviceParams, error) { 432 devices, err := app.DeviceConstraints() 433 if err != nil { 434 return nil, errors.Trace(err) 435 } 436 logger.Debugf("getting device constraints from state: %#v", devices) 437 var devicesParams []params.KubernetesDeviceParams 438 for _, d := range devices { 439 devicesParams = append(devicesParams, params.KubernetesDeviceParams{ 440 Type: params.DeviceType(d.Type), 441 Count: d.Count, 442 Attributes: d.Attributes, 443 }) 444 } 445 return devicesParams, nil 446 } 447 448 // ApplicationsConfig returns the config for the specified applications. 449 func (f *Facade) ApplicationsConfig(args params.Entities) (params.ApplicationGetConfigResults, error) { 450 results := params.ApplicationGetConfigResults{ 451 Results: make([]params.ConfigResult, len(args.Entities)), 452 } 453 for i, arg := range args.Entities { 454 result, err := f.getApplicationConfig(arg.Tag) 455 results.Results[i].Config = result 456 results.Results[i].Error = common.ServerError(err) 457 } 458 return results, nil 459 } 460 461 func (f *Facade) getApplicationConfig(tagString string) (map[string]interface{}, error) { 462 tag, err := names.ParseApplicationTag(tagString) 463 if err != nil { 464 return nil, errors.Trace(err) 465 } 466 app, err := f.state.Application(tag.Id()) 467 if err != nil { 468 return nil, errors.Trace(err) 469 } 470 return app.ApplicationConfig() 471 } 472 473 // UpdateApplicationsUnits updates the Juju data model to reflect the given 474 // units of the specified application. 475 func (a *Facade) UpdateApplicationsUnits(args params.UpdateApplicationUnitArgs) (params.ErrorResults, error) { 476 result := params.ErrorResults{ 477 Results: make([]params.ErrorResult, len(args.Args)), 478 } 479 if len(args.Args) == 0 { 480 return result, nil 481 } 482 for i, appUpdate := range args.Args { 483 appTag, err := names.ParseApplicationTag(appUpdate.ApplicationTag) 484 if err != nil { 485 result.Results[i].Error = common.ServerError(err) 486 continue 487 } 488 app, err := a.state.Application(appTag.Id()) 489 if err != nil { 490 result.Results[i].Error = common.ServerError(err) 491 continue 492 } 493 err = a.updateUnitsFromCloud(app, appUpdate.Units) 494 if err != nil { 495 // Mask any not found errors as the worker (caller) treats them specially 496 // and they are not relevant here. 497 result.Results[i].Error = common.ServerError(errors.Mask(err)) 498 } 499 } 500 return result, nil 501 } 502 503 // updateStatus constructs the agent and cloud container status values. 504 func (a *Facade) updateStatus(params params.ApplicationUnitParams) ( 505 agentStatus *status.StatusInfo, 506 cloudContainerStatus *status.StatusInfo, 507 ) { 508 var containerStatus status.Status 509 switch status.Status(params.Status) { 510 case status.Unknown: 511 // The container runtime can spam us with unimportant 512 // status updates, so ignore any irrelevant ones. 513 return nil, nil 514 case status.Allocating: 515 // The container runtime has decided to restart the pod. 516 agentStatus = &status.StatusInfo{ 517 Status: status.Allocating, 518 Message: params.Info, 519 } 520 containerStatus = status.Waiting 521 case status.Running: 522 // A pod has finished starting so the workload is now active. 523 agentStatus = &status.StatusInfo{ 524 Status: status.Idle, 525 } 526 containerStatus = status.Running 527 case status.Error: 528 agentStatus = &status.StatusInfo{ 529 Status: status.Error, 530 Message: params.Info, 531 Data: params.Data, 532 } 533 containerStatus = status.Error 534 case status.Blocked: 535 containerStatus = status.Blocked 536 agentStatus = &status.StatusInfo{ 537 Status: status.Idle, 538 } 539 } 540 cloudContainerStatus = &status.StatusInfo{ 541 Status: containerStatus, 542 Message: params.Info, 543 Data: params.Data, 544 } 545 return agentStatus, cloudContainerStatus 546 } 547 548 // updateUnitsFromCloud takes a slice of unit information provided by an external 549 // source (typically a cloud update event) and merges that with the existing unit 550 // data model in state. The passed in units are the complete set for the cloud, so 551 // any existing units in state with provider ids which aren't in the set will be removed. 552 func (a *Facade) updateUnitsFromCloud(app Application, unitUpdates []params.ApplicationUnitParams) error { 553 logger.Debugf("unit updates: %#v", unitUpdates) 554 // Set up the initial data structures. 555 existingStateUnits, err := app.AllUnits() 556 if err != nil { 557 return errors.Trace(err) 558 } 559 560 stateUnitsById := make(map[string]Unit) 561 cloudPodsById := make(map[string]params.ApplicationUnitParams) 562 563 // Record all unit provider ids known to exist in the cloud. 564 for _, u := range unitUpdates { 565 cloudPodsById[u.ProviderId] = u 566 } 567 568 stateUnitExistsInCloud := func(providerId string) bool { 569 if providerId == "" { 570 return false 571 } 572 _, ok := cloudPodsById[providerId] 573 return ok 574 } 575 576 unitInfo := &updateStateUnitParams{ 577 stateUnitsInCloud: make(map[string]Unit), 578 deletedRemoved: true, 579 } 580 var ( 581 // aliveStateIds holds the provider ids of alive units in state. 582 aliveStateIds = set.NewStrings() 583 584 // extraStateIds holds the provider ids of units in state which 585 // no longer exist in the cloud. 586 extraStateIds = set.NewStrings() 587 ) 588 589 // Loop over any existing state units and record those which do not yet have 590 // provider ids, and those which have been removed or updated. 591 for _, u := range existingStateUnits { 592 var providerId string 593 info, err := u.ContainerInfo() 594 if err != nil && !errors.IsNotFound(err) { 595 return errors.Trace(err) 596 } 597 if err == nil { 598 providerId = info.ProviderId() 599 } 600 601 unitAlive := u.Life() == state.Alive 602 if !unitAlive { 603 continue 604 } 605 606 if providerId == "" { 607 logger.Debugf("unit %q is not associated with any pod", u.Name()) 608 unitInfo.unassociatedUnits = append(unitInfo.unassociatedUnits, u) 609 continue 610 } 611 stateUnitsById[providerId] = u 612 stateUnitInCloud := stateUnitExistsInCloud(providerId) 613 aliveStateIds.Add(providerId) 614 615 if stateUnitInCloud { 616 logger.Debugf("unit %q (%v) has changed in the cloud", u.Name(), providerId) 617 unitInfo.stateUnitsInCloud[u.UnitTag().String()] = u 618 } else { 619 extraStateIds.Add(providerId) 620 } 621 } 622 623 // Do it in sorted order so it's deterministic for tests. 624 var ids []string 625 for id := range cloudPodsById { 626 ids = append(ids, id) 627 } 628 sort.Strings(ids) 629 630 // Sort extra ids also to guarantee order. 631 var extraIds []string 632 for id := range extraStateIds { 633 extraIds = append(extraIds, id) 634 } 635 sort.Strings(extraIds) 636 unassociatedUnitCount := len(unitInfo.unassociatedUnits) 637 638 for _, id := range ids { 639 u := cloudPodsById[id] 640 if aliveStateIds.Contains(id) { 641 u.UnitTag = stateUnitsById[id].UnitTag().String() 642 unitInfo.existingCloudPods = append(unitInfo.existingCloudPods, u) 643 continue 644 } 645 646 // First attempt to add any new cloud pod not yet represented in state 647 // to a unit which does not yet have a provider id. 648 if unassociatedUnitCount > 0 { 649 unassociatedUnitCount -= 1 650 unitInfo.addedCloudPods = append(unitInfo.addedCloudPods, u) 651 continue 652 } 653 654 // A new pod was added to the cloud but does not yet have a unit in state. 655 unitInfo.addedCloudPods = append(unitInfo.addedCloudPods, u) 656 } 657 658 // If there are any extra provider ids left over after allocating all the cloud pods, 659 // then consider those state units as terminated. 660 for _, providerId := range extraStateIds.Values() { 661 u := stateUnitsById[providerId] 662 logger.Debugf("unit %q (%v) has been removed from the cloud", u.Name(), providerId) 663 unitInfo.removedUnits = append(unitInfo.removedUnits, u) 664 } 665 666 return a.updateStateUnits(app, unitInfo) 667 } 668 669 type updateStateUnitParams struct { 670 stateUnitsInCloud map[string]Unit 671 addedCloudPods []params.ApplicationUnitParams 672 existingCloudPods []params.ApplicationUnitParams 673 removedUnits []Unit 674 unassociatedUnits []Unit 675 deletedRemoved bool 676 } 677 678 type filesystemInfo struct { 679 unitTag names.UnitTag 680 providerId string 681 mountPoint string 682 readOnly bool 683 size uint64 684 filesystemId string 685 } 686 687 type volumeInfo struct { 688 unitTag names.UnitTag 689 providerId string 690 readOnly bool 691 persistent bool 692 size uint64 693 volumeId string 694 } 695 696 func (a *Facade) updateStateUnits(app Application, unitInfo *updateStateUnitParams) error { 697 698 if app.Life() != state.Alive { 699 // We ignore any updates for dying applications. 700 logger.Debugf("ignoring unit updates for dying application: %v", app.Name()) 701 return nil 702 } 703 704 logger.Tracef("added cloud units: %+v", unitInfo.addedCloudPods) 705 logger.Tracef("existing cloud units: %+v", unitInfo.existingCloudPods) 706 logger.Tracef("removed units: %+v", unitInfo.removedUnits) 707 logger.Tracef("unassociated units: %+v", unitInfo.unassociatedUnits) 708 709 // Now we have the added, removed, updated units all sorted, 710 // generate the state update operations. 711 var unitUpdate state.UpdateUnitsOperation 712 713 filesystemUpdates := make(map[string]filesystemInfo) 714 filesystemStatus := make(map[string]status.StatusInfo) 715 volumeUpdates := make(map[string]volumeInfo) 716 volumeStatus := make(map[string]status.StatusInfo) 717 718 for _, u := range unitInfo.removedUnits { 719 // If a unit is removed from the cloud, all filesystems are considered detached. 720 unitStorage, err := a.storage.UnitStorageAttachments(u.UnitTag()) 721 if err != nil { 722 return errors.Trace(err) 723 } 724 for _, sa := range unitStorage { 725 fs, err := a.storage.StorageInstanceFilesystem(sa.StorageInstance()) 726 if err != nil { 727 return errors.Trace(err) 728 } 729 filesystemStatus[fs.FilesystemTag().String()] = status.StatusInfo{Status: status.Detached} 730 } 731 732 if unitInfo.deletedRemoved { 733 unitUpdate.Deletes = append(unitUpdate.Deletes, u.DestroyOperation()) 734 } 735 // We'll set the status as Terminated. This will either be transient, as will 736 // occur when a pod is restarted external to Juju, or permanent if the pod has 737 // been deleted external to Juju. In the latter case, juju remove-unit will be 738 // need to clean things up on the Juju side. 739 cloudContainerStatus := &status.StatusInfo{ 740 Status: status.Terminated, 741 Message: "unit stopped by the cloud", 742 } 743 agentStatus := &status.StatusInfo{ 744 Status: status.Idle, 745 } 746 updateProps := state.UnitUpdateProperties{ 747 CloudContainerStatus: cloudContainerStatus, 748 AgentStatus: agentStatus, 749 } 750 unitUpdate.Updates = append(unitUpdate.Updates, 751 u.UpdateOperation(updateProps)) 752 } 753 754 processUnitParams := func(unitParams params.ApplicationUnitParams) *state.UnitUpdateProperties { 755 agentStatus, cloudContainerStatus := a.updateStatus(unitParams) 756 return &state.UnitUpdateProperties{ 757 ProviderId: &unitParams.ProviderId, 758 Address: &unitParams.Address, 759 Ports: &unitParams.Ports, 760 AgentStatus: agentStatus, 761 CloudContainerStatus: cloudContainerStatus, 762 } 763 } 764 765 processFilesystemParams := func(processedFilesystemIds set.Strings, unitTag names.UnitTag, unitParams params.ApplicationUnitParams) error { 766 // Once a unit is available in the cluster, we consider 767 // its filesystem(s) to be attached since the unit is 768 // not considered ready until this happens. 769 filesystemInfoByName := make(map[string][]params.KubernetesFilesystemInfo) 770 for _, fsInfo := range unitParams.FilesystemInfo { 771 infos := filesystemInfoByName[fsInfo.StorageName] 772 infos = append(infos, fsInfo) 773 filesystemInfoByName[fsInfo.StorageName] = infos 774 } 775 776 for storageName, infos := range filesystemInfoByName { 777 logger.Debugf("updating storage %v for %v", storageName, unitTag) 778 if len(infos) == 0 { 779 continue 780 } 781 782 unitStorage, err := a.storage.UnitStorageAttachments(unitTag) 783 if err != nil { 784 return errors.Trace(err) 785 } 786 787 // Loop over all the storage for the unit ans skip storage not 788 // relevant for storageName. 789 // TODO(caas) - Add storage bankend API to get all unit storage instances for a named storage. 790 for _, sa := range unitStorage { 791 si, err := a.storage.StorageInstance(sa.StorageInstance()) 792 if errors.IsNotFound(err) { 793 logger.Warningf("ignoring non-existent storage instance %v for unit %v", sa.StorageInstance(), unitTag.Id()) 794 continue 795 } 796 if err != nil { 797 return errors.Trace(err) 798 } 799 if si.StorageName() != storageName { 800 continue 801 } 802 fs, err := a.storage.StorageInstanceFilesystem(sa.StorageInstance()) 803 if err != nil { 804 return errors.Trace(err) 805 } 806 fsInfo := infos[0] 807 processedFilesystemIds.Add(fsInfo.FilesystemId) 808 809 // k8s reports provisioned info even when the volume is not ready. 810 // Only update state when volume is created so Juju doesn't think 811 // the volume is active when it's not. 812 if fsInfo.Status != status.Pending.String() { 813 filesystemUpdates[fs.FilesystemTag().String()] = filesystemInfo{ 814 unitTag: unitTag, 815 providerId: unitParams.ProviderId, 816 mountPoint: fsInfo.MountPoint, 817 readOnly: fsInfo.ReadOnly, 818 size: fsInfo.Size, 819 filesystemId: fsInfo.FilesystemId, 820 } 821 } 822 filesystemStatus[fs.FilesystemTag().String()] = status.StatusInfo{ 823 Status: status.Status(fsInfo.Status), 824 Message: fsInfo.Info, 825 Data: fsInfo.Data, 826 } 827 828 vol, err := a.storage.StorageInstanceVolume(sa.StorageInstance()) 829 if err != nil { 830 return errors.Trace(err) 831 } 832 if fsInfo.Volume.Status != status.Pending.String() { 833 volumeUpdates[vol.VolumeTag().String()] = volumeInfo{ 834 unitTag: unitTag, 835 providerId: unitParams.ProviderId, 836 size: fsInfo.Volume.Size, 837 volumeId: fsInfo.Volume.VolumeId, 838 persistent: fsInfo.Volume.Persistent, 839 readOnly: fsInfo.ReadOnly, 840 } 841 } 842 volumeStatus[vol.VolumeTag().String()] = status.StatusInfo{ 843 Status: status.Status(fsInfo.Volume.Status), 844 Message: fsInfo.Volume.Info, 845 Data: fsInfo.Volume.Data, 846 } 847 848 infos = infos[1:] 849 if len(infos) == 0 { 850 break 851 } 852 } 853 } 854 return nil 855 } 856 857 var unitParamsWithFilesystemInfo []params.ApplicationUnitParams 858 859 for _, unitParams := range unitInfo.existingCloudPods { 860 u, ok := unitInfo.stateUnitsInCloud[unitParams.UnitTag] 861 if !ok { 862 logger.Warningf("unexpected unit parameters %+v not in state", unitParams) 863 continue 864 } 865 updateProps := processUnitParams(unitParams) 866 if len(unitParams.FilesystemInfo) > 0 { 867 unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams) 868 } 869 unitUpdate.Updates = append(unitUpdate.Updates, 870 u.UpdateOperation(*updateProps)) 871 } 872 873 // For newly added units in the cloud, either update state units which 874 // exist but which do not yet have provider ids (recording the provider 875 // id as well), or add a brand new unit. 876 idx := 0 877 for _, unitParams := range unitInfo.addedCloudPods { 878 if idx < len(unitInfo.unassociatedUnits) { 879 u := unitInfo.unassociatedUnits[idx] 880 updateProps := processUnitParams(unitParams) 881 unitUpdate.Updates = append(unitUpdate.Updates, 882 u.UpdateOperation(*updateProps)) 883 idx += 1 884 if len(unitParams.FilesystemInfo) > 0 { 885 unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams) 886 } 887 continue 888 } 889 890 // Process units added directly in the cloud instead of via Juju. 891 updateProps := processUnitParams(unitParams) 892 if len(unitParams.FilesystemInfo) > 0 { 893 unitParamsWithFilesystemInfo = append(unitParamsWithFilesystemInfo, unitParams) 894 } 895 unitUpdate.Adds = append(unitUpdate.Adds, 896 app.AddOperation(*updateProps)) 897 } 898 err := app.UpdateUnits(&unitUpdate) 899 // We ignore any updates for dying applications. 900 if state.IsNotAlive(err) { 901 return nil 902 } 903 904 // Now update filesystem info - attachment data and status. 905 // For units added to the cloud directly, we first need to lookup the 906 // newly created unit tag from Juju using the cloud provider ids. 907 var providerIds []string 908 for _, unitParams := range unitParamsWithFilesystemInfo { 909 if unitParams.UnitTag == "" { 910 providerIds = append(providerIds, unitParams.ProviderId) 911 } 912 } 913 m, err := a.state.Model() 914 if err != nil { 915 return errors.Trace(err) 916 } 917 var providerIdToUnit = make(map[string]names.UnitTag) 918 containers, err := m.Containers(providerIds...) 919 if err != nil { 920 return errors.Trace(err) 921 } 922 for _, c := range containers { 923 providerIdToUnit[c.ProviderId()] = names.NewUnitTag(c.Unit()) 924 } 925 926 processedFilesystemIds := set.NewStrings() 927 for _, unitParams := range unitParamsWithFilesystemInfo { 928 var ( 929 unitTag names.UnitTag 930 ok bool 931 ) 932 // For units added to the cloud directly, we first need to lookup the 933 // newly created unit tag from Juju using the cloud provider ids. 934 if unitParams.UnitTag == "" { 935 unitTag, ok = providerIdToUnit[unitParams.ProviderId] 936 if !ok { 937 logger.Warningf("cannot update filesystem data for unknown pod %q", unitParams.ProviderId) 938 continue 939 } 940 } else { 941 unitTag, _ = names.ParseUnitTag(unitParams.UnitTag) 942 } 943 if err := processFilesystemParams(processedFilesystemIds, unitTag, unitParams); err != nil { 944 return errors.Annotatef(err, "processing filesystem info for unit %q", unitTag.Id()) 945 } 946 } 947 948 // If pods are recreated on the Kubernetes side, new units are created on the Juju 949 // side and so any previously attached filesystems become orphaned and need to 950 // be cleaned up. 951 appName := app.Name() 952 if err := a.cleaupOrphanedFilesystems(processedFilesystemIds); err != nil { 953 return errors.Annotatef(err, "deleting orphaned filesystems for %v", appName) 954 } 955 956 // First do the volume updates as volumes need to be attached before the filesystem updates. 957 if err := a.updateVolumeInfo(volumeUpdates, volumeStatus); err != nil { 958 return errors.Annotatef(err, "updating volume information for %v", appName) 959 } 960 961 err = a.updateFilesystemInfo(filesystemUpdates, filesystemStatus) 962 return errors.Annotatef(err, "updating filesystem information for %v", appName) 963 } 964 965 func (a *Facade) cleaupOrphanedFilesystems(processedFilesystemIds set.Strings) error { 966 // TODO(caas) - record unit id on the filesystem so we can query by unit 967 allFilesystems, err := a.storage.AllFilesystems() 968 if err != nil { 969 return errors.Trace(err) 970 } 971 for _, fs := range allFilesystems { 972 fsInfo, err := fs.Info() 973 if errors.IsNotProvisioned(err) { 974 continue 975 } 976 if err != nil { 977 return errors.Trace(err) 978 } 979 if !processedFilesystemIds.Contains(fsInfo.FilesystemId) { 980 continue 981 } 982 983 storageTag, err := fs.Storage() 984 if err != nil && !errors.IsNotFound(err) { 985 return errors.Trace(err) 986 } 987 if err != nil { 988 continue 989 } 990 991 si, err := a.storage.StorageInstance(storageTag) 992 if err != nil && !errors.IsNotFound(err) { 993 return errors.Trace(err) 994 } 995 if err != nil { 996 continue 997 } 998 _, ok := si.Owner() 999 if ok { 1000 continue 1001 } 1002 1003 logger.Debugf("found orphaned filesystem %v", fs.FilesystemTag()) 1004 err = a.storage.DestroyStorageInstance(storageTag, false) 1005 if err != nil && !errors.IsNotFound(err) { 1006 return errors.Trace(err) 1007 } 1008 err = a.storage.DestroyFilesystem(fs.FilesystemTag()) 1009 if err != nil && !errors.IsNotFound(err) { 1010 return errors.Trace(err) 1011 } 1012 } 1013 return nil 1014 } 1015 1016 func (a *Facade) updateVolumeInfo(volumeUpdates map[string]volumeInfo, volumeStatus map[string]status.StatusInfo) error { 1017 // Do it in sorted order so it's deterministic for tests. 1018 var volTags []string 1019 for tag := range volumeUpdates { 1020 volTags = append(volTags, tag) 1021 } 1022 sort.Strings(volTags) 1023 1024 logger.Debugf("updating volume data: %+v", volumeUpdates) 1025 for _, tagString := range volTags { 1026 volTag, _ := names.ParseVolumeTag(tagString) 1027 volData := volumeUpdates[tagString] 1028 1029 vol, err := a.storage.Volume(volTag) 1030 if err != nil { 1031 return errors.Trace(err) 1032 } 1033 // If we have already recorded the provisioning info, 1034 // it's an error to try and do it again. 1035 _, err = vol.Info() 1036 if err != nil && !errors.IsNotProvisioned(err) { 1037 return errors.Trace(err) 1038 } 1039 if err != nil { 1040 // Provisioning info not set yet. 1041 err = a.storage.SetVolumeInfo(volTag, state.VolumeInfo{ 1042 Size: volData.size, 1043 VolumeId: volData.volumeId, 1044 Persistent: volData.persistent, 1045 }) 1046 if err != nil { 1047 return errors.Trace(err) 1048 } 1049 } 1050 1051 err = a.storage.SetVolumeAttachmentInfo(volData.unitTag, volTag, state.VolumeAttachmentInfo{ 1052 ReadOnly: volData.readOnly, 1053 }) 1054 if err != nil { 1055 return errors.Trace(err) 1056 } 1057 } 1058 1059 // Do it in sorted order so it's deterministic for tests. 1060 volTags = []string{} 1061 for tag := range volumeStatus { 1062 volTags = append(volTags, tag) 1063 } 1064 sort.Strings(volTags) 1065 1066 logger.Debugf("updating volume status: %+v", volumeStatus) 1067 for _, tagString := range volTags { 1068 volTag, _ := names.ParseVolumeTag(tagString) 1069 volStatus := volumeStatus[tagString] 1070 vol, err := a.storage.Volume(volTag) 1071 if err != nil { 1072 return errors.Trace(err) 1073 } 1074 now := a.clock.Now() 1075 err = vol.SetStatus(status.StatusInfo{ 1076 Status: volStatus.Status, 1077 Message: volStatus.Message, 1078 Data: volStatus.Data, 1079 Since: &now, 1080 }) 1081 if err != nil { 1082 return errors.Trace(err) 1083 } 1084 } 1085 1086 return nil 1087 } 1088 1089 func (a *Facade) updateFilesystemInfo(filesystemUpdates map[string]filesystemInfo, filesystemStatus map[string]status.StatusInfo) error { 1090 // Do it in sorted order so it's deterministic for tests. 1091 var fsTags []string 1092 for tag := range filesystemUpdates { 1093 fsTags = append(fsTags, tag) 1094 } 1095 sort.Strings(fsTags) 1096 1097 logger.Debugf("updating filesystem data: %+v", filesystemUpdates) 1098 for _, tagString := range fsTags { 1099 fsTag, _ := names.ParseFilesystemTag(tagString) 1100 fsData := filesystemUpdates[tagString] 1101 1102 fs, err := a.storage.Filesystem(fsTag) 1103 if err != nil { 1104 return errors.Trace(err) 1105 } 1106 // If we have already recorded the provisioning info, 1107 // it's an error to try and do it again. 1108 _, err = fs.Info() 1109 if err != nil && !errors.IsNotProvisioned(err) { 1110 return errors.Trace(err) 1111 } 1112 if err != nil { 1113 // Provisioning info not set yet. 1114 err = a.storage.SetFilesystemInfo(fsTag, state.FilesystemInfo{ 1115 Size: fsData.size, 1116 FilesystemId: fsData.filesystemId, 1117 }) 1118 if err != nil { 1119 return errors.Trace(err) 1120 } 1121 } 1122 1123 err = a.storage.SetFilesystemAttachmentInfo(fsData.unitTag, fsTag, state.FilesystemAttachmentInfo{ 1124 MountPoint: fsData.mountPoint, 1125 ReadOnly: fsData.readOnly, 1126 }) 1127 if err != nil { 1128 return errors.Trace(err) 1129 } 1130 } 1131 1132 // Do it in sorted order so it's deterministic for tests. 1133 fsTags = []string{} 1134 for tag := range filesystemStatus { 1135 fsTags = append(fsTags, tag) 1136 } 1137 sort.Strings(fsTags) 1138 1139 logger.Debugf("updating filesystem status: %+v", filesystemStatus) 1140 for _, tagString := range fsTags { 1141 fsTag, _ := names.ParseFilesystemTag(tagString) 1142 fsStatus := filesystemStatus[tagString] 1143 fs, err := a.storage.Filesystem(fsTag) 1144 if err != nil { 1145 return errors.Trace(err) 1146 } 1147 now := a.clock.Now() 1148 err = fs.SetStatus(status.StatusInfo{ 1149 Status: fsStatus.Status, 1150 Message: fsStatus.Message, 1151 Data: fsStatus.Data, 1152 Since: &now, 1153 }) 1154 if err != nil { 1155 return errors.Trace(err) 1156 } 1157 } 1158 1159 return nil 1160 } 1161 1162 // UpdateApplicationsService updates the Juju data model to reflect the given 1163 // service details of the specified application. 1164 func (a *Facade) UpdateApplicationsService(args params.UpdateApplicationServiceArgs) (params.ErrorResults, error) { 1165 result := params.ErrorResults{ 1166 Results: make([]params.ErrorResult, len(args.Args)), 1167 } 1168 if len(args.Args) == 0 { 1169 return result, nil 1170 } 1171 for i, appUpdate := range args.Args { 1172 appTag, err := names.ParseApplicationTag(appUpdate.ApplicationTag) 1173 if err != nil { 1174 result.Results[i].Error = common.ServerError(err) 1175 continue 1176 } 1177 app, err := a.state.Application(appTag.Id()) 1178 if err != nil { 1179 result.Results[i].Error = common.ServerError(err) 1180 continue 1181 } 1182 if err := app.UpdateCloudService(appUpdate.ProviderId, params.NetworkAddresses(appUpdate.Addresses...)); err != nil { 1183 result.Results[i].Error = common.ServerError(err) 1184 } 1185 } 1186 return result, nil 1187 } 1188 1189 // SetOperatorStatus updates the operator status for each given application. 1190 func (a *Facade) SetOperatorStatus(args params.SetStatus) (params.ErrorResults, error) { 1191 result := params.ErrorResults{ 1192 Results: make([]params.ErrorResult, len(args.Entities)), 1193 } 1194 for i, arg := range args.Entities { 1195 appTag, err := names.ParseApplicationTag(arg.Tag) 1196 if err != nil { 1197 result.Results[i].Error = common.ServerError(err) 1198 continue 1199 } 1200 app, err := a.state.Application(appTag.Id()) 1201 if err != nil { 1202 result.Results[i].Error = common.ServerError(err) 1203 continue 1204 } 1205 now := a.clock.Now() 1206 s := status.StatusInfo{ 1207 Status: status.Status(arg.Status), 1208 Message: arg.Info, 1209 Data: arg.Data, 1210 Since: &now, 1211 } 1212 if err := app.SetOperatorStatus(s); err != nil { 1213 result.Results[i].Error = common.ServerError(err) 1214 } 1215 } 1216 return result, nil 1217 }