github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"regexp"
    10  	"strings"
    11  
    12  	"github.com/juju/charm/v12"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/mgo/v3"
    15  	"github.com/juju/mgo/v3/bson"
    16  	"github.com/juju/mgo/v3/txn"
    17  	"github.com/juju/names/v5"
    18  	jujutxn "github.com/juju/txn/v3"
    19  
    20  	"github.com/juju/juju/core/paths"
    21  	"github.com/juju/juju/core/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  	Lifer
    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  	// Detachable reports whether or not the filesystem is detachable.
    65  	Detachable() bool
    66  
    67  	// Releasing reports whether or not the filesystem is to be released
    68  	// from the model when it is Dying/Dead.
    69  	Releasing() bool
    70  }
    71  
    72  // FilesystemAttachment describes an attachment of a filesystem to a machine.
    73  type FilesystemAttachment interface {
    74  	Lifer
    75  
    76  	// Filesystem returns the tag of the related Filesystem.
    77  	Filesystem() names.FilesystemTag
    78  
    79  	// Host returns the tag of the entity to which this attachment belongs.
    80  	Host() names.Tag
    81  
    82  	// Info returns the filesystem attachment's FilesystemAttachmentInfo, or a
    83  	// NotProvisioned error if the attachment has not yet been made.
    84  	//
    85  	// Note that the presence of FilesystemAttachmentInfo does not necessarily
    86  	// imply that the filesystem is mounted; model storage providers may
    87  	// need to prepare a filesystem for attachment to a machine before it can
    88  	// be mounted.
    89  	Info() (FilesystemAttachmentInfo, error)
    90  
    91  	// Params returns the parameters for creating the filesystem attachment,
    92  	// if it has not already been made. Params returns true if the returned
    93  	// parameters are usable for creating an attachment, otherwise false.
    94  	Params() (FilesystemAttachmentParams, bool)
    95  }
    96  
    97  type filesystem struct {
    98  	mb  modelBackend
    99  	doc filesystemDoc
   100  }
   101  
   102  type filesystemAttachment struct {
   103  	doc filesystemAttachmentDoc
   104  }
   105  
   106  // filesystemDoc records information about a filesystem in the model.
   107  type filesystemDoc struct {
   108  	DocID           string            `bson:"_id"`
   109  	FilesystemId    string            `bson:"filesystemid"`
   110  	ModelUUID       string            `bson:"model-uuid"`
   111  	Life            Life              `bson:"life"`
   112  	Releasing       bool              `bson:"releasing,omitempty"`
   113  	StorageId       string            `bson:"storageid,omitempty"`
   114  	VolumeId        string            `bson:"volumeid,omitempty"`
   115  	AttachmentCount int               `bson:"attachmentcount"`
   116  	Info            *FilesystemInfo   `bson:"info,omitempty"`
   117  	Params          *FilesystemParams `bson:"params,omitempty"`
   118  
   119  	// HostId is the ID of the host that a non-detachable
   120  	// volume is initially attached to. We use this to identify
   121  	// the filesystem as being non-detachable, and to determine
   122  	// which filesystems must be removed along with said machine.
   123  	HostId string `bson:"hostid,omitempty"`
   124  }
   125  
   126  // filesystemAttachmentDoc records information about a filesystem attachment.
   127  type filesystemAttachmentDoc struct {
   128  	// DocID is the machine global key followed by the filesystem name.
   129  	DocID      string `bson:"_id"`
   130  	ModelUUID  string `bson:"model-uuid"`
   131  	Filesystem string `bson:"filesystemid"`
   132  
   133  	Host   string                      `bson:"hostid"`
   134  	Life   Life                        `bson:"life"`
   135  	Info   *FilesystemAttachmentInfo   `bson:"info,omitempty"`
   136  	Params *FilesystemAttachmentParams `bson:"params,omitempty"`
   137  }
   138  
   139  // FilesystemParams records parameters for provisioning a new filesystem.
   140  type FilesystemParams struct {
   141  	// storage, if non-zero, is the tag of the storage instance
   142  	// that the filesystem is to be assigned to.
   143  	storage names.StorageTag
   144  
   145  	// filesystemId, if non-empty, is the provider-allocated unique ID
   146  	// of the filesystem. This will be unspecified for filesystems backed
   147  	// by volumes. This is only set when creating a filesystem entity
   148  	// for an existing, non-volume backed, filesystem.
   149  	filesystemId string
   150  
   151  	// volumeInfo, if non-empty, is the information for an already
   152  	// provisioned backing volume. This is only set when creating a
   153  	// filesystem entity for an existing volume backed filesystem.
   154  	volumeInfo *VolumeInfo
   155  
   156  	Pool string `bson:"pool"`
   157  	Size uint64 `bson:"size"`
   158  }
   159  
   160  // FilesystemInfo describes information about a filesystem.
   161  type FilesystemInfo struct {
   162  	Size uint64 `bson:"size"`
   163  	Pool string `bson:"pool"`
   164  
   165  	// FilesystemId is the provider-allocated unique ID of the
   166  	// filesystem. This will be the string representation of
   167  	// the filesystem tag for filesystems backed by volumes.
   168  	FilesystemId string `bson:"filesystemid"`
   169  }
   170  
   171  // FilesystemAttachmentInfo describes information about a filesystem attachment.
   172  type FilesystemAttachmentInfo struct {
   173  	// MountPoint is the path at which the filesystem is mounted on the
   174  	// machine. MountPoint may be empty, meaning that the filesystem is
   175  	// not mounted yet.
   176  	MountPoint string `bson:"mountpoint"`
   177  	ReadOnly   bool   `bson:"read-only"`
   178  }
   179  
   180  // FilesystemAttachmentParams records parameters for attaching a filesystem to a
   181  // machine.
   182  type FilesystemAttachmentParams struct {
   183  	// locationAutoGenerated records whether or not the Location
   184  	// field's value was automatically generated, and thus known
   185  	// to be unique. This is used to optimise away mount point
   186  	// conflict checks.
   187  	locationAutoGenerated bool
   188  	Location              string `bson:"location"`
   189  	ReadOnly              bool   `bson:"read-only"`
   190  }
   191  
   192  // validate validates the contents of the filesystem document.
   193  func (f *filesystemDoc) validate() error {
   194  	return nil
   195  }
   196  
   197  // globalKey is required to implement GlobalEntity.
   198  func (f *filesystem) globalKey() string {
   199  	return filesystemGlobalKey(f.doc.FilesystemId)
   200  }
   201  
   202  // Tag is required to implement GlobalEntity.
   203  func (f *filesystem) Tag() names.Tag {
   204  	return f.FilesystemTag()
   205  }
   206  
   207  // FilesystemTag is required to implement Filesystem.
   208  func (f *filesystem) FilesystemTag() names.FilesystemTag {
   209  	return names.NewFilesystemTag(f.doc.FilesystemId)
   210  }
   211  
   212  // Life is required to implement Filesystem.
   213  func (f *filesystem) Life() Life {
   214  	return f.doc.Life
   215  }
   216  
   217  // Storage is required to implement Filesystem.
   218  func (f *filesystem) Storage() (names.StorageTag, error) {
   219  	if f.doc.StorageId == "" {
   220  		msg := fmt.Sprintf("filesystem %q is not assigned to any storage instance", f.Tag().Id())
   221  		return names.StorageTag{}, errors.NewNotAssigned(nil, msg)
   222  	}
   223  	return names.NewStorageTag(f.doc.StorageId), nil
   224  }
   225  
   226  // Volume is required to implement Filesystem.
   227  func (f *filesystem) Volume() (names.VolumeTag, error) {
   228  	if f.doc.VolumeId == "" {
   229  		return names.VolumeTag{}, ErrNoBackingVolume
   230  	}
   231  	return names.NewVolumeTag(f.doc.VolumeId), nil
   232  }
   233  
   234  // Info is required to implement Filesystem.
   235  func (f *filesystem) Info() (FilesystemInfo, error) {
   236  	if f.doc.Info == nil {
   237  		return FilesystemInfo{}, errors.NotProvisionedf("filesystem %q", f.doc.FilesystemId)
   238  	}
   239  	return *f.doc.Info, nil
   240  }
   241  
   242  // Params is required to implement Filesystem.
   243  func (f *filesystem) Params() (FilesystemParams, bool) {
   244  	if f.doc.Params == nil {
   245  		return FilesystemParams{}, false
   246  	}
   247  	return *f.doc.Params, true
   248  }
   249  
   250  // Releasing is required to implement Filesystem.
   251  func (f *filesystem) Releasing() bool {
   252  	return f.doc.Releasing
   253  }
   254  
   255  // Status is required to implement StatusGetter.
   256  func (f *filesystem) Status() (status.StatusInfo, error) {
   257  	return getStatus(f.mb.db(), filesystemGlobalKey(f.FilesystemTag().Id()), "filesystem")
   258  }
   259  
   260  // SetStatus is required to implement StatusSetter.
   261  func (f *filesystem) SetStatus(fsStatus status.StatusInfo) error {
   262  	switch fsStatus.Status {
   263  	case status.Attaching, status.Attached, status.Detaching, status.Detached, status.Destroying:
   264  	case status.Error:
   265  		if fsStatus.Message == "" {
   266  			return errors.Errorf("cannot set status %q without info", fsStatus.Status)
   267  		}
   268  	case status.Pending:
   269  		// If a filesystem is not yet provisioned, we allow its status
   270  		// to be set back to pending (when a retry is to occur).
   271  		// First refresh.
   272  		f, err := getFilesystemByTag(f.mb, f.FilesystemTag())
   273  		if err != nil {
   274  			return errors.Trace(err)
   275  		}
   276  		_, err = f.Info()
   277  		if errors.IsNotProvisioned(err) {
   278  			break
   279  		}
   280  		return errors.Errorf("cannot set status %q", fsStatus.Status)
   281  	default:
   282  		return errors.Errorf("cannot set invalid status %q", fsStatus.Status)
   283  	}
   284  	return setStatus(f.mb.db(), setStatusParams{
   285  		badge:     "filesystem",
   286  		globalKey: filesystemGlobalKey(f.FilesystemTag().Id()),
   287  		status:    fsStatus.Status,
   288  		message:   fsStatus.Message,
   289  		rawData:   fsStatus.Data,
   290  		updated:   timeOrNow(fsStatus.Since, f.mb.clock()),
   291  	})
   292  }
   293  
   294  // Filesystem is required to implement FilesystemAttachment.
   295  func (f *filesystemAttachment) Filesystem() names.FilesystemTag {
   296  	return names.NewFilesystemTag(f.doc.Filesystem)
   297  }
   298  
   299  func storageAttachmentHost(id string) names.Tag {
   300  	if names.IsValidUnit(id) {
   301  		return names.NewUnitTag(id)
   302  	}
   303  	return names.NewMachineTag(id)
   304  }
   305  
   306  // Host is required to implement FilesystemAttachment.
   307  func (f *filesystemAttachment) Host() names.Tag {
   308  	return storageAttachmentHost(f.doc.Host)
   309  }
   310  
   311  // Life is required to implement FilesystemAttachment.
   312  func (f *filesystemAttachment) Life() Life {
   313  	return f.doc.Life
   314  }
   315  
   316  // Info is required to implement FilesystemAttachment.
   317  func (f *filesystemAttachment) Info() (FilesystemAttachmentInfo, error) {
   318  	if f.doc.Info == nil {
   319  		hostTag := storageAttachmentHost(f.doc.Host)
   320  		return FilesystemAttachmentInfo{}, errors.NotProvisionedf(
   321  			"filesystem attachment %q on %q", f.doc.Filesystem, names.ReadableString(hostTag))
   322  	}
   323  	return *f.doc.Info, nil
   324  }
   325  
   326  // Params is required to implement FilesystemAttachment.
   327  func (f *filesystemAttachment) Params() (FilesystemAttachmentParams, bool) {
   328  	if f.doc.Params == nil {
   329  		return FilesystemAttachmentParams{}, false
   330  	}
   331  	return *f.doc.Params, true
   332  }
   333  
   334  // Filesystem returns the Filesystem with the specified name.
   335  func (sb *storageBackend) Filesystem(tag names.FilesystemTag) (Filesystem, error) {
   336  	f, err := getFilesystemByTag(sb.mb, tag)
   337  	return f, err
   338  }
   339  
   340  func getFilesystemByTag(mb modelBackend, tag names.FilesystemTag) (*filesystem, error) {
   341  	doc, err := getFilesystemDocByTag(mb.db(), tag)
   342  	if err != nil {
   343  		return nil, errors.Trace(err)
   344  	}
   345  	return &filesystem{mb, doc}, nil
   346  }
   347  
   348  func (sb *storageBackend) storageInstanceFilesystem(tag names.StorageTag) (*filesystem, error) {
   349  	query := bson.D{{"storageid", tag.Id()}}
   350  	description := fmt.Sprintf("filesystem for storage instance %q", tag.Id())
   351  	return sb.filesystem(query, description)
   352  }
   353  
   354  // StorageInstanceFilesystem returns the Filesystem assigned to the specified
   355  // storage instance.
   356  func (sb *storageBackend) StorageInstanceFilesystem(tag names.StorageTag) (Filesystem, error) {
   357  	f, err := sb.storageInstanceFilesystem(tag)
   358  	return f, err
   359  }
   360  
   361  func (sb *storageBackend) volumeFilesystem(tag names.VolumeTag) (*filesystem, error) {
   362  	query := bson.D{{"volumeid", tag.Id()}}
   363  	description := fmt.Sprintf("filesystem for volume %q", tag.Id())
   364  	return sb.filesystem(query, description)
   365  }
   366  
   367  // VolumeFilesystem returns the Filesystem backed by the specified volume.
   368  func (sb *storageBackend) VolumeFilesystem(tag names.VolumeTag) (Filesystem, error) {
   369  	f, err := sb.volumeFilesystem(tag)
   370  	return f, err
   371  }
   372  
   373  func (sb *storageBackend) filesystems(query interface{}) ([]*filesystem, error) {
   374  	fDocs, err := getFilesystemDocs(sb.mb.db(), query)
   375  	if err != nil {
   376  		return nil, errors.Trace(err)
   377  	}
   378  	filesystems := make([]*filesystem, len(fDocs))
   379  	for i, doc := range fDocs {
   380  		filesystems[i] = &filesystem{sb.mb, doc}
   381  	}
   382  	return filesystems, nil
   383  }
   384  
   385  func (sb *storageBackend) filesystem(query bson.D, description string) (*filesystem, error) {
   386  	doc, err := getFilesystemDoc(sb.mb.db(), query, description)
   387  	if err != nil {
   388  		return nil, errors.Trace(err)
   389  	}
   390  	return &filesystem{sb.mb, doc}, nil
   391  }
   392  
   393  func getFilesystemDocByTag(db Database, tag names.FilesystemTag) (filesystemDoc, error) {
   394  	query := bson.D{{"_id", tag.Id()}}
   395  	description := fmt.Sprintf("filesystem %q", tag.Id())
   396  	return getFilesystemDoc(db, query, description)
   397  }
   398  
   399  func getFilesystemDoc(db Database, query bson.D, description string) (filesystemDoc, error) {
   400  	coll, cleanup := db.GetCollection(filesystemsC)
   401  	defer cleanup()
   402  
   403  	var doc filesystemDoc
   404  	err := coll.Find(query).One(&doc)
   405  	if err == mgo.ErrNotFound {
   406  		return doc, errors.NotFoundf(description)
   407  	} else if err != nil {
   408  		return doc, errors.Annotate(err, "cannot get filesystem")
   409  	}
   410  	if err := doc.validate(); err != nil {
   411  		return doc, errors.Annotate(err, "validating filesystem")
   412  	}
   413  	return doc, nil
   414  }
   415  
   416  func getFilesystemDocs(db Database, query interface{}) ([]filesystemDoc, error) {
   417  	coll, cleanup := db.GetCollection(filesystemsC)
   418  	defer cleanup()
   419  
   420  	var docs []filesystemDoc
   421  	err := coll.Find(query).All(&docs)
   422  	if err != nil {
   423  		return nil, errors.Trace(err)
   424  	}
   425  	for _, doc := range docs {
   426  		if err := doc.validate(); err != nil {
   427  			return nil, errors.Annotate(err, "filesystem validation failed")
   428  		}
   429  	}
   430  	return docs, nil
   431  }
   432  
   433  // FilesystemAttachment returns the FilesystemAttachment corresponding to
   434  // the specified filesystem and machine.
   435  func (sb *storageBackend) FilesystemAttachment(host names.Tag, filesystem names.FilesystemTag) (FilesystemAttachment, error) {
   436  	coll, cleanup := sb.mb.db().GetCollection(filesystemAttachmentsC)
   437  	defer cleanup()
   438  
   439  	var att filesystemAttachment
   440  	err := coll.FindId(filesystemAttachmentId(host.Id(), filesystem.Id())).One(&att.doc)
   441  	if err == mgo.ErrNotFound {
   442  		return nil, errors.NotFoundf("filesystem %q on %q", filesystem.Id(), names.ReadableString(host))
   443  	} else if err != nil {
   444  		return nil, errors.Annotatef(err, "getting filesystem %q on %q", filesystem.Id(), names.ReadableString(host))
   445  	}
   446  	return &att, nil
   447  }
   448  
   449  // FilesystemAttachments returns all of the FilesystemAttachments for the
   450  // specified filesystem.
   451  func (sb *storageBackend) FilesystemAttachments(filesystem names.FilesystemTag) ([]FilesystemAttachment, error) {
   452  	attachments, err := sb.filesystemAttachments(bson.D{{"filesystemid", filesystem.Id()}})
   453  	if err != nil {
   454  		return nil, errors.Annotatef(err, "getting attachments for filesystem %q", filesystem.Id())
   455  	}
   456  	return attachments, nil
   457  }
   458  
   459  // MachineFilesystemAttachments returns all of the FilesystemAttachments for the
   460  // specified machine.
   461  func (sb *storageBackend) MachineFilesystemAttachments(machine names.MachineTag) ([]FilesystemAttachment, error) {
   462  	attachments, err := sb.filesystemAttachments(bson.D{{"hostid", machine.Id()}})
   463  	if err != nil {
   464  		return nil, errors.Annotatef(err, "getting filesystem attachments for %q", names.ReadableString(machine))
   465  	}
   466  	return attachments, nil
   467  }
   468  
   469  // UnitFilesystemAttachments returns all of the FilesystemAttachments for the
   470  // specified unit.
   471  func (sb *storageBackend) UnitFilesystemAttachments(unit names.UnitTag) ([]FilesystemAttachment, error) {
   472  	attachments, err := sb.filesystemAttachments(bson.D{{"hostid", unit.Id()}})
   473  	if err != nil {
   474  		return nil, errors.Annotatef(err, "getting filesystem attachments for %q", names.ReadableString(unit))
   475  	}
   476  	return attachments, nil
   477  }
   478  
   479  func (sb *storageBackend) filesystemAttachments(query bson.D) ([]FilesystemAttachment, error) {
   480  	coll, cleanup := sb.mb.db().GetCollection(filesystemAttachmentsC)
   481  	defer cleanup()
   482  
   483  	var docs []filesystemAttachmentDoc
   484  	err := coll.Find(query).All(&docs)
   485  	if err == mgo.ErrNotFound {
   486  		return nil, nil
   487  	} else if err != nil {
   488  		return nil, errors.Trace(err)
   489  	}
   490  	attachments := make([]FilesystemAttachment, len(docs))
   491  	for i, doc := range docs {
   492  		attachments[i] = &filesystemAttachment{doc}
   493  	}
   494  	return attachments, nil
   495  }
   496  
   497  // removeMachineFilesystemsOps returns txn.Ops to remove non-persistent filesystems
   498  // attached to the specified machine. This is used when the given machine is
   499  // being removed from state.
   500  func (sb *storageBackend) removeMachineFilesystemsOps(m *Machine) ([]txn.Op, error) {
   501  	// A machine cannot transition to Dead if it has any detachable storage
   502  	// attached, so any attachments are for machine-bound storage.
   503  	//
   504  	// Even if a filesystem is "non-detachable", there still exist filesystem
   505  	// attachments, and they may be removed independently of the filesystem.
   506  	// For example, the user may request that the filesystem be destroyed.
   507  	// This will cause the filesystem to become Dying, and the attachment
   508  	// to be Dying, then Dead, and finally removed. Only once the attachment
   509  	// is removed will the filesystem transition to Dead and then be removed.
   510  	// Therefore, there may be filesystems that are bound, but not attached,
   511  	// to the machine.
   512  	machineFilesystems, err := sb.filesystems(bson.D{{"hostid", m.Id()}})
   513  	if err != nil {
   514  		return nil, errors.Trace(err)
   515  	}
   516  	ops := make([]txn.Op, 0, 2*len(machineFilesystems)+len(m.doc.Filesystems))
   517  	for _, filesystemId := range m.doc.Filesystems {
   518  		ops = append(ops, txn.Op{
   519  			C:      filesystemAttachmentsC,
   520  			Id:     filesystemAttachmentId(m.Id(), filesystemId),
   521  			Assert: txn.DocExists,
   522  			Remove: true,
   523  		})
   524  	}
   525  	for _, f := range machineFilesystems {
   526  		if f.doc.StorageId != "" {
   527  			// The volume is assigned to a storage instance;
   528  			// make sure we also remove the storage instance.
   529  			// There should be no storage attachments remaining,
   530  			// as the units must have been removed before the
   531  			// machine can be; and the storage attachments must
   532  			// have been removed before the unit can be.
   533  			ops = append(ops,
   534  				txn.Op{
   535  					C:      storageInstancesC,
   536  					Id:     f.doc.StorageId,
   537  					Assert: txn.DocExists,
   538  					Remove: true,
   539  				},
   540  			)
   541  		}
   542  		fsOps, err := removeFilesystemOps(sb, f, f.doc.Releasing, false, nil)
   543  		if err != nil {
   544  			return nil, errors.Trace(err)
   545  		}
   546  		ops = append(ops, fsOps...)
   547  	}
   548  	return ops, nil
   549  }
   550  
   551  // isDetachableFilesystemTag reports whether or not the filesystem with the
   552  // specified tag is detachable.
   553  func isDetachableFilesystemTag(db Database, tag names.FilesystemTag) (bool, error) {
   554  	doc, err := getFilesystemDocByTag(db, tag)
   555  	if err != nil {
   556  		return false, errors.Trace(err)
   557  	}
   558  	return doc.HostId == "", nil
   559  }
   560  
   561  // Detachable reports whether or not the filesystem is detachable.
   562  func (f *filesystem) Detachable() bool {
   563  	return f.doc.HostId == ""
   564  }
   565  
   566  func (f *filesystem) pool() string {
   567  	if f.doc.Info != nil {
   568  		return f.doc.Info.Pool
   569  	}
   570  	return f.doc.Params.Pool
   571  }
   572  
   573  // isDetachableFilesystemPool reports whether or not the given
   574  // storage pool will create a filesystem that is not inherently
   575  // bound to a machine, and therefore can be detached.
   576  func isDetachableFilesystemPool(sb *storageBackend, pool string) (bool, error) {
   577  	_, provider, _, err := poolStorageProvider(sb, pool)
   578  	if err != nil {
   579  		return false, errors.Trace(err)
   580  	}
   581  	if provider.Scope() == storage.ScopeMachine {
   582  		// Any storage created by a machine cannot be detached from
   583  		// the machine, and must be destroyed along with it.
   584  		return false, nil
   585  	}
   586  	if !provider.Dynamic() {
   587  		// The storage provider only accommodates provisioning storage
   588  		// statically along with the machine. Such storage is bound
   589  		// to the machine.
   590  		return false, nil
   591  	}
   592  	return true, nil
   593  }
   594  
   595  // DetachFilesystem marks the filesystem attachment identified by the specified machine
   596  // and filesystem tags as Dying, if it is Alive. DetachFilesystem will fail for
   597  // inherently machine-bound filesystems.
   598  func (sb *storageBackend) DetachFilesystem(host names.Tag, filesystem names.FilesystemTag) (err error) {
   599  	defer errors.DeferredAnnotatef(&err, "detaching filesystem %s from %s", filesystem.Id(), names.ReadableString(host))
   600  	buildTxn := func(attempt int) ([]txn.Op, error) {
   601  		fsa, err := sb.FilesystemAttachment(host, filesystem)
   602  		if err != nil {
   603  			return nil, errors.Trace(err)
   604  		}
   605  		if fsa.Life() != Alive {
   606  			return nil, jujutxn.ErrNoOperations
   607  		}
   608  		detachable, err := isDetachableFilesystemTag(sb.mb.db(), filesystem)
   609  		if err != nil {
   610  			return nil, errors.Trace(err)
   611  		}
   612  		if !detachable {
   613  			return nil, errors.New("filesystem is not detachable")
   614  		}
   615  		ops := detachFilesystemOps(host, filesystem)
   616  		return ops, nil
   617  	}
   618  	return sb.mb.db().Run(buildTxn)
   619  }
   620  
   621  func (sb *storageBackend) filesystemVolumeAttachment(host names.Tag, f names.FilesystemTag) (VolumeAttachment, error) {
   622  	filesystem, err := getFilesystemByTag(sb.mb, f)
   623  	if err != nil {
   624  		return nil, errors.Trace(err)
   625  	}
   626  	v, err := filesystem.Volume()
   627  	if err != nil {
   628  		return nil, errors.Trace(err)
   629  	}
   630  	return sb.VolumeAttachment(host, v)
   631  }
   632  
   633  func detachFilesystemOps(host names.Tag, f names.FilesystemTag) []txn.Op {
   634  	return []txn.Op{{
   635  		C:      filesystemAttachmentsC,
   636  		Id:     filesystemAttachmentId(host.Id(), f.Id()),
   637  		Assert: isAliveDoc,
   638  		Update: bson.D{{"$set", bson.D{{"life", Dying}}}},
   639  	}}
   640  }
   641  
   642  // RemoveFilesystemAttachment removes the filesystem attachment from state.
   643  // Removing a volume-backed filesystem attachment will cause the volume to
   644  // be detached.
   645  func (sb *storageBackend) RemoveFilesystemAttachment(host names.Tag, filesystem names.FilesystemTag, force bool) (err error) {
   646  	defer errors.DeferredAnnotatef(&err, "removing attachment of filesystem %s from %s", filesystem.Id(), names.ReadableString(host))
   647  	buildTxn := func(attempt int) ([]txn.Op, error) {
   648  		attachment, err := sb.FilesystemAttachment(host, filesystem)
   649  		if errors.IsNotFound(err) && attempt > 0 {
   650  			// We only ignore IsNotFound on attempts after the
   651  			// first, since we expect the filesystem attachment to
   652  			// be there initially.
   653  			return nil, jujutxn.ErrNoOperations
   654  		}
   655  		if err != nil {
   656  			return nil, errors.Trace(err)
   657  		}
   658  		if attachment.Life() != Dying {
   659  			return nil, errors.New("filesystem attachment is not dying")
   660  		}
   661  		f, err := getFilesystemByTag(sb.mb, filesystem)
   662  		if err != nil {
   663  			return nil, errors.Trace(err)
   664  		}
   665  		ops, err := removeFilesystemAttachmentOps(sb, host, f, force)
   666  		if err != nil {
   667  			return nil, errors.Trace(err)
   668  		}
   669  		volumeAttachment, err := sb.filesystemVolumeAttachment(host, filesystem)
   670  		if err != nil {
   671  			if errors.Cause(err) != ErrNoBackingVolume && !errors.IsNotFound(err) {
   672  				return nil, errors.Trace(err)
   673  			}
   674  		} else {
   675  			// The filesystem is backed by a volume. Since the
   676  			// filesystem has been detached, we should now
   677  			// detach the volume as well if it is detachable.
   678  			// If the volume is not detachable, we'll just
   679  			// destroy it along with the filesystem.
   680  			volume := volumeAttachment.Volume()
   681  			v, err := sb.Volume(volume)
   682  			if err != nil {
   683  				return nil, errors.Trace(err)
   684  			}
   685  			if v.Detachable() {
   686  				plans, err := sb.machineVolumeAttachmentPlans(host, volume)
   687  				if err != nil {
   688  					return nil, errors.Trace(err)
   689  				}
   690  				// NOTE(gsamfira): if we're upgrading, we might not have plans set up,
   691  				// so we check if volume plans were created, and if not, just skip to
   692  				// detaching the actual disk
   693  				var volOps []txn.Op
   694  				if len(plans) == 0 {
   695  					volOps, err = sb.detachVolumeOps(host, volume, force)
   696  				} else {
   697  					volOps, err = sb.detachVolumeAttachmentPlanOps(host, volume, force)
   698  				}
   699  				if err != nil {
   700  					return nil, errors.Trace(err)
   701  				}
   702  				ops = append(ops, volOps...)
   703  			}
   704  		}
   705  		return ops, nil
   706  	}
   707  	return sb.mb.db().Run(buildTxn)
   708  }
   709  
   710  func removeFilesystemAttachmentOps(sb *storageBackend, host names.Tag, f *filesystem, force bool) ([]txn.Op, error) {
   711  	var ops []txn.Op
   712  	if f.doc.VolumeId != "" && f.doc.Life == Dying && f.doc.AttachmentCount == 1 {
   713  		// Volume-backed filesystems are removed immediately, instead
   714  		// of transitioning to Dead.
   715  		assert := bson.D{
   716  			{"life", Dying},
   717  			{"attachmentcount", 1},
   718  		}
   719  		removeFilesystemOps, err := removeFilesystemOps(sb, f, f.doc.Releasing, force, assert)
   720  		if err != nil {
   721  			return nil, errors.Trace(err)
   722  		}
   723  		ops = removeFilesystemOps
   724  	} else {
   725  		decrefFilesystemOp := machineStorageDecrefOp(
   726  			filesystemsC, f.doc.FilesystemId,
   727  			f.doc.AttachmentCount, f.doc.Life,
   728  		)
   729  		ops = []txn.Op{decrefFilesystemOp}
   730  	}
   731  	ops = append(ops, txn.Op{
   732  		C:      filesystemAttachmentsC,
   733  		Id:     filesystemAttachmentId(host.Id(), f.doc.FilesystemId),
   734  		Assert: bson.D{{"life", Dying}},
   735  		Remove: true,
   736  	})
   737  	if host.Kind() == names.MachineTagKind {
   738  		ops = append(ops, txn.Op{
   739  			C:      machinesC,
   740  			Id:     host.Id(),
   741  			Assert: txn.DocExists,
   742  			Update: bson.D{{"$pull", bson.D{{"filesystems", f.doc.FilesystemId}}}},
   743  		})
   744  	}
   745  	return ops, nil
   746  }
   747  
   748  // DestroyFilesystem ensures that the filesystem and any attachments to it will
   749  // be destroyed and removed from state at some point in the future.
   750  func (sb *storageBackend) DestroyFilesystem(tag names.FilesystemTag, force bool) (err error) {
   751  	defer errors.DeferredAnnotatef(&err, "destroying filesystem %s", tag.Id())
   752  	buildTxn := func(attempt int) ([]txn.Op, error) {
   753  		filesystem, err := getFilesystemByTag(sb.mb, tag)
   754  		if errors.IsNotFound(err) && attempt > 0 {
   755  			// On the first attempt, we expect it to exist.
   756  			return nil, jujutxn.ErrNoOperations
   757  		} else if err != nil {
   758  			return nil, errors.Trace(err)
   759  		}
   760  
   761  		if !force && filesystem.Life() != Alive || filesystem.Life() == Dead {
   762  			return nil, jujutxn.ErrNoOperations
   763  		}
   764  
   765  		// If we are not forcing the destruction of this file system,
   766  		// it must not be attached to a storage instance.
   767  		if filesystem.doc.StorageId != "" {
   768  			err := errors.Errorf("filesystem is assigned to %s",
   769  				names.ReadableString(names.NewStorageTag(filesystem.doc.StorageId)))
   770  			if !force {
   771  				return nil, err
   772  			}
   773  			logger.Warningf("%s", err.Error())
   774  		}
   775  
   776  		var assertNoStorageAssignment bson.D
   777  		if !force {
   778  			assertNoStorageAssignment = bson.D{{"$or", []bson.D{
   779  				{{"storageid", ""}},
   780  				{{"storageid", bson.D{{"$exists", false}}}},
   781  			}}}
   782  		}
   783  
   784  		ops, err := destroyFilesystemOps(sb, filesystem, false, force, assertNoStorageAssignment)
   785  		return ops, errors.Trace(err)
   786  	}
   787  
   788  	return errors.Trace(sb.mb.db().Run(buildTxn))
   789  }
   790  
   791  func destroyFilesystemOps(sb *storageBackend, f *filesystem, release, force bool, extraAssert bson.D) ([]txn.Op, error) {
   792  	lifeAssert := isAliveDoc
   793  	if force {
   794  		// Since we are force destroying, life assert should be current volume's life.
   795  		lifeAssert = bson.D{{"life", f.doc.Life}}
   796  	}
   797  	baseAssert := append(lifeAssert, extraAssert...)
   798  	setFields := bson.D{}
   799  	if release {
   800  		setFields = append(setFields, bson.DocElem{Name: "releasing", Value: true})
   801  	}
   802  	if f.doc.AttachmentCount == 0 {
   803  		hasNoAttachments := bson.D{{"attachmentcount", 0}}
   804  		assert := append(hasNoAttachments, baseAssert...)
   805  		if f.doc.VolumeId != "" {
   806  			// Filesystem is volume-backed, and since it has no
   807  			// attachments, it has no provisioner responsible
   808  			// for it. Removing the filesystem will destroy the
   809  			// backing volume, which effectively destroys the
   810  			// filesystem contents anyway.
   811  			return removeFilesystemOps(sb, f, release, force, assert)
   812  		}
   813  		// The filesystem is not volume-backed, so leave it to the
   814  		// storage provisioner to destroy it.
   815  		setFields = append(setFields, bson.DocElem{Name: "life", Value: Dead})
   816  		return []txn.Op{{
   817  			C:      filesystemsC,
   818  			Id:     f.doc.FilesystemId,
   819  			Assert: assert,
   820  			Update: bson.D{{"$set", setFields}},
   821  		}}, nil
   822  	}
   823  	hasAttachments := bson.D{{"attachmentcount", bson.D{{"$gt", 0}}}}
   824  	setFields = append(setFields, bson.DocElem{Name: "life", Value: Dying})
   825  	ops := []txn.Op{{
   826  		C:      filesystemsC,
   827  		Id:     f.doc.FilesystemId,
   828  		Assert: append(hasAttachments, baseAssert...),
   829  		Update: bson.D{{"$set", setFields}},
   830  	}}
   831  	if !f.Detachable() {
   832  		// This filesystem cannot be directly detached, so we do
   833  		// not issue a cleanup. Since there can (should!) be only
   834  		// one attachment for the lifetime of the filesystem, we
   835  		// can look it up and destroy it along with the filesystem.
   836  		attachments, err := sb.FilesystemAttachments(f.FilesystemTag())
   837  		if err != nil {
   838  			return nil, errors.Trace(err)
   839  		}
   840  		if len(attachments) != 1 {
   841  			return nil, errors.Errorf(
   842  				"expected 1 attachment, found %d",
   843  				len(attachments),
   844  			)
   845  		}
   846  		detachOps := detachFilesystemOps(
   847  			attachments[0].Host(),
   848  			f.FilesystemTag(),
   849  		)
   850  		ops = append(ops, detachOps...)
   851  	} else {
   852  		ops = append(ops, newCleanupOp(
   853  			cleanupAttachmentsForDyingFilesystem,
   854  			f.doc.FilesystemId,
   855  		))
   856  	}
   857  	return ops, nil
   858  }
   859  
   860  // RemoveFilesystem removes the filesystem from state. RemoveFilesystem will
   861  // fail if there are any attachments remaining, or if the filesystem is not
   862  // Dying. Removing a volume-backed filesystem will cause the volume to be
   863  // destroyed.
   864  func (sb *storageBackend) RemoveFilesystem(tag names.FilesystemTag) (err error) {
   865  	defer errors.DeferredAnnotatef(&err, "removing filesystem %s", tag.Id())
   866  	buildTxn := func(attempt int) ([]txn.Op, error) {
   867  		filesystem, err := getFilesystemByTag(sb.mb, tag)
   868  		if errors.IsNotFound(err) {
   869  			return nil, jujutxn.ErrNoOperations
   870  		} else if err != nil {
   871  			return nil, errors.Trace(err)
   872  		}
   873  		if filesystem.Life() != Dead {
   874  			return nil, errors.New("filesystem is not dead")
   875  		}
   876  		return removeFilesystemOps(sb, filesystem, false, false, isDeadDoc)
   877  	}
   878  	return sb.mb.db().Run(buildTxn)
   879  }
   880  
   881  func removeFilesystemOps(sb *storageBackend, filesystem Filesystem, release, force bool, assert interface{}) ([]txn.Op, error) {
   882  	ops := []txn.Op{
   883  		{
   884  			C:      filesystemsC,
   885  			Id:     filesystem.Tag().Id(),
   886  			Assert: assert,
   887  			Remove: true,
   888  		},
   889  		removeModelFilesystemRefOp(sb.mb, filesystem.Tag().Id()),
   890  		removeStatusOp(sb.mb, filesystem.globalKey()),
   891  	}
   892  	// If the filesystem is backed by a volume, the volume should
   893  	// be destroyed once the filesystem is removed. The volume must
   894  	// not be destroyed before the filesystem is removed.
   895  	volumeTag, err := filesystem.Volume()
   896  	if err == nil {
   897  		volume, err := getVolumeByTag(sb.mb, volumeTag)
   898  		if err != nil {
   899  			return nil, errors.Trace(err)
   900  		}
   901  		volOps, err := destroyVolumeOps(sb, volume, release, force, nil)
   902  		if err != nil {
   903  			return nil, errors.Trace(err)
   904  		}
   905  		ops = append(ops, volOps...)
   906  	} else if err != ErrNoBackingVolume {
   907  		return nil, errors.Trace(err)
   908  	}
   909  	return ops, nil
   910  }
   911  
   912  // AddExistingFilesystem imports an existing, already-provisioned
   913  // filesystem into the model. The model will start out with
   914  // the status "detached". The filesystem and associated backing
   915  // volume (if any) will be associated with the given storage
   916  // name, with the allocated storage tag being returned.
   917  func (sb *storageBackend) AddExistingFilesystem(
   918  	info FilesystemInfo,
   919  	backingVolume *VolumeInfo,
   920  	storageName string,
   921  ) (_ names.StorageTag, err error) {
   922  	defer errors.DeferredAnnotatef(&err, "cannot add existing filesystem")
   923  	if err := validateAddExistingFilesystem(sb, info, backingVolume, storageName); err != nil {
   924  		return names.StorageTag{}, errors.Trace(err)
   925  	}
   926  	storageId, err := newStorageInstanceId(sb.mb, storageName)
   927  	if err != nil {
   928  		return names.StorageTag{}, errors.Trace(err)
   929  	}
   930  	storageTag := names.NewStorageTag(storageId)
   931  	fsOps, _, volumeTag, err := sb.addFilesystemOps(
   932  		FilesystemParams{
   933  			Pool:         info.Pool,
   934  			Size:         info.Size,
   935  			filesystemId: info.FilesystemId,
   936  			volumeInfo:   backingVolume,
   937  			storage:      storageTag,
   938  		},
   939  		"", // no machine ID
   940  	)
   941  	if err != nil {
   942  		return names.StorageTag{}, errors.Trace(err)
   943  	}
   944  	if volumeTag != (names.VolumeTag{}) && backingVolume == nil {
   945  		return names.StorageTag{}, errors.Errorf("backing volume info missing")
   946  	}
   947  	ops := []txn.Op{{
   948  		C:      storageInstancesC,
   949  		Id:     storageId,
   950  		Assert: txn.DocMissing,
   951  		Insert: &storageInstanceDoc{
   952  			Id:          storageId,
   953  			Kind:        StorageKindFilesystem,
   954  			StorageName: storageName,
   955  			Constraints: storageInstanceConstraints{
   956  				Pool: info.Pool,
   957  				Size: info.Size,
   958  			},
   959  		},
   960  	}}
   961  	ops = append(ops, fsOps...)
   962  	if err := sb.mb.db().RunTransaction(ops); err != nil {
   963  		return names.StorageTag{}, errors.Trace(err)
   964  	}
   965  	return storageTag, nil
   966  }
   967  
   968  var storageNameRE = regexp.MustCompile(names.StorageNameSnippet)
   969  
   970  func validateAddExistingFilesystem(
   971  	sb *storageBackend,
   972  	info FilesystemInfo,
   973  	backingVolume *VolumeInfo,
   974  	storageName string,
   975  ) error {
   976  	if !storage.IsValidPoolName(info.Pool) {
   977  		return errors.NotValidf("pool name %q", info.Pool)
   978  	}
   979  	if !storageNameRE.MatchString(storageName) {
   980  		return errors.NotValidf("storage name %q", storageName)
   981  	}
   982  	if backingVolume == nil {
   983  		if info.FilesystemId == "" {
   984  			return errors.NotValidf("empty filesystem ID")
   985  		}
   986  	} else {
   987  		if info.FilesystemId != "" {
   988  			return errors.NotValidf("non-empty filesystem ID with backing volume")
   989  		}
   990  		if backingVolume.VolumeId == "" {
   991  			return errors.NotValidf("empty backing volume ID")
   992  		}
   993  		if backingVolume.Pool != info.Pool {
   994  			return errors.Errorf(
   995  				"volume pool %q does not match filesystem pool %q",
   996  				backingVolume.Pool, info.Pool,
   997  			)
   998  		}
   999  		if backingVolume.Size != info.Size {
  1000  			return errors.Errorf(
  1001  				"volume size %d does not match filesystem size %d",
  1002  				backingVolume.Size, info.Size,
  1003  			)
  1004  		}
  1005  	}
  1006  	_, provider, _, err := poolStorageProvider(sb, info.Pool)
  1007  	if err != nil {
  1008  		return errors.Trace(err)
  1009  	}
  1010  	if !provider.Supports(storage.StorageKindFilesystem) {
  1011  		if backingVolume == nil {
  1012  			return errors.New("backing volume info missing")
  1013  		}
  1014  	} else {
  1015  		if backingVolume != nil {
  1016  			return errors.New("unexpected volume info")
  1017  		}
  1018  	}
  1019  	return nil
  1020  }
  1021  
  1022  // filesystemAttachmentId returns a filesystem attachment document ID,
  1023  // given the corresponding filesystem name and machine ID.
  1024  func filesystemAttachmentId(hostId, filesystemId string) string {
  1025  	return fmt.Sprintf("%s:%s", hostId, filesystemId)
  1026  }
  1027  
  1028  // ParseFilesystemAttachmentId parses a string as a filesystem attachment ID,
  1029  // returning the host and filesystem components.
  1030  func ParseFilesystemAttachmentId(id string) (names.Tag, names.FilesystemTag, error) {
  1031  	fields := strings.SplitN(id, ":", 2)
  1032  	isValidHost := names.IsValidMachine(fields[0]) || names.IsValidUnit(fields[0])
  1033  	if len(fields) != 2 || !isValidHost || !names.IsValidFilesystem(fields[1]) {
  1034  		return names.MachineTag{}, names.FilesystemTag{}, errors.Errorf("invalid filesystem attachment ID %q", id)
  1035  	}
  1036  	var hostTag names.Tag
  1037  	if names.IsValidMachine(fields[0]) {
  1038  		hostTag = names.NewMachineTag(fields[0])
  1039  	} else {
  1040  		hostTag = names.NewUnitTag(fields[0])
  1041  	}
  1042  	filesystemTag := names.NewFilesystemTag(fields[1])
  1043  	return hostTag, filesystemTag, nil
  1044  }
  1045  
  1046  // newFilesystemId returns a unique filesystem ID.
  1047  // If the host ID supplied is non-empty, the
  1048  // filesystem ID will incorporate it as the
  1049  // filesystem's machine scope.
  1050  func newFilesystemId(mb modelBackend, hostId string) (string, error) {
  1051  	seq, err := sequence(mb, "filesystem")
  1052  	if err != nil {
  1053  		return "", errors.Trace(err)
  1054  	}
  1055  	id := fmt.Sprint(seq)
  1056  	if hostId != "" {
  1057  		id = hostId + "/" + id
  1058  	}
  1059  	return id, nil
  1060  }
  1061  
  1062  // addFilesystemOps returns txn.Ops to create a new filesystem with the
  1063  // specified parameters. If the storage source cannot create filesystems
  1064  // directly, a volume will be created and Juju will manage a filesystem
  1065  // on it.
  1066  func (sb *storageBackend) addFilesystemOps(params FilesystemParams, hostId string) ([]txn.Op, names.FilesystemTag, names.VolumeTag, error) {
  1067  	var err error
  1068  	params, err = sb.filesystemParamsWithDefaults(params)
  1069  	if err != nil {
  1070  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err)
  1071  	}
  1072  	detachable, err := isDetachableFilesystemPool(sb, params.Pool)
  1073  	if err != nil {
  1074  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err)
  1075  	}
  1076  	origHostId := hostId
  1077  	hostId, err = sb.validateFilesystemParams(params, hostId)
  1078  	if err != nil {
  1079  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "validating filesystem params")
  1080  	}
  1081  
  1082  	filesystemId, err := newFilesystemId(sb.mb, hostId)
  1083  	if err != nil {
  1084  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "cannot generate filesystem name")
  1085  	}
  1086  	filesystemTag := names.NewFilesystemTag(filesystemId)
  1087  
  1088  	// Check if the filesystem needs a volume.
  1089  	var volumeId string
  1090  	var volumeTag names.VolumeTag
  1091  	var ops []txn.Op
  1092  	_, provider, _, err := poolStorageProvider(sb, params.Pool)
  1093  	if err != nil {
  1094  		return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err)
  1095  	}
  1096  	if !provider.Supports(storage.StorageKindFilesystem) {
  1097  		var volumeOps []txn.Op
  1098  		if params.volumeInfo != nil {
  1099  			// The filesystem ID for volume-backed filesystems
  1100  			// is the string representation of the filesystem tag.
  1101  			params.filesystemId = filesystemTag.String()
  1102  		}
  1103  		volumeParams := VolumeParams{
  1104  			params.storage,
  1105  			params.volumeInfo,
  1106  			params.Pool,
  1107  			params.Size,
  1108  		}
  1109  		volumeOps, volumeTag, err = sb.addVolumeOps(volumeParams, hostId)
  1110  		if err != nil {
  1111  			return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "creating backing volume")
  1112  		}
  1113  		volumeId = volumeTag.Id()
  1114  		ops = append(ops, volumeOps...)
  1115  	}
  1116  
  1117  	statusDoc := statusDoc{
  1118  		Status:  status.Pending,
  1119  		Updated: sb.mb.clock().Now().UnixNano(),
  1120  	}
  1121  	doc := filesystemDoc{
  1122  		FilesystemId: filesystemId,
  1123  		VolumeId:     volumeId,
  1124  		StorageId:    params.storage.Id(),
  1125  	}
  1126  	if params.filesystemId != "" {
  1127  		// We're importing an already provisioned filesystem into the
  1128  		// model. Set provisioned info rather than params, and set the
  1129  		// status to "detached".
  1130  		statusDoc.Status = status.Detached
  1131  		doc.Info = &FilesystemInfo{
  1132  			Size:         params.Size,
  1133  			Pool:         params.Pool,
  1134  			FilesystemId: params.filesystemId,
  1135  		}
  1136  	} else {
  1137  		// Every new filesystem is created with one attachment.
  1138  		doc.Params = &params
  1139  		doc.AttachmentCount = 1
  1140  	}
  1141  	if !detachable {
  1142  		doc.HostId = origHostId
  1143  	}
  1144  	ops = append(ops, sb.newFilesystemOps(doc, statusDoc)...)
  1145  	return ops, filesystemTag, volumeTag, nil
  1146  }
  1147  
  1148  func (sb *storageBackend) newFilesystemOps(doc filesystemDoc, status statusDoc) []txn.Op {
  1149  	return []txn.Op{
  1150  		createStatusOp(sb.mb, filesystemGlobalKey(doc.FilesystemId), status),
  1151  		{
  1152  			C:      filesystemsC,
  1153  			Id:     doc.FilesystemId,
  1154  			Assert: txn.DocMissing,
  1155  			Insert: &doc,
  1156  		},
  1157  		addModelFilesystemRefOp(sb.mb, doc.FilesystemId),
  1158  	}
  1159  }
  1160  
  1161  func (sb *storageBackend) filesystemParamsWithDefaults(params FilesystemParams) (FilesystemParams, error) {
  1162  	if params.Pool == "" {
  1163  		modelConfig, err := sb.config()
  1164  		if err != nil {
  1165  			return FilesystemParams{}, errors.Trace(err)
  1166  		}
  1167  		cons := StorageConstraints{
  1168  			Pool:  params.Pool,
  1169  			Size:  params.Size,
  1170  			Count: 1,
  1171  		}
  1172  		poolName, err := defaultStoragePool(sb.modelType, modelConfig, storage.StorageKindFilesystem, cons)
  1173  		if err != nil {
  1174  			return FilesystemParams{}, errors.Annotate(err, "getting default filesystem storage pool")
  1175  		}
  1176  		params.Pool = poolName
  1177  	}
  1178  	return params, nil
  1179  }
  1180  
  1181  // validateFilesystemParams validates the filesystem parameters, and returns the
  1182  // machine ID to use as the scope in the filesystem tag.
  1183  func (sb *storageBackend) validateFilesystemParams(params FilesystemParams, machineId string) (maybeMachineId string, _ error) {
  1184  	err := validateStoragePool(sb, params.Pool, storage.StorageKindFilesystem, &machineId)
  1185  	if err != nil {
  1186  		return "", errors.Trace(err)
  1187  	}
  1188  	if params.Size == 0 {
  1189  		return "", errors.New("invalid size 0")
  1190  	}
  1191  	return machineId, nil
  1192  }
  1193  
  1194  type filesystemAttachmentTemplate struct {
  1195  	tag      names.FilesystemTag
  1196  	storage  names.StorageTag // may be zero-value
  1197  	params   FilesystemAttachmentParams
  1198  	existing bool
  1199  }
  1200  
  1201  // createMachineFilesystemAttachmentInfo creates filesystem
  1202  // attachments for the specified host, and attachment
  1203  // parameters keyed by filesystem tags.
  1204  func createMachineFilesystemAttachmentsOps(hostId string, attachments []filesystemAttachmentTemplate) []txn.Op {
  1205  	ops := make([]txn.Op, len(attachments))
  1206  	for i, attachment := range attachments {
  1207  		paramsCopy := attachment.params
  1208  		ops[i] = txn.Op{
  1209  			C:      filesystemAttachmentsC,
  1210  			Id:     filesystemAttachmentId(hostId, attachment.tag.Id()),
  1211  			Assert: txn.DocMissing,
  1212  			Insert: &filesystemAttachmentDoc{
  1213  				Filesystem: attachment.tag.Id(),
  1214  				Host:       hostId,
  1215  				Params:     &paramsCopy,
  1216  			},
  1217  		}
  1218  		if attachment.existing {
  1219  			ops = append(ops, txn.Op{
  1220  				C:      filesystemsC,
  1221  				Id:     attachment.tag.Id(),
  1222  				Assert: txn.DocExists,
  1223  				Update: bson.D{{"$inc", bson.D{{"attachmentcount", 1}}}},
  1224  			})
  1225  		}
  1226  	}
  1227  	return ops
  1228  }
  1229  
  1230  // SetFilesystemInfo sets the FilesystemInfo for the specified filesystem.
  1231  func (sb *storageBackend) SetFilesystemInfo(tag names.FilesystemTag, info FilesystemInfo) (err error) {
  1232  	defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem %q", tag.Id())
  1233  
  1234  	if info.FilesystemId == "" {
  1235  		return errors.New("filesystem ID not set")
  1236  	}
  1237  	fs, err := sb.Filesystem(tag)
  1238  	if err != nil {
  1239  		return errors.Trace(err)
  1240  	}
  1241  	// If the filesystem is volume-backed, the volume must be provisioned
  1242  	// and attached first.
  1243  	if volumeTag, err := fs.Volume(); err == nil {
  1244  		volumeAttachments, err := sb.VolumeAttachments(volumeTag)
  1245  		if err != nil {
  1246  			return errors.Trace(err)
  1247  		}
  1248  		var anyAttached bool
  1249  		for _, a := range volumeAttachments {
  1250  			if _, err := a.Info(); err == nil {
  1251  				anyAttached = true
  1252  			} else if !errors.IsNotProvisioned(err) {
  1253  				return err
  1254  			}
  1255  		}
  1256  		if !anyAttached {
  1257  			return errors.Errorf(
  1258  				"backing volume %q is not attached",
  1259  				volumeTag.Id(),
  1260  			)
  1261  		}
  1262  	} else if errors.Cause(err) != ErrNoBackingVolume {
  1263  		return errors.Trace(err)
  1264  	}
  1265  	buildTxn := func(attempt int) ([]txn.Op, error) {
  1266  		if attempt > 0 {
  1267  			fs, err = sb.Filesystem(tag)
  1268  			if err != nil {
  1269  				return nil, errors.Trace(err)
  1270  			}
  1271  		}
  1272  		// If the filesystem has parameters, unset them
  1273  		// when we set info for the first time, ensuring
  1274  		// that params and info are mutually exclusive.
  1275  		var unsetParams bool
  1276  		if params, ok := fs.Params(); ok {
  1277  			info.Pool = params.Pool
  1278  			unsetParams = true
  1279  		} else {
  1280  			// Ensure immutable properties do not change.
  1281  			oldInfo, err := fs.Info()
  1282  			if err != nil {
  1283  				return nil, err
  1284  			}
  1285  			if err := validateFilesystemInfoChange(info, oldInfo); err != nil {
  1286  				return nil, err
  1287  			}
  1288  		}
  1289  		ops := setFilesystemInfoOps(tag, info, unsetParams)
  1290  		return ops, nil
  1291  	}
  1292  	return sb.mb.db().Run(buildTxn)
  1293  }
  1294  
  1295  func validateFilesystemInfoChange(newInfo, oldInfo FilesystemInfo) error {
  1296  	if newInfo.Pool != oldInfo.Pool {
  1297  		return errors.Errorf(
  1298  			"cannot change pool from %q to %q",
  1299  			oldInfo.Pool, newInfo.Pool,
  1300  		)
  1301  	}
  1302  	if newInfo.FilesystemId != oldInfo.FilesystemId {
  1303  		return errors.Errorf(
  1304  			"cannot change filesystem ID from %q to %q",
  1305  			oldInfo.FilesystemId, newInfo.FilesystemId,
  1306  		)
  1307  	}
  1308  	return nil
  1309  }
  1310  
  1311  func setFilesystemInfoOps(tag names.FilesystemTag, info FilesystemInfo, unsetParams bool) []txn.Op {
  1312  	asserts := isAliveDoc
  1313  	update := bson.D{
  1314  		{"$set", bson.D{{"info", &info}}},
  1315  	}
  1316  	if unsetParams {
  1317  		asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}})
  1318  		asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}})
  1319  		update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}})
  1320  	}
  1321  	return []txn.Op{{
  1322  		C:      filesystemsC,
  1323  		Id:     tag.Id(),
  1324  		Assert: asserts,
  1325  		Update: update,
  1326  	}}
  1327  }
  1328  
  1329  // SetFilesystemAttachmentInfo sets the FilesystemAttachmentInfo for the
  1330  // specified filesystem attachment.
  1331  func (sb *storageBackend) SetFilesystemAttachmentInfo(
  1332  	hostTag names.Tag,
  1333  	filesystemTag names.FilesystemTag,
  1334  	info FilesystemAttachmentInfo,
  1335  ) (err error) {
  1336  	defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem attachment %s:%s", filesystemTag.Id(), hostTag.Id())
  1337  	f, err := sb.Filesystem(filesystemTag)
  1338  	if err != nil {
  1339  		return errors.Trace(err)
  1340  	}
  1341  	// Ensure filesystem is provisioned before setting attachment info.
  1342  	// A filesystem cannot go from being provisioned to unprovisioned,
  1343  	// so there is no txn.Op for this below.
  1344  	if _, err := f.Info(); err != nil {
  1345  		return errors.Trace(err)
  1346  	}
  1347  	// Also ensure the machine is provisioned.
  1348  	if _, ok := hostTag.(names.MachineTag); ok {
  1349  		m, err := sb.machine(hostTag.Id())
  1350  		if err != nil {
  1351  			return errors.Trace(err)
  1352  		}
  1353  		if _, err := m.InstanceId(); err != nil {
  1354  			return errors.Trace(err)
  1355  		}
  1356  	}
  1357  	buildTxn := func(attempt int) ([]txn.Op, error) {
  1358  		fsa, err := sb.FilesystemAttachment(hostTag, filesystemTag)
  1359  		if err != nil {
  1360  			return nil, errors.Trace(err)
  1361  		}
  1362  		// If the filesystem attachment has parameters, unset them
  1363  		// when we set info for the first time, ensuring that params
  1364  		// and info are mutually exclusive.
  1365  		_, unsetParams := fsa.Params()
  1366  		ops := setFilesystemAttachmentInfoOps(hostTag, filesystemTag, info, unsetParams)
  1367  		return ops, nil
  1368  	}
  1369  	return sb.mb.db().Run(buildTxn)
  1370  }
  1371  
  1372  func setFilesystemAttachmentInfoOps(
  1373  	host names.Tag,
  1374  	filesystem names.FilesystemTag,
  1375  	info FilesystemAttachmentInfo,
  1376  	unsetParams bool,
  1377  ) []txn.Op {
  1378  	asserts := isAliveDoc
  1379  	update := bson.D{
  1380  		{"$set", bson.D{{"info", &info}}},
  1381  	}
  1382  	if unsetParams {
  1383  		asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}})
  1384  		asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}})
  1385  		update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}})
  1386  	}
  1387  	return []txn.Op{{
  1388  		C:      filesystemAttachmentsC,
  1389  		Id:     filesystemAttachmentId(host.Id(), filesystem.Id()),
  1390  		Assert: asserts,
  1391  		Update: update,
  1392  	}}
  1393  }
  1394  
  1395  // FilesystemMountPoint returns a mount point to use for the given charm
  1396  // storage. For stores with potentially multiple instances, the instance
  1397  // name is appended to the location.
  1398  func FilesystemMountPoint(
  1399  	meta charm.Storage,
  1400  	tag names.StorageTag,
  1401  	osname string,
  1402  ) (string, error) {
  1403  	storageDir := paths.StorageDir(paths.OSType(osname))
  1404  	if strings.HasPrefix(meta.Location, storageDir) {
  1405  		return "", errors.Errorf(
  1406  			"invalid location %q: must not fall within %q",
  1407  			meta.Location, storageDir,
  1408  		)
  1409  	}
  1410  	if meta.Location != "" && meta.CountMax == 1 {
  1411  		// The location is specified and it's a singleton
  1412  		// store, so just use the location as-is.
  1413  		return meta.Location, nil
  1414  	}
  1415  	// If the location is unspecified then we use
  1416  	// <storage-dir>/<storage-id> as the location.
  1417  	// Otherwise, we use <location>/<storage-id>.
  1418  	if meta.Location != "" {
  1419  		storageDir = meta.Location
  1420  	}
  1421  	return path.Join(storageDir, tag.Id()), nil
  1422  }
  1423  
  1424  // validateFilesystemMountPoints validates the mount points of filesystems
  1425  // being attached to the specified machine. If there are any mount point
  1426  // path conflicts, an error will be returned.
  1427  func validateFilesystemMountPoints(m *Machine, newFilesystems []filesystemAttachmentTemplate) error {
  1428  	sb, err := NewStorageBackend(m.st)
  1429  	if err != nil {
  1430  		return errors.Trace(err)
  1431  	}
  1432  
  1433  	attachments, err := sb.MachineFilesystemAttachments(m.MachineTag())
  1434  	if err != nil {
  1435  		return errors.Trace(err)
  1436  	}
  1437  	existing := make(map[names.FilesystemTag]string)
  1438  	for _, a := range attachments {
  1439  		params, ok := a.Params()
  1440  		if ok {
  1441  			existing[a.Filesystem()] = params.Location
  1442  			continue
  1443  		}
  1444  		info, err := a.Info()
  1445  		if err != nil {
  1446  			return errors.Trace(err)
  1447  		}
  1448  		existing[a.Filesystem()] = info.MountPoint
  1449  	}
  1450  
  1451  	storageName := func(
  1452  		filesystemTag names.FilesystemTag,
  1453  		storageTag names.StorageTag,
  1454  	) string {
  1455  		if storageTag == (names.StorageTag{}) {
  1456  			return names.ReadableString(filesystemTag)
  1457  		}
  1458  		// We know the tag is valid, so ignore the error.
  1459  		storageName, _ := names.StorageName(storageTag.Id())
  1460  		return fmt.Sprintf("%q storage", storageName)
  1461  	}
  1462  
  1463  	containsPath := func(a, b string) bool {
  1464  		a = path.Clean(a) + "/"
  1465  		b = path.Clean(b) + "/"
  1466  		return strings.HasPrefix(b, a)
  1467  	}
  1468  
  1469  	// These sets are expected to be small, so sorting and comparing
  1470  	// adjacent values is not worth the cost of creating a reverse
  1471  	// lookup from location to filesystem.
  1472  	for _, template := range newFilesystems {
  1473  		newMountPoint := template.params.Location
  1474  		for oldFilesystemTag, oldMountPoint := range existing {
  1475  			var conflicted, swapOrder bool
  1476  			if containsPath(oldMountPoint, newMountPoint) {
  1477  				conflicted = true
  1478  			} else if containsPath(newMountPoint, oldMountPoint) {
  1479  				conflicted = true
  1480  				swapOrder = true
  1481  			}
  1482  			if !conflicted {
  1483  				continue
  1484  			}
  1485  
  1486  			// Get a helpful identifier for the new filesystem. If it
  1487  			// is being created for a storage instance, then use
  1488  			// the storage name; otherwise use the filesystem name.
  1489  			newStorageName := storageName(template.tag, template.storage)
  1490  
  1491  			// Likewise for the old filesystem, but this time we'll
  1492  			// need to consult state.
  1493  			oldFilesystem, err := sb.Filesystem(oldFilesystemTag)
  1494  			if err != nil {
  1495  				return errors.Trace(err)
  1496  			}
  1497  			storageTag, err := oldFilesystem.Storage()
  1498  			if errors.IsNotAssigned(err) {
  1499  				storageTag = names.StorageTag{}
  1500  			} else if err != nil {
  1501  				return errors.Trace(err)
  1502  			}
  1503  			oldStorageName := storageName(oldFilesystemTag, storageTag)
  1504  
  1505  			lhs := fmt.Sprintf("mount point %q for %s", oldMountPoint, oldStorageName)
  1506  			rhs := fmt.Sprintf("mount point %q for %s", newMountPoint, newStorageName)
  1507  			if swapOrder {
  1508  				lhs, rhs = rhs, lhs
  1509  			}
  1510  			return errors.Errorf("%s contains %s", lhs, rhs)
  1511  		}
  1512  	}
  1513  	return nil
  1514  }
  1515  
  1516  // AllFilesystems returns all Filesystems for this state.
  1517  func (sb *storageBackend) AllFilesystems() ([]Filesystem, error) {
  1518  	filesystems, err := sb.filesystems(nil)
  1519  	if err != nil {
  1520  		return nil, errors.Annotate(err, "cannot get filesystems")
  1521  	}
  1522  	return filesystemsToInterfaces(filesystems), nil
  1523  }
  1524  
  1525  func filesystemsToInterfaces(sb []*filesystem) []Filesystem {
  1526  	result := make([]Filesystem, len(sb))
  1527  	for i, f := range sb {
  1528  		result[i] = f
  1529  	}
  1530  	return result
  1531  }
  1532  
  1533  func filesystemGlobalKey(name string) string {
  1534  	return "f#" + name
  1535  }