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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"path"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	jujutxn "github.com/juju/txn"
    14  	"gopkg.in/juju/charm.v6-unstable"
    15  	"gopkg.in/juju/names.v2"
    16  	"gopkg.in/mgo.v2"
    17  	"gopkg.in/mgo.v2/bson"
    18  	"gopkg.in/mgo.v2/txn"
    19  
    20  	"github.com/juju/juju/juju/paths"
    21  	"github.com/juju/juju/status"
    22  	"github.com/juju/juju/storage"
    23  )
    24  
    25  // ErrNoBackingVolume is returned by Filesystem.Volume() for filesystems
    26  // without a backing volume.
    27  var ErrNoBackingVolume = errors.New("filesystem has no backing volume")
    28  
    29  // Filesystem describes a filesystem in the model. Filesystems may be
    30  // backed by a volume, and managed by Juju; otherwise they are first-class
    31  // entities managed by a filesystem provider.
    32  type Filesystem interface {
    33  	GlobalEntity
    34  	LifeBinder
    35  	status.StatusGetter
    36  	status.StatusSetter
    37  
    38  	// FilesystemTag returns the tag for the filesystem.
    39  	FilesystemTag() names.FilesystemTag
    40  
    41  	// Storage returns the tag of the storage instance that this
    42  	// filesystem is assigned to, if any. If the filesystem is not
    43  	// assigned to a storage instance, an error satisfying
    44  	// errors.IsNotAssigned will be returned.
    45  	//
    46  	// A filesystem can be assigned to at most one storage instance, and
    47  	// a storage instance can have at most one associated filesystem.
    48  	Storage() (names.StorageTag, error)
    49  
    50  	// Volume returns the tag of the volume backing this filesystem,
    51  	// or ErrNoBackingVolume if the filesystem is not backed by a volume
    52  	// managed by Juju.
    53  	Volume() (names.VolumeTag, error)
    54  
    55  	// Info returns the filesystem's FilesystemInfo, or a NotProvisioned
    56  	// error if the filesystem has not yet been provisioned.
    57  	Info() (FilesystemInfo, error)
    58  
    59  	// Params returns the parameters for provisioning the filesystem,
    60  	// if it needs to be provisioned. Params returns true if the returned
    61  	// parameters are usable for provisioning, otherwise false.
    62  	Params() (FilesystemParams, bool)
    63  }
    64  
    65  // FilesystemAttachment describes an attachment of a filesystem to a machine.
    66  type FilesystemAttachment interface {
    67  	Lifer
    68  
    69  	// Filesystem returns the tag of the related Filesystem.
    70  	Filesystem() names.FilesystemTag
    71  
    72  	// Machine returns the tag of the related Machine.
    73  	Machine() names.MachineTag
    74  
    75  	// Info returns the filesystem attachment's FilesystemAttachmentInfo, or a
    76  	// NotProvisioned error if the attachment has not yet been made.
    77  	//
    78  	// Note that the presence of FilesystemAttachmentInfo does not necessarily
    79  	// imply that the filesystem is mounted; model storage providers may
    80  	// need to prepare a filesystem for attachment to a machine before it can
    81  	// be mounted.
    82  	Info() (FilesystemAttachmentInfo, error)
    83  
    84  	// Params returns the parameters for creating the filesystem attachment,
    85  	// if it has not already been made. Params returns true if the returned
    86  	// parameters are usable for creating an attachment, otherwise false.
    87  	Params() (FilesystemAttachmentParams, bool)
    88  }
    89  
    90  type filesystem struct {
    91  	st  *State
    92  	doc filesystemDoc
    93  }
    94  
    95  type filesystemAttachment struct {
    96  	doc filesystemAttachmentDoc
    97  }
    98  
    99  // filesystemDoc records information about a filesystem in the model.
   100  type filesystemDoc struct {
   101  	DocID        string `bson:"_id"`
   102  	FilesystemId string `bson:"filesystemid"`
   103  	ModelUUID    string `bson:"model-uuid"`
   104  	Life         Life   `bson:"life"`
   105  	StorageId    string `bson:"storageid,omitempty"`
   106  	VolumeId     string `bson:"volumeid,omitempty"`
   107  	// TODO(axw) 2015-06-22 #1467379
   108  	// upgrade step to set "attachmentcount" and "binding"
   109  	// for 1.24 models.
   110  	AttachmentCount int               `bson:"attachmentcount"`
   111  	Binding         string            `bson:"binding,omitempty"`
   112  	Info            *FilesystemInfo   `bson:"info,omitempty"`
   113  	Params          *FilesystemParams `bson:"params,omitempty"`
   114  }
   115  
   116  // filesystemAttachmentDoc records information about a filesystem attachment.
   117  type filesystemAttachmentDoc struct {
   118  	// DocID is the machine global key followed by the filesystem name.
   119  	DocID      string                      `bson:"_id"`
   120  	ModelUUID  string                      `bson:"model-uuid"`
   121  	Filesystem string                      `bson:"filesystemid"`
   122  	Machine    string                      `bson:"machineid"`
   123  	Life       Life                        `bson:"life"`
   124  	Info       *FilesystemAttachmentInfo   `bson:"info,omitempty"`
   125  	Params     *FilesystemAttachmentParams `bson:"params,omitempty"`
   126  }
   127  
   128  // FilesystemParams records parameters for provisioning a new filesystem.
   129  type FilesystemParams struct {
   130  	// storage, if non-zero, is the tag of the storage instance
   131  	// that the filesystem is to be assigned to.
   132  	storage names.StorageTag
   133  
   134  	// binding, if non-nil, is the tag of the entity to which
   135  	// the filesystem's lifecycle will be bound.
   136  	binding names.Tag
   137  
   138  	Pool string `bson:"pool"`
   139  	Size uint64 `bson:"size"`
   140  }
   141  
   142  // FilesystemInfo describes information about a filesystem.
   143  type FilesystemInfo struct {
   144  	Size uint64 `bson:"size"`
   145  	Pool string `bson:"pool"`
   146  
   147  	// FilesystemId is the provider-allocated unique ID of the
   148  	// filesystem. This will be unspecified for filesystems
   149  	// backed by volumes.
   150  	FilesystemId string `bson:"filesystemid"`
   151  }
   152  
   153  // FilesystemAttachmentInfo describes information about a filesystem attachment.
   154  type FilesystemAttachmentInfo struct {
   155  	// MountPoint is the path at which the filesystem is mounted on the
   156  	// machine. MountPoint may be empty, meaning that the filesystem is
   157  	// not mounted yet.
   158  	MountPoint string `bson:"mountpoint"`
   159  	ReadOnly   bool   `bson:"read-only"`
   160  }
   161  
   162  // FilesystemAttachmentParams records parameters for attaching a filesystem to a
   163  // machine.
   164  type FilesystemAttachmentParams struct {
   165  	// locationAutoGenerated records whether or not the Location
   166  	// field's value was automatically generated, and thus known
   167  	// to be unique. This is used to optimise away mount point
   168  	// conflict checks.
   169  	locationAutoGenerated bool
   170  	Location              string `bson:"location"`
   171  	ReadOnly              bool   `bson:"read-only"`
   172  }
   173  
   174  // validate validates the contents of the filesystem document.
   175  func (f *filesystem) validate() error {
   176  	if f.doc.Binding != "" {
   177  		tag, err := names.ParseTag(f.doc.Binding)
   178  		if err != nil {
   179  			return errors.Annotate(err, "parsing binding")
   180  		}
   181  		switch tag.(type) {
   182  		case names.ModelTag:
   183  			// TODO(axw) support binding to model
   184  			return errors.NotSupportedf("binding to model")
   185  		case names.MachineTag:
   186  		case names.StorageTag:
   187  		default:
   188  			return errors.Errorf("invalid binding: %v", f.doc.Binding)
   189  		}
   190  	}
   191  	return nil
   192  }
   193  
   194  // globalKey is required to implement GlobalEntity.
   195  func (f *filesystem) globalKey() string {
   196  	return filesystemGlobalKey(f.doc.FilesystemId)
   197  }
   198  
   199  // Tag is required to implement GlobalEntity.
   200  func (f *filesystem) Tag() names.Tag {
   201  	return f.FilesystemTag()
   202  }
   203  
   204  // FilesystemTag is required to implement Filesystem.
   205  func (f *filesystem) FilesystemTag() names.FilesystemTag {
   206  	return names.NewFilesystemTag(f.doc.FilesystemId)
   207  }
   208  
   209  // Life is required to implement Filesystem.
   210  func (f *filesystem) Life() Life {
   211  	return f.doc.Life
   212  }
   213  
   214  // LifeBinding is required to implement LifeBinder.
   215  //
   216  // Below is the set of possible entity types that a volume may be bound
   217  // to, and a description of the effects of doing so:
   218  //
   219  //   Machine:     If the filesystem is bound to a machine, then the
   220  //                filesystem will be destroyed when it is detached from
   221  //                the machine. It is not permitted for a filesystem to
   222  //                be attached to multiple machines while it is bound to
   223  //                a machine.
   224  //   Storage:     If the filesystem is bound to a storage instance,
   225  //                then the filesystem will be destroyed when the
   226  //                storage insance is removed from state.
   227  //   Model: If the filesystem is bound to the model, then
   228  //                the filesystem must be destroyed prior to the
   229  //                model being destroyed.
   230  func (f *filesystem) LifeBinding() names.Tag {
   231  	if f.doc.Binding == "" {
   232  		return nil
   233  	}
   234  	// Tag is validated in filesystem.validate.
   235  	tag, _ := names.ParseTag(f.doc.Binding)
   236  	return tag
   237  }
   238  
   239  // Storage is required to implement Filesystem.
   240  func (f *filesystem) Storage() (names.StorageTag, error) {
   241  	if f.doc.StorageId == "" {
   242  		msg := fmt.Sprintf("filesystem %q is not assigned to any storage instance", f.Tag().Id())
   243  		return names.StorageTag{}, errors.NewNotAssigned(nil, msg)
   244  	}
   245  	return names.NewStorageTag(f.doc.StorageId), nil
   246  }
   247  
   248  // Volume is required to implement Filesystem.
   249  func (f *filesystem) Volume() (names.VolumeTag, error) {
   250  	if f.doc.VolumeId == "" {
   251  		return names.VolumeTag{}, ErrNoBackingVolume
   252  	}
   253  	return names.NewVolumeTag(f.doc.VolumeId), nil
   254  }
   255  
   256  // Info is required to implement Filesystem.
   257  func (f *filesystem) Info() (FilesystemInfo, error) {
   258  	if f.doc.Info == nil {
   259  		return FilesystemInfo{}, errors.NotProvisionedf("filesystem %q", f.doc.FilesystemId)
   260  	}
   261  	return *f.doc.Info, nil
   262  }
   263  
   264  // Params is required to implement Filesystem.
   265  func (f *filesystem) Params() (FilesystemParams, bool) {
   266  	if f.doc.Params == nil {
   267  		return FilesystemParams{}, false
   268  	}
   269  	return *f.doc.Params, true
   270  }
   271  
   272  // Status is required to implement StatusGetter.
   273  func (f *filesystem) Status() (status.StatusInfo, error) {
   274  	return f.st.FilesystemStatus(f.FilesystemTag())
   275  }
   276  
   277  // SetStatus is required to implement StatusSetter.
   278  func (f *filesystem) SetStatus(fsStatus status.StatusInfo) error {
   279  	return f.st.SetFilesystemStatus(f.FilesystemTag(), fsStatus.Status, fsStatus.Message, fsStatus.Data, fsStatus.Since)
   280  }
   281  
   282  // Filesystem is required to implement FilesystemAttachment.
   283  func (f *filesystemAttachment) Filesystem() names.FilesystemTag {
   284  	return names.NewFilesystemTag(f.doc.Filesystem)
   285  }
   286  
   287  // Machine is required to implement FilesystemAttachment.
   288  func (f *filesystemAttachment) Machine() names.MachineTag {
   289  	return names.NewMachineTag(f.doc.Machine)
   290  }
   291  
   292  // Life is required to implement FilesystemAttachment.
   293  func (f *filesystemAttachment) Life() Life {
   294  	return f.doc.Life
   295  }
   296  
   297  // Info is required to implement FilesystemAttachment.
   298  func (f *filesystemAttachment) Info() (FilesystemAttachmentInfo, error) {
   299  	if f.doc.Info == nil {
   300  		return FilesystemAttachmentInfo{}, errors.NotProvisionedf("filesystem attachment %q on %q", f.doc.Filesystem, f.doc.Machine)
   301  	}
   302  	return *f.doc.Info, nil
   303  }
   304  
   305  // Params is required to implement FilesystemAttachment.
   306  func (f *filesystemAttachment) Params() (FilesystemAttachmentParams, bool) {
   307  	if f.doc.Params == nil {
   308  		return FilesystemAttachmentParams{}, false
   309  	}
   310  	return *f.doc.Params, true
   311  }
   312  
   313  // Filesystem returns the Filesystem with the specified name.
   314  func (st *State) Filesystem(tag names.FilesystemTag) (Filesystem, error) {
   315  	f, err := st.filesystemByTag(tag)
   316  	return f, err
   317  }
   318  
   319  func (st *State) filesystemByTag(tag names.FilesystemTag) (*filesystem, error) {
   320  	query := bson.D{{"_id", tag.Id()}}
   321  	description := fmt.Sprintf("filesystem %q", tag.Id())
   322  	return st.filesystem(query, description)
   323  }
   324  
   325  func (st *State) storageInstanceFilesystem(tag names.StorageTag) (*filesystem, error) {
   326  	query := bson.D{{"storageid", tag.Id()}}
   327  	description := fmt.Sprintf("filesystem for storage instance %q", tag.Id())
   328  	return st.filesystem(query, description)
   329  }
   330  
   331  // StorageInstanceFilesystem returns the Filesystem assigned to the specified
   332  // storage instance.
   333  func (st *State) StorageInstanceFilesystem(tag names.StorageTag) (Filesystem, error) {
   334  	f, err := st.storageInstanceFilesystem(tag)
   335  	return f, err
   336  }
   337  
   338  func (st *State) volumeFilesystem(tag names.VolumeTag) (*filesystem, error) {
   339  	query := bson.D{{"volumeid", tag.Id()}}
   340  	description := fmt.Sprintf("filesystem for volume %q", tag.Id())
   341  	return st.filesystem(query, description)
   342  }
   343  
   344  // VolumeFilesystem returns the Filesystem backed by the specified volume.
   345  func (st *State) VolumeFilesystem(tag names.VolumeTag) (Filesystem, error) {
   346  	f, err := st.volumeFilesystem(tag)
   347  	return f, err
   348  }
   349  
   350  func (st *State) filesystems(query interface{}) ([]*filesystem, error) {
   351  	coll, cleanup := st.getCollection(filesystemsC)
   352  	defer cleanup()
   353  
   354  	var fDocs []filesystemDoc
   355  	err := coll.Find(query).All(&fDocs)
   356  	if err != nil {
   357  		return nil, errors.Trace(err)
   358  	}
   359  	filesystems := make([]*filesystem, len(fDocs))
   360  	for i, doc := range fDocs {
   361  		f := &filesystem{st, doc}
   362  		if err := f.validate(); err != nil {
   363  			return nil, errors.Annotate(err, "filesystem validation failed")
   364  		}
   365  		filesystems[i] = f
   366  	}
   367  	return filesystems, nil
   368  }
   369  
   370  func (st *State) filesystem(query bson.D, description string) (*filesystem, error) {
   371  	coll, cleanup := st.getCollection(filesystemsC)
   372  	defer cleanup()
   373  
   374  	f := filesystem{st: st}
   375  	err := coll.Find(query).One(&f.doc)
   376  	if err == mgo.ErrNotFound {
   377  		return nil, errors.NotFoundf(description)
   378  	} else if err != nil {
   379  		return nil, errors.Annotate(err, "cannot get filesystem")
   380  	}
   381  	if err := f.validate(); err != nil {
   382  		return nil, errors.Annotate(err, "validating filesystem")
   383  	}
   384  	return &f, nil
   385  }
   386  
   387  // FilesystemAttachment returns the FilesystemAttachment corresponding to
   388  // the specified filesystem and machine.
   389  func (st *State) FilesystemAttachment(machine names.MachineTag, filesystem names.FilesystemTag) (FilesystemAttachment, error) {
   390  	coll, cleanup := st.getCollection(filesystemAttachmentsC)
   391  	defer cleanup()
   392  
   393  	var att filesystemAttachment
   394  	err := coll.FindId(filesystemAttachmentId(machine.Id(), filesystem.Id())).One(&att.doc)
   395  	if err == mgo.ErrNotFound {
   396  		return nil, errors.NotFoundf("filesystem %q on machine %q", filesystem.Id(), machine.Id())
   397  	} else if err != nil {
   398  		return nil, errors.Annotatef(err, "getting filesystem %q on machine %q", filesystem.Id(), machine.Id())
   399  	}
   400  	return &att, nil
   401  }
   402  
   403  // FilesystemAttachments returns all of the FilesystemAttachments for the
   404  // specified filesystem.
   405  func (st *State) FilesystemAttachments(filesystem names.FilesystemTag) ([]FilesystemAttachment, error) {
   406  	attachments, err := st.filesystemAttachments(bson.D{{"filesystemid", filesystem.Id()}})
   407  	if err != nil {
   408  		return nil, errors.Annotatef(err, "getting attachments for filesystem %q", filesystem.Id())
   409  	}
   410  	return attachments, nil
   411  }
   412  
   413  // MachineFilesystemAttachments returns all of the FilesystemAttachments for the
   414  // specified machine.
   415  func (st *State) MachineFilesystemAttachments(machine names.MachineTag) ([]FilesystemAttachment, error) {
   416  	attachments, err := st.filesystemAttachments(bson.D{{"machineid", machine.Id()}})
   417  	if err != nil {
   418  		return nil, errors.Annotatef(err, "getting filesystem attachments for machine %q", machine.Id())
   419  	}
   420  	return attachments, nil
   421  }
   422  
   423  func (st *State) filesystemAttachments(query bson.D) ([]FilesystemAttachment, error) {
   424  	coll, cleanup := st.getCollection(filesystemAttachmentsC)
   425  	defer cleanup()
   426  
   427  	var docs []filesystemAttachmentDoc
   428  	err := coll.Find(query).All(&docs)
   429  	if err == mgo.ErrNotFound {
   430  		return nil, nil
   431  	} else if err != nil {
   432  		return nil, errors.Trace(err)
   433  	}
   434  	attachments := make([]FilesystemAttachment, len(docs))
   435  	for i, doc := range docs {
   436  		attachments[i] = &filesystemAttachment{doc}
   437  	}
   438  	return attachments, nil
   439  }
   440  
   441  // removeMachineFilesystemsOps returns txn.Ops to remove non-persistent filesystems
   442  // attached to the specified machine. This is used when the given machine is
   443  // being removed from state.
   444  func (st *State) removeMachineFilesystemsOps(machine names.MachineTag) ([]txn.Op, error) {
   445  	attachments, err := st.MachineFilesystemAttachments(machine)
   446  	if err != nil {
   447  		return nil, errors.Trace(err)
   448  	}
   449  	ops := make([]txn.Op, 0, len(attachments))
   450  	for _, a := range attachments {
   451  		filesystemTag := a.Filesystem()
   452  		// When removing the machine, there should only remain
   453  		// non-persistent storage. This will be implicitly
   454  		// removed when the machine is removed, so we do not
   455  		// use removeFilesystemAttachmentOps or removeFilesystemOps,
   456  		// which track and update related documents.
   457  		ops = append(ops, txn.Op{
   458  			C:      filesystemAttachmentsC,
   459  			Id:     filesystemAttachmentId(machine.Id(), filesystemTag.Id()),
   460  			Assert: txn.DocExists,
   461  			Remove: true,
   462  		})
   463  		canRemove, err := isFilesystemInherentlyMachineBound(st, filesystemTag)
   464  		if err != nil {
   465  			return nil, errors.Trace(err)
   466  		}
   467  		if !canRemove {
   468  			return nil, errors.Errorf("machine has non-machine bound filesystem %v", filesystemTag.Id())
   469  		}
   470  		ops = append(ops, txn.Op{
   471  			C:      filesystemsC,
   472  			Id:     filesystemTag.Id(),
   473  			Assert: txn.DocExists,
   474  			Remove: true,
   475  		})
   476  	}
   477  	return ops, nil
   478  }
   479  
   480  // isFilesystemInherentlyMachineBound reports whether or not the filesystem
   481  // with the specified tag is inherently bound to the lifetime of the machine,
   482  // and will be removed along with it, leaving no resources dangling.
   483  func isFilesystemInherentlyMachineBound(st *State, tag names.FilesystemTag) (bool, error) {
   484  	// TODO(axw) when we have support for persistent filesystems,
   485  	// e.g. NFS shares, then we need to check the filesystem info
   486  	// to decide whether or not to remove.
   487  	return true, nil
   488  }
   489  
   490  // DetachFilesystem marks the filesystem attachment identified by the specified machine
   491  // and filesystem tags as Dying, if it is Alive.
   492  func (st *State) DetachFilesystem(machine names.MachineTag, filesystem names.FilesystemTag) (err error) {
   493  	defer errors.DeferredAnnotatef(&err, "detaching filesystem %s from machine %s", filesystem.Id(), machine.Id())
   494  	buildTxn := func(attempt int) ([]txn.Op, error) {
   495  		fsa, err := st.FilesystemAttachment(machine, filesystem)
   496  		if err != nil {
   497  			return nil, errors.Trace(err)
   498  		}
   499  		if fsa.Life() != Alive {
   500  			return nil, jujutxn.ErrNoOperations
   501  		}
   502  		ops := detachFilesystemOps(machine, filesystem)
   503  		return ops, nil
   504  	}
   505  	return st.run(buildTxn)
   506  }
   507  
   508  func (st *State) filesystemVolumeAttachment(m names.MachineTag, f names.FilesystemTag) (VolumeAttachment, error) {
   509  	filesystem, err := st.Filesystem(f)
   510  	if err != nil {
   511  		return nil, errors.Trace(err)
   512  	}
   513  	v, err := filesystem.Volume()
   514  	if err != nil {
   515  		return nil, errors.Trace(err)
   516  	}
   517  	return st.VolumeAttachment(m, v)
   518  }
   519  
   520  func detachFilesystemOps(m names.MachineTag, f names.FilesystemTag) []txn.Op {
   521  	return []txn.Op{{
   522  		C:      filesystemAttachmentsC,
   523  		Id:     filesystemAttachmentId(m.Id(), f.Id()),
   524  		Assert: isAliveDoc,
   525  		Update: bson.D{{"$set", bson.D{{"life", Dying}}}},
   526  	}}
   527  }
   528  
   529  // RemoveFilesystemAttachment removes the filesystem attachment from state.
   530  // Removing a volume-backed filesystem attachment will cause the volume to
   531  // be detached.
   532  func (st *State) RemoveFilesystemAttachment(machine names.MachineTag, filesystem names.FilesystemTag) (err error) {
   533  	defer errors.DeferredAnnotatef(&err, "removing attachment of filesystem %s from machine %s", filesystem.Id(), machine.Id())
   534  	buildTxn := func(attempt int) ([]txn.Op, error) {
   535  		attachment, err := st.FilesystemAttachment(machine, filesystem)
   536  		if errors.IsNotFound(err) && attempt > 0 {
   537  			// We only ignore IsNotFound on attempts after the
   538  			// first, since we expect the filesystem attachment to
   539  			// be there initially.
   540  			return nil, jujutxn.ErrNoOperations
   541  		}
   542  		if err != nil {
   543  			return nil, errors.Trace(err)
   544  		}
   545  		if attachment.Life() != Dying {
   546  			return nil, errors.New("filesystem attachment is not dying")
   547  		}
   548  		f, err := st.filesystemByTag(filesystem)
   549  		if err != nil {
   550  			return nil, errors.Trace(err)
   551  		}
   552  		ops := removeFilesystemAttachmentOps(machine, f)
   553  		// If the filesystem is backed by a volume, the volume
   554  		// attachment can and should be destroyed once the
   555  		// filesystem attachment is removed.
   556  		volumeAttachment, err := st.filesystemVolumeAttachment(machine, filesystem)
   557  		if err != nil {
   558  			if errors.Cause(err) != ErrNoBackingVolume && !errors.IsNotFound(err) {
   559  				return nil, errors.Trace(err)
   560  			}
   561  		} else if volumeAttachment.Life() == Alive {
   562  			ops = append(ops, detachVolumeOps(machine, volumeAttachment.Volume())...)
   563  		}
   564  		return ops, nil
   565  	}
   566  	return st.run(buildTxn)
   567  }
   568  
   569  func removeFilesystemAttachmentOps(m names.MachineTag, f *filesystem) []txn.Op {
   570  	decrefFilesystemOp := machineStorageDecrefOp(
   571  		filesystemsC, f.doc.FilesystemId,
   572  		f.doc.AttachmentCount, f.doc.Life,
   573  		m, f.doc.Binding,
   574  	)
   575  	return []txn.Op{{
   576  		C:      filesystemAttachmentsC,
   577  		Id:     filesystemAttachmentId(m.Id(), f.doc.FilesystemId),
   578  		Assert: bson.D{{"life", Dying}},
   579  		Remove: true,
   580  	}, decrefFilesystemOp, {
   581  		C:      machinesC,
   582  		Id:     m.Id(),
   583  		Assert: txn.DocExists,
   584  		Update: bson.D{{"$pull", bson.D{{"filesystems", f.doc.FilesystemId}}}},
   585  	}}
   586  }
   587  
   588  // DestroyFilesystem ensures that the filesystem and any attachments to it will
   589  // be destroyed and removed from state at some point in the future.
   590  func (st *State) DestroyFilesystem(tag names.FilesystemTag) (err error) {
   591  	buildTxn := func(attempt int) ([]txn.Op, error) {
   592  		filesystem, err := st.filesystemByTag(tag)
   593  		if errors.IsNotFound(err) {
   594  			return nil, jujutxn.ErrNoOperations
   595  		} else if err != nil {
   596  			return nil, errors.Trace(err)
   597  		}
   598  		if filesystem.doc.Life != Alive {
   599  			return nil, jujutxn.ErrNoOperations
   600  		}
   601  		return destroyFilesystemOps(st, filesystem), nil
   602  	}
   603  	return st.run(buildTxn)
   604  }
   605  
   606  func destroyFilesystemOps(st *State, f *filesystem) []txn.Op {
   607  	if f.doc.AttachmentCount == 0 {
   608  		hasNoAttachments := bson.D{{"attachmentcount", 0}}
   609  		return []txn.Op{{
   610  			C:      filesystemsC,
   611  			Id:     f.doc.FilesystemId,
   612  			Assert: append(hasNoAttachments, isAliveDoc...),
   613  			Update: bson.D{{"$set", bson.D{{"life", Dead}}}},
   614  		}}
   615  	}
   616  	hasAttachments := bson.D{{"attachmentcount", bson.D{{"$gt", 0}}}}
   617  	cleanupOp := newCleanupOp(cleanupAttachmentsForDyingFilesystem, f.doc.FilesystemId)
   618  	return []txn.Op{{
   619  		C:      filesystemsC,
   620  		Id:     f.doc.FilesystemId,
   621  		Assert: append(hasAttachments, isAliveDoc...),
   622  		Update: bson.D{{"$set", bson.D{{"life", Dying}}}},
   623  	}, cleanupOp}
   624  }
   625  
   626  // RemoveFilesystem removes the filesystem from state. RemoveFilesystem will
   627  // fail if there are any attachments remaining, or if the filesystem is not
   628  // Dying. Removing a volume-backed filesystem will cause the volume to be
   629  // destroyed.
   630  func (st *State) RemoveFilesystem(tag names.FilesystemTag) (err error) {
   631  	defer errors.DeferredAnnotatef(&err, "removing filesystem %s", tag.Id())
   632  	buildTxn := func(attempt int) ([]txn.Op, error) {
   633  		filesystem, err := st.Filesystem(tag)
   634  		if errors.IsNotFound(err) {
   635  			return nil, jujutxn.ErrNoOperations
   636  		} else if err != nil {
   637  			return nil, errors.Trace(err)
   638  		}
   639  		if filesystem.Life() != Dead {
   640  			return nil, errors.New("filesystem is not dead")
   641  		}
   642  		return removeFilesystemOps(st, filesystem)
   643  	}
   644  	return st.run(buildTxn)
   645  }
   646  
   647  func removeFilesystemOps(st *State, filesystem Filesystem) ([]txn.Op, error) {
   648  	ops := []txn.Op{
   649  		{
   650  			C:      filesystemsC,
   651  			Id:     filesystem.Tag().Id(),
   652  			Assert: txn.DocExists,
   653  			Remove: true,
   654  		},
   655  		removeStatusOp(st, filesystem.globalKey()),
   656  	}
   657  	// If the filesystem is backed by a volume, the volume should
   658  	// be destroyed once the filesystem is removed if it is bound
   659  	// to the filesystem.
   660  	volumeTag, err := filesystem.Volume()
   661  	if err == nil {
   662  		volume, err := st.volumeByTag(volumeTag)
   663  		if err != nil {
   664  			return nil, errors.Trace(err)
   665  		}
   666  		if volume.LifeBinding() == filesystem.Tag() {
   667  			ops = append(ops, destroyVolumeOps(st, volume)...)
   668  		}
   669  	} else if err != ErrNoBackingVolume {
   670  		return nil, errors.Trace(err)
   671  	}
   672  	return ops, nil
   673  }
   674  
   675  // filesystemAttachmentId returns a filesystem attachment document ID,
   676  // given the corresponding filesystem name and machine ID.
   677  func filesystemAttachmentId(machineId, filesystemId string) string {
   678  	return fmt.Sprintf("%s:%s", machineId, filesystemId)
   679  }
   680  
   681  // ParseFilesystemAttachmentId parses a string as a filesystem attachment ID,
   682  // returning the machine and filesystem components.
   683  func ParseFilesystemAttachmentId(id string) (names.MachineTag, names.FilesystemTag, error) {
   684  	fields := strings.SplitN(id, ":", 2)
   685  	if len(fields) != 2 || !names.IsValidMachine(fields[0]) || !names.IsValidFilesystem(fields[1]) {
   686  		return names.MachineTag{}, names.FilesystemTag{}, errors.Errorf("invalid filesystem attachment ID %q", id)
   687  	}
   688  	machineTag := names.NewMachineTag(fields[0])
   689  	filesystemTag := names.NewFilesystemTag(fields[1])
   690  	return machineTag, filesystemTag, nil
   691  }
   692  
   693  // newFilesystemId returns a unique filesystem ID.
   694  // If the machine ID supplied is non-empty, the
   695  // filesystem ID will incorporate it as the
   696  // filesystem's machine scope.
   697  func newFilesystemId(st *State, machineId string) (string, error) {
   698  	seq, err := st.sequence("filesystem")
   699  	if err != nil {
   700  		return "", errors.Trace(err)
   701  	}
   702  	id := fmt.Sprint(seq)
   703  	if machineId != "" {
   704  		id = machineId + "/" + id
   705  	}
   706  	return id, nil
   707  }
   708  
   709  // addFilesystemOps returns txn.Ops to create a new filesystem with the
   710  // specified parameters. If the storage source cannot create filesystems
   711  // directly, a volume will be created and Juju will manage a filesystem
   712  // on it.
   713  func (st *State) addFilesystemOps(params FilesystemParams, machineId string) ([]txn.Op, names.FilesystemTag, names.VolumeTag, error) {
   714  	if params.binding == nil {
   715  		params.binding = names.NewMachineTag(machineId)
   716  	}
   717  	params, err := st.filesystemParamsWithDefaults(params)
   718  	if err != nil {
   719  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err)
   720  	}
   721  	machineId, err = st.validateFilesystemParams(params, machineId)
   722  	if err != nil {
   723  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "validating filesystem params")
   724  	}
   725  
   726  	filesystemId, err := newFilesystemId(st, machineId)
   727  	if err != nil {
   728  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "cannot generate filesystem name")
   729  	}
   730  	filesystemTag := names.NewFilesystemTag(filesystemId)
   731  
   732  	// Check if the filesystem needs a volume.
   733  	var volumeId string
   734  	var volumeTag names.VolumeTag
   735  	var ops []txn.Op
   736  	_, provider, err := poolStorageProvider(st, params.Pool)
   737  	if err != nil {
   738  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err)
   739  	}
   740  	if !provider.Supports(storage.StorageKindFilesystem) {
   741  		var volumeOps []txn.Op
   742  		volumeParams := VolumeParams{
   743  			params.storage,
   744  			filesystemTag, // volume is bound to filesystem
   745  			params.Pool,
   746  			params.Size,
   747  		}
   748  		volumeOps, volumeTag, err = st.addVolumeOps(volumeParams, machineId)
   749  		if err != nil {
   750  			return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "creating backing volume")
   751  		}
   752  		volumeId = volumeTag.Id()
   753  		ops = append(ops, volumeOps...)
   754  	}
   755  
   756  	status := statusDoc{
   757  		Status:  status.Pending,
   758  		Updated: st.clock.Now().UnixNano(),
   759  	}
   760  	doc := filesystemDoc{
   761  		FilesystemId: filesystemId,
   762  		VolumeId:     volumeId,
   763  		StorageId:    params.storage.Id(),
   764  		Binding:      params.binding.String(),
   765  		Params:       &params,
   766  		// Every filesystem is created with one attachment.
   767  		AttachmentCount: 1,
   768  	}
   769  	ops = append(ops, st.newFilesystemOps(doc, status)...)
   770  	return ops, filesystemTag, volumeTag, nil
   771  }
   772  
   773  func (st *State) newFilesystemOps(doc filesystemDoc, status statusDoc) []txn.Op {
   774  	return []txn.Op{
   775  		createStatusOp(st, filesystemGlobalKey(doc.FilesystemId), status),
   776  		{
   777  			C:      filesystemsC,
   778  			Id:     doc.FilesystemId,
   779  			Assert: txn.DocMissing,
   780  			Insert: &doc,
   781  		},
   782  	}
   783  }
   784  
   785  func (st *State) filesystemParamsWithDefaults(params FilesystemParams) (FilesystemParams, error) {
   786  	if params.Pool != "" {
   787  		return params, nil
   788  	}
   789  	envConfig, err := st.ModelConfig()
   790  	if err != nil {
   791  		return FilesystemParams{}, errors.Trace(err)
   792  	}
   793  	cons := StorageConstraints{
   794  		Pool:  params.Pool,
   795  		Size:  params.Size,
   796  		Count: 1,
   797  	}
   798  	poolName, err := defaultStoragePool(envConfig, storage.StorageKindFilesystem, cons)
   799  	if err != nil {
   800  		return FilesystemParams{}, errors.Annotate(err, "getting default filesystem storage pool")
   801  	}
   802  	params.Pool = poolName
   803  	return params, nil
   804  }
   805  
   806  // validateFilesystemParams validates the filesystem parameters, and returns the
   807  // machine ID to use as the scope in the filesystem tag.
   808  func (st *State) validateFilesystemParams(params FilesystemParams, machineId string) (maybeMachineId string, _ error) {
   809  	err := validateStoragePool(st, params.Pool, storage.StorageKindFilesystem, &machineId)
   810  	if err != nil {
   811  		return "", errors.Trace(err)
   812  	}
   813  	if params.Size == 0 {
   814  		return "", errors.New("invalid size 0")
   815  	}
   816  	return machineId, nil
   817  }
   818  
   819  type filesystemAttachmentTemplate struct {
   820  	tag     names.FilesystemTag
   821  	storage names.StorageTag // may be zero-value
   822  	params  FilesystemAttachmentParams
   823  }
   824  
   825  // createMachineFilesystemAttachmentInfo creates filesystem
   826  // attachments for the specified machine, and attachment
   827  // parameters keyed by filesystem tags.
   828  func createMachineFilesystemAttachmentsOps(machineId string, attachments []filesystemAttachmentTemplate) []txn.Op {
   829  	ops := make([]txn.Op, len(attachments))
   830  	for i, attachment := range attachments {
   831  		paramsCopy := attachment.params
   832  		ops[i] = txn.Op{
   833  			C:      filesystemAttachmentsC,
   834  			Id:     filesystemAttachmentId(machineId, attachment.tag.Id()),
   835  			Assert: txn.DocMissing,
   836  			Insert: &filesystemAttachmentDoc{
   837  				Filesystem: attachment.tag.Id(),
   838  				Machine:    machineId,
   839  				Params:     &paramsCopy,
   840  			},
   841  		}
   842  	}
   843  	return ops
   844  }
   845  
   846  // SetFilesystemInfo sets the FilesystemInfo for the specified filesystem.
   847  func (st *State) SetFilesystemInfo(tag names.FilesystemTag, info FilesystemInfo) (err error) {
   848  	defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem %q", tag.Id())
   849  	if info.FilesystemId == "" {
   850  		return errors.New("filesystem ID not set")
   851  	}
   852  	fs, err := st.Filesystem(tag)
   853  	if err != nil {
   854  		return errors.Trace(err)
   855  	}
   856  	// If the filesystem is volume-backed, the volume must be provisioned
   857  	// and attachment first.
   858  	if volumeTag, err := fs.Volume(); err == nil {
   859  		machineTag, ok := names.FilesystemMachine(tag)
   860  		if !ok {
   861  			return errors.Errorf("filesystem %s is not machine-scoped, but volume-backed", tag.Id())
   862  		}
   863  		volumeAttachment, err := st.VolumeAttachment(machineTag, volumeTag)
   864  		if err != nil {
   865  			return errors.Trace(err)
   866  		}
   867  		if _, err := volumeAttachment.Info(); err != nil {
   868  			return errors.Trace(err)
   869  		}
   870  	} else if errors.Cause(err) != ErrNoBackingVolume {
   871  		return errors.Trace(err)
   872  	}
   873  	buildTxn := func(attempt int) ([]txn.Op, error) {
   874  		if attempt > 0 {
   875  			fs, err = st.Filesystem(tag)
   876  			if err != nil {
   877  				return nil, errors.Trace(err)
   878  			}
   879  		}
   880  		// If the filesystem has parameters, unset them
   881  		// when we set info for the first time, ensuring
   882  		// that params and info are mutually exclusive.
   883  		var unsetParams bool
   884  		if params, ok := fs.Params(); ok {
   885  			info.Pool = params.Pool
   886  			unsetParams = true
   887  		} else {
   888  			// Ensure immutable properties do not change.
   889  			oldInfo, err := fs.Info()
   890  			if err != nil {
   891  				return nil, err
   892  			}
   893  			if err := validateFilesystemInfoChange(info, oldInfo); err != nil {
   894  				return nil, err
   895  			}
   896  		}
   897  		ops := setFilesystemInfoOps(tag, info, unsetParams)
   898  		return ops, nil
   899  	}
   900  	return st.run(buildTxn)
   901  }
   902  
   903  func validateFilesystemInfoChange(newInfo, oldInfo FilesystemInfo) error {
   904  	if newInfo.Pool != oldInfo.Pool {
   905  		return errors.Errorf(
   906  			"cannot change pool from %q to %q",
   907  			oldInfo.Pool, newInfo.Pool,
   908  		)
   909  	}
   910  	if newInfo.FilesystemId != oldInfo.FilesystemId {
   911  		return errors.Errorf(
   912  			"cannot change filesystem ID from %q to %q",
   913  			oldInfo.FilesystemId, newInfo.FilesystemId,
   914  		)
   915  	}
   916  	return nil
   917  }
   918  
   919  func setFilesystemInfoOps(tag names.FilesystemTag, info FilesystemInfo, unsetParams bool) []txn.Op {
   920  	asserts := isAliveDoc
   921  	update := bson.D{
   922  		{"$set", bson.D{{"info", &info}}},
   923  	}
   924  	if unsetParams {
   925  		asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}})
   926  		asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}})
   927  		update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}})
   928  	}
   929  	return []txn.Op{{
   930  		C:      filesystemsC,
   931  		Id:     tag.Id(),
   932  		Assert: asserts,
   933  		Update: update,
   934  	}}
   935  }
   936  
   937  // SetFilesystemAttachmentInfo sets the FilesystemAttachmentInfo for the
   938  // specified filesystem attachment.
   939  func (st *State) SetFilesystemAttachmentInfo(
   940  	machineTag names.MachineTag,
   941  	filesystemTag names.FilesystemTag,
   942  	info FilesystemAttachmentInfo,
   943  ) (err error) {
   944  	defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem attachment %s:%s", filesystemTag.Id(), machineTag.Id())
   945  	f, err := st.Filesystem(filesystemTag)
   946  	if err != nil {
   947  		return errors.Trace(err)
   948  	}
   949  	// Ensure filesystem is provisioned before setting attachment info.
   950  	// A filesystem cannot go from being provisioned to unprovisioned,
   951  	// so there is no txn.Op for this below.
   952  	if _, err := f.Info(); err != nil {
   953  		return errors.Trace(err)
   954  	}
   955  	// Also ensure the machine is provisioned.
   956  	m, err := st.Machine(machineTag.Id())
   957  	if err != nil {
   958  		return errors.Trace(err)
   959  	}
   960  	if _, err := m.InstanceId(); err != nil {
   961  		return errors.Trace(err)
   962  	}
   963  	buildTxn := func(attempt int) ([]txn.Op, error) {
   964  		fsa, err := st.FilesystemAttachment(machineTag, filesystemTag)
   965  		if err != nil {
   966  			return nil, errors.Trace(err)
   967  		}
   968  		// If the filesystem attachment has parameters, unset them
   969  		// when we set info for the first time, ensuring that params
   970  		// and info are mutually exclusive.
   971  		_, unsetParams := fsa.Params()
   972  		ops := setFilesystemAttachmentInfoOps(machineTag, filesystemTag, info, unsetParams)
   973  		return ops, nil
   974  	}
   975  	return st.run(buildTxn)
   976  }
   977  
   978  func setFilesystemAttachmentInfoOps(
   979  	machine names.MachineTag,
   980  	filesystem names.FilesystemTag,
   981  	info FilesystemAttachmentInfo,
   982  	unsetParams bool,
   983  ) []txn.Op {
   984  	asserts := isAliveDoc
   985  	update := bson.D{
   986  		{"$set", bson.D{{"info", &info}}},
   987  	}
   988  	if unsetParams {
   989  		asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}})
   990  		asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}})
   991  		update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}})
   992  	}
   993  	return []txn.Op{{
   994  		C:      filesystemAttachmentsC,
   995  		Id:     filesystemAttachmentId(machine.Id(), filesystem.Id()),
   996  		Assert: asserts,
   997  		Update: update,
   998  	}}
   999  }
  1000  
  1001  // filesystemMountPoint returns a mount point to use for the given charm
  1002  // storage. For stores with potentially multiple instances, the instance
  1003  // name is appended to the location.
  1004  func filesystemMountPoint(
  1005  	meta charm.Storage,
  1006  	tag names.StorageTag,
  1007  	series string,
  1008  ) (string, error) {
  1009  	storageDir, err := paths.StorageDir(series)
  1010  	if err != nil {
  1011  		return "", errors.Trace(err)
  1012  	}
  1013  	if strings.HasPrefix(meta.Location, storageDir) {
  1014  		return "", errors.Errorf(
  1015  			"invalid location %q: must not fall within %q",
  1016  			meta.Location, storageDir,
  1017  		)
  1018  	}
  1019  	if meta.Location != "" && meta.CountMax == 1 {
  1020  		// The location is specified and it's a singleton
  1021  		// store, so just use the location as-is.
  1022  		return meta.Location, nil
  1023  	}
  1024  	// If the location is unspecified then we use
  1025  	// <storage-dir>/<storage-id> as the location.
  1026  	// Otherwise, we use <location>/<storage-id>.
  1027  	if meta.Location != "" {
  1028  		storageDir = meta.Location
  1029  	}
  1030  	return path.Join(storageDir, tag.Id()), nil
  1031  }
  1032  
  1033  // validateFilesystemMountPoints validates the mount points of filesystems
  1034  // being attached to the specified machine. If there are any mount point
  1035  // path conflicts, an error will be returned.
  1036  func validateFilesystemMountPoints(m *Machine, newFilesystems []filesystemAttachmentTemplate) error {
  1037  	attachments, err := m.st.MachineFilesystemAttachments(m.MachineTag())
  1038  	if err != nil {
  1039  		return errors.Trace(err)
  1040  	}
  1041  	existing := make(map[names.FilesystemTag]string)
  1042  	for _, a := range attachments {
  1043  		params, ok := a.Params()
  1044  		if ok {
  1045  			existing[a.Filesystem()] = params.Location
  1046  			continue
  1047  		}
  1048  		info, err := a.Info()
  1049  		if err != nil {
  1050  			return errors.Trace(err)
  1051  		}
  1052  		existing[a.Filesystem()] = info.MountPoint
  1053  	}
  1054  
  1055  	storageName := func(
  1056  		filesystemTag names.FilesystemTag,
  1057  		storageTag names.StorageTag,
  1058  	) string {
  1059  		if storageTag == (names.StorageTag{}) {
  1060  			return names.ReadableString(filesystemTag)
  1061  		}
  1062  		// We know the tag is valid, so ignore the error.
  1063  		storageName, _ := names.StorageName(storageTag.Id())
  1064  		return fmt.Sprintf("%q storage", storageName)
  1065  	}
  1066  
  1067  	containsPath := func(a, b string) bool {
  1068  		a = path.Clean(a) + "/"
  1069  		b = path.Clean(b) + "/"
  1070  		return strings.HasPrefix(b, a)
  1071  	}
  1072  
  1073  	// These sets are expected to be small, so sorting and comparing
  1074  	// adjacent values is not worth the cost of creating a reverse
  1075  	// lookup from location to filesystem.
  1076  	for _, template := range newFilesystems {
  1077  		newMountPoint := template.params.Location
  1078  		for oldFilesystemTag, oldMountPoint := range existing {
  1079  			var conflicted, swapOrder bool
  1080  			if containsPath(oldMountPoint, newMountPoint) {
  1081  				conflicted = true
  1082  			} else if containsPath(newMountPoint, oldMountPoint) {
  1083  				conflicted = true
  1084  				swapOrder = true
  1085  			}
  1086  			if !conflicted {
  1087  				continue
  1088  			}
  1089  
  1090  			// Get a helpful identifier for the new filesystem. If it
  1091  			// is being created for a storage instance, then use
  1092  			// the storage name; otherwise use the filesystem name.
  1093  			newStorageName := storageName(template.tag, template.storage)
  1094  
  1095  			// Likewise for the old filesystem, but this time we'll
  1096  			// need to consult state.
  1097  			oldFilesystem, err := m.st.Filesystem(oldFilesystemTag)
  1098  			if err != nil {
  1099  				return errors.Trace(err)
  1100  			}
  1101  			storageTag, err := oldFilesystem.Storage()
  1102  			if errors.IsNotAssigned(err) {
  1103  				storageTag = names.StorageTag{}
  1104  			} else if err != nil {
  1105  				return errors.Trace(err)
  1106  			}
  1107  			oldStorageName := storageName(oldFilesystemTag, storageTag)
  1108  
  1109  			lhs := fmt.Sprintf("mount point %q for %s", oldMountPoint, oldStorageName)
  1110  			rhs := fmt.Sprintf("mount point %q for %s", newMountPoint, newStorageName)
  1111  			if swapOrder {
  1112  				lhs, rhs = rhs, lhs
  1113  			}
  1114  			return errors.Errorf("%s contains %s", lhs, rhs)
  1115  		}
  1116  	}
  1117  	return nil
  1118  }
  1119  
  1120  // AllFilesystems returns all Filesystems for this state.
  1121  func (st *State) AllFilesystems() ([]Filesystem, error) {
  1122  	filesystems, err := st.filesystems(nil)
  1123  	if err != nil {
  1124  		return nil, errors.Annotate(err, "cannot get filesystems")
  1125  	}
  1126  	return filesystemsToInterfaces(filesystems), nil
  1127  }
  1128  
  1129  func filesystemsToInterfaces(fs []*filesystem) []Filesystem {
  1130  	result := make([]Filesystem, len(fs))
  1131  	for i, f := range fs {
  1132  		result[i] = f
  1133  	}
  1134  	return result
  1135  }
  1136  
  1137  func filesystemGlobalKey(name string) string {
  1138  	return "f#" + name
  1139  }
  1140  
  1141  // FilesystemStatus returns the status of the specified filesystem.
  1142  func (st *State) FilesystemStatus(tag names.FilesystemTag) (status.StatusInfo, error) {
  1143  	return getStatus(st, filesystemGlobalKey(tag.Id()), "filesystem")
  1144  }
  1145  
  1146  // SetFilesystemStatus sets the status of the specified filesystem.
  1147  func (st *State) SetFilesystemStatus(tag names.FilesystemTag, fsStatus status.Status, info string, data map[string]interface{}, updated *time.Time) error {
  1148  	switch fsStatus {
  1149  	case status.Attaching, status.Attached, status.Detaching, status.Detached, status.Destroying:
  1150  	case status.Error:
  1151  		if info == "" {
  1152  			return errors.Errorf("cannot set status %q without info", fsStatus)
  1153  		}
  1154  	case status.Pending:
  1155  		// If a filesystem is not yet provisioned, we allow its status
  1156  		// to be set back to pending (when a retry is to occur).
  1157  		v, err := st.Filesystem(tag)
  1158  		if err != nil {
  1159  			return errors.Trace(err)
  1160  		}
  1161  		_, err = v.Info()
  1162  		if errors.IsNotProvisioned(err) {
  1163  			break
  1164  		}
  1165  		return errors.Errorf("cannot set status %q", fsStatus)
  1166  	default:
  1167  		return errors.Errorf("cannot set invalid status %q", fsStatus)
  1168  	}
  1169  	return setStatus(st, setStatusParams{
  1170  		badge:     "filesystem",
  1171  		globalKey: filesystemGlobalKey(tag.Id()),
  1172  		status:    fsStatus,
  1173  		message:   info,
  1174  		rawData:   data,
  1175  		updated:   updated,
  1176  	})
  1177  }