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