github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 jujutxn "github.com/juju/txn" 14 "gopkg.in/mgo.v2" 15 "gopkg.in/mgo.v2/bson" 16 "gopkg.in/mgo.v2/txn" 17 18 "github.com/juju/juju/status" 19 "github.com/juju/juju/storage" 20 ) 21 22 // Volume describes a volume (disk, logical volume, etc.) in the model. 23 type Volume interface { 24 GlobalEntity 25 LifeBinder 26 status.StatusGetter 27 status.StatusSetter 28 29 // VolumeTag returns the tag for the volume. 30 VolumeTag() names.VolumeTag 31 32 // StorageInstance returns the tag of the storage instance that this 33 // volume is assigned to, if any. If the volume is not assigned to 34 // a storage instance, an error satisfying errors.IsNotAssigned will 35 // be returned. 36 // 37 // A volume can be assigned to at most one storage instance, and a 38 // storage instance can have at most one associated volume. 39 StorageInstance() (names.StorageTag, error) 40 41 // Info returns the volume's VolumeInfo, or a NotProvisioned 42 // error if the volume has not yet been provisioned. 43 Info() (VolumeInfo, error) 44 45 // Params returns the parameters for provisioning the volume, 46 // if it has not already been provisioned. Params returns true if the 47 // returned parameters are usable for provisioning, otherwise false. 48 Params() (VolumeParams, bool) 49 } 50 51 // VolumeAttachment describes an attachment of a volume to a machine. 52 type VolumeAttachment interface { 53 Lifer 54 55 // Volume returns the tag of the related Volume. 56 Volume() names.VolumeTag 57 58 // Machine returns the tag of the related Machine. 59 Machine() names.MachineTag 60 61 // Info returns the volume attachment's VolumeAttachmentInfo, or a 62 // NotProvisioned error if the attachment has not yet been made. 63 // 64 // TODO(axw) use a different error, rather than NotProvisioned 65 // (say, NotAttached or NotAssociated). 66 Info() (VolumeAttachmentInfo, error) 67 68 // Params returns the parameters for creating the volume attachment, 69 // if it has not already been made. Params returns true if the returned 70 // parameters are usable for creating an attachment, otherwise false. 71 Params() (VolumeAttachmentParams, bool) 72 } 73 74 type volume struct { 75 st *State 76 doc volumeDoc 77 } 78 79 type volumeAttachment struct { 80 doc volumeAttachmentDoc 81 } 82 83 // volumeDoc records information about a volume in the model. 84 type volumeDoc struct { 85 DocID string `bson:"_id"` 86 Name string `bson:"name"` 87 ModelUUID string `bson:"model-uuid"` 88 Life Life `bson:"life"` 89 StorageId string `bson:"storageid,omitempty"` 90 AttachmentCount int `bson:"attachmentcount"` 91 Binding string `bson:"binding,omitempty"` 92 Info *VolumeInfo `bson:"info,omitempty"` 93 Params *VolumeParams `bson:"params,omitempty"` 94 } 95 96 // volumeAttachmentDoc records information about a volume attachment. 97 type volumeAttachmentDoc struct { 98 // DocID is the machine global key followed by the volume name. 99 DocID string `bson:"_id"` 100 ModelUUID string `bson:"model-uuid"` 101 Volume string `bson:"volumeid"` 102 Machine string `bson:"machineid"` 103 Life Life `bson:"life"` 104 Info *VolumeAttachmentInfo `bson:"info,omitempty"` 105 Params *VolumeAttachmentParams `bson:"params,omitempty"` 106 } 107 108 // VolumeParams records parameters for provisioning a new volume. 109 type VolumeParams struct { 110 // storage, if non-zero, is the tag of the storage instance 111 // that the volume is to be assigned to. 112 storage names.StorageTag 113 114 // binding, if non-nil, is the tag of the entity to which 115 // the volume's lifecycle will be bound. 116 binding names.Tag 117 118 Pool string `bson:"pool"` 119 Size uint64 `bson:"size"` 120 } 121 122 // VolumeInfo describes information about a volume. 123 type VolumeInfo struct { 124 HardwareId string `bson:"hardwareid,omitempty"` 125 Size uint64 `bson:"size"` 126 Pool string `bson:"pool"` 127 VolumeId string `bson:"volumeid"` 128 Persistent bool `bson:"persistent"` 129 } 130 131 // VolumeAttachmentInfo describes information about a volume attachment. 132 type VolumeAttachmentInfo struct { 133 DeviceName string `bson:"devicename,omitempty"` 134 DeviceLink string `bson:"devicelink,omitempty"` 135 BusAddress string `bson:"busaddress,omitempty"` 136 ReadOnly bool `bson:"read-only"` 137 } 138 139 // VolumeAttachmentParams records parameters for attaching a volume to a 140 // machine. 141 type VolumeAttachmentParams struct { 142 ReadOnly bool `bson:"read-only"` 143 } 144 145 // validate validates the contents of the volume document. 146 func (v *volume) validate() error { 147 if v.doc.Binding != "" { 148 tag, err := names.ParseTag(v.doc.Binding) 149 if err != nil { 150 return errors.Annotate(err, "parsing binding") 151 } 152 switch tag.(type) { 153 case names.ModelTag: 154 // TODO(axw) support binding to model 155 return errors.NotSupportedf("binding to model") 156 case names.MachineTag: 157 case names.FilesystemTag: 158 case names.StorageTag: 159 default: 160 return errors.Errorf("invalid binding: %v", v.doc.Binding) 161 } 162 } 163 return nil 164 } 165 166 // globalKey is required to implement GlobalEntity. 167 func (v *volume) globalKey() string { 168 return volumeGlobalKey(v.doc.Name) 169 } 170 171 // Tag is required to implement GlobalEntity. 172 func (v *volume) Tag() names.Tag { 173 return v.VolumeTag() 174 } 175 176 // VolumeTag is required to implement Volume. 177 func (v *volume) VolumeTag() names.VolumeTag { 178 return names.NewVolumeTag(v.doc.Name) 179 } 180 181 // Life returns the volume's current lifecycle state. 182 func (v *volume) Life() Life { 183 return v.doc.Life 184 } 185 186 // LifeBinding is required to implement LifeBinder. 187 // 188 // Below is the set of possible entity types that a volume may be bound 189 // to, and a description of the effects of doing so: 190 // 191 // Machine: If the volume is bound to a machine, then the volume 192 // will be destroyed when it is detached from the 193 // machine. It is not permitted for a volume to be 194 // attached to multiple machines while it is bound to a 195 // machine. 196 // Storage: If the volume is bound to a storage instance, then 197 // the volume will be destroyed when the storage insance 198 // is removed from state. 199 // Filesystem: If the volume is bound to a filesystem, i.e. the 200 // volume backs that filesystem, then it will be 201 // destroyed when the filesystem is removed from state. 202 // Model: If the volume is bound to the model, then the 203 // volume must be destroyed prior to the model 204 // being destroyed. 205 func (v *volume) LifeBinding() names.Tag { 206 if v.doc.Binding == "" { 207 return nil 208 } 209 // Tag is validated in volume.validate. 210 tag, _ := names.ParseTag(v.doc.Binding) 211 return tag 212 } 213 214 // StorageInstance is required to implement Volume. 215 func (v *volume) StorageInstance() (names.StorageTag, error) { 216 if v.doc.StorageId == "" { 217 msg := fmt.Sprintf("volume %q is not assigned to any storage instance", v.Tag().Id()) 218 return names.StorageTag{}, errors.NewNotAssigned(nil, msg) 219 } 220 return names.NewStorageTag(v.doc.StorageId), nil 221 } 222 223 // Info is required to implement Volume. 224 func (v *volume) Info() (VolumeInfo, error) { 225 if v.doc.Info == nil { 226 return VolumeInfo{}, errors.NotProvisionedf("volume %q", v.doc.Name) 227 } 228 return *v.doc.Info, nil 229 } 230 231 // Params is required to implement Volume. 232 func (v *volume) Params() (VolumeParams, bool) { 233 if v.doc.Params == nil { 234 return VolumeParams{}, false 235 } 236 return *v.doc.Params, true 237 } 238 239 // Status is required to implement StatusGetter. 240 func (v *volume) Status() (status.StatusInfo, error) { 241 return v.st.VolumeStatus(v.VolumeTag()) 242 } 243 244 // SetStatus is required to implement StatusSetter. 245 func (v *volume) SetStatus(volumeStatus status.Status, info string, data map[string]interface{}) error { 246 return v.st.SetVolumeStatus(v.VolumeTag(), volumeStatus, info, data) 247 } 248 249 // Volume is required to implement VolumeAttachment. 250 func (v *volumeAttachment) Volume() names.VolumeTag { 251 return names.NewVolumeTag(v.doc.Volume) 252 } 253 254 // Machine is required to implement VolumeAttachment. 255 func (v *volumeAttachment) Machine() names.MachineTag { 256 return names.NewMachineTag(v.doc.Machine) 257 } 258 259 // Life is required to implement VolumeAttachment. 260 func (v *volumeAttachment) Life() Life { 261 return v.doc.Life 262 } 263 264 // Info is required to implement VolumeAttachment. 265 func (v *volumeAttachment) Info() (VolumeAttachmentInfo, error) { 266 if v.doc.Info == nil { 267 return VolumeAttachmentInfo{}, errors.NotProvisionedf("volume attachment %q on %q", v.doc.Volume, v.doc.Machine) 268 } 269 return *v.doc.Info, nil 270 } 271 272 // Params is required to implement VolumeAttachment. 273 func (v *volumeAttachment) Params() (VolumeAttachmentParams, bool) { 274 if v.doc.Params == nil { 275 return VolumeAttachmentParams{}, false 276 } 277 return *v.doc.Params, true 278 } 279 280 // Volume returns the Volume with the specified name. 281 func (st *State) Volume(tag names.VolumeTag) (Volume, error) { 282 v, err := st.volumeByTag(tag) 283 return v, err 284 } 285 286 func (st *State) volumes(query interface{}) ([]*volume, error) { 287 coll, cleanup := st.getCollection(volumesC) 288 defer cleanup() 289 290 var docs []volumeDoc 291 err := coll.Find(query).All(&docs) 292 if err != nil { 293 return nil, errors.Annotate(err, "querying volumes") 294 } 295 volumes := make([]*volume, len(docs)) 296 for i := range docs { 297 volume := &volume{st, docs[i]} 298 if err := volume.validate(); err != nil { 299 return nil, errors.Annotate(err, "validating volume") 300 } 301 volumes[i] = volume 302 } 303 return volumes, nil 304 } 305 306 func (st *State) volume(query bson.D, description string) (*volume, error) { 307 volumes, err := st.volumes(query) 308 if err != nil { 309 return nil, errors.Trace(err) 310 } 311 if len(volumes) == 0 { 312 return nil, errors.NotFoundf("%s", description) 313 } else if len(volumes) != 1 { 314 return nil, errors.Errorf("expected 1 volume, got %d", len(volumes)) 315 } 316 return volumes[0], nil 317 } 318 319 func (st *State) volumeByTag(tag names.VolumeTag) (*volume, error) { 320 return st.volume(bson.D{{"_id", tag.Id()}}, fmt.Sprintf("volume %q", tag.Id())) 321 } 322 323 func volumesToInterfaces(volumes []*volume) []Volume { 324 result := make([]Volume, len(volumes)) 325 for i, v := range volumes { 326 result[i] = v 327 } 328 return result 329 } 330 331 func (st *State) storageInstanceVolume(tag names.StorageTag) (*volume, error) { 332 return st.volume( 333 bson.D{{"storageid", tag.Id()}}, 334 fmt.Sprintf("volume for storage instance %q", tag.Id()), 335 ) 336 } 337 338 // StorageInstanceVolume returns the Volume assigned to the specified 339 // storage instance. 340 func (st *State) StorageInstanceVolume(tag names.StorageTag) (Volume, error) { 341 v, err := st.storageInstanceVolume(tag) 342 return v, err 343 } 344 345 // VolumeAttachment returns the VolumeAttachment corresponding to 346 // the specified volume and machine. 347 func (st *State) VolumeAttachment(machine names.MachineTag, volume names.VolumeTag) (VolumeAttachment, error) { 348 coll, cleanup := st.getCollection(volumeAttachmentsC) 349 defer cleanup() 350 351 var att volumeAttachment 352 err := coll.FindId(volumeAttachmentId(machine.Id(), volume.Id())).One(&att.doc) 353 if err == mgo.ErrNotFound { 354 return nil, errors.NotFoundf("volume %q on machine %q", volume.Id(), machine.Id()) 355 } else if err != nil { 356 return nil, errors.Annotatef(err, "getting volume %q on machine %q", volume.Id(), machine.Id()) 357 } 358 return &att, nil 359 } 360 361 // MachineVolumeAttachments returns all of the VolumeAttachments for the 362 // specified machine. 363 func (st *State) MachineVolumeAttachments(machine names.MachineTag) ([]VolumeAttachment, error) { 364 attachments, err := st.volumeAttachments(bson.D{{"machineid", machine.Id()}}) 365 if err != nil { 366 return nil, errors.Annotatef(err, "getting volume attachments for machine %q", machine.Id()) 367 } 368 return attachments, nil 369 } 370 371 // VolumeAttachments returns all of the VolumeAttachments for the specified 372 // volume. 373 func (st *State) VolumeAttachments(volume names.VolumeTag) ([]VolumeAttachment, error) { 374 attachments, err := st.volumeAttachments(bson.D{{"volumeid", volume.Id()}}) 375 if err != nil { 376 return nil, errors.Annotatef(err, "getting volume attachments for volume %q", volume.Id()) 377 } 378 return attachments, nil 379 } 380 381 func (st *State) volumeAttachments(query bson.D) ([]VolumeAttachment, error) { 382 coll, cleanup := st.getCollection(volumeAttachmentsC) 383 defer cleanup() 384 385 var docs []volumeAttachmentDoc 386 err := coll.Find(query).All(&docs) 387 if err == mgo.ErrNotFound { 388 return nil, nil 389 } else if err != nil { 390 return nil, errors.Trace(err) 391 } 392 attachments := make([]VolumeAttachment, len(docs)) 393 for i, doc := range docs { 394 attachments[i] = &volumeAttachment{doc} 395 } 396 return attachments, nil 397 } 398 399 type errContainsFilesystem struct { 400 error 401 } 402 403 func IsContainsFilesystem(err error) bool { 404 _, ok := errors.Cause(err).(*errContainsFilesystem) 405 return ok 406 } 407 408 // removeMachineVolumesOps returns txn.Ops to remove non-persistent volumes 409 // attached to the specified machine. This is used when the given machine is 410 // being removed from state. 411 func (st *State) removeMachineVolumesOps(machine names.MachineTag) ([]txn.Op, error) { 412 attachments, err := st.MachineVolumeAttachments(machine) 413 if err != nil { 414 return nil, errors.Trace(err) 415 } 416 ops := make([]txn.Op, 0, len(attachments)) 417 for _, a := range attachments { 418 volumeTag := a.Volume() 419 // When removing the machine, there should only remain 420 // non-persistent storage. This will be implicitly 421 // removed when the machine is removed, so we do not 422 // use removeVolumeAttachmentOps or removeVolumeOps, 423 // which track and update related documents. 424 ops = append(ops, txn.Op{ 425 C: volumeAttachmentsC, 426 Id: volumeAttachmentId(machine.Id(), volumeTag.Id()), 427 Assert: txn.DocExists, 428 Remove: true, 429 }) 430 canRemove, err := isVolumeInherentlyMachineBound(st, volumeTag) 431 if err != nil { 432 return nil, errors.Trace(err) 433 } 434 if !canRemove { 435 return nil, errors.Errorf("machine has non-machine bound volume %v", volumeTag.Id()) 436 } 437 ops = append(ops, txn.Op{ 438 C: volumesC, 439 Id: volumeTag.Id(), 440 Assert: txn.DocExists, 441 Remove: true, 442 }) 443 } 444 return ops, nil 445 } 446 447 // isVolumeInherentlyMachineBound reports whether or not the volume with the 448 // specified tag is inherently bound to the lifetime of the machine, and will 449 // be removed along with it, leaving no resources dangling. 450 func isVolumeInherentlyMachineBound(st *State, tag names.VolumeTag) (bool, error) { 451 volume, err := st.Volume(tag) 452 if err != nil { 453 return false, errors.Trace(err) 454 } 455 volumeInfo, err := volume.Info() 456 if errors.IsNotProvisioned(err) { 457 params, _ := volume.Params() 458 _, provider, err := poolStorageProvider(st, params.Pool) 459 if err != nil { 460 return false, errors.Trace(err) 461 } 462 if provider.Dynamic() { 463 // Even machine-scoped storage could be provisioned 464 // while the machine is Dying, and we don't know at 465 // this layer whether or not it will be Persistent. 466 // 467 // TODO(axw) extend storage provider interface to 468 // determine up-front whether or not a volume will 469 // be persistent. This will have to depend on the 470 // machine type, since, e.g., loop devices will 471 // outlive LXC containers. 472 return false, nil 473 } 474 // Volume is static, so even if it is provisioned, it will 475 // be tied to the machine. 476 return true, nil 477 } else if err != nil { 478 return false, errors.Trace(err) 479 } 480 // If volume does not outlive machine it can be removed. 481 return !volumeInfo.Persistent, nil 482 } 483 484 // DetachVolume marks the volume attachment identified by the specified machine 485 // and volume tags as Dying, if it is Alive. DetachVolume will fail with a 486 // IsContainsFilesystem error if the volume contains an attached filesystem; the 487 // filesystem attachment must be removed first. 488 func (st *State) DetachVolume(machine names.MachineTag, volume names.VolumeTag) (err error) { 489 defer errors.DeferredAnnotatef(&err, "detaching volume %s from machine %s", volume.Id(), machine.Id()) 490 // If the volume is backing a filesystem, the volume cannot be detached 491 // until the filesystem has been detached. 492 if _, err := st.volumeFilesystemAttachment(machine, volume); err == nil { 493 return &errContainsFilesystem{errors.New("volume contains attached filesystem")} 494 } else if !errors.IsNotFound(err) { 495 return errors.Trace(err) 496 } 497 buildTxn := func(attempt int) ([]txn.Op, error) { 498 va, err := st.VolumeAttachment(machine, volume) 499 if err != nil { 500 return nil, errors.Trace(err) 501 } 502 if va.Life() != Alive { 503 return nil, jujutxn.ErrNoOperations 504 } 505 return detachVolumeOps(machine, volume), nil 506 } 507 return st.run(buildTxn) 508 } 509 510 func (st *State) volumeFilesystemAttachment(machine names.MachineTag, volume names.VolumeTag) (FilesystemAttachment, error) { 511 filesystem, err := st.VolumeFilesystem(volume) 512 if err != nil { 513 return nil, errors.Trace(err) 514 } 515 return st.FilesystemAttachment(machine, filesystem.FilesystemTag()) 516 } 517 518 func detachVolumeOps(m names.MachineTag, v names.VolumeTag) []txn.Op { 519 return []txn.Op{{ 520 C: volumeAttachmentsC, 521 Id: volumeAttachmentId(m.Id(), v.Id()), 522 Assert: isAliveDoc, 523 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 524 }} 525 } 526 527 // RemoveVolumeAttachment removes the volume attachment from state. 528 // RemoveVolumeAttachment will fail if the attachment is not Dying. 529 func (st *State) RemoveVolumeAttachment(machine names.MachineTag, volume names.VolumeTag) (err error) { 530 defer errors.DeferredAnnotatef(&err, "removing attachment of volume %s from machine %s", volume.Id(), machine.Id()) 531 buildTxn := func(attempt int) ([]txn.Op, error) { 532 attachment, err := st.VolumeAttachment(machine, volume) 533 if errors.IsNotFound(err) && attempt > 0 { 534 // We only ignore IsNotFound on attempts after the 535 // first, since we expect the volume attachment to 536 // be there initially. 537 return nil, jujutxn.ErrNoOperations 538 } 539 if err != nil { 540 return nil, errors.Trace(err) 541 } 542 if attachment.Life() != Dying { 543 return nil, errors.New("volume attachment is not dying") 544 } 545 v, err := st.volumeByTag(volume) 546 if err != nil { 547 return nil, errors.Trace(err) 548 } 549 return removeVolumeAttachmentOps(machine, v), nil 550 } 551 return st.run(buildTxn) 552 } 553 554 func removeVolumeAttachmentOps(m names.MachineTag, v *volume) []txn.Op { 555 decrefVolumeOp := machineStorageDecrefOp( 556 volumesC, v.doc.Name, 557 v.doc.AttachmentCount, v.doc.Life, 558 m, v.doc.Binding, 559 ) 560 return []txn.Op{{ 561 C: volumeAttachmentsC, 562 Id: volumeAttachmentId(m.Id(), v.doc.Name), 563 Assert: bson.D{{"life", Dying}}, 564 Remove: true, 565 }, decrefVolumeOp, { 566 C: machinesC, 567 Id: m.Id(), 568 Assert: txn.DocExists, 569 Update: bson.D{{"$pull", bson.D{{"volumes", v.doc.Name}}}}, 570 }} 571 } 572 573 // machineStorageDecrefOp returns a txn.Op that will decrement the attachment 574 // count for a given machine storage entity (volume or filesystem), given its 575 // current attachment count and lifecycle state. If the attachment count goes 576 // to zero, then the entity should become Dead. 577 func machineStorageDecrefOp( 578 collection, id string, 579 attachmentCount int, life Life, 580 machine names.MachineTag, 581 binding string, 582 ) txn.Op { 583 op := txn.Op{ 584 C: collection, 585 Id: id, 586 } 587 if life == Dying { 588 if attachmentCount == 1 { 589 // This is the last attachment: the volume can be 590 // marked Dead. There can be no concurrent attachments 591 // since it is Dying. 592 op.Assert = bson.D{ 593 {"life", Dying}, 594 {"attachmentcount", 1}, 595 } 596 op.Update = bson.D{ 597 {"$inc", bson.D{{"attachmentcount", -1}}}, 598 {"$set", bson.D{{"life", Dead}}}, 599 } 600 } else { 601 // This is not the last attachment; just decref, 602 // allowing for concurrent attachment removals but 603 // ensuring we don't drop to zero without marking 604 // the volume Dead. 605 op.Assert = bson.D{ 606 {"life", Dying}, 607 {"attachmentcount", bson.D{{"$gt", 1}}}, 608 } 609 op.Update = bson.D{ 610 {"$inc", bson.D{{"attachmentcount", -1}}}, 611 } 612 } 613 } else { 614 // The volume is still Alive: decref, retrying if the 615 // volume is destroyed concurrently or the binding changes. 616 // If the volume is bound to the machine, advance it to 617 // Dead; binding storage to a machine and attaching the 618 // storage to multiple machines will be mutually exclusive. 619 // 620 // Otherwise, when DestroyVolume is called, the volume will 621 // be marked Dead if it has no attachments. 622 update := bson.D{ 623 {"$inc", bson.D{{"attachmentcount", -1}}}, 624 } 625 if binding == machine.String() { 626 update = append(update, bson.DocElem{ 627 "$set", bson.D{{"life", Dead}}, 628 }) 629 } 630 op.Assert = bson.D{ 631 {"life", Alive}, 632 {"binding", binding}, 633 {"attachmentcount", bson.D{{"$gt", 0}}}, 634 } 635 op.Update = update 636 } 637 return op 638 } 639 640 // DestroyVolume ensures that the volume and any attachments to it will be 641 // destroyed and removed from state at some point in the future. DestroyVolume 642 // will fail with an IsContainsFilesystem error if the volume contains a 643 // filesystem; the filesystem must be fully removed first. 644 func (st *State) DestroyVolume(tag names.VolumeTag) (err error) { 645 defer errors.DeferredAnnotatef(&err, "destroying volume %s", tag.Id()) 646 if _, err := st.VolumeFilesystem(tag); err == nil { 647 return &errContainsFilesystem{errors.New("volume contains filesystem")} 648 } else if !errors.IsNotFound(err) { 649 return errors.Trace(err) 650 } 651 buildTxn := func(attempt int) ([]txn.Op, error) { 652 volume, err := st.volumeByTag(tag) 653 if errors.IsNotFound(err) { 654 return nil, jujutxn.ErrNoOperations 655 } else if err != nil { 656 return nil, errors.Trace(err) 657 } 658 if volume.Life() != Alive { 659 return nil, jujutxn.ErrNoOperations 660 } 661 return destroyVolumeOps(st, volume), nil 662 } 663 return st.run(buildTxn) 664 } 665 666 func destroyVolumeOps(st *State, v *volume) []txn.Op { 667 if v.doc.AttachmentCount == 0 { 668 hasNoAttachments := bson.D{{"attachmentcount", 0}} 669 return []txn.Op{{ 670 C: volumesC, 671 Id: v.doc.Name, 672 Assert: append(hasNoAttachments, isAliveDoc...), 673 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 674 }} 675 } 676 cleanupOp := st.newCleanupOp(cleanupAttachmentsForDyingVolume, v.doc.Name) 677 hasAttachments := bson.D{{"attachmentcount", bson.D{{"$gt", 0}}}} 678 return []txn.Op{{ 679 C: volumesC, 680 Id: v.doc.Name, 681 Assert: append(hasAttachments, isAliveDoc...), 682 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 683 }, cleanupOp} 684 } 685 686 // RemoveVolume removes the volume from state. RemoveVolume will fail if 687 // the volume is not Dead, which implies that it still has attachments. 688 func (st *State) RemoveVolume(tag names.VolumeTag) (err error) { 689 defer errors.DeferredAnnotatef(&err, "removing volume %s", tag.Id()) 690 buildTxn := func(attempt int) ([]txn.Op, error) { 691 volume, err := st.Volume(tag) 692 if errors.IsNotFound(err) { 693 return nil, jujutxn.ErrNoOperations 694 } else if err != nil { 695 return nil, errors.Trace(err) 696 } 697 if volume.Life() != Dead { 698 return nil, errors.New("volume is not dead") 699 } 700 return []txn.Op{ 701 { 702 C: volumesC, 703 Id: tag.Id(), 704 Assert: txn.DocExists, 705 Remove: true, 706 }, 707 removeStatusOp(st, volumeGlobalKey(tag.Id())), 708 }, nil 709 } 710 return st.run(buildTxn) 711 } 712 713 // newVolumeName returns a unique volume name. 714 // If the machine ID supplied is non-empty, the 715 // volume ID will incorporate it as the volume's 716 // machine scope. 717 func newVolumeName(st *State, machineId string) (string, error) { 718 seq, err := st.sequence("volume") 719 if err != nil { 720 return "", errors.Trace(err) 721 } 722 id := fmt.Sprint(seq) 723 if machineId != "" { 724 id = machineId + "/" + id 725 } 726 return id, nil 727 } 728 729 // addVolumeOps returns txn.Ops to create a new volume with the specified 730 // parameters. If the supplied machine ID is non-empty, and the storage 731 // provider is machine-scoped, then the volume will be scoped to that 732 // machine. 733 func (st *State) addVolumeOps(params VolumeParams, machineId string) ([]txn.Op, names.VolumeTag, error) { 734 if params.binding == nil { 735 params.binding = names.NewMachineTag(machineId) 736 } 737 params, err := st.volumeParamsWithDefaults(params) 738 if err != nil { 739 return nil, names.VolumeTag{}, errors.Trace(err) 740 } 741 machineId, err = st.validateVolumeParams(params, machineId) 742 if err != nil { 743 return nil, names.VolumeTag{}, errors.Annotate(err, "validating volume params") 744 } 745 name, err := newVolumeName(st, machineId) 746 if err != nil { 747 return nil, names.VolumeTag{}, errors.Annotate(err, "cannot generate volume name") 748 } 749 ops := []txn.Op{ 750 createStatusOp(st, volumeGlobalKey(name), statusDoc{ 751 Status: status.StatusPending, 752 // TODO(fwereade): 2016-03-17 lp:1558657 753 Updated: time.Now().UnixNano(), 754 }), 755 { 756 C: volumesC, 757 Id: name, 758 Assert: txn.DocMissing, 759 Insert: &volumeDoc{ 760 Name: name, 761 StorageId: params.storage.Id(), 762 Binding: params.binding.String(), 763 Params: ¶ms, 764 // Every volume is created with one attachment. 765 AttachmentCount: 1, 766 }, 767 }, 768 } 769 return ops, names.NewVolumeTag(name), nil 770 } 771 772 func (st *State) volumeParamsWithDefaults(params VolumeParams) (VolumeParams, error) { 773 if params.Pool != "" { 774 return params, nil 775 } 776 envConfig, err := st.ModelConfig() 777 if err != nil { 778 return VolumeParams{}, errors.Trace(err) 779 } 780 cons := StorageConstraints{ 781 Pool: params.Pool, 782 Size: params.Size, 783 Count: 1, 784 } 785 poolName, err := defaultStoragePool(envConfig, storage.StorageKindBlock, cons) 786 if err != nil { 787 return VolumeParams{}, errors.Annotate(err, "getting default block storage pool") 788 } 789 params.Pool = poolName 790 return params, nil 791 } 792 793 // validateVolumeParams validates the volume parameters, and returns the 794 // machine ID to use as the scope in the volume tag. 795 func (st *State) validateVolumeParams(params VolumeParams, machineId string) (maybeMachineId string, _ error) { 796 if err := validateStoragePool(st, params.Pool, storage.StorageKindBlock, &machineId); err != nil { 797 return "", err 798 } 799 if params.Size == 0 { 800 return "", errors.New("invalid size 0") 801 } 802 return machineId, nil 803 } 804 805 // volumeAttachmentId returns a volume attachment document ID, 806 // given the corresponding volume name and machine ID. 807 func volumeAttachmentId(machineId, volumeName string) string { 808 return fmt.Sprintf("%s:%s", machineId, volumeName) 809 } 810 811 // ParseVolumeAttachmentId parses a string as a volume attachment ID, 812 // returning the machine and volume components. 813 func ParseVolumeAttachmentId(id string) (names.MachineTag, names.VolumeTag, error) { 814 fields := strings.SplitN(id, ":", 2) 815 if len(fields) != 2 || !names.IsValidMachine(fields[0]) || !names.IsValidVolume(fields[1]) { 816 return names.MachineTag{}, names.VolumeTag{}, errors.Errorf("invalid volume attachment ID %q", id) 817 } 818 machineTag := names.NewMachineTag(fields[0]) 819 volumeTag := names.NewVolumeTag(fields[1]) 820 return machineTag, volumeTag, nil 821 } 822 823 type volumeAttachmentTemplate struct { 824 tag names.VolumeTag 825 params VolumeAttachmentParams 826 } 827 828 // createMachineVolumeAttachmentInfo creates volume attachments 829 // for the specified machine, and attachment parameters keyed 830 // by volume tags. The caller is responsible for incrementing 831 // the volume's attachmentcount field. 832 func createMachineVolumeAttachmentsOps(machineId string, attachments []volumeAttachmentTemplate) []txn.Op { 833 ops := make([]txn.Op, len(attachments)) 834 for i, attachment := range attachments { 835 paramsCopy := attachment.params 836 ops[i] = txn.Op{ 837 C: volumeAttachmentsC, 838 Id: volumeAttachmentId(machineId, attachment.tag.Id()), 839 Assert: txn.DocMissing, 840 Insert: &volumeAttachmentDoc{ 841 Volume: attachment.tag.Id(), 842 Machine: machineId, 843 Params: ¶msCopy, 844 }, 845 } 846 } 847 return ops 848 } 849 850 // setMachineVolumeAttachmentInfo sets the volume attachment 851 // info for the specified machine. Each volume attachment info 852 // structure is keyed by the name of the volume it corresponds 853 // to. 854 func setMachineVolumeAttachmentInfo( 855 st *State, 856 machineId string, 857 attachments map[names.VolumeTag]VolumeAttachmentInfo, 858 ) (err error) { 859 defer errors.DeferredAnnotatef(&err, "cannot set volume attachment info for machine %s", machineId) 860 machineTag := names.NewMachineTag(machineId) 861 for volumeTag, info := range attachments { 862 if err := st.setVolumeAttachmentInfo(machineTag, volumeTag, info); err != nil { 863 return errors.Annotatef(err, "setting attachment info for volume %s", volumeTag.Id()) 864 } 865 } 866 return nil 867 } 868 869 // SetVolumeAttachmentInfo sets the VolumeAttachmentInfo for the specified 870 // volume attachment. 871 func (st *State) SetVolumeAttachmentInfo(machineTag names.MachineTag, volumeTag names.VolumeTag, info VolumeAttachmentInfo) (err error) { 872 defer errors.DeferredAnnotatef(&err, "cannot set info for volume attachment %s:%s", volumeTag.Id(), machineTag.Id()) 873 v, err := st.Volume(volumeTag) 874 if err != nil { 875 return errors.Trace(err) 876 } 877 // Ensure volume is provisioned before setting attachment info. 878 // A volume cannot go from being provisioned to unprovisioned, 879 // so there is no txn.Op for this below. 880 if _, err := v.Info(); err != nil { 881 return errors.Trace(err) 882 } 883 // Also ensure the machine is provisioned. 884 m, err := st.Machine(machineTag.Id()) 885 if err != nil { 886 return errors.Trace(err) 887 } 888 if _, err := m.InstanceId(); err != nil { 889 return errors.Trace(err) 890 } 891 return st.setVolumeAttachmentInfo(machineTag, volumeTag, info) 892 } 893 894 func (st *State) setVolumeAttachmentInfo( 895 machineTag names.MachineTag, 896 volumeTag names.VolumeTag, 897 info VolumeAttachmentInfo, 898 ) error { 899 buildTxn := func(attempt int) ([]txn.Op, error) { 900 va, err := st.VolumeAttachment(machineTag, volumeTag) 901 if err != nil { 902 return nil, errors.Trace(err) 903 } 904 // If the volume attachment has parameters, unset them 905 // when we set info for the first time, ensuring that 906 // params and info are mutually exclusive. 907 _, unsetParams := va.Params() 908 ops := setVolumeAttachmentInfoOps( 909 machineTag, volumeTag, info, unsetParams, 910 ) 911 return ops, nil 912 } 913 return st.run(buildTxn) 914 } 915 916 func setVolumeAttachmentInfoOps(machine names.MachineTag, volume names.VolumeTag, info VolumeAttachmentInfo, unsetParams bool) []txn.Op { 917 asserts := isAliveDoc 918 update := bson.D{ 919 {"$set", bson.D{{"info", &info}}}, 920 } 921 if unsetParams { 922 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 923 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 924 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 925 } 926 return []txn.Op{{ 927 C: volumeAttachmentsC, 928 Id: volumeAttachmentId(machine.Id(), volume.Id()), 929 Assert: asserts, 930 Update: update, 931 }} 932 } 933 934 // setProvisionedVolumeInfo sets the initial info for newly 935 // provisioned volumes. If non-empty, machineId must be the 936 // machine ID associated with the volumes. 937 func setProvisionedVolumeInfo(st *State, volumes map[names.VolumeTag]VolumeInfo) error { 938 for volumeTag, info := range volumes { 939 if err := st.SetVolumeInfo(volumeTag, info); err != nil { 940 return errors.Trace(err) 941 } 942 } 943 return nil 944 } 945 946 // SetVolumeInfo sets the VolumeInfo for the specified volume. 947 func (st *State) SetVolumeInfo(tag names.VolumeTag, info VolumeInfo) (err error) { 948 defer errors.DeferredAnnotatef(&err, "cannot set info for volume %q", tag.Id()) 949 if info.VolumeId == "" { 950 return errors.New("volume ID not set") 951 } 952 // TODO(axw) we should reject info without VolumeId set; can't do this 953 // until the providers all set it correctly. 954 buildTxn := func(attempt int) ([]txn.Op, error) { 955 v, err := st.Volume(tag) 956 if err != nil { 957 return nil, errors.Trace(err) 958 } 959 // If the volume has parameters, unset them when 960 // we set info for the first time, ensuring that 961 // params and info are mutually exclusive. 962 var unsetParams bool 963 var ops []txn.Op 964 if params, ok := v.Params(); ok { 965 info.Pool = params.Pool 966 unsetParams = true 967 } else { 968 // Ensure immutable properties do not change. 969 oldInfo, err := v.Info() 970 if err != nil { 971 return nil, err 972 } 973 if err := validateVolumeInfoChange(info, oldInfo); err != nil { 974 return nil, err 975 } 976 } 977 ops = append(ops, setVolumeInfoOps(tag, info, unsetParams)...) 978 return ops, nil 979 } 980 return st.run(buildTxn) 981 } 982 983 func validateVolumeInfoChange(newInfo, oldInfo VolumeInfo) error { 984 if newInfo.Pool != oldInfo.Pool { 985 return errors.Errorf( 986 "cannot change pool from %q to %q", 987 oldInfo.Pool, newInfo.Pool, 988 ) 989 } 990 if newInfo.VolumeId != oldInfo.VolumeId { 991 return errors.Errorf( 992 "cannot change volume ID from %q to %q", 993 oldInfo.VolumeId, newInfo.VolumeId, 994 ) 995 } 996 return nil 997 } 998 999 func setVolumeInfoOps(tag names.VolumeTag, info VolumeInfo, unsetParams bool) []txn.Op { 1000 asserts := isAliveDoc 1001 update := bson.D{ 1002 {"$set", bson.D{{"info", &info}}}, 1003 } 1004 if unsetParams { 1005 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 1006 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 1007 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 1008 } 1009 return []txn.Op{{ 1010 C: volumesC, 1011 Id: tag.Id(), 1012 Assert: asserts, 1013 Update: update, 1014 }} 1015 } 1016 1017 // AllVolumes returns all Volumes scoped to the model. 1018 func (st *State) AllVolumes() ([]Volume, error) { 1019 volumes, err := st.volumes(nil) 1020 if err != nil { 1021 return nil, errors.Annotate(err, "cannot get volumes") 1022 } 1023 return volumesToInterfaces(volumes), nil 1024 } 1025 1026 func volumeGlobalKey(name string) string { 1027 return "v#" + name 1028 } 1029 1030 // VolumeStatus returns the status of the specified volume. 1031 func (st *State) VolumeStatus(tag names.VolumeTag) (status.StatusInfo, error) { 1032 return getStatus(st, volumeGlobalKey(tag.Id()), "volume") 1033 } 1034 1035 // SetVolumeStatus sets the status of the specified volume. 1036 func (st *State) SetVolumeStatus(tag names.VolumeTag, volumeStatus status.Status, info string, data map[string]interface{}) error { 1037 switch volumeStatus { 1038 case status.StatusAttaching, status.StatusAttached, status.StatusDetaching, status.StatusDetached, status.StatusDestroying: 1039 case status.StatusError: 1040 if info == "" { 1041 return errors.Errorf("cannot set status %q without info", volumeStatus) 1042 } 1043 case status.StatusPending: 1044 // If a volume is not yet provisioned, we allow its status 1045 // to be set back to pending (when a retry is to occur). 1046 v, err := st.Volume(tag) 1047 if err != nil { 1048 return errors.Trace(err) 1049 } 1050 _, err = v.Info() 1051 if errors.IsNotProvisioned(err) { 1052 break 1053 } 1054 return errors.Errorf("cannot set status %q", volumeStatus) 1055 default: 1056 return errors.Errorf("cannot set invalid status %q", volumeStatus) 1057 } 1058 return setStatus(st, setStatusParams{ 1059 badge: "volume", 1060 globalKey: volumeGlobalKey(tag.Id()), 1061 status: volumeStatus, 1062 message: info, 1063 rawData: data, 1064 }) 1065 }