// SetVolumeInfo sets the VolumeInfo for the specified volume. func (st *State) SetVolumeInfo(tag names.VolumeTag, info VolumeInfo) (err error) { defer errors.DeferredAnnotatef(&err, "cannot set info for volume %q", tag.Id()) if info.VolumeId == "" { return errors.New("volume ID not set") } // TODO(axw) we should reject info without VolumeId set; can't do this // until the providers all set it correctly. buildTxn := func(attempt int) ([]txn.Op, error) { v, err := st.Volume(tag) if err != nil { return nil, errors.Trace(err) } // If the volume has parameters, unset them when // we set info for the first time, ensuring that // params and info are mutually exclusive. var unsetParams bool var ops []txn.Op if params, ok := v.Params(); ok { info.Pool = params.Pool unsetParams = true } else { // Ensure immutable properties do not change. oldInfo, err := v.Info() if err != nil { return nil, err } if err := validateVolumeInfoChange(info, oldInfo); err != nil { return nil, err } } ops = append(ops, setVolumeInfoOps(tag, info, unsetParams)...) return ops, nil } return st.run(buildTxn) }
// VolumeAttachments returns all of the VolumeAttachments for the specified // volume. func (st *State) VolumeAttachments(volume names.VolumeTag) ([]VolumeAttachment, error) { attachments, err := st.volumeAttachments(bson.D{{"volumeid", volume.Id()}}) if err != nil { return nil, errors.Annotatef(err, "getting volume attachments for volume %q", volume.Id()) } return attachments, nil }
// SetVolumeStatus sets the status of the specified volume. func (st *State) SetVolumeStatus(tag names.VolumeTag, status Status, info string, data map[string]interface{}) error { switch status { case StatusAttaching, StatusAttached, StatusDetaching, StatusDestroying: case StatusError: if info == "" { return errors.Errorf("cannot set status %q without info", status) } case StatusPending: // If a volume is not yet provisioned, we allow its status // to be set back to pending (when a retry is to occur). v, err := st.Volume(tag) if err != nil { return errors.Trace(err) } _, err = v.Info() if errors.IsNotProvisioned(err) { break } return errors.Errorf("cannot set status %q", status) default: return errors.Errorf("cannot set invalid status %q", status) } return setStatus(st, setStatusParams{ badge: "volume", globalKey: volumeGlobalKey(tag.Id()), status: status, message: info, rawData: data, }) }
// addFilesystemOps returns txn.Ops to create a new filesystem with the // specified parameters. If the storage source cannot create filesystems // directly, a volume will be created and Juju will manage a filesystem // on it. func (st *State) addFilesystemOps(params FilesystemParams, machineId string) ([]txn.Op, names.FilesystemTag, names.VolumeTag, error) { if params.binding == nil { params.binding = names.NewMachineTag(machineId) } params, err := st.filesystemParamsWithDefaults(params) if err != nil { return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err) } machineId, err = st.validateFilesystemParams(params, machineId) if err != nil { return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "validating filesystem params") } filesystemId, err := newFilesystemId(st, machineId) if err != nil { return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "cannot generate filesystem name") } filesystemTag := names.NewFilesystemTag(filesystemId) // Check if the filesystem needs a volume. var volumeId string var volumeTag names.VolumeTag var ops []txn.Op _, provider, err := poolStorageProvider(st, params.Pool) if err != nil { return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err) } if !provider.Supports(storage.StorageKindFilesystem) { var volumeOp txn.Op volumeParams := VolumeParams{ params.storage, filesystemTag, // volume is bound to filesystem params.Pool, params.Size, } volumeOp, volumeTag, err = st.addVolumeOp(volumeParams, machineId) if err != nil { return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "creating backing volume") } volumeId = volumeTag.Id() ops = append(ops, volumeOp) } filesystemOp := txn.Op{ C: filesystemsC, Id: filesystemId, Assert: txn.DocMissing, Insert: &filesystemDoc{ FilesystemId: filesystemId, VolumeId: volumeId, StorageId: params.storage.Id(), Binding: params.binding.String(), Params: ¶ms, // Every filesystem is created with one attachment. AttachmentCount: 1, }, } ops = append(ops, filesystemOp) return ops, filesystemTag, volumeTag, nil }
func detachVolumeOps(m names.MachineTag, v names.VolumeTag) []txn.Op { return []txn.Op{{ C: volumeAttachmentsC, Id: volumeAttachmentId(m.Id(), v.Id()), Assert: isAliveDoc, Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, }} }
func (s *managedFilesystemSource) backingVolumeBlockDevice(v names.VolumeTag) (storage.BlockDevice, error) { blockDevice, ok := s.volumeBlockDevices[v] if !ok { return storage.BlockDevice{}, errors.Errorf( "backing-volume %s is not yet attached", v.Id(), ) } return blockDevice, nil }
func setVolumeInfoOps(tag names.VolumeTag, info VolumeInfo, unsetParams bool) []txn.Op { asserts := isAliveDoc update := bson.D{ {"$set", bson.D{{"info", &info}}}, } if unsetParams { asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) } return []txn.Op{{ C: volumesC, Id: tag.Id(), Assert: asserts, Update: update, }} }
// DetachVolume marks the volume attachment identified by the specified machine // and volume tags as Dying, if it is Alive. DetachVolume will fail with a // IsContainsFilesystem error if the volume contains an attached filesystem; the // filesystem attachment must be removed first. func (st *State) DetachVolume(machine names.MachineTag, volume names.VolumeTag) (err error) { defer errors.DeferredAnnotatef(&err, "detaching volume %s from machine %s", volume.Id(), machine.Id()) // If the volume is backing a filesystem, the volume cannot be detached // until the filesystem has been detached. if _, err := st.volumeFilesystemAttachment(machine, volume); err == nil { return &errContainsFilesystem{errors.New("volume contains attached filesystem")} } else if !errors.IsNotFound(err) { return errors.Trace(err) } buildTxn := func(attempt int) ([]txn.Op, error) { va, err := st.VolumeAttachment(machine, volume) if err != nil { return nil, errors.Trace(err) } if va.Life() != Alive { return nil, jujutxn.ErrNoOperations } return detachVolumeOps(machine, volume), nil } return st.run(buildTxn) }
// RemoveVolume removes the volume from state. RemoveVolume will fail if // the volume is not Dead, which implies that it still has attachments. func (st *State) RemoveVolume(tag names.VolumeTag) (err error) { defer errors.DeferredAnnotatef(&err, "removing volume %s", tag.Id()) buildTxn := func(attempt int) ([]txn.Op, error) { volume, err := st.Volume(tag) if errors.IsNotFound(err) { return nil, jujutxn.ErrNoOperations } else if err != nil { return nil, errors.Trace(err) } if volume.Life() != Dead { return nil, errors.New("volume is not dead") } return []txn.Op{{ C: volumesC, Id: tag.Id(), Assert: txn.DocExists, Remove: true, }}, nil } return st.run(buildTxn) }
// DestroyVolume ensures that the volume and any attachments to it will be // destroyed and removed from state at some point in the future. DestroyVolume // will fail with an IsContainsFilesystem error if the volume contains a // filesystem; the filesystem must be fully removed first. func (st *State) DestroyVolume(tag names.VolumeTag) (err error) { defer errors.DeferredAnnotatef(&err, "destroying volume %s", tag.Id()) if _, err := st.VolumeFilesystem(tag); err == nil { return &errContainsFilesystem{errors.New("volume contains filesystem")} } else if !errors.IsNotFound(err) { return errors.Trace(err) } buildTxn := func(attempt int) ([]txn.Op, error) { volume, err := st.volumeByTag(tag) if errors.IsNotFound(err) { return nil, jujutxn.ErrNoOperations } else if err != nil { return nil, errors.Trace(err) } if volume.Life() != Alive { return nil, jujutxn.ErrNoOperations } return destroyVolumeOps(st, volume), nil } return st.run(buildTxn) }
// SetVolumeAttachmentInfo sets the VolumeAttachmentInfo for the specified // volume attachment. func (st *State) SetVolumeAttachmentInfo(machineTag names.MachineTag, volumeTag names.VolumeTag, info VolumeAttachmentInfo) (err error) { defer errors.DeferredAnnotatef(&err, "cannot set info for volume attachment %s:%s", volumeTag.Id(), machineTag.Id()) v, err := st.Volume(volumeTag) if err != nil { return errors.Trace(err) } // Ensure volume is provisioned before setting attachment info. // A volume cannot go from being provisioned to unprovisioned, // so there is no txn.Op for this below. if _, err := v.Info(); err != nil { return errors.Trace(err) } // Also ensure the machine is provisioned. m, err := st.Machine(machineTag.Id()) if err != nil { return errors.Trace(err) } if _, err := m.InstanceId(); err != nil { return errors.Trace(err) } return st.setVolumeAttachmentInfo(machineTag, volumeTag, info) }
func (m *mockVolumeAccessor) provisionVolume(tag names.VolumeTag) params.Volume { v := params.Volume{ VolumeTag: tag.String(), Info: params.VolumeInfo{ VolumeId: "vol-" + tag.Id(), }, } m.provisionedVolumes[tag.String()] = v return v }
// VolumeAttachment returns the VolumeAttachment corresponding to // the specified volume and machine. func (st *State) VolumeAttachment(machine names.MachineTag, volume names.VolumeTag) (VolumeAttachment, error) { coll, cleanup := st.getCollection(volumeAttachmentsC) defer cleanup() var att volumeAttachment err := coll.FindId(volumeAttachmentId(machine.Id(), volume.Id())).One(&att.doc) if err == mgo.ErrNotFound { return nil, errors.NotFoundf("volume %q on machine %q", volume.Id(), machine.Id()) } else if err != nil { return nil, errors.Annotatef(err, "getting volume %q on machine %q", volume.Id(), machine.Id()) } return &att, nil }
// RemoveVolumeAttachment removes the volume attachment from state. // RemoveVolumeAttachment will fail if the attachment is not Dying. func (st *State) RemoveVolumeAttachment(machine names.MachineTag, volume names.VolumeTag) (err error) { defer errors.DeferredAnnotatef(&err, "removing attachment of volume %s from machine %s", volume.Id(), machine.Id()) buildTxn := func(attempt int) ([]txn.Op, error) { attachment, err := st.VolumeAttachment(machine, volume) if errors.IsNotFound(err) && attempt > 0 { // We only ignore IsNotFound on attempts after the // first, since we expect the volume attachment to // be there initially. return nil, jujutxn.ErrNoOperations } if err != nil { return nil, errors.Trace(err) } if attachment.Life() != Dying { return nil, errors.New("volume attachment is not dying") } v, err := st.volumeByTag(volume) if err != nil { return nil, errors.Trace(err) } return removeVolumeAttachmentOps(machine, v), nil } return st.run(buildTxn) }
func (st *State) volumeFilesystem(tag names.VolumeTag) (*filesystem, error) { query := bson.D{{"volumeid", tag.Id()}} description := fmt.Sprintf("filesystem for volume %q", tag.Id()) return st.filesystem(query, description) }
func (lvs *loopVolumeSource) volumeFilePath(tag names.VolumeTag) string { return filepath.Join(lvs.storageDir, tag.String()) }
// VolumeStatus returns the status of the specified volume. func (st *State) VolumeStatus(tag names.VolumeTag) (StatusInfo, error) { return getStatus(st, volumeGlobalKey(tag.Id()), "volume") }
func (st *State) volumeByTag(tag names.VolumeTag) (*volume, error) { return st.volume(bson.D{{"_id", tag.Id()}}, fmt.Sprintf("volume %q", tag.Id())) }