github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/storageprovisioner/storageprovisioner.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storageprovisioner
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"gopkg.in/juju/names.v2"
    10  
    11  	"github.com/juju/juju/apiserver/common"
    12  	"github.com/juju/juju/apiserver/common/storagecommon"
    13  	"github.com/juju/juju/apiserver/facade"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/state"
    16  	"github.com/juju/juju/state/watcher"
    17  	"github.com/juju/juju/storage"
    18  	"github.com/juju/juju/storage/poolmanager"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.apiserver.storageprovisioner")
    22  
    23  // StorageProvisionerAPI provides access to the Provisioner API facade.
    24  type StorageProvisionerAPI struct {
    25  	*common.LifeGetter
    26  	*common.DeadEnsurer
    27  	*common.InstanceIdGetter
    28  	*common.StatusSetter
    29  
    30  	st                       Backend
    31  	resources                facade.Resources
    32  	authorizer               facade.Authorizer
    33  	registry                 storage.ProviderRegistry
    34  	poolManager              poolmanager.PoolManager
    35  	getScopeAuthFunc         common.GetAuthFunc
    36  	getStorageEntityAuthFunc common.GetAuthFunc
    37  	getMachineAuthFunc       common.GetAuthFunc
    38  	getBlockDevicesAuthFunc  common.GetAuthFunc
    39  	getAttachmentAuthFunc    func() (func(names.MachineTag, names.Tag) bool, error)
    40  }
    41  
    42  // NewStorageProvisionerAPI creates a new server-side StorageProvisionerAPI facade.
    43  func NewStorageProvisionerAPI(
    44  	st Backend,
    45  	resources facade.Resources,
    46  	authorizer facade.Authorizer,
    47  	registry storage.ProviderRegistry,
    48  	poolManager poolmanager.PoolManager,
    49  ) (*StorageProvisionerAPI, error) {
    50  	if !authorizer.AuthMachineAgent() {
    51  		return nil, common.ErrPerm
    52  	}
    53  	canAccessStorageMachine := func(tag names.MachineTag, allowEnvironManager bool) bool {
    54  		authEntityTag := authorizer.GetAuthTag()
    55  		if tag == authEntityTag {
    56  			// Machine agents can access volumes
    57  			// scoped to their own machine.
    58  			return true
    59  		}
    60  		parentId := state.ParentId(tag.Id())
    61  		if parentId == "" {
    62  			return allowEnvironManager && authorizer.AuthModelManager()
    63  		}
    64  		// All containers with the authenticated
    65  		// machine as a parent are accessible by it.
    66  		return names.NewMachineTag(parentId) == authEntityTag
    67  	}
    68  	getScopeAuthFunc := func() (common.AuthFunc, error) {
    69  		return func(tag names.Tag) bool {
    70  			switch tag := tag.(type) {
    71  			case names.ModelTag:
    72  				// Environment managers can access all volumes
    73  				// and filesystems scoped to the environment.
    74  				isModelManager := authorizer.AuthModelManager()
    75  				return isModelManager && tag == st.ModelTag()
    76  			case names.MachineTag:
    77  				return canAccessStorageMachine(tag, false)
    78  			default:
    79  				return false
    80  			}
    81  		}, nil
    82  	}
    83  	canAccessStorageEntity := func(tag names.Tag, allowMachines bool) bool {
    84  		switch tag := tag.(type) {
    85  		case names.VolumeTag:
    86  			machineTag, ok := names.VolumeMachine(tag)
    87  			if ok {
    88  				return canAccessStorageMachine(machineTag, false)
    89  			}
    90  			return authorizer.AuthModelManager()
    91  		case names.FilesystemTag:
    92  			machineTag, ok := names.FilesystemMachine(tag)
    93  			if ok {
    94  				return canAccessStorageMachine(machineTag, false)
    95  			}
    96  			return authorizer.AuthModelManager()
    97  		case names.MachineTag:
    98  			return allowMachines && canAccessStorageMachine(tag, true)
    99  		default:
   100  			return false
   101  		}
   102  	}
   103  	getStorageEntityAuthFunc := func() (common.AuthFunc, error) {
   104  		return func(tag names.Tag) bool {
   105  			return canAccessStorageEntity(tag, false)
   106  		}, nil
   107  	}
   108  	getLifeAuthFunc := func() (common.AuthFunc, error) {
   109  		return func(tag names.Tag) bool {
   110  			return canAccessStorageEntity(tag, true)
   111  		}, nil
   112  	}
   113  	getAttachmentAuthFunc := func() (func(names.MachineTag, names.Tag) bool, error) {
   114  		// getAttachmentAuthFunc returns a function that validates
   115  		// access by the authenticated user to an attachment.
   116  		return func(machineTag names.MachineTag, attachmentTag names.Tag) bool {
   117  			// Machine agents can access their own machine, and
   118  			// machines contained. Environment managers can access
   119  			// top-level machines.
   120  			if !canAccessStorageMachine(machineTag, true) {
   121  				return false
   122  			}
   123  			// Environment managers can access model-scoped
   124  			// volumes and volumes scoped to their own machines.
   125  			// Other machine agents can access volumes regardless
   126  			// of their scope.
   127  			if !authorizer.AuthModelManager() {
   128  				return true
   129  			}
   130  			var machineScope names.MachineTag
   131  			var hasMachineScope bool
   132  			switch attachmentTag := attachmentTag.(type) {
   133  			case names.VolumeTag:
   134  				machineScope, hasMachineScope = names.VolumeMachine(attachmentTag)
   135  			case names.FilesystemTag:
   136  				machineScope, hasMachineScope = names.FilesystemMachine(attachmentTag)
   137  			}
   138  			return !hasMachineScope || machineScope == authorizer.GetAuthTag()
   139  		}, nil
   140  	}
   141  	getMachineAuthFunc := func() (common.AuthFunc, error) {
   142  		return func(tag names.Tag) bool {
   143  			if tag, ok := tag.(names.MachineTag); ok {
   144  				return canAccessStorageMachine(tag, true)
   145  			}
   146  			return false
   147  		}, nil
   148  	}
   149  	getBlockDevicesAuthFunc := func() (common.AuthFunc, error) {
   150  		return func(tag names.Tag) bool {
   151  			if tag, ok := tag.(names.MachineTag); ok {
   152  				return canAccessStorageMachine(tag, false)
   153  			}
   154  			return false
   155  		}, nil
   156  	}
   157  	return &StorageProvisionerAPI{
   158  		LifeGetter:       common.NewLifeGetter(st, getLifeAuthFunc),
   159  		DeadEnsurer:      common.NewDeadEnsurer(st, getStorageEntityAuthFunc),
   160  		InstanceIdGetter: common.NewInstanceIdGetter(st, getMachineAuthFunc),
   161  		StatusSetter:     common.NewStatusSetter(st, getStorageEntityAuthFunc),
   162  
   163  		st:                       st,
   164  		resources:                resources,
   165  		authorizer:               authorizer,
   166  		registry:                 registry,
   167  		poolManager:              poolManager,
   168  		getScopeAuthFunc:         getScopeAuthFunc,
   169  		getStorageEntityAuthFunc: getStorageEntityAuthFunc,
   170  		getAttachmentAuthFunc:    getAttachmentAuthFunc,
   171  		getMachineAuthFunc:       getMachineAuthFunc,
   172  		getBlockDevicesAuthFunc:  getBlockDevicesAuthFunc,
   173  	}, nil
   174  }
   175  
   176  // WatchBlockDevices watches for changes to the specified machines' block devices.
   177  func (s *StorageProvisionerAPI) WatchBlockDevices(args params.Entities) (params.NotifyWatchResults, error) {
   178  	canAccess, err := s.getBlockDevicesAuthFunc()
   179  	if err != nil {
   180  		return params.NotifyWatchResults{}, common.ServerError(common.ErrPerm)
   181  	}
   182  	results := params.NotifyWatchResults{
   183  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   184  	}
   185  	one := func(arg params.Entity) (string, error) {
   186  		machineTag, err := names.ParseMachineTag(arg.Tag)
   187  		if err != nil {
   188  			return "", err
   189  		}
   190  		if !canAccess(machineTag) {
   191  			return "", common.ErrPerm
   192  		}
   193  		w := s.st.WatchBlockDevices(machineTag)
   194  		if _, ok := <-w.Changes(); ok {
   195  			return s.resources.Register(w), nil
   196  		}
   197  		return "", watcher.EnsureErr(w)
   198  	}
   199  	for i, arg := range args.Entities {
   200  		var result params.NotifyWatchResult
   201  		id, err := one(arg)
   202  		if err != nil {
   203  			result.Error = common.ServerError(err)
   204  		} else {
   205  			result.NotifyWatcherId = id
   206  		}
   207  		results.Results[i] = result
   208  	}
   209  	return results, nil
   210  }
   211  
   212  // WatchMachines watches for changes to the specified machines.
   213  func (s *StorageProvisionerAPI) WatchMachines(args params.Entities) (params.NotifyWatchResults, error) {
   214  	canAccess, err := s.getMachineAuthFunc()
   215  	if err != nil {
   216  		return params.NotifyWatchResults{}, common.ServerError(common.ErrPerm)
   217  	}
   218  	results := params.NotifyWatchResults{
   219  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   220  	}
   221  	one := func(arg params.Entity) (string, error) {
   222  		machineTag, err := names.ParseMachineTag(arg.Tag)
   223  		if err != nil {
   224  			return "", err
   225  		}
   226  		if !canAccess(machineTag) {
   227  			return "", common.ErrPerm
   228  		}
   229  		w, err := s.st.WatchMachine(machineTag)
   230  		if err != nil {
   231  			return "", errors.Trace(err)
   232  		}
   233  		if _, ok := <-w.Changes(); ok {
   234  			return s.resources.Register(w), nil
   235  		}
   236  		return "", watcher.EnsureErr(w)
   237  	}
   238  	for i, arg := range args.Entities {
   239  		var result params.NotifyWatchResult
   240  		id, err := one(arg)
   241  		if err != nil {
   242  			result.Error = common.ServerError(err)
   243  		} else {
   244  			result.NotifyWatcherId = id
   245  		}
   246  		results.Results[i] = result
   247  	}
   248  	return results, nil
   249  }
   250  
   251  // WatchVolumes watches for changes to volumes scoped to the
   252  // entity with the tag passed to NewState.
   253  func (s *StorageProvisionerAPI) WatchVolumes(args params.Entities) (params.StringsWatchResults, error) {
   254  	return s.watchStorageEntities(args, s.st.WatchModelVolumes, s.st.WatchMachineVolumes)
   255  }
   256  
   257  // WatchFilesystems watches for changes to filesystems scoped
   258  // to the entity with the tag passed to NewState.
   259  func (s *StorageProvisionerAPI) WatchFilesystems(args params.Entities) (params.StringsWatchResults, error) {
   260  	return s.watchStorageEntities(args, s.st.WatchModelFilesystems, s.st.WatchMachineFilesystems)
   261  }
   262  
   263  func (s *StorageProvisionerAPI) watchStorageEntities(
   264  	args params.Entities,
   265  	watchEnvironStorage func() state.StringsWatcher,
   266  	watchMachineStorage func(names.MachineTag) state.StringsWatcher,
   267  ) (params.StringsWatchResults, error) {
   268  	canAccess, err := s.getScopeAuthFunc()
   269  	if err != nil {
   270  		return params.StringsWatchResults{}, common.ServerError(common.ErrPerm)
   271  	}
   272  	results := params.StringsWatchResults{
   273  		Results: make([]params.StringsWatchResult, len(args.Entities)),
   274  	}
   275  	one := func(arg params.Entity) (string, []string, error) {
   276  		tag, err := names.ParseTag(arg.Tag)
   277  		if err != nil || !canAccess(tag) {
   278  			return "", nil, common.ErrPerm
   279  		}
   280  		var w state.StringsWatcher
   281  		if tag, ok := tag.(names.MachineTag); ok {
   282  			w = watchMachineStorage(tag)
   283  		} else {
   284  			w = watchEnvironStorage()
   285  		}
   286  		if changes, ok := <-w.Changes(); ok {
   287  			return s.resources.Register(w), changes, nil
   288  		}
   289  		return "", nil, watcher.EnsureErr(w)
   290  	}
   291  	for i, arg := range args.Entities {
   292  		var result params.StringsWatchResult
   293  		id, changes, err := one(arg)
   294  		if err != nil {
   295  			result.Error = common.ServerError(err)
   296  		} else {
   297  			result.StringsWatcherId = id
   298  			result.Changes = changes
   299  		}
   300  		results.Results[i] = result
   301  	}
   302  	return results, nil
   303  }
   304  
   305  // WatchVolumeAttachments watches for changes to volume attachments scoped to
   306  // the entity with the tag passed to NewState.
   307  func (s *StorageProvisionerAPI) WatchVolumeAttachments(args params.Entities) (params.MachineStorageIdsWatchResults, error) {
   308  	return s.watchAttachments(
   309  		args,
   310  		s.st.WatchEnvironVolumeAttachments,
   311  		s.st.WatchMachineVolumeAttachments,
   312  		storagecommon.ParseVolumeAttachmentIds,
   313  	)
   314  }
   315  
   316  // WatchFilesystemAttachments watches for changes to filesystem attachments
   317  // scoped to the entity with the tag passed to NewState.
   318  func (s *StorageProvisionerAPI) WatchFilesystemAttachments(args params.Entities) (params.MachineStorageIdsWatchResults, error) {
   319  	return s.watchAttachments(
   320  		args,
   321  		s.st.WatchEnvironFilesystemAttachments,
   322  		s.st.WatchMachineFilesystemAttachments,
   323  		storagecommon.ParseFilesystemAttachmentIds,
   324  	)
   325  }
   326  
   327  func (s *StorageProvisionerAPI) watchAttachments(
   328  	args params.Entities,
   329  	watchEnvironAttachments func() state.StringsWatcher,
   330  	watchMachineAttachments func(names.MachineTag) state.StringsWatcher,
   331  	parseAttachmentIds func([]string) ([]params.MachineStorageId, error),
   332  ) (params.MachineStorageIdsWatchResults, error) {
   333  	canAccess, err := s.getScopeAuthFunc()
   334  	if err != nil {
   335  		return params.MachineStorageIdsWatchResults{}, common.ServerError(common.ErrPerm)
   336  	}
   337  	results := params.MachineStorageIdsWatchResults{
   338  		Results: make([]params.MachineStorageIdsWatchResult, len(args.Entities)),
   339  	}
   340  	one := func(arg params.Entity) (string, []params.MachineStorageId, error) {
   341  		tag, err := names.ParseTag(arg.Tag)
   342  		if err != nil || !canAccess(tag) {
   343  			return "", nil, common.ErrPerm
   344  		}
   345  		var w state.StringsWatcher
   346  		if tag, ok := tag.(names.MachineTag); ok {
   347  			w = watchMachineAttachments(tag)
   348  		} else {
   349  			w = watchEnvironAttachments()
   350  		}
   351  		if stringChanges, ok := <-w.Changes(); ok {
   352  			changes, err := parseAttachmentIds(stringChanges)
   353  			if err != nil {
   354  				w.Stop()
   355  				return "", nil, err
   356  			}
   357  			return s.resources.Register(w), changes, nil
   358  		}
   359  		return "", nil, watcher.EnsureErr(w)
   360  	}
   361  	for i, arg := range args.Entities {
   362  		var result params.MachineStorageIdsWatchResult
   363  		id, changes, err := one(arg)
   364  		if err != nil {
   365  			result.Error = common.ServerError(err)
   366  		} else {
   367  			result.MachineStorageIdsWatcherId = id
   368  			result.Changes = changes
   369  		}
   370  		results.Results[i] = result
   371  	}
   372  	return results, nil
   373  }
   374  
   375  // Volumes returns details of volumes with the specified tags.
   376  func (s *StorageProvisionerAPI) Volumes(args params.Entities) (params.VolumeResults, error) {
   377  	canAccess, err := s.getStorageEntityAuthFunc()
   378  	if err != nil {
   379  		return params.VolumeResults{}, common.ServerError(common.ErrPerm)
   380  	}
   381  	results := params.VolumeResults{
   382  		Results: make([]params.VolumeResult, len(args.Entities)),
   383  	}
   384  	one := func(arg params.Entity) (params.Volume, error) {
   385  		tag, err := names.ParseVolumeTag(arg.Tag)
   386  		if err != nil || !canAccess(tag) {
   387  			return params.Volume{}, common.ErrPerm
   388  		}
   389  		volume, err := s.st.Volume(tag)
   390  		if errors.IsNotFound(err) {
   391  			return params.Volume{}, common.ErrPerm
   392  		} else if err != nil {
   393  			return params.Volume{}, err
   394  		}
   395  		return storagecommon.VolumeFromState(volume)
   396  	}
   397  	for i, arg := range args.Entities {
   398  		var result params.VolumeResult
   399  		volume, err := one(arg)
   400  		if err != nil {
   401  			result.Error = common.ServerError(err)
   402  		} else {
   403  			result.Result = volume
   404  		}
   405  		results.Results[i] = result
   406  	}
   407  	return results, nil
   408  }
   409  
   410  // Filesystems returns details of filesystems with the specified tags.
   411  func (s *StorageProvisionerAPI) Filesystems(args params.Entities) (params.FilesystemResults, error) {
   412  	canAccess, err := s.getStorageEntityAuthFunc()
   413  	if err != nil {
   414  		return params.FilesystemResults{}, common.ServerError(common.ErrPerm)
   415  	}
   416  	results := params.FilesystemResults{
   417  		Results: make([]params.FilesystemResult, len(args.Entities)),
   418  	}
   419  	one := func(arg params.Entity) (params.Filesystem, error) {
   420  		tag, err := names.ParseFilesystemTag(arg.Tag)
   421  		if err != nil || !canAccess(tag) {
   422  			return params.Filesystem{}, common.ErrPerm
   423  		}
   424  		filesystem, err := s.st.Filesystem(tag)
   425  		if errors.IsNotFound(err) {
   426  			return params.Filesystem{}, common.ErrPerm
   427  		} else if err != nil {
   428  			return params.Filesystem{}, err
   429  		}
   430  		return storagecommon.FilesystemFromState(filesystem)
   431  	}
   432  	for i, arg := range args.Entities {
   433  		var result params.FilesystemResult
   434  		filesystem, err := one(arg)
   435  		if err != nil {
   436  			result.Error = common.ServerError(err)
   437  		} else {
   438  			result.Result = filesystem
   439  		}
   440  		results.Results[i] = result
   441  	}
   442  	return results, nil
   443  }
   444  
   445  // VolumeAttachments returns details of volume attachments with the specified IDs.
   446  func (s *StorageProvisionerAPI) VolumeAttachments(args params.MachineStorageIds) (params.VolumeAttachmentResults, error) {
   447  	canAccess, err := s.getAttachmentAuthFunc()
   448  	if err != nil {
   449  		return params.VolumeAttachmentResults{}, common.ServerError(common.ErrPerm)
   450  	}
   451  	results := params.VolumeAttachmentResults{
   452  		Results: make([]params.VolumeAttachmentResult, len(args.Ids)),
   453  	}
   454  	one := func(arg params.MachineStorageId) (params.VolumeAttachment, error) {
   455  		volumeAttachment, err := s.oneVolumeAttachment(arg, canAccess)
   456  		if err != nil {
   457  			return params.VolumeAttachment{}, err
   458  		}
   459  		return storagecommon.VolumeAttachmentFromState(volumeAttachment)
   460  	}
   461  	for i, arg := range args.Ids {
   462  		var result params.VolumeAttachmentResult
   463  		volumeAttachment, err := one(arg)
   464  		if err != nil {
   465  			result.Error = common.ServerError(err)
   466  		} else {
   467  			result.Result = volumeAttachment
   468  		}
   469  		results.Results[i] = result
   470  	}
   471  	return results, nil
   472  }
   473  
   474  // VolumeBlockDevices returns details of the block devices corresponding to the
   475  // volume attachments with the specified IDs.
   476  func (s *StorageProvisionerAPI) VolumeBlockDevices(args params.MachineStorageIds) (params.BlockDeviceResults, error) {
   477  	canAccess, err := s.getAttachmentAuthFunc()
   478  	if err != nil {
   479  		return params.BlockDeviceResults{}, common.ServerError(common.ErrPerm)
   480  	}
   481  	results := params.BlockDeviceResults{
   482  		Results: make([]params.BlockDeviceResult, len(args.Ids)),
   483  	}
   484  	one := func(arg params.MachineStorageId) (storage.BlockDevice, error) {
   485  		stateBlockDevice, err := s.oneVolumeBlockDevice(arg, canAccess)
   486  		if err != nil {
   487  			return storage.BlockDevice{}, err
   488  		}
   489  		return storagecommon.BlockDeviceFromState(stateBlockDevice), nil
   490  	}
   491  	for i, arg := range args.Ids {
   492  		var result params.BlockDeviceResult
   493  		blockDevice, err := one(arg)
   494  		if err != nil {
   495  			result.Error = common.ServerError(err)
   496  		} else {
   497  			result.Result = blockDevice
   498  		}
   499  		results.Results[i] = result
   500  	}
   501  	return results, nil
   502  }
   503  
   504  // FilesystemAttachments returns details of filesystem attachments with the specified IDs.
   505  func (s *StorageProvisionerAPI) FilesystemAttachments(args params.MachineStorageIds) (params.FilesystemAttachmentResults, error) {
   506  	canAccess, err := s.getAttachmentAuthFunc()
   507  	if err != nil {
   508  		return params.FilesystemAttachmentResults{}, common.ServerError(common.ErrPerm)
   509  	}
   510  	results := params.FilesystemAttachmentResults{
   511  		Results: make([]params.FilesystemAttachmentResult, len(args.Ids)),
   512  	}
   513  	one := func(arg params.MachineStorageId) (params.FilesystemAttachment, error) {
   514  		filesystemAttachment, err := s.oneFilesystemAttachment(arg, canAccess)
   515  		if err != nil {
   516  			return params.FilesystemAttachment{}, err
   517  		}
   518  		return storagecommon.FilesystemAttachmentFromState(filesystemAttachment)
   519  	}
   520  	for i, arg := range args.Ids {
   521  		var result params.FilesystemAttachmentResult
   522  		filesystemAttachment, err := one(arg)
   523  		if err != nil {
   524  			result.Error = common.ServerError(err)
   525  		} else {
   526  			result.Result = filesystemAttachment
   527  		}
   528  		results.Results[i] = result
   529  	}
   530  	return results, nil
   531  }
   532  
   533  // VolumeParams returns the parameters for creating or destroying
   534  // the volumes with the specified tags.
   535  func (s *StorageProvisionerAPI) VolumeParams(args params.Entities) (params.VolumeParamsResults, error) {
   536  	canAccess, err := s.getStorageEntityAuthFunc()
   537  	if err != nil {
   538  		return params.VolumeParamsResults{}, err
   539  	}
   540  	modelCfg, err := s.st.ModelConfig()
   541  	if err != nil {
   542  		return params.VolumeParamsResults{}, err
   543  	}
   544  	controllerCfg, err := s.st.ControllerConfig()
   545  	if err != nil {
   546  		return params.VolumeParamsResults{}, err
   547  	}
   548  	results := params.VolumeParamsResults{
   549  		Results: make([]params.VolumeParamsResult, len(args.Entities)),
   550  	}
   551  	one := func(arg params.Entity) (params.VolumeParams, error) {
   552  		tag, err := names.ParseVolumeTag(arg.Tag)
   553  		if err != nil || !canAccess(tag) {
   554  			return params.VolumeParams{}, common.ErrPerm
   555  		}
   556  		volume, err := s.st.Volume(tag)
   557  		if errors.IsNotFound(err) {
   558  			return params.VolumeParams{}, common.ErrPerm
   559  		} else if err != nil {
   560  			return params.VolumeParams{}, err
   561  		}
   562  		volumeAttachments, err := s.st.VolumeAttachments(tag)
   563  		if err != nil {
   564  			return params.VolumeParams{}, err
   565  		}
   566  		storageInstance, err := storagecommon.MaybeAssignedStorageInstance(
   567  			volume.StorageInstance,
   568  			s.st.StorageInstance,
   569  		)
   570  		if err != nil {
   571  			return params.VolumeParams{}, err
   572  		}
   573  		volumeParams, err := storagecommon.VolumeParams(
   574  			volume, storageInstance, modelCfg.UUID(), controllerCfg.ControllerUUID(),
   575  			modelCfg, s.poolManager, s.registry,
   576  		)
   577  		if err != nil {
   578  			return params.VolumeParams{}, err
   579  		}
   580  		if len(volumeAttachments) == 1 {
   581  			// There is exactly one attachment to be made, so make
   582  			// it immediately. Otherwise we will defer attachments
   583  			// until later.
   584  			volumeAttachment := volumeAttachments[0]
   585  			volumeAttachmentParams, ok := volumeAttachment.Params()
   586  			if !ok {
   587  				return params.VolumeParams{}, errors.Errorf(
   588  					"volume %q is already attached to machine %q",
   589  					volumeAttachment.Volume().Id(),
   590  					volumeAttachment.Machine().Id(),
   591  				)
   592  			}
   593  			machineTag := volumeAttachment.Machine()
   594  			instanceId, err := s.st.MachineInstanceId(machineTag)
   595  			if errors.IsNotProvisioned(err) {
   596  				// Leave the attachment until later.
   597  				instanceId = ""
   598  			} else if err != nil {
   599  				return params.VolumeParams{}, err
   600  			}
   601  			volumeParams.Attachment = &params.VolumeAttachmentParams{
   602  				tag.String(),
   603  				machineTag.String(),
   604  				"", // volume ID
   605  				string(instanceId),
   606  				volumeParams.Provider,
   607  				volumeAttachmentParams.ReadOnly,
   608  			}
   609  		}
   610  		return volumeParams, nil
   611  	}
   612  	for i, arg := range args.Entities {
   613  		var result params.VolumeParamsResult
   614  		volumeParams, err := one(arg)
   615  		if err != nil {
   616  			result.Error = common.ServerError(err)
   617  		} else {
   618  			result.Result = volumeParams
   619  		}
   620  		results.Results[i] = result
   621  	}
   622  	return results, nil
   623  }
   624  
   625  // FilesystemParams returns the parameters for creating the filesystems
   626  // with the specified tags.
   627  func (s *StorageProvisionerAPI) FilesystemParams(args params.Entities) (params.FilesystemParamsResults, error) {
   628  	canAccess, err := s.getStorageEntityAuthFunc()
   629  	if err != nil {
   630  		return params.FilesystemParamsResults{}, err
   631  	}
   632  	modelConfig, err := s.st.ModelConfig()
   633  	if err != nil {
   634  		return params.FilesystemParamsResults{}, err
   635  	}
   636  	controllerCfg, err := s.st.ControllerConfig()
   637  	if err != nil {
   638  		return params.FilesystemParamsResults{}, err
   639  	}
   640  	results := params.FilesystemParamsResults{
   641  		Results: make([]params.FilesystemParamsResult, len(args.Entities)),
   642  	}
   643  	one := func(arg params.Entity) (params.FilesystemParams, error) {
   644  		tag, err := names.ParseFilesystemTag(arg.Tag)
   645  		if err != nil || !canAccess(tag) {
   646  			return params.FilesystemParams{}, common.ErrPerm
   647  		}
   648  		filesystem, err := s.st.Filesystem(tag)
   649  		if errors.IsNotFound(err) {
   650  			return params.FilesystemParams{}, common.ErrPerm
   651  		} else if err != nil {
   652  			return params.FilesystemParams{}, err
   653  		}
   654  		storageInstance, err := storagecommon.MaybeAssignedStorageInstance(
   655  			filesystem.Storage,
   656  			s.st.StorageInstance,
   657  		)
   658  		if err != nil {
   659  			return params.FilesystemParams{}, err
   660  		}
   661  		filesystemParams, err := storagecommon.FilesystemParams(
   662  			filesystem, storageInstance, modelConfig.UUID(), controllerCfg.ControllerUUID(),
   663  			modelConfig, s.poolManager, s.registry,
   664  		)
   665  		if err != nil {
   666  			return params.FilesystemParams{}, err
   667  		}
   668  		return filesystemParams, nil
   669  	}
   670  	for i, arg := range args.Entities {
   671  		var result params.FilesystemParamsResult
   672  		filesystemParams, err := one(arg)
   673  		if err != nil {
   674  			result.Error = common.ServerError(err)
   675  		} else {
   676  			result.Result = filesystemParams
   677  		}
   678  		results.Results[i] = result
   679  	}
   680  	return results, nil
   681  }
   682  
   683  // VolumeAttachmentParams returns the parameters for creating the volume
   684  // attachments with the specified IDs.
   685  func (s *StorageProvisionerAPI) VolumeAttachmentParams(
   686  	args params.MachineStorageIds,
   687  ) (params.VolumeAttachmentParamsResults, error) {
   688  	canAccess, err := s.getAttachmentAuthFunc()
   689  	if err != nil {
   690  		return params.VolumeAttachmentParamsResults{}, common.ServerError(common.ErrPerm)
   691  	}
   692  	results := params.VolumeAttachmentParamsResults{
   693  		Results: make([]params.VolumeAttachmentParamsResult, len(args.Ids)),
   694  	}
   695  	one := func(arg params.MachineStorageId) (params.VolumeAttachmentParams, error) {
   696  		volumeAttachment, err := s.oneVolumeAttachment(arg, canAccess)
   697  		if err != nil {
   698  			return params.VolumeAttachmentParams{}, err
   699  		}
   700  		instanceId, err := s.st.MachineInstanceId(volumeAttachment.Machine())
   701  		if errors.IsNotProvisioned(err) {
   702  			// The worker must watch for machine provisioning events.
   703  			instanceId = ""
   704  		} else if err != nil {
   705  			return params.VolumeAttachmentParams{}, err
   706  		}
   707  		volume, err := s.st.Volume(volumeAttachment.Volume())
   708  		if err != nil {
   709  			return params.VolumeAttachmentParams{}, err
   710  		}
   711  		var volumeId string
   712  		var pool string
   713  		if volumeParams, ok := volume.Params(); ok {
   714  			pool = volumeParams.Pool
   715  		} else {
   716  			volumeInfo, err := volume.Info()
   717  			if err != nil {
   718  				return params.VolumeAttachmentParams{}, err
   719  			}
   720  			volumeId = volumeInfo.VolumeId
   721  			pool = volumeInfo.Pool
   722  		}
   723  		providerType, _, err := storagecommon.StoragePoolConfig(pool, s.poolManager, s.registry)
   724  		if err != nil {
   725  			return params.VolumeAttachmentParams{}, errors.Trace(err)
   726  		}
   727  		var readOnly bool
   728  		if volumeAttachmentParams, ok := volumeAttachment.Params(); ok {
   729  			readOnly = volumeAttachmentParams.ReadOnly
   730  		} else {
   731  			// Attachment parameters may be requested even if the
   732  			// attachment exists; i.e. for reattachment.
   733  			volumeAttachmentInfo, err := volumeAttachment.Info()
   734  			if err != nil {
   735  				return params.VolumeAttachmentParams{}, errors.Trace(err)
   736  			}
   737  			readOnly = volumeAttachmentInfo.ReadOnly
   738  		}
   739  		return params.VolumeAttachmentParams{
   740  			volumeAttachment.Volume().String(),
   741  			volumeAttachment.Machine().String(),
   742  			volumeId,
   743  			string(instanceId),
   744  			string(providerType),
   745  			readOnly,
   746  		}, nil
   747  	}
   748  	for i, arg := range args.Ids {
   749  		var result params.VolumeAttachmentParamsResult
   750  		volumeAttachment, err := one(arg)
   751  		if err != nil {
   752  			result.Error = common.ServerError(err)
   753  		} else {
   754  			result.Result = volumeAttachment
   755  		}
   756  		results.Results[i] = result
   757  	}
   758  	return results, nil
   759  }
   760  
   761  // FilesystemAttachmentParams returns the parameters for creating the filesystem
   762  // attachments with the specified IDs.
   763  func (s *StorageProvisionerAPI) FilesystemAttachmentParams(
   764  	args params.MachineStorageIds,
   765  ) (params.FilesystemAttachmentParamsResults, error) {
   766  	canAccess, err := s.getAttachmentAuthFunc()
   767  	if err != nil {
   768  		return params.FilesystemAttachmentParamsResults{}, common.ServerError(common.ErrPerm)
   769  	}
   770  	results := params.FilesystemAttachmentParamsResults{
   771  		Results: make([]params.FilesystemAttachmentParamsResult, len(args.Ids)),
   772  	}
   773  	one := func(arg params.MachineStorageId) (params.FilesystemAttachmentParams, error) {
   774  		filesystemAttachment, err := s.oneFilesystemAttachment(arg, canAccess)
   775  		if err != nil {
   776  			return params.FilesystemAttachmentParams{}, err
   777  		}
   778  		instanceId, err := s.st.MachineInstanceId(filesystemAttachment.Machine())
   779  		if errors.IsNotProvisioned(err) {
   780  			// The worker must watch for machine provisioning events.
   781  			instanceId = ""
   782  		} else if err != nil {
   783  			return params.FilesystemAttachmentParams{}, err
   784  		}
   785  		filesystem, err := s.st.Filesystem(filesystemAttachment.Filesystem())
   786  		if err != nil {
   787  			return params.FilesystemAttachmentParams{}, err
   788  		}
   789  		var filesystemId string
   790  		var pool string
   791  		if filesystemParams, ok := filesystem.Params(); ok {
   792  			pool = filesystemParams.Pool
   793  		} else {
   794  			filesystemInfo, err := filesystem.Info()
   795  			if err != nil {
   796  				return params.FilesystemAttachmentParams{}, err
   797  			}
   798  			filesystemId = filesystemInfo.FilesystemId
   799  			pool = filesystemInfo.Pool
   800  		}
   801  		providerType, _, err := storagecommon.StoragePoolConfig(pool, s.poolManager, s.registry)
   802  		if err != nil {
   803  			return params.FilesystemAttachmentParams{}, errors.Trace(err)
   804  		}
   805  		var location string
   806  		var readOnly bool
   807  		if filesystemAttachmentParams, ok := filesystemAttachment.Params(); ok {
   808  			location = filesystemAttachmentParams.Location
   809  			readOnly = filesystemAttachmentParams.ReadOnly
   810  		} else {
   811  			// Attachment parameters may be requested even if the
   812  			// attachment exists; i.e. for reattachment.
   813  			filesystemAttachmentInfo, err := filesystemAttachment.Info()
   814  			if err != nil {
   815  				return params.FilesystemAttachmentParams{}, errors.Trace(err)
   816  			}
   817  			location = filesystemAttachmentInfo.MountPoint
   818  			readOnly = filesystemAttachmentInfo.ReadOnly
   819  		}
   820  		return params.FilesystemAttachmentParams{
   821  			filesystemAttachment.Filesystem().String(),
   822  			filesystemAttachment.Machine().String(),
   823  			filesystemId,
   824  			string(instanceId),
   825  			string(providerType),
   826  			// TODO(axw) dealias MountPoint. We now have
   827  			// Path, MountPoint and Location in different
   828  			// parts of the codebase.
   829  			location,
   830  			readOnly,
   831  		}, nil
   832  	}
   833  	for i, arg := range args.Ids {
   834  		var result params.FilesystemAttachmentParamsResult
   835  		filesystemAttachment, err := one(arg)
   836  		if err != nil {
   837  			result.Error = common.ServerError(err)
   838  		} else {
   839  			result.Result = filesystemAttachment
   840  		}
   841  		results.Results[i] = result
   842  	}
   843  	return results, nil
   844  }
   845  
   846  func (s *StorageProvisionerAPI) oneVolumeAttachment(
   847  	id params.MachineStorageId, canAccess func(names.MachineTag, names.Tag) bool,
   848  ) (state.VolumeAttachment, error) {
   849  	machineTag, err := names.ParseMachineTag(id.MachineTag)
   850  	if err != nil {
   851  		return nil, err
   852  	}
   853  	volumeTag, err := names.ParseVolumeTag(id.AttachmentTag)
   854  	if err != nil {
   855  		return nil, err
   856  	}
   857  	if !canAccess(machineTag, volumeTag) {
   858  		return nil, common.ErrPerm
   859  	}
   860  	volumeAttachment, err := s.st.VolumeAttachment(machineTag, volumeTag)
   861  	if errors.IsNotFound(err) {
   862  		return nil, common.ErrPerm
   863  	} else if err != nil {
   864  		return nil, err
   865  	}
   866  	return volumeAttachment, nil
   867  }
   868  
   869  func (s *StorageProvisionerAPI) oneVolumeBlockDevice(
   870  	id params.MachineStorageId, canAccess func(names.MachineTag, names.Tag) bool,
   871  ) (state.BlockDeviceInfo, error) {
   872  	volumeAttachment, err := s.oneVolumeAttachment(id, canAccess)
   873  	if err != nil {
   874  		return state.BlockDeviceInfo{}, err
   875  	}
   876  	volume, err := s.st.Volume(volumeAttachment.Volume())
   877  	if err != nil {
   878  		return state.BlockDeviceInfo{}, err
   879  	}
   880  	volumeInfo, err := volume.Info()
   881  	if err != nil {
   882  		return state.BlockDeviceInfo{}, err
   883  	}
   884  	volumeAttachmentInfo, err := volumeAttachment.Info()
   885  	if err != nil {
   886  		return state.BlockDeviceInfo{}, err
   887  	}
   888  	blockDevices, err := s.st.BlockDevices(volumeAttachment.Machine())
   889  	if err != nil {
   890  		return state.BlockDeviceInfo{}, err
   891  	}
   892  	blockDevice, ok := storagecommon.MatchingBlockDevice(
   893  		blockDevices,
   894  		volumeInfo,
   895  		volumeAttachmentInfo,
   896  	)
   897  	if !ok {
   898  		return state.BlockDeviceInfo{}, errors.NotFoundf(
   899  			"block device for volume %v on machine %v",
   900  			volumeAttachment.Volume().Id(),
   901  			volumeAttachment.Machine().Id(),
   902  		)
   903  	}
   904  	return *blockDevice, nil
   905  }
   906  
   907  func (s *StorageProvisionerAPI) oneFilesystemAttachment(
   908  	id params.MachineStorageId, canAccess func(names.MachineTag, names.Tag) bool,
   909  ) (state.FilesystemAttachment, error) {
   910  	machineTag, err := names.ParseMachineTag(id.MachineTag)
   911  	if err != nil {
   912  		return nil, err
   913  	}
   914  	filesystemTag, err := names.ParseFilesystemTag(id.AttachmentTag)
   915  	if err != nil {
   916  		return nil, err
   917  	}
   918  	if !canAccess(machineTag, filesystemTag) {
   919  		return nil, common.ErrPerm
   920  	}
   921  	filesystemAttachment, err := s.st.FilesystemAttachment(machineTag, filesystemTag)
   922  	if errors.IsNotFound(err) {
   923  		return nil, common.ErrPerm
   924  	} else if err != nil {
   925  		return nil, err
   926  	}
   927  	return filesystemAttachment, nil
   928  }
   929  
   930  // SetVolumeInfo records the details of newly provisioned volumes.
   931  func (s *StorageProvisionerAPI) SetVolumeInfo(args params.Volumes) (params.ErrorResults, error) {
   932  	canAccessVolume, err := s.getStorageEntityAuthFunc()
   933  	if err != nil {
   934  		return params.ErrorResults{}, err
   935  	}
   936  	results := params.ErrorResults{
   937  		Results: make([]params.ErrorResult, len(args.Volumes)),
   938  	}
   939  	one := func(arg params.Volume) error {
   940  		volumeTag, volumeInfo, err := storagecommon.VolumeToState(arg)
   941  		if err != nil {
   942  			return errors.Trace(err)
   943  		} else if !canAccessVolume(volumeTag) {
   944  			return common.ErrPerm
   945  		}
   946  		err = s.st.SetVolumeInfo(volumeTag, volumeInfo)
   947  		if errors.IsNotFound(err) {
   948  			return common.ErrPerm
   949  		}
   950  		return errors.Trace(err)
   951  	}
   952  	for i, arg := range args.Volumes {
   953  		err := one(arg)
   954  		results.Results[i].Error = common.ServerError(err)
   955  	}
   956  	return results, nil
   957  }
   958  
   959  // SetFilesystemInfo records the details of newly provisioned filesystems.
   960  func (s *StorageProvisionerAPI) SetFilesystemInfo(args params.Filesystems) (params.ErrorResults, error) {
   961  	canAccessFilesystem, err := s.getStorageEntityAuthFunc()
   962  	if err != nil {
   963  		return params.ErrorResults{}, err
   964  	}
   965  	results := params.ErrorResults{
   966  		Results: make([]params.ErrorResult, len(args.Filesystems)),
   967  	}
   968  	one := func(arg params.Filesystem) error {
   969  		filesystemTag, filesystemInfo, err := storagecommon.FilesystemToState(arg)
   970  		if err != nil {
   971  			return errors.Trace(err)
   972  		} else if !canAccessFilesystem(filesystemTag) {
   973  			return common.ErrPerm
   974  		}
   975  		err = s.st.SetFilesystemInfo(filesystemTag, filesystemInfo)
   976  		if errors.IsNotFound(err) {
   977  			return common.ErrPerm
   978  		}
   979  		return errors.Trace(err)
   980  	}
   981  	for i, arg := range args.Filesystems {
   982  		err := one(arg)
   983  		results.Results[i].Error = common.ServerError(err)
   984  	}
   985  	return results, nil
   986  }
   987  
   988  // SetVolumeAttachmentInfo records the details of newly provisioned volume
   989  // attachments.
   990  func (s *StorageProvisionerAPI) SetVolumeAttachmentInfo(
   991  	args params.VolumeAttachments,
   992  ) (params.ErrorResults, error) {
   993  	canAccess, err := s.getAttachmentAuthFunc()
   994  	if err != nil {
   995  		return params.ErrorResults{}, err
   996  	}
   997  	results := params.ErrorResults{
   998  		Results: make([]params.ErrorResult, len(args.VolumeAttachments)),
   999  	}
  1000  	one := func(arg params.VolumeAttachment) error {
  1001  		machineTag, volumeTag, volumeAttachmentInfo, err := storagecommon.VolumeAttachmentToState(arg)
  1002  		if err != nil {
  1003  			return errors.Trace(err)
  1004  		}
  1005  		if !canAccess(machineTag, volumeTag) {
  1006  			return common.ErrPerm
  1007  		}
  1008  		err = s.st.SetVolumeAttachmentInfo(machineTag, volumeTag, volumeAttachmentInfo)
  1009  		if errors.IsNotFound(err) {
  1010  			return common.ErrPerm
  1011  		}
  1012  		return errors.Trace(err)
  1013  	}
  1014  	for i, arg := range args.VolumeAttachments {
  1015  		err := one(arg)
  1016  		results.Results[i].Error = common.ServerError(err)
  1017  	}
  1018  	return results, nil
  1019  }
  1020  
  1021  // SetFilesystemAttachmentInfo records the details of newly provisioned filesystem
  1022  // attachments.
  1023  func (s *StorageProvisionerAPI) SetFilesystemAttachmentInfo(
  1024  	args params.FilesystemAttachments,
  1025  ) (params.ErrorResults, error) {
  1026  	canAccess, err := s.getAttachmentAuthFunc()
  1027  	if err != nil {
  1028  		return params.ErrorResults{}, err
  1029  	}
  1030  	results := params.ErrorResults{
  1031  		Results: make([]params.ErrorResult, len(args.FilesystemAttachments)),
  1032  	}
  1033  	one := func(arg params.FilesystemAttachment) error {
  1034  		machineTag, filesystemTag, filesystemAttachmentInfo, err := storagecommon.FilesystemAttachmentToState(arg)
  1035  		if err != nil {
  1036  			return errors.Trace(err)
  1037  		}
  1038  		if !canAccess(machineTag, filesystemTag) {
  1039  			return common.ErrPerm
  1040  		}
  1041  		err = s.st.SetFilesystemAttachmentInfo(machineTag, filesystemTag, filesystemAttachmentInfo)
  1042  		if errors.IsNotFound(err) {
  1043  			return common.ErrPerm
  1044  		}
  1045  		return errors.Trace(err)
  1046  	}
  1047  	for i, arg := range args.FilesystemAttachments {
  1048  		err := one(arg)
  1049  		results.Results[i].Error = common.ServerError(err)
  1050  	}
  1051  	return results, nil
  1052  }
  1053  
  1054  // AttachmentLife returns the lifecycle state of each specified machine
  1055  // storage attachment.
  1056  func (s *StorageProvisionerAPI) AttachmentLife(args params.MachineStorageIds) (params.LifeResults, error) {
  1057  	canAccess, err := s.getAttachmentAuthFunc()
  1058  	if err != nil {
  1059  		return params.LifeResults{}, err
  1060  	}
  1061  	results := params.LifeResults{
  1062  		Results: make([]params.LifeResult, len(args.Ids)),
  1063  	}
  1064  	one := func(arg params.MachineStorageId) (params.Life, error) {
  1065  		machineTag, err := names.ParseMachineTag(arg.MachineTag)
  1066  		if err != nil {
  1067  			return "", err
  1068  		}
  1069  		attachmentTag, err := names.ParseTag(arg.AttachmentTag)
  1070  		if err != nil {
  1071  			return "", err
  1072  		}
  1073  		if !canAccess(machineTag, attachmentTag) {
  1074  			return "", common.ErrPerm
  1075  		}
  1076  		var lifer state.Lifer
  1077  		switch attachmentTag := attachmentTag.(type) {
  1078  		case names.VolumeTag:
  1079  			lifer, err = s.st.VolumeAttachment(machineTag, attachmentTag)
  1080  		case names.FilesystemTag:
  1081  			lifer, err = s.st.FilesystemAttachment(machineTag, attachmentTag)
  1082  		}
  1083  		if errors.IsNotFound(err) {
  1084  			return "", common.ErrPerm
  1085  		} else if err != nil {
  1086  			return "", errors.Trace(err)
  1087  		}
  1088  		return params.Life(lifer.Life().String()), nil
  1089  	}
  1090  	for i, arg := range args.Ids {
  1091  		life, err := one(arg)
  1092  		if err != nil {
  1093  			results.Results[i].Error = common.ServerError(err)
  1094  		} else {
  1095  			results.Results[i].Life = life
  1096  		}
  1097  	}
  1098  	return results, nil
  1099  }
  1100  
  1101  // Remove removes volumes and filesystems from state.
  1102  func (s *StorageProvisionerAPI) Remove(args params.Entities) (params.ErrorResults, error) {
  1103  	canAccess, err := s.getStorageEntityAuthFunc()
  1104  	if err != nil {
  1105  		return params.ErrorResults{}, err
  1106  	}
  1107  	results := params.ErrorResults{
  1108  		Results: make([]params.ErrorResult, len(args.Entities)),
  1109  	}
  1110  	one := func(arg params.Entity) error {
  1111  		tag, err := names.ParseTag(arg.Tag)
  1112  		if err != nil {
  1113  			return errors.Trace(err)
  1114  		}
  1115  		if !canAccess(tag) {
  1116  			return common.ErrPerm
  1117  		}
  1118  		switch tag := tag.(type) {
  1119  		case names.FilesystemTag:
  1120  			return s.st.RemoveFilesystem(tag)
  1121  		case names.VolumeTag:
  1122  			return s.st.RemoveVolume(tag)
  1123  		default:
  1124  			// should have been picked up by canAccess
  1125  			logger.Debugf("unexpected %v tag", tag.Kind())
  1126  			return common.ErrPerm
  1127  		}
  1128  	}
  1129  	for i, arg := range args.Entities {
  1130  		err := one(arg)
  1131  		results.Results[i].Error = common.ServerError(err)
  1132  	}
  1133  	return results, nil
  1134  }
  1135  
  1136  // RemoveAttachments removes the specified machine storage attachments
  1137  // from state.
  1138  func (s *StorageProvisionerAPI) RemoveAttachment(args params.MachineStorageIds) (params.ErrorResults, error) {
  1139  	canAccess, err := s.getAttachmentAuthFunc()
  1140  	if err != nil {
  1141  		return params.ErrorResults{}, err
  1142  	}
  1143  	results := params.ErrorResults{
  1144  		Results: make([]params.ErrorResult, len(args.Ids)),
  1145  	}
  1146  	removeAttachment := func(arg params.MachineStorageId) error {
  1147  		machineTag, err := names.ParseMachineTag(arg.MachineTag)
  1148  		if err != nil {
  1149  			return err
  1150  		}
  1151  		attachmentTag, err := names.ParseTag(arg.AttachmentTag)
  1152  		if err != nil {
  1153  			return err
  1154  		}
  1155  		if !canAccess(machineTag, attachmentTag) {
  1156  			return common.ErrPerm
  1157  		}
  1158  		switch attachmentTag := attachmentTag.(type) {
  1159  		case names.VolumeTag:
  1160  			return s.st.RemoveVolumeAttachment(machineTag, attachmentTag)
  1161  		case names.FilesystemTag:
  1162  			return s.st.RemoveFilesystemAttachment(machineTag, attachmentTag)
  1163  		default:
  1164  			return common.ErrPerm
  1165  		}
  1166  	}
  1167  	for i, arg := range args.Ids {
  1168  		if err := removeAttachment(arg); err != nil {
  1169  			results.Results[i].Error = common.ServerError(err)
  1170  		}
  1171  	}
  1172  	return results, nil
  1173  }