github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/state/blockdevices.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  	"gopkg.in/mgo.v2"
    12  	"gopkg.in/mgo.v2/bson"
    13  	"gopkg.in/mgo.v2/txn"
    14  )
    15  
    16  // BlockDevice represents the state of a block device in the environment.
    17  type BlockDevice interface {
    18  	// Tag returns the tag for the block device.
    19  	Tag() names.Tag
    20  
    21  	// Name returns the unique name of the block device.
    22  	Name() string
    23  
    24  	// StorageInstance returns the ID of the storage instance that this
    25  	// block device is assigned to, if any, and a boolean indicating whether
    26  	// it is assigned to a store.
    27  	//
    28  	// A block device can be assigned to at most one store. It is possible
    29  	// for multiple block devices to be assigned to the same store, e.g.
    30  	// multi-attach volumes.
    31  	StorageInstance() (string, bool)
    32  
    33  	// Machine returns the ID of the machine the block device is attached to.
    34  	Machine() string
    35  
    36  	// Attached returns true if the block device is known to be attached to
    37  	// its associated machine.
    38  	Attached() bool
    39  
    40  	// Info returns the block device's BlockDeviceInfo, or a NotProvisioned
    41  	// error if the block device has not yet been provisioned.
    42  	Info() (BlockDeviceInfo, error)
    43  
    44  	// Params returns the parameters for provisioning the block device,
    45  	// if it has not already been provisioned. Params returns true if the
    46  	// returned parameters are usable for provisioning, otherwise false.
    47  	Params() (BlockDeviceParams, bool)
    48  }
    49  
    50  type blockDevice struct {
    51  	doc blockDeviceDoc
    52  }
    53  
    54  // blockDeviceDoc records information about a disk attached to a machine.
    55  type blockDeviceDoc struct {
    56  	DocID           string `bson:"_id"`
    57  	Name            string `bson:"name"`
    58  	EnvUUID         string `bson:"env-uuid"`
    59  	Machine         string `bson:"machineid"`
    60  	StorageInstance string `bson:"storageinstanceid,omitempty"`
    61  	// TODO(axw) Attached should be inferred from the presence
    62  	// of block device info discovered on the machine. We should
    63  	// be storing provider block devices separately from machine
    64  	// discovered ones.
    65  	Attached bool               `bson:"attached"`
    66  	Info     *BlockDeviceInfo   `bson:"info,omitempty"`
    67  	Params   *BlockDeviceParams `bson:"params,omitempty"`
    68  }
    69  
    70  // BlockDeviceParams records parameters for provisioning a new block device.
    71  type BlockDeviceParams struct {
    72  	// storageInstance, if non-empty, is the ID of the storage instance
    73  	// that the block device is to be assigned to.
    74  	storageInstance string
    75  
    76  	Size uint64 `bson:"size"`
    77  }
    78  
    79  // BlockDeviceInfo describes information about a block device.
    80  type BlockDeviceInfo struct {
    81  	DeviceName     string `bson:"devicename,omitempty"`
    82  	Label          string `bson:"label,omitempty"`
    83  	UUID           string `bson:"uuid,omitempty"`
    84  	Serial         string `bson:"serial,omitempty"`
    85  	Size           uint64 `bson:"size"`
    86  	FilesystemType string `bson:"fstype"`
    87  	InUse          bool   `bson:"inuse"`
    88  }
    89  
    90  func (b *blockDevice) Tag() names.Tag {
    91  	return names.NewDiskTag(b.doc.Name)
    92  }
    93  
    94  func (b *blockDevice) Name() string {
    95  	return b.doc.Name
    96  }
    97  
    98  func (b *blockDevice) StorageInstance() (string, bool) {
    99  	return b.doc.StorageInstance, b.doc.StorageInstance != ""
   100  }
   101  
   102  func (b *blockDevice) Machine() string {
   103  	return b.doc.Machine
   104  }
   105  
   106  func (b *blockDevice) Attached() bool {
   107  	return b.doc.Attached
   108  }
   109  
   110  func (b *blockDevice) Info() (BlockDeviceInfo, error) {
   111  	if b.doc.Info == nil {
   112  		return BlockDeviceInfo{}, errors.NotProvisionedf("block device %q", b.doc.Name)
   113  	}
   114  	return *b.doc.Info, nil
   115  }
   116  
   117  func (b *blockDevice) Params() (BlockDeviceParams, bool) {
   118  	if b.doc.Params == nil {
   119  		return BlockDeviceParams{}, false
   120  	}
   121  	return *b.doc.Params, true
   122  }
   123  
   124  // BlockDevice returns the BlockDevice with the specified name.
   125  func (st *State) BlockDevice(diskName string) (BlockDevice, error) {
   126  	blockDevices, cleanup := st.getCollection(blockDevicesC)
   127  	defer cleanup()
   128  
   129  	var d blockDevice
   130  	err := blockDevices.FindId(diskName).One(&d.doc)
   131  	if err == mgo.ErrNotFound {
   132  		return nil, errors.NotFoundf("block device %q", diskName)
   133  	} else if err != nil {
   134  		return nil, errors.Annotate(err, "cannot get block device details")
   135  	}
   136  	return &d, nil
   137  }
   138  
   139  // newDiskName returns a unique disk name.
   140  func newDiskName(st *State) (string, error) {
   141  	seq, err := st.sequence("disk")
   142  	if err != nil {
   143  		return "", errors.Trace(err)
   144  	}
   145  	return fmt.Sprint(seq), nil
   146  }
   147  
   148  // setMachineBlockDevices updates the blockdevices collection with the
   149  // currently attached block devices. Previously recorded block devices not in
   150  // the list will be removed.
   151  //
   152  // The Name field of each BlockDevice is ignored, if specified. Block devices
   153  // are matched according to their identifying attributes (device name, UUID,
   154  // etc.), and the existing Name will be retained.
   155  func setMachineBlockDevices(st *State, machineId string, newInfo []BlockDeviceInfo) error {
   156  	buildTxn := func(attempt int) ([]txn.Op, error) {
   157  		oldDevices, err := getMachineBlockDevices(st, machineId)
   158  		if err != nil && err != mgo.ErrNotFound {
   159  			return nil, errors.Trace(err)
   160  		}
   161  
   162  		ops := []txn.Op{{
   163  			C:      machinesC,
   164  			Id:     st.docID(machineId),
   165  			Assert: isAliveDoc,
   166  		}}
   167  
   168  		// Create ops to update and remove existing block devices.
   169  		found := make([]bool, len(newInfo))
   170  		for _, oldDev := range oldDevices {
   171  			oldInfo, err := oldDev.Info()
   172  			if err != nil && errors.IsNotProvisioned(err) {
   173  				// Leave unprovisioned block devices alone.
   174  				continue
   175  			} else if err != nil {
   176  				return nil, errors.Trace(err)
   177  			}
   178  			var updated bool
   179  			for j, newInfo := range newInfo {
   180  				if found[j] {
   181  					continue
   182  				}
   183  				if blockDevicesSame(oldInfo, newInfo) {
   184  					// Merge the two structures by replacing the old document's
   185  					// BlockDeviceInfo with the new one.
   186  					if oldInfo != newInfo || !oldDev.doc.Attached {
   187  						ops = append(ops, txn.Op{
   188  							C:      blockDevicesC,
   189  							Id:     oldDev.doc.DocID,
   190  							Assert: txn.DocExists,
   191  							Update: bson.D{{"$set", bson.D{
   192  								{"info", newInfo},
   193  								{"attached", true},
   194  							}}},
   195  						})
   196  					}
   197  					found[j] = true
   198  					updated = true
   199  					break
   200  				}
   201  			}
   202  			if !updated {
   203  				ops = append(ops, txn.Op{
   204  					C:      blockDevicesC,
   205  					Id:     oldDev.doc.DocID,
   206  					Assert: txn.DocExists,
   207  					Remove: true,
   208  				})
   209  			}
   210  		}
   211  
   212  		// Create ops to insert new block devices.
   213  		for i, info := range newInfo {
   214  			if found[i] {
   215  				continue
   216  			}
   217  			name, err := newDiskName(st)
   218  			if err != nil {
   219  				return nil, errors.Annotate(err, "cannot generate disk name")
   220  			}
   221  			infoCopy := info // copy for the insert
   222  			newDoc := blockDeviceDoc{
   223  				Name:     name,
   224  				Machine:  machineId,
   225  				EnvUUID:  st.EnvironUUID(),
   226  				DocID:    st.docID(name),
   227  				Attached: true,
   228  				Info:     &infoCopy,
   229  			}
   230  			ops = append(ops, txn.Op{
   231  				C:      blockDevicesC,
   232  				Id:     newDoc.DocID,
   233  				Assert: txn.DocMissing,
   234  				Insert: &newDoc,
   235  			})
   236  		}
   237  
   238  		return ops, nil
   239  	}
   240  	return st.run(buildTxn)
   241  }
   242  
   243  // getMachineBlockDevices returns all of the block devices associated with the
   244  // specified machine, including unprovisioned ones.
   245  func getMachineBlockDevices(st *State, machineId string) ([]*blockDevice, error) {
   246  	sel := bson.D{{"machineid", machineId}}
   247  	blockDevices, closer := st.getCollection(blockDevicesC)
   248  	defer closer()
   249  
   250  	var docs []blockDeviceDoc
   251  	err := blockDevices.Find(sel).All(&docs)
   252  	if err != nil {
   253  		return nil, errors.Trace(err)
   254  	}
   255  	devices := make([]*blockDevice, len(docs))
   256  	for i, doc := range docs {
   257  		devices[i] = &blockDevice{doc}
   258  	}
   259  	return devices, nil
   260  }
   261  
   262  func removeMachineBlockDevicesOps(st *State, machineId string) ([]txn.Op, error) {
   263  	sel := bson.D{{"machineid", machineId}}
   264  	blockDevices, closer := st.getCollection(blockDevicesC)
   265  	defer closer()
   266  
   267  	iter := blockDevices.Find(sel).Select(bson.D{{"_id", 1}}).Iter()
   268  	defer iter.Close()
   269  	var ops []txn.Op
   270  	var doc blockDeviceDoc
   271  	for iter.Next(&doc) {
   272  		ops = append(ops, txn.Op{
   273  			C:      blockDevicesC,
   274  			Id:     doc.DocID,
   275  			Remove: true,
   276  		})
   277  	}
   278  	return ops, errors.Trace(iter.Close())
   279  }
   280  
   281  // setProvisionedBlockDeviceInfo sets the initial info for newly
   282  // provisioned block devices. If non-empty, machineId must be the
   283  // machine ID associated with the block devices.
   284  func setProvisionedBlockDeviceInfo(st *State, machineId string, blockDevices map[string]BlockDeviceInfo) error {
   285  	ops := make([]txn.Op, 0, len(blockDevices))
   286  	for name, info := range blockDevices {
   287  		infoCopy := info
   288  		assert := bson.D{
   289  			{"info", bson.D{{"$exists", false}}},
   290  			{"params", bson.D{{"$exists", true}}},
   291  		}
   292  		if machineId != "" {
   293  			assert = append(assert, bson.DocElem{"machineid", machineId})
   294  		}
   295  		ops = append(ops, txn.Op{
   296  			C:      blockDevicesC,
   297  			Id:     name,
   298  			Assert: assert,
   299  			Update: bson.D{
   300  				{"$set", bson.D{{"info", &infoCopy}}},
   301  				{"$unset", bson.D{{"params", nil}}},
   302  			},
   303  		})
   304  	}
   305  	if err := st.runTransaction(ops); err != nil {
   306  		return errors.Errorf("cannot set provisioned block device info: already provisioned")
   307  	}
   308  	return nil
   309  }
   310  
   311  // createMachineBlockDeviceOps creates txn.Ops to create unprovisioned
   312  // block device documents associated with the specified machine, with
   313  // the given parameters.
   314  func createMachineBlockDeviceOps(st *State, machineId string, params ...BlockDeviceParams) (ops []txn.Op, names []string, err error) {
   315  	ops = make([]txn.Op, 0, len(params))
   316  	names = make([]string, len(params))
   317  	for i, params := range params {
   318  		params := params
   319  		name, err := newDiskName(st)
   320  		if err != nil {
   321  			return nil, nil, errors.Annotate(err, "cannot generate disk name")
   322  		}
   323  		names[i] = name
   324  		ops = append(ops, txn.Op{
   325  			C:      blockDevicesC,
   326  			Id:     name,
   327  			Assert: txn.DocMissing,
   328  			Insert: &blockDeviceDoc{
   329  				Name:            name,
   330  				StorageInstance: params.storageInstance,
   331  				Machine:         machineId,
   332  				Params:          &params,
   333  			},
   334  		})
   335  		// Add references to the storage instances.
   336  		if params.storageInstance != "" {
   337  			ops = append(ops, txn.Op{
   338  				C:      storageInstancesC,
   339  				Id:     params.storageInstance,
   340  				Assert: txn.DocExists,
   341  				Update: bson.D{
   342  					{"$push", bson.D{{"blockdevices", names[i]}}},
   343  				},
   344  			})
   345  		}
   346  	}
   347  	return ops, names, nil
   348  }
   349  
   350  // blockDevicesSame reports whether or not two BlockDevices identify the
   351  // same block device.
   352  //
   353  // In descending order of preference, we use: serial number, filesystem
   354  // UUID, device name.
   355  func blockDevicesSame(a, b BlockDeviceInfo) bool {
   356  	if a.Serial != "" && b.Serial != "" {
   357  		return a.Serial == b.Serial
   358  	}
   359  	if a.UUID != "" && b.UUID != "" {
   360  		return a.UUID == b.UUID
   361  	}
   362  	return a.DeviceName != "" && a.DeviceName == b.DeviceName
   363  }