github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/apiserver/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 "github.com/juju/errors" 8 "github.com/juju/names" 9 "github.com/juju/utils/set" 10 11 "github.com/juju/juju/apiserver/common" 12 "github.com/juju/juju/apiserver/params" 13 "github.com/juju/juju/state" 14 "github.com/juju/juju/storage" 15 "github.com/juju/juju/storage/poolmanager" 16 "github.com/juju/juju/storage/provider/registry" 17 ) 18 19 func init() { 20 common.RegisterStandardFacade("Storage", 1, NewAPI) 21 } 22 23 // API implements the storage interface and is the concrete 24 // implementation of the api end point. 25 type API struct { 26 storage storageAccess 27 poolManager poolmanager.PoolManager 28 authorizer common.Authorizer 29 } 30 31 // createAPI returns a new storage API facade. 32 func createAPI( 33 st storageAccess, 34 pm poolmanager.PoolManager, 35 resources *common.Resources, 36 authorizer common.Authorizer, 37 ) (*API, error) { 38 if !authorizer.AuthClient() { 39 return nil, common.ErrPerm 40 } 41 42 return &API{ 43 storage: st, 44 poolManager: pm, 45 authorizer: authorizer, 46 }, nil 47 } 48 49 // NewAPI returns a new storage API facade. 50 func NewAPI( 51 st *state.State, 52 resources *common.Resources, 53 authorizer common.Authorizer, 54 ) (*API, error) { 55 return createAPI(getState(st), poolManager(st), resources, authorizer) 56 } 57 58 func poolManager(st *state.State) poolmanager.PoolManager { 59 return poolmanager.New(state.NewStateSettings(st)) 60 } 61 62 // Show retrieves and returns detailed information about desired storage 63 // identified by supplied tags. If specified storage cannot be retrieved, 64 // individual error is returned instead of storage information. 65 func (api *API) Show(entities params.Entities) (params.StorageDetailsResults, error) { 66 var all []params.StorageDetailsResult 67 for _, entity := range entities.Entities { 68 storageTag, err := names.ParseStorageTag(entity.Tag) 69 if err != nil { 70 all = append(all, params.StorageDetailsResult{ 71 Error: common.ServerError(err), 72 }) 73 continue 74 } 75 found, instance, serverErr := api.getStorageInstance(storageTag) 76 if err != nil { 77 all = append(all, params.StorageDetailsResult{Error: serverErr}) 78 continue 79 } 80 if found { 81 results := api.createStorageDetailsResult(storageTag, instance) 82 all = append(all, results...) 83 } 84 } 85 return params.StorageDetailsResults{Results: all}, nil 86 } 87 88 // List returns all currently known storage. Unlike Show(), 89 // if errors encountered while retrieving a particular 90 // storage, this error is treated as part of the returned storage detail. 91 func (api *API) List() (params.StorageInfosResult, error) { 92 stateInstances, err := api.storage.AllStorageInstances() 93 if err != nil { 94 return params.StorageInfosResult{}, common.ServerError(err) 95 } 96 var infos []params.StorageInfo 97 for _, stateInstance := range stateInstances { 98 storageTag := stateInstance.StorageTag() 99 persistent, err := api.isPersistent(stateInstance) 100 if err != nil { 101 return params.StorageInfosResult{}, err 102 } 103 instance := createParamsStorageInstance(stateInstance, persistent) 104 105 // It is possible to encounter errors here related to getting individual 106 // storage details such as getting attachments, getting machine from the unit, 107 // etc. 108 // Current approach is to do what status command does - treat error 109 // as another valid property, i.e. augment storage details. 110 attachments := api.createStorageDetailsResult(storageTag, instance) 111 for _, one := range attachments { 112 aParam := params.StorageInfo{one.Result, one.Error} 113 infos = append(infos, aParam) 114 } 115 } 116 return params.StorageInfosResult{Results: infos}, nil 117 } 118 119 func (api *API) createStorageDetailsResult( 120 storageTag names.StorageTag, 121 instance params.StorageDetails, 122 ) []params.StorageDetailsResult { 123 attachments, err := api.getStorageAttachments(storageTag, instance) 124 if err != nil { 125 return []params.StorageDetailsResult{params.StorageDetailsResult{Result: instance, Error: err}} 126 } 127 if len(attachments) > 0 { 128 // If any attachments were found for this storage instance, 129 // return them instead. 130 result := make([]params.StorageDetailsResult, len(attachments)) 131 for i, attachment := range attachments { 132 result[i] = params.StorageDetailsResult{Result: attachment} 133 } 134 return result 135 } 136 // If we are here then this storage instance is unattached. 137 return []params.StorageDetailsResult{params.StorageDetailsResult{Result: instance}} 138 } 139 140 func (api *API) getStorageAttachments( 141 storageTag names.StorageTag, 142 instance params.StorageDetails, 143 ) ([]params.StorageDetails, *params.Error) { 144 serverError := func(err error) *params.Error { 145 return common.ServerError(errors.Annotatef(err, "getting attachments for storage %v", storageTag.Id())) 146 } 147 stateAttachments, err := api.storage.StorageAttachments(storageTag) 148 if err != nil { 149 return nil, serverError(err) 150 } 151 result := make([]params.StorageDetails, len(stateAttachments)) 152 for i, one := range stateAttachments { 153 paramsStorageAttachment, err := api.createParamsStorageAttachment(instance, one) 154 if err != nil { 155 return nil, serverError(err) 156 } 157 result[i] = paramsStorageAttachment 158 } 159 return result, nil 160 } 161 162 func (api *API) createParamsStorageAttachment(si params.StorageDetails, sa state.StorageAttachment) (params.StorageDetails, error) { 163 result := params.StorageDetails{Status: "pending"} 164 result.StorageTag = sa.StorageInstance().String() 165 if result.StorageTag != si.StorageTag { 166 panic("attachment does not belong to storage instance") 167 } 168 result.UnitTag = sa.Unit().String() 169 result.OwnerTag = si.OwnerTag 170 result.Kind = si.Kind 171 result.Persistent = si.Persistent 172 // TODO(axw) set status according to whether storage has been provisioned. 173 174 // This is only for provisioned attachments 175 machineTag, err := api.storage.UnitAssignedMachine(sa.Unit()) 176 if err != nil { 177 return params.StorageDetails{}, errors.Annotate(err, "getting unit for storage attachment") 178 } 179 info, err := common.StorageAttachmentInfo(api.storage, sa, machineTag) 180 if err != nil { 181 if errors.IsNotProvisioned(err) { 182 // If Info returns an error, then the storage has not yet been provisioned. 183 return result, nil 184 } 185 return params.StorageDetails{}, errors.Annotate(err, "getting storage attachment info") 186 } 187 result.Location = info.Location 188 if result.Location != "" { 189 result.Status = "attached" 190 } 191 return result, nil 192 } 193 194 func (api *API) getStorageInstance(tag names.StorageTag) (bool, params.StorageDetails, *params.Error) { 195 nothing := params.StorageDetails{} 196 serverError := func(err error) *params.Error { 197 return common.ServerError(errors.Annotatef(err, "getting %v", tag)) 198 } 199 stateInstance, err := api.storage.StorageInstance(tag) 200 if err != nil { 201 if errors.IsNotFound(err) { 202 return false, nothing, nil 203 } 204 return false, nothing, serverError(err) 205 } 206 persistent, err := api.isPersistent(stateInstance) 207 if err != nil { 208 return false, nothing, serverError(err) 209 } 210 return true, createParamsStorageInstance(stateInstance, persistent), nil 211 } 212 213 func createParamsStorageInstance(si state.StorageInstance, persistent bool) params.StorageDetails { 214 result := params.StorageDetails{ 215 OwnerTag: si.Owner().String(), 216 StorageTag: si.Tag().String(), 217 Kind: params.StorageKind(si.Kind()), 218 Status: "pending", 219 Persistent: persistent, 220 } 221 return result 222 } 223 224 // TODO(axw) move this and createParamsStorageInstance to 225 // apiserver/common/storage.go, alongside StorageAttachmentInfo. 226 func (api *API) isPersistent(si state.StorageInstance) (bool, error) { 227 if si.Kind() != state.StorageKindBlock { 228 // TODO(axw) when we support persistent filesystems, 229 // e.g. CephFS, we'll need to do the same thing as 230 // we do for volumes for filesystems. 231 return false, nil 232 } 233 volume, err := api.storage.StorageInstanceVolume(si.StorageTag()) 234 if err != nil { 235 return false, err 236 } 237 // If the volume is not provisioned, we read its config attributes. 238 if params, ok := volume.Params(); ok { 239 _, cfg, err := common.StoragePoolConfig(params.Pool, api.poolManager) 240 if err != nil { 241 return false, err 242 } 243 return cfg.IsPersistent(), nil 244 } 245 // If the volume is provisioned, we look at its provisioning info. 246 info, err := volume.Info() 247 if err != nil { 248 return false, err 249 } 250 return info.Persistent, nil 251 } 252 253 // ListPools returns a list of pools. 254 // If filter is provided, returned list only contains pools that match 255 // the filter. 256 // Pools can be filtered on names and provider types. 257 // If both names and types are provided as filter, 258 // pools that match either are returned. 259 // This method lists union of pools and environment provider types. 260 // If no filter is provided, all pools are returned. 261 func (a *API) ListPools( 262 filter params.StoragePoolFilter, 263 ) (params.StoragePoolsResult, error) { 264 265 if ok, err := a.isValidPoolListFilter(filter); !ok { 266 return params.StoragePoolsResult{}, err 267 } 268 269 pools, err := a.poolManager.List() 270 if err != nil { 271 return params.StoragePoolsResult{}, err 272 } 273 providers, err := a.allProviders() 274 if err != nil { 275 return params.StoragePoolsResult{}, err 276 } 277 matches := buildFilter(filter) 278 results := append( 279 filterPools(pools, matches), 280 filterProviders(providers, matches)..., 281 ) 282 return params.StoragePoolsResult{results}, nil 283 } 284 285 func buildFilter(filter params.StoragePoolFilter) func(n, p string) bool { 286 providerSet := set.NewStrings(filter.Providers...) 287 nameSet := set.NewStrings(filter.Names...) 288 289 matches := func(n, p string) bool { 290 // no filters supplied = pool matches criteria 291 if providerSet.IsEmpty() && nameSet.IsEmpty() { 292 return true 293 } 294 // if at least 1 name and type are supplied, use AND to match 295 if !providerSet.IsEmpty() && !nameSet.IsEmpty() { 296 return nameSet.Contains(n) && providerSet.Contains(string(p)) 297 } 298 // Otherwise, if only names or types are supplied, use OR to match 299 return nameSet.Contains(n) || providerSet.Contains(string(p)) 300 } 301 return matches 302 } 303 304 func filterProviders( 305 providers []storage.ProviderType, 306 matches func(n, p string) bool, 307 ) []params.StoragePool { 308 if len(providers) == 0 { 309 return nil 310 } 311 all := make([]params.StoragePool, 0, len(providers)) 312 for _, p := range providers { 313 ps := string(p) 314 if matches(ps, ps) { 315 all = append(all, params.StoragePool{Name: ps, Provider: ps}) 316 } 317 } 318 return all 319 } 320 321 func filterPools( 322 pools []*storage.Config, 323 matches func(n, p string) bool, 324 ) []params.StoragePool { 325 if len(pools) == 0 { 326 return nil 327 } 328 all := make([]params.StoragePool, 0, len(pools)) 329 for _, p := range pools { 330 if matches(p.Name(), string(p.Provider())) { 331 all = append(all, params.StoragePool{ 332 Name: p.Name(), 333 Provider: string(p.Provider()), 334 Attrs: p.Attrs(), 335 }) 336 } 337 } 338 return all 339 } 340 341 func (a *API) allProviders() ([]storage.ProviderType, error) { 342 envName, err := a.storage.EnvName() 343 if err != nil { 344 return nil, errors.Annotate(err, "getting env name") 345 } 346 if providers, ok := registry.EnvironStorageProviders(envName); ok { 347 return providers, nil 348 } 349 return nil, nil 350 } 351 352 func (a *API) isValidPoolListFilter( 353 filter params.StoragePoolFilter, 354 ) (bool, error) { 355 if len(filter.Providers) != 0 { 356 if valid, err := a.isValidProviderCriteria(filter.Providers); !valid { 357 return false, errors.Trace(err) 358 } 359 } 360 if len(filter.Names) != 0 { 361 if valid, err := a.isValidNameCriteria(filter.Names); !valid { 362 return false, errors.Trace(err) 363 } 364 } 365 return true, nil 366 } 367 368 func (a *API) isValidNameCriteria(names []string) (bool, error) { 369 for _, n := range names { 370 if !storage.IsValidPoolName(n) { 371 return false, errors.NotValidf("pool name %q", n) 372 } 373 } 374 return true, nil 375 } 376 377 func (a *API) isValidProviderCriteria(providers []string) (bool, error) { 378 envName, err := a.storage.EnvName() 379 if err != nil { 380 return false, errors.Annotate(err, "getting env name") 381 } 382 for _, p := range providers { 383 if !registry.IsProviderSupported(envName, storage.ProviderType(p)) { 384 return false, errors.NotSupportedf("%q for environment %q", p, envName) 385 } 386 } 387 return true, nil 388 } 389 390 // CreatePool creates a new pool with specified parameters. 391 func (a *API) CreatePool(p params.StoragePool) error { 392 _, err := a.poolManager.Create( 393 p.Name, 394 storage.ProviderType(p.Provider), 395 p.Attrs) 396 return err 397 } 398 399 func (a *API) ListVolumes(filter params.VolumeFilter) (params.VolumeItemsResult, error) { 400 if !filter.IsEmpty() { 401 return params.VolumeItemsResult{Results: a.filterVolumes(filter)}, nil 402 } 403 volumes, err := a.listVolumeAttachments() 404 if err != nil { 405 return params.VolumeItemsResult{}, common.ServerError(err) 406 } 407 return params.VolumeItemsResult{Results: volumes}, nil 408 } 409 410 func (a *API) listVolumeAttachments() ([]params.VolumeItem, error) { 411 all, err := a.storage.AllVolumes() 412 if err != nil { 413 return nil, errors.Trace(err) 414 } 415 return a.volumeAttachments(all), nil 416 } 417 418 func (a *API) volumeAttachments(all []state.Volume) []params.VolumeItem { 419 if all == nil || len(all) == 0 { 420 return nil 421 } 422 423 result := make([]params.VolumeItem, len(all)) 424 for i, v := range all { 425 volume, err := a.convertStateVolumeToParams(v) 426 if err != nil { 427 result[i] = params.VolumeItem{ 428 Error: common.ServerError(errors.Trace(err)), 429 } 430 continue 431 } 432 result[i] = params.VolumeItem{Volume: volume} 433 atts, err := a.storage.VolumeAttachments(v.VolumeTag()) 434 if err != nil { 435 result[i].Error = common.ServerError(errors.Annotatef( 436 err, "attachments for volume %v", v.VolumeTag())) 437 continue 438 } 439 result[i].Attachments = convertStateVolumeAttachmentsToParams(atts) 440 } 441 return result 442 } 443 444 func (a *API) filterVolumes(f params.VolumeFilter) []params.VolumeItem { 445 var attachments []state.VolumeAttachment 446 var errs []params.VolumeItem 447 448 addErr := func(err error) { 449 errs = append(errs, 450 params.VolumeItem{Error: common.ServerError(err)}) 451 } 452 453 for _, machine := range f.Machines { 454 tag, err := names.ParseMachineTag(machine) 455 if err != nil { 456 addErr(errors.Annotatef(err, "parsing machine tag %v", machine)) 457 } 458 machineAttachments, err := a.storage.MachineVolumeAttachments(tag) 459 if err != nil { 460 addErr(errors.Annotatef(err, 461 "getting volume attachments for machine %v", 462 machine)) 463 } 464 attachments = append(attachments, machineAttachments...) 465 } 466 return append(errs, a.getVolumeItems(attachments)...) 467 } 468 469 func (a *API) convertStateVolumeToParams(st state.Volume) (params.VolumeInstance, error) { 470 volume := params.VolumeInstance{VolumeTag: st.VolumeTag().String()} 471 472 if storage, err := st.StorageInstance(); err == nil { 473 volume.StorageTag = storage.String() 474 storageInstance, err := a.storage.StorageInstance(storage) 475 if err != nil { 476 err = errors.Annotatef(err, 477 "getting storage instance %v for volume %v", 478 storage, volume.VolumeTag) 479 return params.VolumeInstance{}, err 480 } 481 owner := storageInstance.Owner() 482 // only interested in Unit for now 483 if unitTag, ok := owner.(names.UnitTag); ok { 484 volume.UnitTag = unitTag.String() 485 } 486 } 487 if info, err := st.Info(); err == nil { 488 volume.HardwareId = info.HardwareId 489 volume.Size = info.Size 490 volume.Persistent = info.Persistent 491 volume.VolumeId = info.VolumeId 492 } 493 status, err := st.Status() 494 if err != nil { 495 return params.VolumeInstance{}, errors.Trace(err) 496 } 497 volume.Status = common.EntityStatusFromState(status) 498 return volume, nil 499 } 500 501 func convertStateVolumeAttachmentsToParams(all []state.VolumeAttachment) []params.VolumeAttachment { 502 if len(all) == 0 { 503 return nil 504 } 505 result := make([]params.VolumeAttachment, len(all)) 506 for i, one := range all { 507 result[i] = convertStateVolumeAttachmentToParams(one) 508 } 509 return result 510 } 511 512 func convertStateVolumeAttachmentToParams(attachment state.VolumeAttachment) params.VolumeAttachment { 513 result := params.VolumeAttachment{ 514 VolumeTag: attachment.Volume().String(), 515 MachineTag: attachment.Machine().String()} 516 if info, err := attachment.Info(); err == nil { 517 result.Info = params.VolumeAttachmentInfo{ 518 info.DeviceName, 519 info.BusAddress, 520 info.ReadOnly, 521 } 522 } 523 return result 524 } 525 526 func (a *API) getVolumeItems(all []state.VolumeAttachment) []params.VolumeItem { 527 group := groupAttachmentsByVolume(all) 528 529 if len(group) == 0 { 530 return nil 531 } 532 533 result := make([]params.VolumeItem, len(group)) 534 i := 0 535 for volumeTag, attachments := range group { 536 result[i] = a.createVolumeItem(volumeTag, attachments) 537 i++ 538 } 539 return result 540 } 541 542 func (a *API) createVolumeItem(volumeTag string, attachments []params.VolumeAttachment) params.VolumeItem { 543 result := params.VolumeItem{Attachments: attachments} 544 545 tag, err := names.ParseVolumeTag(volumeTag) 546 if err != nil { 547 result.Error = common.ServerError(errors.Annotatef(err, "parsing volume tag %v", volumeTag)) 548 return result 549 } 550 st, err := a.storage.Volume(tag) 551 if err != nil { 552 result.Error = common.ServerError(errors.Annotatef(err, "getting volume for tag %v", tag)) 553 return result 554 } 555 volume, err := a.convertStateVolumeToParams(st) 556 if err != nil { 557 result.Error = common.ServerError(errors.Trace(err)) 558 return result 559 } 560 result.Volume = volume 561 return result 562 } 563 564 // groupAttachmentsByVolume constructs map of attachments grouped by volumeTag 565 func groupAttachmentsByVolume(all []state.VolumeAttachment) map[string][]params.VolumeAttachment { 566 if len(all) == 0 { 567 return nil 568 } 569 group := make(map[string][]params.VolumeAttachment) 570 for _, one := range all { 571 attachment := convertStateVolumeAttachmentToParams(one) 572 group[attachment.VolumeTag] = append( 573 group[attachment.VolumeTag], 574 attachment) 575 } 576 return group 577 } 578 579 // AddToUnit validates and creates additional storage instances for units. 580 // This method handles bulk add operations and 581 // a failure on one individual storage instance does not block remaining 582 // instances from being processed. 583 // A "CHANGE" block can block this operation. 584 func (a *API) AddToUnit(args params.StoragesAddParams) (params.ErrorResults, error) { 585 // Check if changes are allowed and the operation may proceed. 586 blockChecker := common.NewBlockChecker(a.storage) 587 if err := blockChecker.ChangeAllowed(); err != nil { 588 return params.ErrorResults{}, errors.Trace(err) 589 } 590 591 if len(args.Storages) == 0 { 592 return params.ErrorResults{}, nil 593 } 594 595 serverErr := func(err error) params.ErrorResult { 596 if errors.IsNotFound(err) { 597 err = common.ErrPerm 598 } 599 return params.ErrorResult{Error: common.ServerError(err)} 600 } 601 602 paramsToState := func(p params.StorageConstraints) state.StorageConstraints { 603 s := state.StorageConstraints{Pool: p.Pool} 604 if p.Size != nil { 605 s.Size = *p.Size 606 } 607 if p.Count != nil { 608 s.Count = *p.Count 609 } 610 return s 611 } 612 613 result := make([]params.ErrorResult, len(args.Storages)) 614 for i, one := range args.Storages { 615 u, err := names.ParseUnitTag(one.UnitTag) 616 if err != nil { 617 result[i] = serverErr( 618 errors.Annotatef(err, "parsing unit tag %v", one.UnitTag)) 619 continue 620 } 621 622 err = a.storage.AddStorageForUnit(u, 623 one.StorageName, 624 paramsToState(one.Constraints)) 625 if err != nil { 626 result[i] = serverErr( 627 errors.Annotatef(err, "adding storage %v for %v", one.StorageName, one.UnitTag)) 628 } 629 } 630 return params.ErrorResults{Results: result}, nil 631 }