github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/storageprovisioner/filesystems.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  	"path/filepath"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/instance"
    14  	"github.com/juju/juju/storage"
    15  )
    16  
    17  // filesystemsChanged is called when the lifecycle states of the filesystems
    18  // with the provided IDs have been seen to have changed.
    19  func filesystemsChanged(ctx *context, changes []string) error {
    20  	tags := make([]names.Tag, len(changes))
    21  	for i, change := range changes {
    22  		tags[i] = names.NewFilesystemTag(change)
    23  	}
    24  	alive, dying, dead, err := storageEntityLife(ctx, tags)
    25  	if err != nil {
    26  		return errors.Trace(err)
    27  	}
    28  	logger.Debugf("filesystems alive: %v, dying: %v, dead: %v", alive, dying, dead)
    29  	if len(alive)+len(dying)+len(dead) == 0 {
    30  		return nil
    31  	}
    32  
    33  	// Get filesystem information for filesystems, so we can provision,
    34  	// deprovision, attach and detach.
    35  	filesystemTags := make([]names.FilesystemTag, 0, len(alive)+len(dying)+len(dead))
    36  	for _, tag := range alive {
    37  		filesystemTags = append(filesystemTags, tag.(names.FilesystemTag))
    38  	}
    39  	for _, tag := range dying {
    40  		filesystemTags = append(filesystemTags, tag.(names.FilesystemTag))
    41  	}
    42  	for _, tag := range dead {
    43  		filesystemTags = append(filesystemTags, tag.(names.FilesystemTag))
    44  	}
    45  	filesystemResults, err := ctx.filesystemAccessor.Filesystems(filesystemTags)
    46  	if err != nil {
    47  		return errors.Annotatef(err, "getting filesystem information")
    48  	}
    49  
    50  	aliveFilesystemTags := filesystemTags[:len(alive)]
    51  	dyingFilesystemTags := filesystemTags[len(alive) : len(alive)+len(dying)]
    52  	deadFilesystemTags := filesystemTags[len(alive)+len(dying):]
    53  	aliveFilesystemResults := filesystemResults[:len(alive)]
    54  	dyingFilesystemResults := filesystemResults[len(alive) : len(alive)+len(dying)]
    55  	deadFilesystemResults := filesystemResults[len(alive)+len(dying):]
    56  
    57  	if err := processDeadFilesystems(ctx, deadFilesystemTags, deadFilesystemResults); err != nil {
    58  		return errors.Annotate(err, "deprovisioning filesystems")
    59  	}
    60  	if err := processDyingFilesystems(ctx, dyingFilesystemTags, dyingFilesystemResults); err != nil {
    61  		return errors.Annotate(err, "processing dying filesystems")
    62  	}
    63  	if err := processAliveFilesystems(ctx, aliveFilesystemTags, aliveFilesystemResults); err != nil {
    64  		return errors.Annotate(err, "provisioning filesystems")
    65  	}
    66  	return nil
    67  }
    68  
    69  // filesystemAttachmentsChanged is called when the lifecycle states of the filesystem
    70  // attachments with the provided IDs have been seen to have changed.
    71  func filesystemAttachmentsChanged(ctx *context, ids []params.MachineStorageId) error {
    72  	alive, dying, dead, err := attachmentLife(ctx, ids)
    73  	if err != nil {
    74  		return errors.Trace(err)
    75  	}
    76  	logger.Debugf("filesystem attachment alive: %v, dying: %v, dead: %v", alive, dying, dead)
    77  	if len(dead) != 0 {
    78  		// We should not see dead filesystem attachments;
    79  		// attachments go directly from Dying to removed.
    80  		logger.Debugf("unexpected dead filesystem attachments: %v", dead)
    81  	}
    82  	if len(alive)+len(dying) == 0 {
    83  		return nil
    84  	}
    85  
    86  	// Get filesystem information for alive and dying filesystem attachments, so
    87  	// we can attach/detach.
    88  	ids = append(alive, dying...)
    89  	filesystemAttachmentResults, err := ctx.filesystemAccessor.FilesystemAttachments(ids)
    90  	if err != nil {
    91  		return errors.Annotatef(err, "getting filesystem attachment information")
    92  	}
    93  
    94  	// Deprovision Dying filesystem attachments.
    95  	dyingFilesystemAttachmentResults := filesystemAttachmentResults[len(alive):]
    96  	if err := processDyingFilesystemAttachments(ctx, dying, dyingFilesystemAttachmentResults); err != nil {
    97  		return errors.Annotate(err, "destroying filesystem attachments")
    98  	}
    99  
   100  	// Provision Alive filesystem attachments.
   101  	aliveFilesystemAttachmentResults := filesystemAttachmentResults[:len(alive)]
   102  	if err := processAliveFilesystemAttachments(ctx, alive, aliveFilesystemAttachmentResults); err != nil {
   103  		return errors.Annotate(err, "creating filesystem attachments")
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  // processDyingFilesystems processes the FilesystemResults for Dying filesystems,
   110  // removing them from provisioning-pending as necessary, and storing the current
   111  // filesystem info for provisioned filesystems so that attachments may be destroyed.
   112  func processDyingFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error {
   113  	for _, tag := range tags {
   114  		delete(ctx.pendingFilesystems, tag)
   115  	}
   116  	for i, result := range filesystemResults {
   117  		tag := tags[i]
   118  		if result.Error == nil {
   119  			filesystem, err := filesystemFromParams(result.Result)
   120  			if err != nil {
   121  				return errors.Annotate(err, "getting filesystem info")
   122  			}
   123  			ctx.filesystems[tag] = filesystem
   124  		} else if !params.IsCodeNotProvisioned(result.Error) {
   125  			return errors.Annotatef(result.Error, "getting information for filesystem %s", tag.Id())
   126  		}
   127  	}
   128  	return nil
   129  }
   130  
   131  // processDeadFilesystems processes the FilesystemResults for Dead filesystems,
   132  // deprovisioning filesystems and removing from state as necessary.
   133  func processDeadFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error {
   134  	for _, tag := range tags {
   135  		delete(ctx.pendingFilesystems, tag)
   136  	}
   137  	var destroy []names.FilesystemTag
   138  	var remove []names.Tag
   139  	for i, result := range filesystemResults {
   140  		tag := tags[i]
   141  		if result.Error == nil {
   142  			logger.Debugf("filesystem %s is provisioned, queuing for deprovisioning", tag.Id())
   143  			filesystem, err := filesystemFromParams(result.Result)
   144  			if err != nil {
   145  				return errors.Annotate(err, "getting filesystem info")
   146  			}
   147  			ctx.filesystems[tag] = filesystem
   148  			destroy = append(destroy, tag)
   149  			continue
   150  		}
   151  		if params.IsCodeNotProvisioned(result.Error) {
   152  			logger.Debugf("filesystem %s is not provisioned, queuing for removal", tag.Id())
   153  			remove = append(remove, tag)
   154  			continue
   155  		}
   156  		return errors.Annotatef(result.Error, "getting filesystem information for filesystem %s", tag.Id())
   157  	}
   158  	if len(destroy)+len(remove) == 0 {
   159  		return nil
   160  	}
   161  	if len(destroy) > 0 {
   162  		errorResults, err := destroyFilesystems(ctx, destroy)
   163  		if err != nil {
   164  			return errors.Annotate(err, "destroying filesystems")
   165  		}
   166  		for i, tag := range destroy {
   167  			if err := errorResults[i]; err != nil {
   168  				return errors.Annotatef(err, "destroying %s", names.ReadableString(tag))
   169  			}
   170  			remove = append(remove, tag)
   171  		}
   172  	}
   173  	if err := removeEntities(ctx, remove); err != nil {
   174  		return errors.Annotate(err, "removing filesystems from state")
   175  	}
   176  	return nil
   177  }
   178  
   179  // processDyingFilesystemAttachments processes the FilesystemAttachmentResults for
   180  // Dying filesystem attachments, detaching filesystems and updating state as necessary.
   181  func processDyingFilesystemAttachments(
   182  	ctx *context,
   183  	ids []params.MachineStorageId,
   184  	filesystemAttachmentResults []params.FilesystemAttachmentResult,
   185  ) error {
   186  	if len(ids) == 0 {
   187  		return nil
   188  	}
   189  	for _, id := range ids {
   190  		delete(ctx.pendingFilesystemAttachments, id)
   191  	}
   192  	detach := make([]params.MachineStorageId, 0, len(ids))
   193  	remove := make([]params.MachineStorageId, 0, len(ids))
   194  	for i, result := range filesystemAttachmentResults {
   195  		id := ids[i]
   196  		if result.Error == nil {
   197  			detach = append(detach, id)
   198  			continue
   199  		}
   200  		if params.IsCodeNotProvisioned(result.Error) {
   201  			remove = append(remove, id)
   202  			continue
   203  		}
   204  		return errors.Annotatef(result.Error, "getting information for filesystem attachment %v", id)
   205  	}
   206  	if len(detach) > 0 {
   207  		attachmentParams, err := filesystemAttachmentParams(ctx, detach)
   208  		if err != nil {
   209  			return errors.Trace(err)
   210  		}
   211  		for i, params := range attachmentParams {
   212  			ctx.pendingDyingFilesystemAttachments[detach[i]] = params
   213  		}
   214  	}
   215  	if len(remove) > 0 {
   216  		if err := removeAttachments(ctx, remove); err != nil {
   217  			return errors.Annotate(err, "removing attachments from state")
   218  		}
   219  	}
   220  	return nil
   221  }
   222  
   223  // processAliveFilesystems processes the FilesystemResults for Alive filesystems,
   224  // provisioning filesystems and setting the info in state as necessary.
   225  func processAliveFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error {
   226  	// Filter out the already-provisioned filesystems.
   227  	pending := make([]names.FilesystemTag, 0, len(tags))
   228  	for i, result := range filesystemResults {
   229  		tag := tags[i]
   230  		if result.Error == nil {
   231  			// Filesystem is already provisioned: skip.
   232  			logger.Debugf("filesystem %q is already provisioned, nothing to do", tag.Id())
   233  			filesystem, err := filesystemFromParams(result.Result)
   234  			if err != nil {
   235  				return errors.Annotate(err, "getting filesystem info")
   236  			}
   237  			ctx.filesystems[tag] = filesystem
   238  			if filesystem.Volume != (names.VolumeTag{}) {
   239  				// Ensure that volume-backed filesystems' block
   240  				// devices are present even after creating the
   241  				// filesystem, so that attachments can be made.
   242  				maybeAddPendingVolumeBlockDevice(ctx, filesystem.Volume)
   243  			}
   244  			continue
   245  		}
   246  		if !params.IsCodeNotProvisioned(result.Error) {
   247  			return errors.Annotatef(
   248  				result.Error, "getting filesystem information for filesystem %q", tag.Id(),
   249  			)
   250  		}
   251  		// The filesystem has not yet been provisioned, so record its tag
   252  		// to enquire about parameters below.
   253  		pending = append(pending, tag)
   254  	}
   255  	if len(pending) == 0 {
   256  		return nil
   257  	}
   258  	paramsResults, err := ctx.filesystemAccessor.FilesystemParams(pending)
   259  	if err != nil {
   260  		return errors.Annotate(err, "getting filesystem params")
   261  	}
   262  	for i, result := range paramsResults {
   263  		if result.Error != nil {
   264  			return errors.Annotate(result.Error, "getting filesystem parameters")
   265  		}
   266  		params, err := filesystemParamsFromParams(result.Result)
   267  		if err != nil {
   268  			return errors.Annotate(err, "getting filesystem parameters")
   269  		}
   270  		ctx.pendingFilesystems[pending[i]] = params
   271  		if params.Volume != (names.VolumeTag{}) {
   272  			// The filesystem is volume-backed: we must watch for
   273  			// the corresponding block device. This will trigger a
   274  			// one-time (for the volume) forced update of block
   275  			// devices. If the block device is not immediately
   276  			// available, then we rely on the watcher. The forced
   277  			// update is necessary in case the block device was
   278  			// added to state already, and we didn't observe it.
   279  			maybeAddPendingVolumeBlockDevice(ctx, params.Volume)
   280  		}
   281  	}
   282  	return nil
   283  }
   284  
   285  func maybeAddPendingVolumeBlockDevice(ctx *context, v names.VolumeTag) {
   286  	if _, ok := ctx.volumeBlockDevices[v]; !ok {
   287  		ctx.pendingVolumeBlockDevices.Add(v)
   288  	}
   289  }
   290  
   291  // processPendingFilesystems creates as many of the pending filesystems
   292  // as possible, first ensuring that their prerequisites have been met.
   293  func processPendingFilesystems(ctx *context) error {
   294  	if len(ctx.pendingFilesystems) == 0 {
   295  		logger.Tracef("no pending filesystems")
   296  		return nil
   297  	}
   298  	ready := make([]storage.FilesystemParams, 0, len(ctx.pendingFilesystems))
   299  	for tag, filesystemParams := range ctx.pendingFilesystems {
   300  		if filesystemParams.Volume != (names.VolumeTag{}) {
   301  			// The filesystem is backed by a volume; ensure that
   302  			// the volume is attached by virtue of there being a
   303  			// matching block device on the machine.
   304  			if _, ok := ctx.volumeBlockDevices[filesystemParams.Volume]; !ok {
   305  				logger.Debugf(
   306  					"filesystem %v backing-volume %v is not attached yet",
   307  					filesystemParams.Tag.Id(),
   308  					filesystemParams.Volume.Id(),
   309  				)
   310  				continue
   311  			}
   312  		}
   313  		ready = append(ready, filesystemParams)
   314  		delete(ctx.pendingFilesystems, tag)
   315  	}
   316  	if len(ready) == 0 {
   317  		return nil
   318  	}
   319  	filesystems, err := createFilesystems(ctx, ready)
   320  	if err != nil {
   321  		return errors.Annotate(err, "creating filesystems")
   322  	}
   323  	if err := setFilesystemInfo(ctx, filesystems); err != nil {
   324  		return errors.Trace(err)
   325  	}
   326  	return nil
   327  }
   328  
   329  func setFilesystemInfo(ctx *context, filesystems []storage.Filesystem) error {
   330  	if len(filesystems) == 0 {
   331  		return nil
   332  	}
   333  	// TODO(axw) we need to be able to list filesystems in the provider,
   334  	// by environment, so that we can "harvest" them if they're
   335  	// unknown. This will take care of killing filesystems that we fail
   336  	// to record in state.
   337  	errorResults, err := ctx.filesystemAccessor.SetFilesystemInfo(
   338  		filesystemsFromStorage(filesystems),
   339  	)
   340  	if err != nil {
   341  		return errors.Annotate(err, "publishing filesystems to state")
   342  	}
   343  	for i, result := range errorResults {
   344  		if result.Error != nil {
   345  			return errors.Annotatef(
   346  				result.Error, "publishing filesystem %s to state",
   347  				filesystems[i].Tag.Id(),
   348  			)
   349  		}
   350  		ctx.filesystems[filesystems[i].Tag] = filesystems[i]
   351  	}
   352  	return nil
   353  }
   354  
   355  // processAliveFilesystemAttachments processes the FilesystemAttachmentResults
   356  // for Alive filesystem attachments, attaching filesystems and setting the info
   357  // in state as necessary.
   358  func processAliveFilesystemAttachments(
   359  	ctx *context,
   360  	ids []params.MachineStorageId,
   361  	filesystemAttachmentResults []params.FilesystemAttachmentResult,
   362  ) error {
   363  	// Filter out the already-attached.
   364  	pending := make([]params.MachineStorageId, 0, len(ids))
   365  	for i, result := range filesystemAttachmentResults {
   366  		if result.Error == nil {
   367  			delete(ctx.pendingFilesystemAttachments, ids[i])
   368  			// Filesystem attachment is already provisioned: if we
   369  			// didn't (re)attach in this session, then we must do
   370  			// so now.
   371  			action := "nothing to do"
   372  			if _, ok := ctx.filesystemAttachments[ids[i]]; !ok {
   373  				// Not yet (re)attached in this session.
   374  				pending = append(pending, ids[i])
   375  				action = "will reattach"
   376  			}
   377  			logger.Debugf(
   378  				"%s is already attached to %s, %s",
   379  				ids[i].AttachmentTag, ids[i].MachineTag, action,
   380  			)
   381  			continue
   382  		}
   383  		if !params.IsCodeNotProvisioned(result.Error) {
   384  			return errors.Annotatef(
   385  				result.Error, "getting information for attachment %v", ids[i],
   386  			)
   387  		}
   388  		// The filesystem has not yet been attached, so
   389  		// record its tag to enquire about parameters below.
   390  		pending = append(pending, ids[i])
   391  	}
   392  	if len(pending) == 0 {
   393  		return nil
   394  	}
   395  	params, err := filesystemAttachmentParams(ctx, pending)
   396  	if err != nil {
   397  		return errors.Trace(err)
   398  	}
   399  	for i, params := range params {
   400  		if params.InstanceId == "" {
   401  			watchMachine(ctx, params.Machine)
   402  		}
   403  		ctx.pendingFilesystemAttachments[pending[i]] = params
   404  	}
   405  	return nil
   406  }
   407  
   408  // filesystemAttachmentParams obtains the specified attachments' parameters.
   409  func filesystemAttachmentParams(
   410  	ctx *context, ids []params.MachineStorageId,
   411  ) ([]storage.FilesystemAttachmentParams, error) {
   412  	paramsResults, err := ctx.filesystemAccessor.FilesystemAttachmentParams(ids)
   413  	if err != nil {
   414  		return nil, errors.Annotate(err, "getting filesystem attachment params")
   415  	}
   416  	attachmentParams := make([]storage.FilesystemAttachmentParams, len(ids))
   417  	for i, result := range paramsResults {
   418  		if result.Error != nil {
   419  			return nil, errors.Annotate(result.Error, "getting filesystem attachment parameters")
   420  		}
   421  		params, err := filesystemAttachmentParamsFromParams(result.Result)
   422  		if err != nil {
   423  			return nil, errors.Annotate(err, "getting filesystem attachment parameters")
   424  		}
   425  		attachmentParams[i] = params
   426  	}
   427  	return attachmentParams, nil
   428  }
   429  
   430  func processPendingFilesystemAttachments(ctx *context) error {
   431  	if len(ctx.pendingFilesystemAttachments) == 0 {
   432  		logger.Tracef("no pending filesystem attachments")
   433  		return nil
   434  	}
   435  	ready := make([]storage.FilesystemAttachmentParams, 0, len(ctx.pendingFilesystemAttachments))
   436  	for id, params := range ctx.pendingFilesystemAttachments {
   437  		filesystem, ok := ctx.filesystems[params.Filesystem]
   438  		if !ok {
   439  			logger.Debugf("filesystem %v has not been provisioned yet", params.Filesystem.Id())
   440  			continue
   441  		}
   442  		if filesystem.Volume != (names.VolumeTag{}) {
   443  			// The filesystem is volume-backed: if the filesystem
   444  			// was created in another session, then the block device
   445  			// may not have been seen yet. We must wait for the block
   446  			// device watcher to trigger.
   447  			if _, ok := ctx.volumeBlockDevices[filesystem.Volume]; !ok {
   448  				logger.Debugf(
   449  					"filesystem %v backing-volume %v is not attached yet",
   450  					filesystem.Tag.Id(),
   451  					filesystem.Volume.Id(),
   452  				)
   453  				continue
   454  			}
   455  		}
   456  		if params.InstanceId == "" {
   457  			logger.Debugf("machine %v has not been provisioned yet", params.Machine.Id())
   458  			continue
   459  		}
   460  		if params.Path == "" {
   461  			params.Path = filepath.Join(ctx.storageDir, params.Filesystem.Id())
   462  		}
   463  		params.FilesystemId = filesystem.FilesystemId
   464  		ready = append(ready, params)
   465  		delete(ctx.pendingFilesystemAttachments, id)
   466  	}
   467  	if len(ready) == 0 {
   468  		return nil
   469  	}
   470  	filesystemAttachments, err := createFilesystemAttachments(ctx, ready)
   471  	if err != nil {
   472  		return errors.Annotate(err, "creating filesystem attachments")
   473  	}
   474  	if err := setFilesystemAttachmentInfo(ctx, filesystemAttachments); err != nil {
   475  		return errors.Trace(err)
   476  	}
   477  	return nil
   478  }
   479  
   480  func processPendingDyingFilesystemAttachments(ctx *context) error {
   481  	if len(ctx.pendingDyingFilesystemAttachments) == 0 {
   482  		logger.Tracef("no pending, dying filesystem attachments")
   483  		return nil
   484  	}
   485  	var detach []storage.FilesystemAttachmentParams
   486  	var remove []params.MachineStorageId
   487  	for id, params := range ctx.pendingDyingFilesystemAttachments {
   488  		if _, ok := ctx.filesystems[params.Filesystem]; !ok {
   489  			// Wait until the filesystem info is known.
   490  			continue
   491  		}
   492  		delete(ctx.pendingDyingFilesystemAttachments, id)
   493  		detach = append(detach, params)
   494  		remove = append(remove, id)
   495  	}
   496  	if len(detach) == 0 {
   497  		return nil
   498  	}
   499  	if err := detachFilesystems(ctx, detach); err != nil {
   500  		return errors.Annotate(err, "detaching filesystems")
   501  	}
   502  	if err := removeAttachments(ctx, remove); err != nil {
   503  		return errors.Annotate(err, "removing attachments from state")
   504  	}
   505  	return nil
   506  }
   507  
   508  func setFilesystemAttachmentInfo(ctx *context, filesystemAttachments []storage.FilesystemAttachment) error {
   509  	if len(filesystemAttachments) == 0 {
   510  		return nil
   511  	}
   512  	// TODO(axw) we need to be able to list filesystem attachments in the
   513  	// provider, by environment, so that we can "harvest" them if they're
   514  	// unknown. This will take care of killing filesystems that we fail to
   515  	// record in state.
   516  	errorResults, err := ctx.filesystemAccessor.SetFilesystemAttachmentInfo(
   517  		filesystemAttachmentsFromStorage(filesystemAttachments),
   518  	)
   519  	if err != nil {
   520  		return errors.Annotate(err, "publishing filesystems to state")
   521  	}
   522  	for i, result := range errorResults {
   523  		if result.Error != nil {
   524  			return errors.Annotatef(
   525  				result.Error, "publishing attachment of %s to %s to state",
   526  				names.ReadableString(filesystemAttachments[i].Filesystem),
   527  				names.ReadableString(filesystemAttachments[i].Machine),
   528  			)
   529  		}
   530  		// Record the filesystem attachment in the context.
   531  		ctx.filesystemAttachments[params.MachineStorageId{
   532  			MachineTag:    filesystemAttachments[i].Machine.String(),
   533  			AttachmentTag: filesystemAttachments[i].Filesystem.String(),
   534  		}] = filesystemAttachments[i]
   535  	}
   536  	return nil
   537  }
   538  
   539  // createFilesystems creates filesystems with the specified parameters.
   540  func createFilesystems(ctx *context, params []storage.FilesystemParams) ([]storage.Filesystem, error) {
   541  	// TODO(axw) later we may have multiple instantiations (sources)
   542  	// for a storage provider, e.g. multiple Ceph installations. For
   543  	// now we assume a single source for each provider type, with no
   544  	// configuration.
   545  
   546  	// Create filesystem sources.
   547  	filesystemSources := make(map[string]storage.FilesystemSource)
   548  	for _, params := range params {
   549  		sourceName := string(params.Provider)
   550  		if _, ok := filesystemSources[sourceName]; ok {
   551  			continue
   552  		}
   553  		if params.Volume != (names.VolumeTag{}) {
   554  			filesystemSources[sourceName] = ctx.managedFilesystemSource
   555  			continue
   556  		}
   557  		filesystemSource, err := filesystemSource(
   558  			ctx.environConfig, ctx.storageDir, sourceName, params.Provider,
   559  		)
   560  		if err != nil {
   561  			return nil, errors.Annotate(err, "getting filesystem source")
   562  		}
   563  		filesystemSources[sourceName] = filesystemSource
   564  	}
   565  
   566  	// Validate and gather filesystem parameters.
   567  	paramsBySource := make(map[string][]storage.FilesystemParams)
   568  	for _, params := range params {
   569  		sourceName := string(params.Provider)
   570  		filesystemSource := filesystemSources[sourceName]
   571  		err := filesystemSource.ValidateFilesystemParams(params)
   572  		if err != nil {
   573  			// TODO(axw) we should set an error status for params.Tag
   574  			// here, and we should retry periodically.
   575  			logger.Errorf("ignoring invalid filesystem: %v", err)
   576  			continue
   577  		}
   578  		paramsBySource[sourceName] = append(paramsBySource[sourceName], params)
   579  	}
   580  
   581  	var allFilesystems []storage.Filesystem
   582  	for sourceName, params := range paramsBySource {
   583  		logger.Debugf("creating filesystems: %v", params)
   584  		filesystemSource := filesystemSources[sourceName]
   585  		results, err := filesystemSource.CreateFilesystems(params)
   586  		if err != nil {
   587  			return nil, errors.Annotatef(err, "creating filesystems from source %q", sourceName)
   588  		}
   589  		for i, result := range results {
   590  			if result.Error != nil {
   591  				return nil, errors.Annotatef(result.Error, "creating %s", names.ReadableString(params[i].Tag))
   592  			}
   593  			allFilesystems = append(allFilesystems, *result.Filesystem)
   594  		}
   595  	}
   596  	return allFilesystems, nil
   597  }
   598  
   599  // createFilesystemAttachments creates filesystem attachments with the specified parameters.
   600  func createFilesystemAttachments(
   601  	ctx *context,
   602  	params []storage.FilesystemAttachmentParams,
   603  ) ([]storage.FilesystemAttachment, error) {
   604  	paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource(ctx, params)
   605  	if err != nil {
   606  		return nil, errors.Trace(err)
   607  	}
   608  	var allFilesystemAttachments []storage.FilesystemAttachment
   609  	for sourceName, params := range paramsBySource {
   610  		logger.Debugf("attaching filesystems: %v", params)
   611  		filesystemSource := filesystemSources[sourceName]
   612  		results, err := filesystemSource.AttachFilesystems(params)
   613  		if err != nil {
   614  			return nil, errors.Annotatef(err, "attaching filesystems from source %q", sourceName)
   615  		}
   616  		for i, result := range results {
   617  			if result.Error != nil {
   618  				return nil, errors.Annotatef(
   619  					err, "attaching %s to %s",
   620  					names.ReadableString(params[i].Filesystem),
   621  					names.ReadableString(params[i].Machine),
   622  				)
   623  			}
   624  			allFilesystemAttachments = append(allFilesystemAttachments, *result.FilesystemAttachment)
   625  		}
   626  	}
   627  	return allFilesystemAttachments, nil
   628  }
   629  
   630  func destroyFilesystems(ctx *context, tags []names.FilesystemTag) ([]error, error) {
   631  	// TODO(axw) add storage.FilesystemSource.DestroyFilesystems
   632  	return make([]error, len(tags)), nil
   633  }
   634  
   635  func detachFilesystems(ctx *context, attachments []storage.FilesystemAttachmentParams) error {
   636  	paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource(ctx, attachments)
   637  	if err != nil {
   638  		return errors.Trace(err)
   639  	}
   640  	for sourceName, params := range paramsBySource {
   641  		logger.Debugf("detaching filesystems: %v", params)
   642  		filesystemSource := filesystemSources[sourceName]
   643  		results, err := filesystemSource.DetachFilesystems(params)
   644  		if err != nil {
   645  			return errors.Annotatef(err, "detaching filesystems from source %q", sourceName)
   646  		}
   647  		for i, err := range results {
   648  			if err == nil {
   649  				continue
   650  			}
   651  			return errors.Annotatef(
   652  				err, "detaching %s from %s",
   653  				names.ReadableString(params[i].Filesystem),
   654  				names.ReadableString(params[i].Machine),
   655  			)
   656  		}
   657  	}
   658  	return nil
   659  }
   660  
   661  func filesystemAttachmentParamsBySource(
   662  	ctx *context, params []storage.FilesystemAttachmentParams,
   663  ) (map[string][]storage.FilesystemAttachmentParams, map[string]storage.FilesystemSource, error) {
   664  	// TODO(axw) later we may have multiple instantiations (sources)
   665  	// for a storage provider, e.g. multiple Ceph installations. For
   666  	// now we assume a single source for each provider type, with no
   667  	// configuration.
   668  	filesystemSources := make(map[string]storage.FilesystemSource)
   669  	paramsBySource := make(map[string][]storage.FilesystemAttachmentParams)
   670  	for _, params := range params {
   671  		sourceName := string(params.Provider)
   672  		paramsBySource[sourceName] = append(paramsBySource[sourceName], params)
   673  		if _, ok := filesystemSources[sourceName]; ok {
   674  			continue
   675  		}
   676  		filesystem := ctx.filesystems[params.Filesystem]
   677  		if filesystem.Volume != (names.VolumeTag{}) {
   678  			filesystemSources[sourceName] = ctx.managedFilesystemSource
   679  			continue
   680  		}
   681  		filesystemSource, err := filesystemSource(
   682  			ctx.environConfig, ctx.storageDir, sourceName, params.Provider,
   683  		)
   684  		if err != nil {
   685  			return nil, nil, errors.Annotate(err, "getting filesystem source")
   686  		}
   687  		filesystemSources[sourceName] = filesystemSource
   688  	}
   689  	return paramsBySource, filesystemSources, nil
   690  }
   691  
   692  func filesystemsFromStorage(in []storage.Filesystem) []params.Filesystem {
   693  	out := make([]params.Filesystem, len(in))
   694  	for i, f := range in {
   695  		paramsFilesystem := params.Filesystem{
   696  			f.Tag.String(),
   697  			"",
   698  			params.FilesystemInfo{
   699  				f.FilesystemId,
   700  				f.Size,
   701  			},
   702  		}
   703  		if f.Volume != (names.VolumeTag{}) {
   704  			paramsFilesystem.VolumeTag = f.Volume.String()
   705  		}
   706  		out[i] = paramsFilesystem
   707  	}
   708  	return out
   709  }
   710  
   711  func filesystemAttachmentsFromStorage(in []storage.FilesystemAttachment) []params.FilesystemAttachment {
   712  	out := make([]params.FilesystemAttachment, len(in))
   713  	for i, f := range in {
   714  		out[i] = params.FilesystemAttachment{
   715  			f.Filesystem.String(),
   716  			f.Machine.String(),
   717  			params.FilesystemAttachmentInfo{
   718  				f.Path,
   719  				f.ReadOnly,
   720  			},
   721  		}
   722  	}
   723  	return out
   724  }
   725  
   726  func filesystemFromParams(in params.Filesystem) (storage.Filesystem, error) {
   727  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   728  	if err != nil {
   729  		return storage.Filesystem{}, errors.Trace(err)
   730  	}
   731  	var volumeTag names.VolumeTag
   732  	if in.VolumeTag != "" {
   733  		volumeTag, err = names.ParseVolumeTag(in.VolumeTag)
   734  		if err != nil {
   735  			return storage.Filesystem{}, errors.Trace(err)
   736  		}
   737  	}
   738  	return storage.Filesystem{
   739  		filesystemTag,
   740  		volumeTag,
   741  		storage.FilesystemInfo{
   742  			in.Info.FilesystemId,
   743  			in.Info.Size,
   744  		},
   745  	}, nil
   746  }
   747  
   748  func filesystemParamsFromParams(in params.FilesystemParams) (storage.FilesystemParams, error) {
   749  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   750  	if err != nil {
   751  		return storage.FilesystemParams{}, errors.Trace(err)
   752  	}
   753  	var volumeTag names.VolumeTag
   754  	if in.VolumeTag != "" {
   755  		volumeTag, err = names.ParseVolumeTag(in.VolumeTag)
   756  		if err != nil {
   757  			return storage.FilesystemParams{}, errors.Trace(err)
   758  		}
   759  	}
   760  	providerType := storage.ProviderType(in.Provider)
   761  	return storage.FilesystemParams{
   762  		filesystemTag,
   763  		volumeTag,
   764  		in.Size,
   765  		providerType,
   766  		in.Attributes,
   767  		in.Tags,
   768  	}, nil
   769  }
   770  
   771  func filesystemAttachmentParamsFromParams(in params.FilesystemAttachmentParams) (storage.FilesystemAttachmentParams, error) {
   772  	machineTag, err := names.ParseMachineTag(in.MachineTag)
   773  	if err != nil {
   774  		return storage.FilesystemAttachmentParams{}, errors.Trace(err)
   775  	}
   776  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   777  	if err != nil {
   778  		return storage.FilesystemAttachmentParams{}, errors.Trace(err)
   779  	}
   780  	return storage.FilesystemAttachmentParams{
   781  		AttachmentParams: storage.AttachmentParams{
   782  			Provider:   storage.ProviderType(in.Provider),
   783  			Machine:    machineTag,
   784  			InstanceId: instance.Id(in.InstanceId),
   785  			ReadOnly:   in.ReadOnly,
   786  		},
   787  		Filesystem:   filesystemTag,
   788  		FilesystemId: in.FilesystemId,
   789  		Path:         in.MountPoint,
   790  	}, nil
   791  }