github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/storage/storage.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package storage provides an API server facade for managing 5 // storage entities. 6 package storage 7 8 import ( 9 "github.com/juju/errors" 10 "github.com/juju/utils/set" 11 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/apiserver/common" 14 "github.com/juju/juju/apiserver/common/storagecommon" 15 "github.com/juju/juju/apiserver/facade" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/permission" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/status" 20 "github.com/juju/juju/storage" 21 "github.com/juju/juju/storage/poolmanager" 22 ) 23 24 // API implements the storage interface and is the concrete 25 // implementation of the api end point. 26 type API struct { 27 storage storageAccess 28 registry storage.ProviderRegistry 29 poolManager poolmanager.PoolManager 30 authorizer facade.Authorizer 31 } 32 33 // NewAPI returns a new storage API facade. 34 func NewAPI( 35 st storageAccess, 36 registry storage.ProviderRegistry, 37 pm poolmanager.PoolManager, 38 resources facade.Resources, 39 authorizer facade.Authorizer, 40 ) (*API, error) { 41 if !authorizer.AuthClient() { 42 return nil, common.ErrPerm 43 } 44 45 return &API{ 46 storage: st, 47 registry: registry, 48 poolManager: pm, 49 authorizer: authorizer, 50 }, nil 51 } 52 func (api *API) checkCanRead() error { 53 canRead, err := api.authorizer.HasPermission(permission.ReadAccess, api.storage.ModelTag()) 54 if err != nil { 55 return errors.Trace(err) 56 } 57 if !canRead { 58 return common.ErrPerm 59 } 60 return nil 61 } 62 63 func (api *API) checkCanWrite() error { 64 canWrite, err := api.authorizer.HasPermission(permission.WriteAccess, api.storage.ModelTag()) 65 if err != nil { 66 return errors.Trace(err) 67 } 68 if !canWrite { 69 return common.ErrPerm 70 } 71 return nil 72 } 73 74 // StorageDetails retrieves and returns detailed information about desired 75 // storage identified by supplied tags. If specified storage cannot be 76 // retrieved, individual error is returned instead of storage information. 77 func (api *API) StorageDetails(entities params.Entities) (params.StorageDetailsResults, error) { 78 if err := api.checkCanWrite(); err != nil { 79 return params.StorageDetailsResults{}, errors.Trace(err) 80 } 81 results := make([]params.StorageDetailsResult, len(entities.Entities)) 82 for i, entity := range entities.Entities { 83 storageTag, err := names.ParseStorageTag(entity.Tag) 84 if err != nil { 85 results[i].Error = common.ServerError(err) 86 continue 87 } 88 storageInstance, err := api.storage.StorageInstance(storageTag) 89 if err != nil { 90 results[i].Error = common.ServerError(err) 91 continue 92 } 93 details, err := createStorageDetails(api.storage, storageInstance) 94 if err != nil { 95 results[i].Error = common.ServerError(err) 96 continue 97 } 98 results[i].Result = details 99 } 100 return params.StorageDetailsResults{Results: results}, nil 101 } 102 103 // ListStorageDetails returns storage matching a filter. 104 func (api *API) ListStorageDetails(filters params.StorageFilters) (params.StorageDetailsListResults, error) { 105 if err := api.checkCanRead(); err != nil { 106 return params.StorageDetailsListResults{}, errors.Trace(err) 107 } 108 results := params.StorageDetailsListResults{ 109 Results: make([]params.StorageDetailsListResult, len(filters.Filters)), 110 } 111 for i, filter := range filters.Filters { 112 list, err := api.listStorageDetails(filter) 113 if err != nil { 114 results.Results[i].Error = common.ServerError(err) 115 continue 116 } 117 results.Results[i].Result = list 118 } 119 return results, nil 120 } 121 122 func (api *API) listStorageDetails(filter params.StorageFilter) ([]params.StorageDetails, error) { 123 if filter != (params.StorageFilter{}) { 124 // StorageFilter has no fields at the time of writing, but 125 // check that no fields are set in case we forget to update 126 // this code. 127 return nil, errors.NotSupportedf("storage filters") 128 } 129 stateInstances, err := api.storage.AllStorageInstances() 130 if err != nil { 131 return nil, common.ServerError(err) 132 } 133 results := make([]params.StorageDetails, len(stateInstances)) 134 for i, stateInstance := range stateInstances { 135 details, err := createStorageDetails(api.storage, stateInstance) 136 if err != nil { 137 return nil, errors.Annotatef( 138 err, "getting details for %s", 139 names.ReadableString(stateInstance.Tag()), 140 ) 141 } 142 results[i] = *details 143 } 144 return results, nil 145 } 146 147 func createStorageDetails(st storageAccess, si state.StorageInstance) (*params.StorageDetails, error) { 148 // Get information from underlying volume or filesystem. 149 var persistent bool 150 var statusEntity status.StatusGetter 151 if si.Kind() != state.StorageKindBlock { 152 // TODO(axw) when we support persistent filesystems, 153 // e.g. CephFS, we'll need to do set "persistent" 154 // here too. 155 filesystem, err := st.StorageInstanceFilesystem(si.StorageTag()) 156 if err != nil { 157 return nil, errors.Trace(err) 158 } 159 statusEntity = filesystem 160 } else { 161 volume, err := st.StorageInstanceVolume(si.StorageTag()) 162 if err != nil { 163 return nil, errors.Trace(err) 164 } 165 if info, err := volume.Info(); err == nil { 166 persistent = info.Persistent 167 } 168 statusEntity = volume 169 } 170 status, err := statusEntity.Status() 171 if err != nil { 172 return nil, errors.Trace(err) 173 } 174 175 // Get unit storage attachments. 176 var storageAttachmentDetails map[string]params.StorageAttachmentDetails 177 storageAttachments, err := st.StorageAttachments(si.StorageTag()) 178 if err != nil { 179 return nil, errors.Trace(err) 180 } 181 if len(storageAttachments) > 0 { 182 storageAttachmentDetails = make(map[string]params.StorageAttachmentDetails) 183 for _, a := range storageAttachments { 184 machineTag, location, err := storageAttachmentInfo(st, a) 185 if err != nil { 186 return nil, errors.Trace(err) 187 } 188 details := params.StorageAttachmentDetails{ 189 a.StorageInstance().String(), 190 a.Unit().String(), 191 machineTag.String(), 192 location, 193 } 194 storageAttachmentDetails[a.Unit().String()] = details 195 } 196 } 197 198 return ¶ms.StorageDetails{ 199 StorageTag: si.Tag().String(), 200 OwnerTag: si.Owner().String(), 201 Kind: params.StorageKind(si.Kind()), 202 Status: common.EntityStatusFromState(status), 203 Persistent: persistent, 204 Attachments: storageAttachmentDetails, 205 }, nil 206 } 207 208 func storageAttachmentInfo(st storageAccess, a state.StorageAttachment) (_ names.MachineTag, location string, _ error) { 209 machineTag, err := st.UnitAssignedMachine(a.Unit()) 210 if errors.IsNotAssigned(err) { 211 return names.MachineTag{}, "", nil 212 } else if err != nil { 213 return names.MachineTag{}, "", errors.Trace(err) 214 } 215 info, err := storagecommon.StorageAttachmentInfo(st, a, machineTag) 216 if errors.IsNotProvisioned(err) { 217 return machineTag, "", nil 218 } else if err != nil { 219 return names.MachineTag{}, "", errors.Trace(err) 220 } 221 return machineTag, info.Location, nil 222 } 223 224 // ListPools returns a list of pools. 225 // If filter is provided, returned list only contains pools that match 226 // the filter. 227 // Pools can be filtered on names and provider types. 228 // If both names and types are provided as filter, 229 // pools that match either are returned. 230 // This method lists union of pools and environment provider types. 231 // If no filter is provided, all pools are returned. 232 func (a *API) ListPools( 233 filters params.StoragePoolFilters, 234 ) (params.StoragePoolsResults, error) { 235 if err := a.checkCanRead(); err != nil { 236 return params.StoragePoolsResults{}, errors.Trace(err) 237 } 238 239 results := params.StoragePoolsResults{ 240 Results: make([]params.StoragePoolsResult, len(filters.Filters)), 241 } 242 for i, filter := range filters.Filters { 243 pools, err := a.listPools(filter) 244 if err != nil { 245 results.Results[i].Error = common.ServerError(err) 246 continue 247 } 248 results.Results[i].Result = pools 249 } 250 return results, nil 251 } 252 253 func (a *API) listPools(filter params.StoragePoolFilter) ([]params.StoragePool, error) { 254 if err := a.validatePoolListFilter(filter); err != nil { 255 return nil, errors.Trace(err) 256 } 257 pools, err := a.poolManager.List() 258 if err != nil { 259 return nil, errors.Trace(err) 260 } 261 providers, err := a.registry.StorageProviderTypes() 262 if err != nil { 263 return nil, errors.Trace(err) 264 } 265 matches := buildFilter(filter) 266 results := append( 267 filterPools(pools, matches), 268 filterProviders(providers, matches)..., 269 ) 270 return results, nil 271 } 272 273 func buildFilter(filter params.StoragePoolFilter) func(n, p string) bool { 274 providerSet := set.NewStrings(filter.Providers...) 275 nameSet := set.NewStrings(filter.Names...) 276 277 matches := func(n, p string) bool { 278 // no filters supplied = pool matches criteria 279 if providerSet.IsEmpty() && nameSet.IsEmpty() { 280 return true 281 } 282 // if at least 1 name and type are supplied, use AND to match 283 if !providerSet.IsEmpty() && !nameSet.IsEmpty() { 284 return nameSet.Contains(n) && providerSet.Contains(string(p)) 285 } 286 // Otherwise, if only names or types are supplied, use OR to match 287 return nameSet.Contains(n) || providerSet.Contains(string(p)) 288 } 289 return matches 290 } 291 292 func filterProviders( 293 providers []storage.ProviderType, 294 matches func(n, p string) bool, 295 ) []params.StoragePool { 296 if len(providers) == 0 { 297 return nil 298 } 299 all := make([]params.StoragePool, 0, len(providers)) 300 for _, p := range providers { 301 ps := string(p) 302 if matches(ps, ps) { 303 all = append(all, params.StoragePool{Name: ps, Provider: ps}) 304 } 305 } 306 return all 307 } 308 309 func filterPools( 310 pools []*storage.Config, 311 matches func(n, p string) bool, 312 ) []params.StoragePool { 313 if len(pools) == 0 { 314 return nil 315 } 316 all := make([]params.StoragePool, 0, len(pools)) 317 for _, p := range pools { 318 if matches(p.Name(), string(p.Provider())) { 319 all = append(all, params.StoragePool{ 320 Name: p.Name(), 321 Provider: string(p.Provider()), 322 Attrs: p.Attrs(), 323 }) 324 } 325 } 326 return all 327 } 328 329 func (a *API) validatePoolListFilter(filter params.StoragePoolFilter) error { 330 if err := a.validateProviderCriteria(filter.Providers); err != nil { 331 return errors.Trace(err) 332 } 333 if err := a.validateNameCriteria(filter.Names); err != nil { 334 return errors.Trace(err) 335 } 336 return nil 337 } 338 339 func (a *API) validateNameCriteria(names []string) error { 340 for _, n := range names { 341 if !storage.IsValidPoolName(n) { 342 return errors.NotValidf("pool name %q", n) 343 } 344 } 345 return nil 346 } 347 348 func (a *API) validateProviderCriteria(providers []string) error { 349 for _, p := range providers { 350 _, err := a.registry.StorageProvider(storage.ProviderType(p)) 351 if err != nil { 352 return errors.Trace(err) 353 } 354 } 355 return nil 356 } 357 358 // CreatePool creates a new pool with specified parameters. 359 func (a *API) CreatePool(p params.StoragePool) error { 360 _, err := a.poolManager.Create( 361 p.Name, 362 storage.ProviderType(p.Provider), 363 p.Attrs) 364 return err 365 } 366 367 // ListVolumes lists volumes with the given filters. Each filter produces 368 // an independent list of volumes, or an error if the filter is invalid 369 // or the volumes could not be listed. 370 func (a *API) ListVolumes(filters params.VolumeFilters) (params.VolumeDetailsListResults, error) { 371 if err := a.checkCanRead(); err != nil { 372 return params.VolumeDetailsListResults{}, errors.Trace(err) 373 } 374 results := params.VolumeDetailsListResults{ 375 Results: make([]params.VolumeDetailsListResult, len(filters.Filters)), 376 } 377 for i, filter := range filters.Filters { 378 volumes, volumeAttachments, err := filterVolumes(a.storage, filter) 379 if err != nil { 380 results.Results[i].Error = common.ServerError(err) 381 continue 382 } 383 details, err := createVolumeDetailsList( 384 a.storage, volumes, volumeAttachments, 385 ) 386 if err != nil { 387 results.Results[i].Error = common.ServerError(err) 388 continue 389 } 390 results.Results[i].Result = details 391 } 392 return results, nil 393 } 394 395 func filterVolumes( 396 st storageAccess, 397 f params.VolumeFilter, 398 ) ([]state.Volume, map[names.VolumeTag][]state.VolumeAttachment, error) { 399 if f.IsEmpty() { 400 // No filter was specified: get all volumes, and all attachments. 401 volumes, err := st.AllVolumes() 402 if err != nil { 403 return nil, nil, errors.Trace(err) 404 } 405 volumeAttachments := make(map[names.VolumeTag][]state.VolumeAttachment) 406 for _, v := range volumes { 407 attachments, err := st.VolumeAttachments(v.VolumeTag()) 408 if err != nil { 409 return nil, nil, errors.Trace(err) 410 } 411 volumeAttachments[v.VolumeTag()] = attachments 412 } 413 return volumes, volumeAttachments, nil 414 } 415 volumesByTag := make(map[names.VolumeTag]state.Volume) 416 volumeAttachments := make(map[names.VolumeTag][]state.VolumeAttachment) 417 for _, machine := range f.Machines { 418 machineTag, err := names.ParseMachineTag(machine) 419 if err != nil { 420 return nil, nil, errors.Trace(err) 421 } 422 attachments, err := st.MachineVolumeAttachments(machineTag) 423 if err != nil { 424 return nil, nil, errors.Trace(err) 425 } 426 for _, attachment := range attachments { 427 volumeTag := attachment.Volume() 428 volumesByTag[volumeTag] = nil 429 volumeAttachments[volumeTag] = append(volumeAttachments[volumeTag], attachment) 430 } 431 } 432 for volumeTag := range volumesByTag { 433 volume, err := st.Volume(volumeTag) 434 if err != nil { 435 return nil, nil, errors.Trace(err) 436 } 437 volumesByTag[volumeTag] = volume 438 } 439 volumes := make([]state.Volume, 0, len(volumesByTag)) 440 for _, volume := range volumesByTag { 441 volumes = append(volumes, volume) 442 } 443 return volumes, volumeAttachments, nil 444 } 445 446 func createVolumeDetailsList( 447 st storageAccess, 448 volumes []state.Volume, 449 attachments map[names.VolumeTag][]state.VolumeAttachment, 450 ) ([]params.VolumeDetails, error) { 451 452 if len(volumes) == 0 { 453 return nil, nil 454 } 455 results := make([]params.VolumeDetails, len(volumes)) 456 for i, v := range volumes { 457 details, err := createVolumeDetails(st, v, attachments[v.VolumeTag()]) 458 if err != nil { 459 return nil, errors.Annotatef( 460 err, "getting details for %s", 461 names.ReadableString(v.VolumeTag()), 462 ) 463 } 464 results[i] = *details 465 } 466 return results, nil 467 } 468 469 func createVolumeDetails( 470 st storageAccess, v state.Volume, attachments []state.VolumeAttachment, 471 ) (*params.VolumeDetails, error) { 472 473 details := ¶ms.VolumeDetails{ 474 VolumeTag: v.VolumeTag().String(), 475 } 476 477 if info, err := v.Info(); err == nil { 478 details.Info = storagecommon.VolumeInfoFromState(info) 479 } 480 481 if len(attachments) > 0 { 482 details.MachineAttachments = make(map[string]params.VolumeAttachmentInfo, len(attachments)) 483 for _, attachment := range attachments { 484 stateInfo, err := attachment.Info() 485 var info params.VolumeAttachmentInfo 486 if err == nil { 487 info = storagecommon.VolumeAttachmentInfoFromState(stateInfo) 488 } 489 details.MachineAttachments[attachment.Machine().String()] = info 490 } 491 } 492 493 status, err := v.Status() 494 if err != nil { 495 return nil, errors.Trace(err) 496 } 497 details.Status = common.EntityStatusFromState(status) 498 499 if storageTag, err := v.StorageInstance(); err == nil { 500 storageInstance, err := st.StorageInstance(storageTag) 501 if err != nil { 502 return nil, errors.Trace(err) 503 } 504 storageDetails, err := createStorageDetails(st, storageInstance) 505 if err != nil { 506 return nil, errors.Trace(err) 507 } 508 details.Storage = storageDetails 509 } 510 511 return details, nil 512 } 513 514 // ListFilesystems returns a list of filesystems in the environment matching 515 // the provided filter. Each result describes a filesystem in detail, including 516 // the filesystem's attachments. 517 func (a *API) ListFilesystems(filters params.FilesystemFilters) (params.FilesystemDetailsListResults, error) { 518 results := params.FilesystemDetailsListResults{ 519 Results: make([]params.FilesystemDetailsListResult, len(filters.Filters)), 520 } 521 if err := a.checkCanRead(); err != nil { 522 return results, errors.Trace(err) 523 } 524 525 for i, filter := range filters.Filters { 526 filesystems, filesystemAttachments, err := filterFilesystems(a.storage, filter) 527 if err != nil { 528 results.Results[i].Error = common.ServerError(err) 529 continue 530 } 531 details, err := createFilesystemDetailsList( 532 a.storage, filesystems, filesystemAttachments, 533 ) 534 if err != nil { 535 results.Results[i].Error = common.ServerError(err) 536 continue 537 } 538 results.Results[i].Result = details 539 } 540 return results, nil 541 } 542 543 func filterFilesystems( 544 st storageAccess, 545 f params.FilesystemFilter, 546 ) ([]state.Filesystem, map[names.FilesystemTag][]state.FilesystemAttachment, error) { 547 if f.IsEmpty() { 548 // No filter was specified: get all filesystems, and all attachments. 549 filesystems, err := st.AllFilesystems() 550 if err != nil { 551 return nil, nil, errors.Trace(err) 552 } 553 filesystemAttachments := make(map[names.FilesystemTag][]state.FilesystemAttachment) 554 for _, f := range filesystems { 555 attachments, err := st.FilesystemAttachments(f.FilesystemTag()) 556 if err != nil { 557 return nil, nil, errors.Trace(err) 558 } 559 filesystemAttachments[f.FilesystemTag()] = attachments 560 } 561 return filesystems, filesystemAttachments, nil 562 } 563 filesystemsByTag := make(map[names.FilesystemTag]state.Filesystem) 564 filesystemAttachments := make(map[names.FilesystemTag][]state.FilesystemAttachment) 565 for _, machine := range f.Machines { 566 machineTag, err := names.ParseMachineTag(machine) 567 if err != nil { 568 return nil, nil, errors.Trace(err) 569 } 570 attachments, err := st.MachineFilesystemAttachments(machineTag) 571 if err != nil { 572 return nil, nil, errors.Trace(err) 573 } 574 for _, attachment := range attachments { 575 filesystemTag := attachment.Filesystem() 576 filesystemsByTag[filesystemTag] = nil 577 filesystemAttachments[filesystemTag] = append(filesystemAttachments[filesystemTag], attachment) 578 } 579 } 580 for filesystemTag := range filesystemsByTag { 581 filesystem, err := st.Filesystem(filesystemTag) 582 if err != nil { 583 return nil, nil, errors.Trace(err) 584 } 585 filesystemsByTag[filesystemTag] = filesystem 586 } 587 filesystems := make([]state.Filesystem, 0, len(filesystemsByTag)) 588 for _, filesystem := range filesystemsByTag { 589 filesystems = append(filesystems, filesystem) 590 } 591 return filesystems, filesystemAttachments, nil 592 } 593 594 func createFilesystemDetailsList( 595 st storageAccess, 596 filesystems []state.Filesystem, 597 attachments map[names.FilesystemTag][]state.FilesystemAttachment, 598 ) ([]params.FilesystemDetails, error) { 599 600 if len(filesystems) == 0 { 601 return nil, nil 602 } 603 results := make([]params.FilesystemDetails, len(filesystems)) 604 for i, f := range filesystems { 605 details, err := createFilesystemDetails(st, f, attachments[f.FilesystemTag()]) 606 if err != nil { 607 return nil, errors.Annotatef( 608 err, "getting details for %s", 609 names.ReadableString(f.FilesystemTag()), 610 ) 611 } 612 results[i] = *details 613 } 614 return results, nil 615 } 616 617 func createFilesystemDetails( 618 st storageAccess, f state.Filesystem, attachments []state.FilesystemAttachment, 619 ) (*params.FilesystemDetails, error) { 620 621 details := ¶ms.FilesystemDetails{ 622 FilesystemTag: f.FilesystemTag().String(), 623 } 624 625 if volumeTag, err := f.Volume(); err == nil { 626 details.VolumeTag = volumeTag.String() 627 } 628 629 if info, err := f.Info(); err == nil { 630 details.Info = storagecommon.FilesystemInfoFromState(info) 631 } 632 633 if len(attachments) > 0 { 634 details.MachineAttachments = make(map[string]params.FilesystemAttachmentInfo, len(attachments)) 635 for _, attachment := range attachments { 636 stateInfo, err := attachment.Info() 637 var info params.FilesystemAttachmentInfo 638 if err == nil { 639 info = storagecommon.FilesystemAttachmentInfoFromState(stateInfo) 640 } 641 details.MachineAttachments[attachment.Machine().String()] = info 642 } 643 } 644 645 status, err := f.Status() 646 if err != nil { 647 return nil, errors.Trace(err) 648 } 649 details.Status = common.EntityStatusFromState(status) 650 651 if storageTag, err := f.Storage(); err == nil { 652 storageInstance, err := st.StorageInstance(storageTag) 653 if err != nil { 654 return nil, errors.Trace(err) 655 } 656 storageDetails, err := createStorageDetails(st, storageInstance) 657 if err != nil { 658 return nil, errors.Trace(err) 659 } 660 details.Storage = storageDetails 661 } 662 663 return details, nil 664 } 665 666 // AddToUnit validates and creates additional storage instances for units. 667 // This method handles bulk add operations and 668 // a failure on one individual storage instance does not block remaining 669 // instances from being processed. 670 // A "CHANGE" block can block this operation. 671 func (a *API) AddToUnit(args params.StoragesAddParams) (params.ErrorResults, error) { 672 if err := a.checkCanWrite(); err != nil { 673 return params.ErrorResults{}, errors.Trace(err) 674 } 675 676 // Check if changes are allowed and the operation may proceed. 677 blockChecker := common.NewBlockChecker(a.storage) 678 if err := blockChecker.ChangeAllowed(); err != nil { 679 return params.ErrorResults{}, errors.Trace(err) 680 } 681 682 if len(args.Storages) == 0 { 683 return params.ErrorResults{}, nil 684 } 685 686 paramsToState := func(p params.StorageConstraints) state.StorageConstraints { 687 s := state.StorageConstraints{Pool: p.Pool} 688 if p.Size != nil { 689 s.Size = *p.Size 690 } 691 if p.Count != nil { 692 s.Count = *p.Count 693 } 694 return s 695 } 696 697 result := make([]params.ErrorResult, len(args.Storages)) 698 for i, one := range args.Storages { 699 u, err := names.ParseUnitTag(one.UnitTag) 700 if err != nil { 701 result[i] = params.ErrorResult{Error: common.ServerError(err)} 702 continue 703 } 704 705 err = a.storage.AddStorageForUnit(u, one.StorageName, paramsToState(one.Constraints)) 706 if err != nil { 707 result[i] = params.ErrorResult{Error: common.ServerError(err)} 708 } 709 } 710 return params.ErrorResults{Results: result}, nil 711 }