// assertMachineStorageRefs ensures that the specified machine's set of volume // and filesystem references corresponds exactly to the volume and filesystem // attachments that relate to the machine. func assertMachineStorageRefs(c *gc.C, st *state.State, m names.MachineTag) { machines, closer := state.GetRawCollection(st, state.MachinesC) defer closer() var doc struct { Volumes []string `bson:"volumes,omitempty"` Filesystems []string `bson:"filesystems,omitempty"` } err := machines.FindId(state.DocID(st, m.Id())).One(&doc) c.Assert(err, jc.ErrorIsNil) have := make(set.Tags) for _, v := range doc.Volumes { have.Add(names.NewVolumeTag(v)) } for _, f := range doc.Filesystems { have.Add(names.NewFilesystemTag(f)) } expect := make(set.Tags) volumeAttachments, err := st.MachineVolumeAttachments(m) c.Assert(err, jc.ErrorIsNil) for _, a := range volumeAttachments { expect.Add(a.Volume()) } filesystemAttachments, err := st.MachineFilesystemAttachments(m) c.Assert(err, jc.ErrorIsNil) for _, a := range filesystemAttachments { expect.Add(a.Filesystem()) } c.Assert(have, jc.DeepEquals, expect) }
// MachineFilesystemAttachments returns all of the FilesystemAttachments for the // specified machine. func (st *State) MachineFilesystemAttachments(machine names.MachineTag) ([]FilesystemAttachment, error) { attachments, err := st.filesystemAttachments(bson.D{{"machineid", machine.Id()}}) if err != nil { return nil, errors.Annotatef(err, "getting filesystem attachments for machine %q", machine.Id()) } return attachments, nil }
// MachineVolumeAttachments returns all of the VolumeAttachments for the // specified machine. func (st *State) MachineVolumeAttachments(machine names.MachineTag) ([]VolumeAttachment, error) { attachments, err := st.volumeAttachments(bson.D{{"machineid", machine.Id()}}) if err != nil { return nil, errors.Annotatef(err, "getting volume attachments for machine %q", machine.Id()) } return attachments, nil }
// removeMachineFilesystemsOps returns txn.Ops to remove non-persistent filesystems // attached to the specified machine. This is used when the given machine is // being removed from state. func (st *State) removeMachineFilesystemsOps(machine names.MachineTag) ([]txn.Op, error) { attachments, err := st.MachineFilesystemAttachments(machine) if err != nil { return nil, errors.Trace(err) } ops := make([]txn.Op, 0, len(attachments)) for _, a := range attachments { filesystemTag := a.Filesystem() // When removing the machine, there should only remain // non-persistent storage. This will be implicitly // removed when the machine is removed, so we do not // use removeFilesystemAttachmentOps or removeFilesystemOps, // which track and update related documents. ops = append(ops, txn.Op{ C: filesystemAttachmentsC, Id: filesystemAttachmentId(machine.Id(), filesystemTag.Id()), Assert: txn.DocExists, Remove: true, }) canRemove, err := isFilesystemInherentlyMachineBound(st, filesystemTag) if err != nil { return nil, errors.Trace(err) } if !canRemove { return nil, errors.Errorf("machine has non-machine bound filesystem %v", filesystemTag.Id()) } ops = append(ops, txn.Op{ C: filesystemsC, Id: filesystemTag.Id(), Assert: txn.DocExists, Remove: true, }) } return ops, nil }
// refreshMachine refreshes the specified machine's instance ID. If it is set, // then the machine watcher is stopped and pending entities' parameters are // updated. If the machine is not provisioned yet, this method is a no-op. func refreshMachine(ctx *context, tag names.MachineTag) error { w, ok := ctx.machines[tag] if !ok { return errors.Errorf("machine %s is not being watched", tag.Id()) } stopAndRemove := func() error { if err := w.stop(); err != nil { return errors.Annotate(err, "stopping machine watcher") } delete(ctx.machines, tag) return nil } results, err := ctx.machineAccessor.InstanceIds([]names.MachineTag{tag}) if err != nil { return errors.Annotate(err, "getting machine instance ID") } if err := results[0].Error; err != nil { if params.IsCodeNotProvisioned(err) { return nil } else if params.IsCodeNotFound(err) { // Machine is gone, so stop watching. return stopAndRemove() } return errors.Annotate(err, "getting machine instance ID") } machineProvisioned(ctx, tag, instance.Id(results[0].Result)) // machine provisioning is the only thing we care about; // stop the watcher. return stopAndRemove() }
func (s stateShim) WatchMachine(tag names.MachineTag) (state.NotifyWatcher, error) { m, err := s.Machine(tag.Id()) if err != nil { return nil, errors.Trace(err) } return m.Watch(), nil }
func (s stateShim) MachineInstanceId(tag names.MachineTag) (instance.Id, error) { m, err := s.Machine(tag.Id()) if err != nil { return "", errors.Trace(err) } return m.InstanceId() }
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 detachFilesystemOps(m names.MachineTag, f names.FilesystemTag) []txn.Op { return []txn.Op{{ C: filesystemAttachmentsC, Id: filesystemAttachmentId(m.Id(), f.Id()), Assert: isAliveDoc, Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, }} }
// ReleaseContainerAddresses releases a static IP address allocated to a // container. func (st *State) ReleaseContainerAddresses(containerTag names.MachineTag) (err error) { defer errors.DeferredAnnotatef(&err, "cannot release static addresses for %q", containerTag.Id()) var result params.ErrorResults args := params.Entities{ Entities: []params.Entity{{Tag: containerTag.String()}}, } if err := st.facade.FacadeCall("ReleaseContainerAddresses", args, &result); err != nil { return err } return result.OneError() }
// GetSSHHostKeys retrieves the SSH host keys stored for an entity. /// // NOTE: Currently only machines are supported. This can be // generalised to take other tag types later, if and when we need it. func (st *State) GetSSHHostKeys(tag names.MachineTag) (SSHHostKeys, error) { coll, closer := st.getCollection(sshHostKeysC) defer closer() var doc sshHostKeysDoc err := coll.FindId(machineGlobalKey(tag.Id())).One(&doc) if err == mgo.ErrNotFound { return nil, errors.NotFoundf("SSH host keys for %s", tag) } else if err != nil { return nil, errors.Annotate(err, "SSH host key lookup failed") } return SSHHostKeys(doc.Keys), nil }
// FilesystemAttachment returns the FilesystemAttachment corresponding to // the specified filesystem and machine. func (st *State) FilesystemAttachment(machine names.MachineTag, filesystem names.FilesystemTag) (FilesystemAttachment, error) { coll, cleanup := st.getCollection(filesystemAttachmentsC) defer cleanup() var att filesystemAttachment err := coll.FindId(filesystemAttachmentId(machine.Id(), filesystem.Id())).One(&att.doc) if err == mgo.ErrNotFound { return nil, errors.NotFoundf("filesystem %q on machine %q", filesystem.Id(), machine.Id()) } else if err != nil { return nil, errors.Annotatef(err, "getting filesystem %q on machine %q", filesystem.Id(), machine.Id()) } return &att, nil }
// NewAPI returns a new API client for the Singular facade. It exposes methods // for claiming and observing administration responsibility for the apiCaller's // model, on behalf of the supplied controller machine. func NewAPI(apiCaller base.APICaller, controllerTag names.MachineTag) (*API, error) { controllerId := controllerTag.Id() if !names.IsValidMachine(controllerId) { return nil, errors.NotValidf("controller tag") } modelTag, err := apiCaller.ModelTag() if err != nil { return nil, errors.Trace(err) } facadeCaller := base.NewFacadeCaller(apiCaller, "Singular") return &API{ modelTag: modelTag, controllerTag: controllerTag, facadeCaller: facadeCaller, }, nil }
func setVolumeAttachmentInfoOps(machine names.MachineTag, volume names.VolumeTag, info VolumeAttachmentInfo, 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: volumeAttachmentsC, Id: volumeAttachmentId(machine.Id(), volume.Id()), Assert: asserts, Update: update, }} }
// SetSSHHostKeys updates the stored SSH host keys for an entity. // // See the note for GetSSHHostKeys regarding supported entities. func (st *State) SetSSHHostKeys(tag names.MachineTag, keys SSHHostKeys) error { id := machineGlobalKey(tag.Id()) doc := sshHostKeysDoc{ Keys: keys, } err := st.runTransaction([]txn.Op{ { C: sshHostKeysC, Id: id, Insert: doc, }, { C: sshHostKeysC, Id: id, Update: bson.M{"$set": doc}, }, }) return errors.Annotate(err, "SSH host key update failed") }
func removeFilesystemAttachmentOps(m names.MachineTag, f *filesystem) []txn.Op { decrefFilesystemOp := machineStorageDecrefOp( filesystemsC, f.doc.FilesystemId, f.doc.AttachmentCount, f.doc.Life, m, f.doc.Binding, ) return []txn.Op{{ C: filesystemAttachmentsC, Id: filesystemAttachmentId(m.Id(), f.doc.FilesystemId), Assert: bson.D{{"life", Dying}}, Remove: true, }, decrefFilesystemOp, { C: machinesC, Id: m.Id(), Assert: txn.DocExists, Update: bson.D{{"$pull", bson.D{{"filesystems", f.doc.FilesystemId}}}}, }} }
func removeVolumeAttachmentOps(m names.MachineTag, v *volume) []txn.Op { decrefVolumeOp := machineStorageDecrefOp( volumesC, v.doc.Name, v.doc.AttachmentCount, v.doc.Life, m, v.doc.Binding, ) return []txn.Op{{ C: volumeAttachmentsC, Id: volumeAttachmentId(m.Id(), v.doc.Name), Assert: bson.D{{"life", Dying}}, Remove: true, }, decrefVolumeOp, { C: machinesC, Id: m.Id(), Assert: txn.DocExists, Update: bson.D{{"$pull", bson.D{{"volumes", v.doc.Name}}}}, }} }
func isMachineMaster(st *state.State, tag names.MachineTag) (bool, error) { if st == nil { // If there is no state, we aren't a master. return false, nil } // Not calling the agent openState method as it does other checks // we really don't care about here. All we need here is the machine // so we can determine if we are the master or not. machine, err := st.Machine(tag.Id()) if err != nil { // This shouldn't happen, and if it does, the state worker will have // found out before us, and already errored, or is likely to error out // very shortly. All we do here is return the error. The state worker // returns an error that will cause the agent to be terminated. return false, errors.Trace(err) } isMaster, err := mongo.IsMaster(st.MongoSession(), machine) if err != nil { return false, errors.Trace(err) } return isMaster, nil }
// 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) }
// SetFilesystemAttachmentInfo sets the FilesystemAttachmentInfo for the // specified filesystem attachment. func (st *State) SetFilesystemAttachmentInfo( machineTag names.MachineTag, filesystemTag names.FilesystemTag, info FilesystemAttachmentInfo, ) (err error) { defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem attachment %s:%s", filesystemTag.Id(), machineTag.Id()) f, err := st.Filesystem(filesystemTag) if err != nil { return errors.Trace(err) } // Ensure filesystem is provisioned before setting attachment info. // A filesystem cannot go from being provisioned to unprovisioned, // so there is no txn.Op for this below. if _, err := f.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) } buildTxn := func(attempt int) ([]txn.Op, error) { fsa, err := st.FilesystemAttachment(machineTag, filesystemTag) if err != nil { return nil, errors.Trace(err) } // If the filesystem attachment has parameters, unset them // when we set info for the first time, ensuring that params // and info are mutually exclusive. _, unsetParams := fsa.Params() ops := setFilesystemAttachmentInfoOps(machineTag, filesystemTag, info, unsetParams) return ops, nil } return st.run(buildTxn) }
// 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 }
func (u *UniterAPIV1) getMachine(tag names.MachineTag) (*state.Machine, error) { return u.st.Machine(tag.Id()) }
// RemoveFilesystemAttachment removes the filesystem attachment from state. // Removing a volume-backed filesystem attachment will cause the volume to // be detached. func (st *State) RemoveFilesystemAttachment(machine names.MachineTag, filesystem names.FilesystemTag) (err error) { defer errors.DeferredAnnotatef(&err, "removing attachment of filesystem %s from machine %s", filesystem.Id(), machine.Id()) buildTxn := func(attempt int) ([]txn.Op, error) { attachment, err := st.FilesystemAttachment(machine, filesystem) if errors.IsNotFound(err) && attempt > 0 { // We only ignore IsNotFound on attempts after the // first, since we expect the filesystem 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("filesystem attachment is not dying") } f, err := st.filesystemByTag(filesystem) if err != nil { return nil, errors.Trace(err) } ops := removeFilesystemAttachmentOps(machine, f) // If the filesystem is backed by a volume, the volume // attachment can and should be destroyed once the // filesystem attachment is removed. volumeAttachment, err := st.filesystemVolumeAttachment(machine, filesystem) if err != nil { if errors.Cause(err) != ErrNoBackingVolume && !errors.IsNotFound(err) { return nil, errors.Trace(err) } } else if volumeAttachment.Life() == Alive { ops = append(ops, detachVolumeOps(machine, volumeAttachment.Volume())...) } return ops, nil } return st.run(buildTxn) }
// WatchBlockDevices returns a new NotifyWatcher watching for // changes to block devices associated with the specified machine. func (st *State) WatchBlockDevices(machine names.MachineTag) NotifyWatcher { return newBlockDevicesWatcher(st, machine.Id()) }
// DetachFilesystem marks the filesystem attachment identified by the specified machine // and filesystem tags as Dying, if it is Alive. func (st *State) DetachFilesystem(machine names.MachineTag, filesystem names.FilesystemTag) (err error) { defer errors.DeferredAnnotatef(&err, "detaching filesystem %s from machine %s", filesystem.Id(), machine.Id()) buildTxn := func(attempt int) ([]txn.Op, error) { fsa, err := st.FilesystemAttachment(machine, filesystem) if err != nil { return nil, errors.Trace(err) } if fsa.Life() != Alive { return nil, jujutxn.ErrNoOperations } ops := detachFilesystemOps(machine, filesystem) return ops, nil } return st.run(buildTxn) }
// BlockDevices returns the BlockDeviceInfo for the specified machine. func (st *State) BlockDevices(machine names.MachineTag) ([]BlockDeviceInfo, error) { return st.blockDevices(machine.Id()) }
// 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) }
// 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) }