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: ¶ms, 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 }