github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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  		filesystems, err := filesystemSource.CreateFilesystems(params)
   586  		if err != nil {
   587  			return nil, errors.Annotatef(err, "creating filesystems from source %q", sourceName)
   588  		}
   589  		allFilesystems = append(allFilesystems, filesystems...)
   590  	}
   591  	return allFilesystems, nil
   592  }
   593  
   594  // createFilesystemAttachments creates filesystem attachments with the specified parameters.
   595  func createFilesystemAttachments(
   596  	ctx *context,
   597  	params []storage.FilesystemAttachmentParams,
   598  ) ([]storage.FilesystemAttachment, error) {
   599  	paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource(ctx, params)
   600  	if err != nil {
   601  		return nil, errors.Trace(err)
   602  	}
   603  	var allFilesystemAttachments []storage.FilesystemAttachment
   604  	for sourceName, params := range paramsBySource {
   605  		logger.Debugf("attaching filesystems: %v", params)
   606  		filesystemSource := filesystemSources[sourceName]
   607  		filesystemAttachments, err := filesystemSource.AttachFilesystems(params)
   608  		if err != nil {
   609  			return nil, errors.Annotatef(err, "attaching filesystems from source %q", sourceName)
   610  		}
   611  		allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...)
   612  	}
   613  	return allFilesystemAttachments, nil
   614  }
   615  
   616  func destroyFilesystems(ctx *context, tags []names.FilesystemTag) ([]error, error) {
   617  	// TODO(axw) add storage.FilesystemSource.DestroyFilesystems
   618  	return make([]error, len(tags)), nil
   619  }
   620  
   621  func detachFilesystems(ctx *context, attachments []storage.FilesystemAttachmentParams) error {
   622  	paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource(ctx, attachments)
   623  	if err != nil {
   624  		return errors.Trace(err)
   625  	}
   626  	for sourceName, params := range paramsBySource {
   627  		logger.Debugf("detaching filesystems: %v", params)
   628  		filesystemSource := filesystemSources[sourceName]
   629  		if err := filesystemSource.DetachFilesystems(params); err != nil {
   630  			return errors.Annotatef(err, "detaching filesystems from source %q", sourceName)
   631  		}
   632  	}
   633  	return nil
   634  }
   635  
   636  func filesystemAttachmentParamsBySource(
   637  	ctx *context, params []storage.FilesystemAttachmentParams,
   638  ) (map[string][]storage.FilesystemAttachmentParams, map[string]storage.FilesystemSource, error) {
   639  	// TODO(axw) later we may have multiple instantiations (sources)
   640  	// for a storage provider, e.g. multiple Ceph installations. For
   641  	// now we assume a single source for each provider type, with no
   642  	// configuration.
   643  	filesystemSources := make(map[string]storage.FilesystemSource)
   644  	paramsBySource := make(map[string][]storage.FilesystemAttachmentParams)
   645  	for _, params := range params {
   646  		sourceName := string(params.Provider)
   647  		paramsBySource[sourceName] = append(paramsBySource[sourceName], params)
   648  		if _, ok := filesystemSources[sourceName]; ok {
   649  			continue
   650  		}
   651  		filesystem := ctx.filesystems[params.Filesystem]
   652  		if filesystem.Volume != (names.VolumeTag{}) {
   653  			filesystemSources[sourceName] = ctx.managedFilesystemSource
   654  			continue
   655  		}
   656  		filesystemSource, err := filesystemSource(
   657  			ctx.environConfig, ctx.storageDir, sourceName, params.Provider,
   658  		)
   659  		if err != nil {
   660  			return nil, nil, errors.Annotate(err, "getting filesystem source")
   661  		}
   662  		filesystemSources[sourceName] = filesystemSource
   663  	}
   664  	return paramsBySource, filesystemSources, nil
   665  }
   666  
   667  func filesystemsFromStorage(in []storage.Filesystem) []params.Filesystem {
   668  	out := make([]params.Filesystem, len(in))
   669  	for i, f := range in {
   670  		paramsFilesystem := params.Filesystem{
   671  			f.Tag.String(),
   672  			"",
   673  			params.FilesystemInfo{
   674  				f.FilesystemId,
   675  				f.Size,
   676  			},
   677  		}
   678  		if f.Volume != (names.VolumeTag{}) {
   679  			paramsFilesystem.VolumeTag = f.Volume.String()
   680  		}
   681  		out[i] = paramsFilesystem
   682  	}
   683  	return out
   684  }
   685  
   686  func filesystemAttachmentsFromStorage(in []storage.FilesystemAttachment) []params.FilesystemAttachment {
   687  	out := make([]params.FilesystemAttachment, len(in))
   688  	for i, f := range in {
   689  		out[i] = params.FilesystemAttachment{
   690  			f.Filesystem.String(),
   691  			f.Machine.String(),
   692  			params.FilesystemAttachmentInfo{
   693  				f.Path,
   694  				f.ReadOnly,
   695  			},
   696  		}
   697  	}
   698  	return out
   699  }
   700  
   701  func filesystemFromParams(in params.Filesystem) (storage.Filesystem, error) {
   702  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   703  	if err != nil {
   704  		return storage.Filesystem{}, errors.Trace(err)
   705  	}
   706  	var volumeTag names.VolumeTag
   707  	if in.VolumeTag != "" {
   708  		volumeTag, err = names.ParseVolumeTag(in.VolumeTag)
   709  		if err != nil {
   710  			return storage.Filesystem{}, errors.Trace(err)
   711  		}
   712  	}
   713  	return storage.Filesystem{
   714  		filesystemTag,
   715  		volumeTag,
   716  		storage.FilesystemInfo{
   717  			in.Info.FilesystemId,
   718  			in.Info.Size,
   719  		},
   720  	}, nil
   721  }
   722  
   723  func filesystemAttachmentFromParams(in params.FilesystemAttachment) (storage.FilesystemAttachment, error) {
   724  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   725  	if err != nil {
   726  		return storage.FilesystemAttachment{}, errors.Trace(err)
   727  	}
   728  	machineTag, err := names.ParseMachineTag(in.MachineTag)
   729  	if err != nil {
   730  		return storage.FilesystemAttachment{}, errors.Trace(err)
   731  	}
   732  	return storage.FilesystemAttachment{
   733  		filesystemTag,
   734  		machineTag,
   735  		storage.FilesystemAttachmentInfo{
   736  			in.Info.MountPoint,
   737  			in.Info.ReadOnly,
   738  		},
   739  	}, nil
   740  }
   741  
   742  func filesystemParamsFromParams(in params.FilesystemParams) (storage.FilesystemParams, error) {
   743  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   744  	if err != nil {
   745  		return storage.FilesystemParams{}, errors.Trace(err)
   746  	}
   747  	var volumeTag names.VolumeTag
   748  	if in.VolumeTag != "" {
   749  		volumeTag, err = names.ParseVolumeTag(in.VolumeTag)
   750  		if err != nil {
   751  			return storage.FilesystemParams{}, errors.Trace(err)
   752  		}
   753  	}
   754  	providerType := storage.ProviderType(in.Provider)
   755  	return storage.FilesystemParams{
   756  		filesystemTag,
   757  		volumeTag,
   758  		in.Size,
   759  		providerType,
   760  		in.Attributes,
   761  		in.Tags,
   762  	}, nil
   763  }
   764  
   765  func filesystemAttachmentParamsFromParams(in params.FilesystemAttachmentParams) (storage.FilesystemAttachmentParams, error) {
   766  	machineTag, err := names.ParseMachineTag(in.MachineTag)
   767  	if err != nil {
   768  		return storage.FilesystemAttachmentParams{}, errors.Trace(err)
   769  	}
   770  	filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag)
   771  	if err != nil {
   772  		return storage.FilesystemAttachmentParams{}, errors.Trace(err)
   773  	}
   774  	return storage.FilesystemAttachmentParams{
   775  		AttachmentParams: storage.AttachmentParams{
   776  			Provider:   storage.ProviderType(in.Provider),
   777  			Machine:    machineTag,
   778  			InstanceId: instance.Id(in.InstanceId),
   779  			ReadOnly:   in.ReadOnly,
   780  		},
   781  		Filesystem:   filesystemTag,
   782  		FilesystemId: in.FilesystemId,
   783  		Path:         in.MountPoint,
   784  	}, nil
   785  }