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