github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/storage/storage.go (about)

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