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