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