github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/storage/storage.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage 5 6 import ( 7 "time" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 13 "github.com/juju/juju/apiserver/authentication" 14 "github.com/juju/juju/apiserver/common" 15 "github.com/juju/juju/apiserver/common/storagecommon" 16 apiservererrors "github.com/juju/juju/apiserver/errors" 17 "github.com/juju/juju/apiserver/facade" 18 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 19 "github.com/juju/juju/core/permission" 20 "github.com/juju/juju/environs/context" 21 "github.com/juju/juju/environs/tags" 22 "github.com/juju/juju/rpc/params" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/storage" 25 "github.com/juju/juju/storage/poolmanager" 26 ) 27 28 type storageMetadataFunc func() (poolmanager.PoolManager, storage.ProviderRegistry, error) 29 30 // StorageAPI implements the latest version (v6) of the Storage API. 31 type StorageAPI struct { 32 backend backend 33 storageAccess storageAccess 34 storageMetadata storageMetadataFunc 35 authorizer facade.Authorizer 36 callContext context.ProviderCallContext 37 modelType state.ModelType 38 } 39 40 func NewStorageAPI( 41 backend backend, 42 modelType state.ModelType, 43 storageAccess storageAccess, 44 storageMetadata storageMetadataFunc, 45 authorizer facade.Authorizer, 46 callContext context.ProviderCallContext, 47 ) *StorageAPI { 48 return &StorageAPI{ 49 backend: backend, 50 modelType: modelType, 51 storageAccess: storageAccess, 52 storageMetadata: storageMetadata, 53 authorizer: authorizer, 54 callContext: callContext, 55 } 56 } 57 58 func (a *StorageAPI) checkCanRead() error { 59 err := a.authorizer.HasPermission(permission.SuperuserAccess, a.backend.ControllerTag()) 60 if err != nil && !errors.Is(err, authentication.ErrorEntityMissingPermission) { 61 return errors.Trace(err) 62 } 63 64 if err == nil { 65 return nil 66 } 67 return a.authorizer.HasPermission(permission.ReadAccess, a.backend.ModelTag()) 68 } 69 70 func (a *StorageAPI) checkCanWrite() error { 71 return a.authorizer.HasPermission(permission.WriteAccess, a.backend.ModelTag()) 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 (a *StorageAPI) StorageDetails(entities params.Entities) (params.StorageDetailsResults, error) { 78 if err := a.checkCanRead(); 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 = apiservererrors.ServerError(err) 86 continue 87 } 88 storageInstance, err := a.storageAccess.StorageInstance(storageTag) 89 if err != nil { 90 results[i].Error = apiservererrors.ServerError(err) 91 continue 92 } 93 details, err := storagecommon.StorageDetails(a.storageAccess, a.unitAssignedMachine, storageInstance) 94 if err != nil { 95 results[i].Error = apiservererrors.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 (a *StorageAPI) ListStorageDetails(filters params.StorageFilters) (params.StorageDetailsListResults, error) { 105 if err := a.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 := a.listStorageDetails(filter) 113 if err != nil { 114 results.Results[i].Error = apiservererrors.ServerError(err) 115 continue 116 } 117 results.Results[i].Result = list 118 } 119 return results, nil 120 } 121 122 func (a *StorageAPI) 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 := a.storageAccess.AllStorageInstances() 130 if err != nil { 131 return nil, apiservererrors.ServerError(err) 132 } 133 results := make([]params.StorageDetails, len(stateInstances)) 134 for i, stateInstance := range stateInstances { 135 details, err := storagecommon.StorageDetails(a.storageAccess, a.unitAssignedMachine, 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 // ListPools returns a list of pools. 148 // If filter is provided, returned list only contains pools that match 149 // the filter. 150 // Pools can be filtered on names and provider types. 151 // If both names and types are provided as filter, 152 // pools that match either are returned. 153 // This method lists union of pools and environment provider types. 154 // If no filter is provided, all pools are returned. 155 func (a *StorageAPI) ListPools( 156 filters params.StoragePoolFilters, 157 ) (params.StoragePoolsResults, error) { 158 if err := a.checkCanRead(); err != nil { 159 return params.StoragePoolsResults{}, errors.Trace(err) 160 } 161 162 results := params.StoragePoolsResults{ 163 Results: make([]params.StoragePoolsResult, len(filters.Filters)), 164 } 165 for i, filter := range filters.Filters { 166 pools, err := a.listPools(a.ensureStoragePoolFilter(filter)) 167 if err != nil { 168 results.Results[i].Error = apiservererrors.ServerError(err) 169 continue 170 } 171 results.Results[i].Result = pools 172 } 173 return results, nil 174 } 175 176 func (a *StorageAPI) ensureStoragePoolFilter(filter params.StoragePoolFilter) params.StoragePoolFilter { 177 if a.modelType == state.ModelTypeCAAS { 178 filter.Providers = append(filter.Providers, k8sconstants.CAASProviderType) 179 } 180 return filter 181 } 182 183 func (a *StorageAPI) listPools(filter params.StoragePoolFilter) ([]params.StoragePool, error) { 184 pm, registry, err := a.storageMetadata() 185 if err != nil { 186 return nil, errors.Trace(err) 187 } 188 if err := a.validatePoolListFilter(registry, filter); err != nil { 189 return nil, errors.Trace(err) 190 } 191 192 pools, err := pm.List() 193 if err != nil { 194 return nil, errors.Trace(err) 195 } 196 providers, err := registry.StorageProviderTypes() 197 if err != nil { 198 return nil, errors.Trace(err) 199 } 200 matches := buildFilter(filter) 201 results := append( 202 filterPools(pools, matches), 203 filterProviders(providers, matches)..., 204 ) 205 return results, nil 206 } 207 208 func buildFilter(filter params.StoragePoolFilter) func(n, p string) bool { 209 providerSet := set.NewStrings(filter.Providers...) 210 nameSet := set.NewStrings(filter.Names...) 211 212 matches := func(n, p string) bool { 213 // no filters supplied = pool matches criteria 214 if providerSet.IsEmpty() && nameSet.IsEmpty() { 215 return true 216 } 217 // if at least 1 name and type are supplied, use AND to match 218 if !providerSet.IsEmpty() && !nameSet.IsEmpty() { 219 return nameSet.Contains(n) && providerSet.Contains(p) 220 } 221 // Otherwise, if only names or types are supplied, use OR to match 222 return nameSet.Contains(n) || providerSet.Contains(p) 223 } 224 return matches 225 } 226 227 func filterProviders( 228 providers []storage.ProviderType, 229 matches func(n, p string) bool, 230 ) []params.StoragePool { 231 if len(providers) == 0 { 232 return nil 233 } 234 all := make([]params.StoragePool, 0, len(providers)) 235 for _, p := range providers { 236 ps := string(p) 237 if matches(ps, ps) { 238 all = append(all, params.StoragePool{Name: ps, Provider: ps}) 239 } 240 } 241 return all 242 } 243 244 func filterPools( 245 pools []*storage.Config, 246 matches func(n, p string) bool, 247 ) []params.StoragePool { 248 if len(pools) == 0 { 249 return nil 250 } 251 all := make([]params.StoragePool, 0, len(pools)) 252 for _, p := range pools { 253 if matches(p.Name(), string(p.Provider())) { 254 all = append(all, params.StoragePool{ 255 Name: p.Name(), 256 Provider: string(p.Provider()), 257 Attrs: p.Attrs(), 258 }) 259 } 260 } 261 return all 262 } 263 264 func (a *StorageAPI) validatePoolListFilter(registry storage.ProviderRegistry, filter params.StoragePoolFilter) error { 265 if err := a.validateProviderCriteria(registry, filter.Providers); err != nil { 266 return errors.Trace(err) 267 } 268 if err := a.validateNameCriteria(filter.Names); err != nil { 269 return errors.Trace(err) 270 } 271 return nil 272 } 273 274 func (a *StorageAPI) validateNameCriteria(names []string) error { 275 for _, n := range names { 276 if !storage.IsValidPoolName(n) { 277 return errors.NotValidf("pool name %q", n) 278 } 279 } 280 return nil 281 } 282 283 func (a *StorageAPI) validateProviderCriteria(registry storage.ProviderRegistry, providers []string) error { 284 for _, p := range providers { 285 _, err := registry.StorageProvider(storage.ProviderType(p)) 286 if err != nil { 287 return errors.Trace(err) 288 } 289 } 290 return nil 291 } 292 293 // CreatePool creates a new pool with specified parameters. 294 func (a *StorageAPI) CreatePool(p params.StoragePoolArgs) (params.ErrorResults, error) { 295 results := params.ErrorResults{ 296 Results: make([]params.ErrorResult, len(p.Pools)), 297 } 298 pm, _, err := a.storageMetadata() 299 if err != nil { 300 return params.ErrorResults{}, errors.Trace(err) 301 } 302 for i, pool := range p.Pools { 303 _, err := pm.Create( 304 pool.Name, 305 storage.ProviderType(pool.Provider), 306 pool.Attrs) 307 results.Results[i].Error = apiservererrors.ServerError(err) 308 } 309 return results, nil 310 } 311 312 // ListVolumes lists volumes with the given filters. Each filter produces 313 // an independent list of volumes, or an error if the filter is invalid 314 // or the volumes could not be listed. 315 func (a *StorageAPI) ListVolumes(filters params.VolumeFilters) (params.VolumeDetailsListResults, error) { 316 if err := a.checkCanRead(); err != nil { 317 return params.VolumeDetailsListResults{}, errors.Trace(err) 318 } 319 results := params.VolumeDetailsListResults{ 320 Results: make([]params.VolumeDetailsListResult, len(filters.Filters)), 321 } 322 for i, filter := range filters.Filters { 323 volumes, volumeAttachments, err := filterVolumes(a.storageAccess, filter) 324 if err != nil { 325 results.Results[i].Error = apiservererrors.ServerError(err) 326 continue 327 } 328 details, err := a.createVolumeDetailsList(volumes, volumeAttachments) 329 if err != nil { 330 results.Results[i].Error = apiservererrors.ServerError(err) 331 continue 332 } 333 results.Results[i].Result = details 334 } 335 return results, nil 336 } 337 338 func filterVolumes( 339 stVolume storageVolume, 340 f params.VolumeFilter, 341 ) ([]state.Volume, map[names.VolumeTag][]state.VolumeAttachment, error) { 342 // Exit early if there's no volume support. 343 if stVolume == nil { 344 return nil, nil, nil 345 } 346 if f.IsEmpty() { 347 // No filter was specified: get all volumes, and all attachments. 348 volumes, err := stVolume.AllVolumes() 349 if err != nil { 350 return nil, nil, errors.Trace(err) 351 } 352 volumeAttachments := make(map[names.VolumeTag][]state.VolumeAttachment) 353 for _, v := range volumes { 354 attachments, err := stVolume.VolumeAttachments(v.VolumeTag()) 355 if err != nil { 356 return nil, nil, errors.Trace(err) 357 } 358 volumeAttachments[v.VolumeTag()] = attachments 359 } 360 return volumes, volumeAttachments, nil 361 } 362 volumesByTag := make(map[names.VolumeTag]state.Volume) 363 volumeAttachments := make(map[names.VolumeTag][]state.VolumeAttachment) 364 for _, machine := range f.Machines { 365 machineTag, err := names.ParseMachineTag(machine) 366 if err != nil { 367 return nil, nil, errors.Trace(err) 368 } 369 attachments, err := stVolume.MachineVolumeAttachments(machineTag) 370 if err != nil { 371 return nil, nil, errors.Trace(err) 372 } 373 for _, attachment := range attachments { 374 volumeTag := attachment.Volume() 375 volumesByTag[volumeTag] = nil 376 volumeAttachments[volumeTag] = append(volumeAttachments[volumeTag], attachment) 377 } 378 } 379 for volumeTag := range volumesByTag { 380 volume, err := stVolume.Volume(volumeTag) 381 if err != nil { 382 return nil, nil, errors.Trace(err) 383 } 384 volumesByTag[volumeTag] = volume 385 } 386 volumes := make([]state.Volume, 0, len(volumesByTag)) 387 for _, volume := range volumesByTag { 388 volumes = append(volumes, volume) 389 } 390 return volumes, volumeAttachments, nil 391 } 392 393 func (a *StorageAPI) createVolumeDetailsList( 394 volumes []state.Volume, 395 attachments map[names.VolumeTag][]state.VolumeAttachment, 396 ) ([]params.VolumeDetails, error) { 397 if len(volumes) == 0 { 398 return nil, nil 399 } 400 results := make([]params.VolumeDetails, len(volumes)) 401 for i, v := range volumes { 402 details, err := storagecommon.VolumeDetails(a.storageAccess, a.unitAssignedMachine, v, attachments[v.VolumeTag()]) 403 if err != nil { 404 return nil, errors.Annotatef( 405 err, "getting details for %s", 406 names.ReadableString(v.VolumeTag()), 407 ) 408 } 409 results[i] = *details 410 } 411 return results, nil 412 } 413 414 // ListFilesystems returns a list of filesystems in the environment matching 415 // the provided filter. Each result describes a filesystem in detail, including 416 // the filesystem's attachments. 417 func (a *StorageAPI) ListFilesystems(filters params.FilesystemFilters) (params.FilesystemDetailsListResults, error) { 418 results := params.FilesystemDetailsListResults{ 419 Results: make([]params.FilesystemDetailsListResult, len(filters.Filters)), 420 } 421 if err := a.checkCanRead(); err != nil { 422 return results, errors.Trace(err) 423 } 424 425 for i, filter := range filters.Filters { 426 filesystems, filesystemAttachments, err := filterFilesystems(a.storageAccess, filter) 427 if err != nil { 428 results.Results[i].Error = apiservererrors.ServerError(err) 429 continue 430 } 431 details, err := a.createFilesystemDetailsList(filesystems, filesystemAttachments) 432 if err != nil { 433 results.Results[i].Error = apiservererrors.ServerError(err) 434 continue 435 } 436 results.Results[i].Result = details 437 } 438 return results, nil 439 } 440 441 func filterFilesystems( 442 stFile storageFile, 443 f params.FilesystemFilter, 444 ) ([]state.Filesystem, map[names.FilesystemTag][]state.FilesystemAttachment, error) { 445 if f.IsEmpty() { 446 // No filter was specified: get all filesystems, and all attachments. 447 filesystems, err := stFile.AllFilesystems() 448 if err != nil { 449 return nil, nil, errors.Trace(err) 450 } 451 filesystemAttachments := make(map[names.FilesystemTag][]state.FilesystemAttachment) 452 for _, f := range filesystems { 453 attachments, err := stFile.FilesystemAttachments(f.FilesystemTag()) 454 if err != nil { 455 return nil, nil, errors.Trace(err) 456 } 457 filesystemAttachments[f.FilesystemTag()] = attachments 458 } 459 return filesystems, filesystemAttachments, nil 460 } 461 filesystemsByTag := make(map[names.FilesystemTag]state.Filesystem) 462 filesystemAttachments := make(map[names.FilesystemTag][]state.FilesystemAttachment) 463 for _, machine := range f.Machines { 464 machineTag, err := names.ParseMachineTag(machine) 465 if err != nil { 466 return nil, nil, errors.Trace(err) 467 } 468 attachments, err := stFile.MachineFilesystemAttachments(machineTag) 469 if err != nil { 470 return nil, nil, errors.Trace(err) 471 } 472 for _, attachment := range attachments { 473 filesystemTag := attachment.Filesystem() 474 filesystemsByTag[filesystemTag] = nil 475 filesystemAttachments[filesystemTag] = append(filesystemAttachments[filesystemTag], attachment) 476 } 477 } 478 for filesystemTag := range filesystemsByTag { 479 filesystem, err := stFile.Filesystem(filesystemTag) 480 if err != nil { 481 return nil, nil, errors.Trace(err) 482 } 483 filesystemsByTag[filesystemTag] = filesystem 484 } 485 filesystems := make([]state.Filesystem, 0, len(filesystemsByTag)) 486 for _, filesystem := range filesystemsByTag { 487 filesystems = append(filesystems, filesystem) 488 } 489 return filesystems, filesystemAttachments, nil 490 } 491 492 func (a *StorageAPI) createFilesystemDetailsList( 493 filesystems []state.Filesystem, 494 attachments map[names.FilesystemTag][]state.FilesystemAttachment, 495 ) ([]params.FilesystemDetails, error) { 496 if len(filesystems) == 0 { 497 return nil, nil 498 } 499 results := make([]params.FilesystemDetails, len(filesystems)) 500 for i, f := range filesystems { 501 details, err := storagecommon.FilesystemDetails(a.storageAccess, a.unitAssignedMachine, f, attachments[f.FilesystemTag()]) 502 if err != nil { 503 return nil, errors.Annotatef( 504 err, "getting details for %s", 505 names.ReadableString(f.FilesystemTag()), 506 ) 507 } 508 results[i] = *details 509 } 510 return results, nil 511 } 512 513 // AddToUnit validates and creates additional storage instances for units. 514 // A "CHANGE" block can block this operation. 515 func (a *StorageAPI) AddToUnit(args params.StoragesAddParams) (params.AddStorageResults, error) { 516 return a.addToUnit(args) 517 } 518 519 func (a *StorageAPI) addToUnit(args params.StoragesAddParams) (params.AddStorageResults, error) { 520 if err := a.checkCanWrite(); err != nil { 521 return params.AddStorageResults{}, errors.Trace(err) 522 } 523 524 // Check if changes are allowed and the operation may proceed. 525 blockChecker := common.NewBlockChecker(a.backend) 526 if err := blockChecker.ChangeAllowed(); err != nil { 527 return params.AddStorageResults{}, errors.Trace(err) 528 } 529 530 paramsToState := func(p params.StorageConstraints) state.StorageConstraints { 531 s := state.StorageConstraints{Pool: p.Pool} 532 if p.Size != nil { 533 s.Size = *p.Size 534 } 535 if p.Count != nil { 536 s.Count = *p.Count 537 } 538 return s 539 } 540 541 result := make([]params.AddStorageResult, len(args.Storages)) 542 for i, one := range args.Storages { 543 u, err := names.ParseUnitTag(one.UnitTag) 544 if err != nil { 545 result[i].Error = apiservererrors.ServerError(err) 546 continue 547 } 548 549 storageTags, err := a.storageAccess.AddStorageForUnit( 550 u, one.StorageName, paramsToState(one.Constraints), 551 ) 552 if err != nil { 553 result[i].Error = apiservererrors.ServerError(err) 554 } 555 tagStrings := make([]string, len(storageTags)) 556 for i, tag := range storageTags { 557 tagStrings[i] = tag.String() 558 } 559 result[i].Result = ¶ms.AddStorageDetails{ 560 StorageTags: tagStrings, 561 } 562 } 563 return params.AddStorageResults{Results: result}, nil 564 } 565 566 // Remove sets the specified storage entities to Dying, unless they are 567 // already Dying or Dead, such that the storage will eventually be removed 568 // from the model. If the arguments specify that the storage should be 569 // destroyed, then the associated cloud storage will be destroyed first; 570 // otherwise it will only be released from Juju's control. 571 func (a *StorageAPI) Remove(args params.RemoveStorage) (params.ErrorResults, error) { 572 return a.remove(args) 573 } 574 575 func (a *StorageAPI) remove(args params.RemoveStorage) (params.ErrorResults, error) { 576 if err := a.checkCanWrite(); err != nil { 577 return params.ErrorResults{}, errors.Trace(err) 578 } 579 580 blockChecker := common.NewBlockChecker(a.backend) 581 if err := blockChecker.RemoveAllowed(); err != nil { 582 return params.ErrorResults{}, errors.Trace(err) 583 } 584 585 result := make([]params.ErrorResult, len(args.Storage)) 586 for i, arg := range args.Storage { 587 tag, err := names.ParseStorageTag(arg.Tag) 588 if err != nil { 589 result[i].Error = apiservererrors.ServerError(err) 590 continue 591 } 592 remove := a.storageAccess.DestroyStorageInstance 593 if !arg.DestroyStorage { 594 remove = a.storageAccess.ReleaseStorageInstance 595 } 596 force := arg.Force != nil && *arg.Force 597 result[i].Error = apiservererrors.ServerError(remove(tag, arg.DestroyAttachments, force, common.MaxWait(arg.MaxWait))) 598 } 599 return params.ErrorResults{result}, nil 600 } 601 602 // DetachStorage sets the specified storage attachments to Dying, unless they are 603 // already Dying or Dead. Any associated, persistent storage will remain 604 // alive. This call can be forced. 605 func (a *StorageAPI) DetachStorage(args params.StorageDetachmentParams) (params.ErrorResults, error) { 606 return a.internalDetach(args.StorageIds, args.Force, args.MaxWait) 607 } 608 609 func (a *StorageAPI) internalDetach(args params.StorageAttachmentIds, force *bool, maxWait *time.Duration) (params.ErrorResults, error) { 610 if err := a.checkCanWrite(); err != nil { 611 return params.ErrorResults{}, errors.Trace(err) 612 } 613 614 blockChecker := common.NewBlockChecker(a.backend) 615 if err := blockChecker.ChangeAllowed(); err != nil { 616 return params.ErrorResults{}, errors.Trace(err) 617 } 618 619 detachOne := func(arg params.StorageAttachmentId) error { 620 storageTag, err := names.ParseStorageTag(arg.StorageTag) 621 if err != nil { 622 return err 623 } 624 var unitTag names.UnitTag 625 if arg.UnitTag != "" { 626 var err error 627 unitTag, err = names.ParseUnitTag(arg.UnitTag) 628 if err != nil { 629 return err 630 } 631 } 632 return a.detachStorage(storageTag, unitTag, force, maxWait) 633 } 634 635 result := make([]params.ErrorResult, len(args.Ids)) 636 for i, arg := range args.Ids { 637 result[i].Error = apiservererrors.ServerError(detachOne(arg)) 638 } 639 return params.ErrorResults{result}, nil 640 } 641 642 func (a *StorageAPI) detachStorage(storageTag names.StorageTag, unitTag names.UnitTag, force *bool, maxWait *time.Duration) error { 643 forcing := force != nil && *force 644 if unitTag != (names.UnitTag{}) { 645 // The caller has specified a unit explicitly. Do 646 // not filter out "not found" errors in this case. 647 return a.storageAccess.DetachStorage(storageTag, unitTag, forcing, common.MaxWait(maxWait)) 648 } 649 attachments, err := a.storageAccess.StorageAttachments(storageTag) 650 if err != nil { 651 return errors.Trace(err) 652 } 653 if len(attachments) == 0 { 654 // No attachments: check if the storage exists at all. 655 if _, err := a.storageAccess.StorageInstance(storageTag); err != nil { 656 return errors.Trace(err) 657 } 658 } 659 for _, att := range attachments { 660 if att.Life() != state.Alive { 661 continue 662 } 663 err := a.storageAccess.DetachStorage(storageTag, att.Unit(), forcing, common.MaxWait(maxWait)) 664 if err != nil && !errors.IsNotFound(err) { 665 // We only care about NotFound errors if 666 // the user specified a unit explicitly. 667 return errors.Trace(err) 668 } 669 } 670 return nil 671 } 672 673 // Attach attaches existing storage instances to units. 674 // A "CHANGE" block can block this operation. 675 func (a *StorageAPI) Attach(args params.StorageAttachmentIds) (params.ErrorResults, error) { 676 if err := a.checkCanWrite(); err != nil { 677 return params.ErrorResults{}, errors.Trace(err) 678 } 679 680 blockChecker := common.NewBlockChecker(a.backend) 681 if err := blockChecker.ChangeAllowed(); err != nil { 682 return params.ErrorResults{}, errors.Trace(err) 683 } 684 685 attachOne := func(arg params.StorageAttachmentId) error { 686 storageTag, err := names.ParseStorageTag(arg.StorageTag) 687 if err != nil { 688 return err 689 } 690 unitTag, err := names.ParseUnitTag(arg.UnitTag) 691 if err != nil { 692 return err 693 } 694 return a.storageAccess.AttachStorage(storageTag, unitTag) 695 } 696 697 result := make([]params.ErrorResult, len(args.Ids)) 698 for i, arg := range args.Ids { 699 result[i].Error = apiservererrors.ServerError(attachOne(arg)) 700 } 701 return params.ErrorResults{Results: result}, nil 702 } 703 704 // Import imports existing storage into the model. 705 // A "CHANGE" block can block this operation. 706 func (a *StorageAPI) Import(args params.BulkImportStorageParams) (params.ImportStorageResults, error) { 707 if err := a.checkCanWrite(); err != nil { 708 return params.ImportStorageResults{}, errors.Trace(err) 709 } 710 711 blockChecker := common.NewBlockChecker(a.backend) 712 if err := blockChecker.ChangeAllowed(); err != nil { 713 return params.ImportStorageResults{}, errors.Trace(err) 714 } 715 716 results := make([]params.ImportStorageResult, len(args.Storage)) 717 for i, arg := range args.Storage { 718 details, err := a.importStorage(arg) 719 if err != nil { 720 results[i].Error = apiservererrors.ServerError(err) 721 continue 722 } 723 results[i].Result = details 724 } 725 return params.ImportStorageResults{Results: results}, nil 726 } 727 728 func (a *StorageAPI) importStorage(arg params.ImportStorageParams) (*params.ImportStorageDetails, error) { 729 if arg.Kind != params.StorageKindFilesystem { 730 // TODO(axw) implement support for volumes. 731 return nil, errors.NotSupportedf("storage kind %q", arg.Kind.String()) 732 } 733 if !storage.IsValidPoolName(arg.Pool) { 734 return nil, errors.NotValidf("pool name %q", arg.Pool) 735 } 736 737 pm, registry, err := a.storageMetadata() 738 if err != nil { 739 return nil, errors.Trace(err) 740 } 741 742 cfg, err := pm.Get(arg.Pool) 743 if errors.IsNotFound(err) { 744 cfg, err = storage.NewConfig( 745 arg.Pool, 746 storage.ProviderType(arg.Pool), 747 map[string]interface{}{}, 748 ) 749 if err != nil { 750 return nil, errors.Trace(err) 751 } 752 } else if err != nil { 753 return nil, errors.Trace(err) 754 } 755 provider, err := registry.StorageProvider(cfg.Provider()) 756 if err != nil { 757 return nil, errors.Trace(err) 758 } 759 return a.importFilesystem(arg, provider, cfg) 760 } 761 762 func (a *StorageAPI) importFilesystem( 763 arg params.ImportStorageParams, 764 provider storage.Provider, 765 cfg *storage.Config, 766 ) (*params.ImportStorageDetails, error) { 767 resourceTags := map[string]string{ 768 tags.JujuModel: a.backend.ModelTag().Id(), 769 tags.JujuController: a.backend.ControllerTag().Id(), 770 } 771 var volumeInfo *state.VolumeInfo 772 filesystemInfo := state.FilesystemInfo{Pool: arg.Pool} 773 774 // If the storage provider supports filesystems, import the filesystem, 775 // otherwise import a volume which will back a filesystem. 776 if provider.Supports(storage.StorageKindFilesystem) { 777 filesystemSource, err := provider.FilesystemSource(cfg) 778 if err != nil { 779 return nil, errors.Trace(err) 780 } 781 filesystemImporter, ok := filesystemSource.(storage.FilesystemImporter) 782 if !ok { 783 return nil, errors.NotSupportedf( 784 "importing filesystem with storage provider %q", 785 cfg.Provider(), 786 ) 787 } 788 info, err := filesystemImporter.ImportFilesystem(a.callContext, arg.ProviderId, resourceTags) 789 if err != nil { 790 return nil, errors.Annotate(err, "importing filesystem") 791 } 792 filesystemInfo.FilesystemId = arg.ProviderId 793 filesystemInfo.Size = info.Size 794 } else { 795 volumeSource, err := provider.VolumeSource(cfg) 796 if err != nil { 797 return nil, errors.Trace(err) 798 } 799 volumeImporter, ok := volumeSource.(storage.VolumeImporter) 800 if !ok { 801 return nil, errors.NotSupportedf( 802 "importing volume with storage provider %q", 803 cfg.Provider(), 804 ) 805 } 806 info, err := volumeImporter.ImportVolume(a.callContext, arg.ProviderId, resourceTags) 807 if err != nil { 808 return nil, errors.Annotate(err, "importing volume") 809 } 810 volumeInfo = &state.VolumeInfo{ 811 HardwareId: info.HardwareId, 812 WWN: info.WWN, 813 Size: info.Size, 814 Pool: arg.Pool, 815 VolumeId: info.VolumeId, 816 Persistent: info.Persistent, 817 } 818 filesystemInfo.Size = info.Size 819 } 820 821 storageTag, err := a.storageAccess.AddExistingFilesystem(filesystemInfo, volumeInfo, arg.StorageName) 822 if err != nil { 823 return nil, errors.Trace(err) 824 } 825 return ¶ms.ImportStorageDetails{ 826 StorageTag: storageTag.String(), 827 }, nil 828 } 829 830 // RemovePool deletes the named pool 831 func (a *StorageAPI) RemovePool(p params.StoragePoolDeleteArgs) (params.ErrorResults, error) { 832 results := params.ErrorResults{ 833 Results: make([]params.ErrorResult, len(p.Pools)), 834 } 835 if err := a.checkCanWrite(); err != nil { 836 return results, errors.Trace(err) 837 } 838 for i, pool := range p.Pools { 839 err := a.storageAccess.RemoveStoragePool(pool.Name) 840 if err != nil { 841 results.Results[i].Error = apiservererrors.ServerError(err) 842 } 843 } 844 return results, nil 845 } 846 847 // UpdatePool deletes the named pool 848 func (a *StorageAPI) UpdatePool(p params.StoragePoolArgs) (params.ErrorResults, error) { 849 results := params.ErrorResults{ 850 Results: make([]params.ErrorResult, len(p.Pools)), 851 } 852 if err := a.checkCanWrite(); err != nil { 853 return results, errors.Trace(err) 854 } 855 pm, _, err := a.storageMetadata() 856 if err != nil { 857 return results, errors.Trace(err) 858 } 859 860 for i, pool := range p.Pools { 861 err := pm.Replace(pool.Name, pool.Provider, pool.Attrs) 862 if err != nil { 863 results.Results[i].Error = apiservererrors.ServerError(err) 864 } 865 } 866 return results, nil 867 } 868 869 // unitAssignedMachine returns the tag of the machine that the unit 870 // is assigned to, or an error if the unit cannot be obtained or is 871 // not assigned to a machine. 872 func (a *StorageAPI) unitAssignedMachine(tag names.UnitTag) (names.MachineTag, error) { 873 unit, err := a.backend.Unit(tag.Id()) 874 if err != nil { 875 return names.MachineTag{}, errors.Trace(err) 876 } 877 mid, err := unit.AssignedMachineId() 878 if err != nil { 879 return names.MachineTag{}, errors.Trace(err) 880 } 881 return names.NewMachineTag(mid), nil 882 }