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