Last active
July 6, 2018 01:39
-
-
Save victorock/c8319b62b7ce11b4bb51d2666fc9556f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
# Copyright: (c) 2007, 2012 Red Hat, Inc | |
# Michael DeHaan <[email protected]> | |
# Seth Vidal <[email protected]> | |
# Victor da Costa <[email protected]> | |
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | |
from __future__ import absolute_import, division, print_function | |
__metaclass__ = type | |
ANSIBLE_METADATA = {'metadata_version': '1.1', | |
'status': ['preview'], | |
'supported_by': 'community'} | |
DOCUMENTATION = ''' | |
--- | |
module: virt | |
short_description: Manages virtual machines supported by libvirt | |
description: | |
- Manages virtual machines supported by I(libvirt). | |
version_added: "0.2" | |
options: | |
name: | |
description: | |
- name of the guest VM being managed. Note that name must match the domain name defined in the xml. | |
required: true | |
state: | |
description: | |
- Note that there may be some lag for state requests like C(shutdown) | |
since these refer only to VM states. After starting a guest, it may not | |
be immediately accessible. | |
choices: [ 'present', 'absent', 'running', 'stopped', 'poweredoff', 'paused' ] | |
comments: | |
- Presence of Domain is enforced to ensure idempotency. | |
- `present` ensure domain presence without changing the current state. | |
- `absent` poweroff -> then remove domain. | |
- `running` present -> then unpause (if required) before starting the domain. | |
- `stopped` present -> then gracefully shutdown the domain. | |
- `poweredoff` present -> then unplug the virtual cable of the domain. | |
- `paused` present -> then pause the domain. | |
autostart: | |
description: | |
- start VM at host startup. | |
type: bool | |
version_added: "2.3" | |
uri: | |
description: | |
- libvirt connection uri. | |
default: qemu:///system | |
xml: | |
description: | |
- XML document used with the states. | |
- Must be raw XML content using C(lookup). XML cannot be reference to a file. | |
requirements: | |
- python >= 2.6 | |
- libvirt-python | |
author: | |
- Ansible Core Team | |
- Michael DeHaan | |
- Seth Vidal | |
- Victor da Costa | |
''' | |
EXAMPLES = ''' | |
# a playbook task line: | |
- virt: | |
name: alpha | |
xml: "{{ lookup('template', 'machine-template.xml.j2') }}" | |
state: running | |
''' | |
RETURN = ''' | |
# for list_vms command | |
list_vms: | |
description: The list of vms defined on the remote system | |
type: dictionary | |
returned: success | |
sample: [ | |
"build.example.org", | |
"dev.example.org" | |
] | |
# for status command | |
status: | |
description: The status of the VM, among running, crashed, paused and shutdown | |
type: string | |
sample: "success" | |
returned: success | |
''' | |
import traceback | |
import time | |
try: | |
import libvirt | |
except ImportError: | |
HAS_VIRT = False | |
else: | |
HAS_VIRT = True | |
from ansible.module_utils.basic import AnsibleModule | |
from ansible.module_utils._text import to_native | |
VIRT_FAILED = 1 | |
VIRT_SUCCESS = 0 | |
VIRT_UNAVAILABLE = 2 | |
STATES = [ 'present', 'absent', 'running', 'poweredoff', 'stopped', 'paused' ] | |
VIRT_STATE_NAME_MAP = { | |
0: 'running', | |
1: 'running', | |
2: 'running', | |
3: 'paused', | |
4: 'shutdown', | |
5: 'shutdown', | |
6: 'crashed', | |
} | |
class VMNotFound(Exception): | |
pass | |
class WaitForStatusTimeout(Exception): | |
pass | |
class LibvirtConnection(object): | |
def __init__(self, uri, module): | |
self.module = module | |
cmd = "uname -r" | |
rc, stdout, stderr = self.module.run_command(cmd) | |
if "xen" in stdout: | |
conn = libvirt.open(None) | |
elif "esx" in uri: | |
auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], [], None] | |
conn = libvirt.openAuth(uri, auth) | |
else: | |
conn = libvirt.open(uri) | |
if not conn: | |
raise Exception("hypervisor connection failure") | |
self.conn = conn | |
def find_vm(self, vmid): | |
""" | |
Extra bonus feature: vmid = -1 returns a list of everything | |
""" | |
conn = self.conn | |
vms = [] | |
# this block of code borrowed from virt-manager: | |
# get working domain's name | |
ids = conn.listDomainsID() | |
for id in ids: | |
vm = conn.lookupByID(id) | |
vms.append(vm) | |
# get defined domain | |
names = conn.listDefinedDomains() | |
for name in names: | |
vm = conn.lookupByName(name) | |
vms.append(vm) | |
if vmid == -1: | |
return vms | |
for vm in vms: | |
if vm.name() == vmid: | |
return vm | |
raise VMNotFound("virtual machine %s not found" % vmid) | |
def shutdown(self, vmid): | |
return self.find_vm(vmid).shutdown() | |
def pause(self, vmid): | |
return self.suspend(vmid) | |
def unpause(self, vmid): | |
return self.resume(vmid) | |
def suspend(self, vmid): | |
return self.find_vm(vmid).suspend() | |
def resume(self, vmid): | |
return self.find_vm(vmid).resume() | |
def create(self, vmid): | |
return self.find_vm(vmid).create() | |
def destroy(self, vmid): | |
return self.find_vm(vmid).destroy() | |
def undefine(self, vmid): | |
return self.find_vm(vmid).undefine() | |
def get_status2(self, vm): | |
state = vm.info()[0] | |
return VIRT_STATE_NAME_MAP.get(state, "unknown") | |
def get_status(self, vmid): | |
state = self.find_vm(vmid).info()[0] | |
return VIRT_STATE_NAME_MAP.get(state, "unknown") | |
def nodeinfo(self): | |
return self.conn.getInfo() | |
def get_type(self): | |
return self.conn.getType() | |
def get_xml(self, vmid): | |
vm = self.conn.lookupByName(vmid) | |
return vm.XMLDesc(0) | |
def get_maxVcpus(self, vmid): | |
vm = self.conn.lookupByName(vmid) | |
return vm.maxVcpus() | |
def get_maxMemory(self, vmid): | |
vm = self.conn.lookupByName(vmid) | |
return vm.maxMemory() | |
def getFreeMemory(self): | |
return self.conn.getFreeMemory() | |
def get_autostart(self, vmid): | |
vm = self.conn.lookupByName(vmid) | |
return vm.autostart() | |
def set_autostart(self, vmid, val): | |
vm = self.conn.lookupByName(vmid) | |
return vm.setAutostart(val) | |
def define_from_xml(self, xml): | |
return self.conn.defineXML(xml) | |
class Virt(object): | |
def __init__(self, module): | |
self.module = module | |
self.uri = module.params.get('uri') | |
def __get_conn(self): | |
self.conn = LibvirtConnection(self.uri, self.module) | |
return self.conn | |
def get_vm(self, vmid): | |
self.__get_conn() | |
return self.conn.find_vm(vmid) | |
def state(self): | |
vms = self.list_vms() | |
state = [] | |
for vm in vms: | |
state_blurb = self.conn.get_status(vm) | |
state.append("%s %s" % (vm, state_blurb)) | |
return state | |
def info(self): | |
vms = self.list_vms() | |
info = dict() | |
for vm in vms: | |
data = self.conn.find_vm(vm).info() | |
# libvirt returns maxMem, memory, and cpuTime as long()'s, which | |
# xmlrpclib tries to convert to regular int's during serialization. | |
# This throws exceptions, so convert them to strings here and | |
# assume the other end of the xmlrpc connection can figure things | |
# out or doesn't care. | |
info[vm] = dict( | |
state=VIRT_STATE_NAME_MAP.get(data[0], "unknown"), | |
maxMem=str(data[1]), | |
memory=str(data[2]), | |
nrVirtCpu=data[3], | |
cpuTime=str(data[4]), | |
autostart=self.conn.get_autostart(vm), | |
) | |
return info | |
def nodeinfo(self): | |
self.__get_conn() | |
data = self.conn.nodeinfo() | |
info = dict( | |
cpumodel=str(data[0]), | |
phymemory=str(data[1]), | |
cpus=str(data[2]), | |
cpumhz=str(data[3]), | |
numanodes=str(data[4]), | |
sockets=str(data[5]), | |
cpucores=str(data[6]), | |
cputhreads=str(data[7]) | |
) | |
return info | |
def list_vms(self, state=None): | |
self.conn = self.__get_conn() | |
vms = self.conn.find_vm(-1) | |
results = [] | |
for x in vms: | |
try: | |
if state: | |
vmstate = self.conn.get_status2(x) | |
if vmstate == state: | |
results.append(x.name()) | |
else: | |
results.append(x.name()) | |
except: | |
pass | |
return results | |
def virttype(self): | |
return self.__get_conn().get_type() | |
def autostart(self, vmid, as_flag): | |
self.conn = self.__get_conn() | |
# Change autostart flag only if needed | |
if self.conn.get_autostart(vmid) != as_flag: | |
self.conn.set_autostart(vmid, as_flag) | |
return True | |
return False | |
def freemem(self): | |
self.conn = self.__get_conn() | |
return self.conn.getFreeMemory() | |
def shutdown(self, vmid): | |
""" Make the machine with the given vmid stop running. Whatever that takes. """ | |
self.__get_conn() | |
return self.conn.shutdown(vmid) | |
def pause(self, vmid): | |
""" Pause the machine with the given vmid. """ | |
self.__get_conn() | |
return self.conn.suspend(vmid) | |
def unpause(self, vmid): | |
""" Unpause the machine with the given vmid. """ | |
self.__get_conn() | |
return self.conn.resume(vmid) | |
def create(self, vmid): | |
""" Start the machine via the given vmid """ | |
self.__get_conn() | |
return self.conn.create(vmid) | |
def start(self, vmid): | |
""" Start the machine via the given id/name """ | |
self.__get_conn() | |
return self.conn.create(vmid) | |
def destroy(self, vmid): | |
""" Pull the virtual power from the virtual domain, giving it virtually no time to virtually shut down. """ | |
self.__get_conn() | |
return self.conn.destroy(vmid) | |
def undefine(self, vmid): | |
""" Stop a domain, and then wipe it from the face of the earth. (delete disk/config file) """ | |
self.__get_conn() | |
return self.conn.undefine(vmid) | |
def status(self, vmid): | |
""" | |
Return a state suitable for server consumption. Aka, codes.py values, not XM output. | |
""" | |
self.__get_conn() | |
return self.conn.get_status(vmid) | |
def get_xml(self, vmid): | |
""" | |
Receive a Vm id as input | |
Return an xml describing vm config returned by a libvirt call | |
""" | |
self.__get_conn() | |
return self.conn.get_xml(vmid) | |
def get_maxVcpus(self, vmid): | |
""" | |
Gets the max number of VCPUs on a guest | |
""" | |
self.__get_conn() | |
return self.conn.get_maxVcpus(vmid) | |
def get_max_memory(self, vmid): | |
""" | |
Gets the max memory on a guest | |
""" | |
self.__get_conn() | |
return self.conn.get_MaxMemory(vmid) | |
def define(self, xml): | |
""" | |
Define a guest with the given xml | |
""" | |
self.__get_conn() | |
return self.conn.define_from_xml(xml) | |
# https://libvirt.org/guide/html/Application_Development_Guide-Guest_Domains-Lifecycle.html | |
class DomainStateMachine(object): | |
def __init__(self, virt, module): | |
self._result = dict() | |
self._virt = virt | |
self._module = module | |
self._guest = module.params.get('name') | |
self._xml = module.params.get('xml') | |
self._autostart = module.params.get('autostart') | |
def result(self): | |
return self._result | |
def waitfor_status(self, status="running", timeout=1): | |
""" | |
Ensure statefull operations are sync | |
""" | |
for count in range(0, timeout): | |
time.sleep(1) | |
if self._virt.status(self._guest) == status: | |
return True | |
raise WaitForTimeout( | |
""" | |
State didn't transition to {status} after {timeout} | |
""".format( | |
status=status, | |
timeout=timeout | |
) | |
) | |
def shutdown(self): | |
if self._virt.status(self._guest) == "running": | |
self._virt.shutdown(self._guest) | |
self._result['changed'] = self.waitfor_status("shutdown", 50) | |
self._result['virt'] = self._virt.info() | |
return self | |
def poweroff(self): | |
if self._virt.status(self._guest) == "running": | |
self._virt.destroy(self._guest) | |
self._result['changed'] = self.waitfor_status("shutdown", 30) | |
self._result['virt'] = self._virt.info() | |
return self | |
def start(self): | |
if self._virt.status(self._guest) == "shutdown": | |
self._virt.start(self._guest) | |
self._result['changed'] = self.waitfor_status("running", 10) | |
self._result['virt'] = self._virt.info() | |
return self | |
def autostart(self): | |
self._result['changed'] = self._virt.autostart(self._guest, self._autostart) | |
self._result['virt'] = self._virt.info() | |
return self | |
def pause(self): | |
if self._virt.status(self._guest) == "running": | |
self._virt.pause(self._guest) | |
self._result['changed'] = self.waitfor_status("paused", 50) | |
self._result['virt'] = self._virt.info() | |
return self | |
def unpause(self): | |
if self._virt.status(self._guest) == "paused": | |
self._virt.unpause(self._guest) | |
self._result['changed'] = self.waitfor_status("running", 50) | |
self._result['virt'] = self._virt.info() | |
return self | |
def paused(self): | |
try: | |
self._result['changed'] = False | |
self.present() | |
self.pause() | |
except WaitForStatusTimeout: | |
raise | |
return self.result() | |
def absent(self): | |
try: | |
self._result['changed'] = False | |
self._virt.status(self._guest) | |
except VMNotFound: | |
pass | |
else: | |
try: | |
self.poweredoff() | |
# Check if domain still exists after being poweredoff | |
# Transitient domains will simply disappear | |
self._virt.status(self._guest) | |
self._virt.undefine(self._guest) | |
# Check if the domain was undefined | |
self._virt.status(self._guest) | |
except VMNotFound: | |
# The domain doesn't exists anymore | |
self._result['changed'] = True | |
self._result['virt'] = self._virt.info() | |
return self.result() | |
def present(self): | |
try: | |
self._result['changed'] = False | |
self._virt.status(self._guest) | |
except VMNotFound: | |
self._virt.define(self._xml) | |
self._result['changed'] = self.waitfor_status("shutdown", 10) | |
self._result['virt'] = self._virt.info() | |
return self.result() | |
def poweredoff(self): | |
try: | |
self._result['changed'] = False | |
self.present() | |
self.poweroff() | |
except WaitForStatusTimeout: | |
raise | |
finally: | |
return self.result() | |
def stopped(self): | |
try: | |
self._result['changed'] = False | |
self.present() | |
self.shutdown() | |
except WaitForStatusTimeout: | |
# Pull the vpower-cable (poweroff) if domain is not transitioning to shutdown | |
self.poweroff() | |
finally: | |
return self.result() | |
def running(self): | |
try: | |
self._result['changed'] = False | |
self.present() | |
self.unpause() | |
self.start() | |
self.autostart() | |
except WaitForStatusTimeout: | |
raise | |
return self.result() | |
def main(): | |
module = AnsibleModule( | |
argument_spec=dict( | |
name=dict(required=True, type='str', aliases=['guest']), | |
state=dict(type='str', default='present', choices=STATES), | |
autostart=dict(type='bool', default=False), | |
uri=dict(type='str', default='qemu:///system'), | |
xml=dict(required=True, type='str'), | |
), | |
) | |
if not HAS_VIRT: | |
module.fail_json(msg='The `libvirt` module is not importable. Check the requirements.') | |
try: | |
virt = Virt(module) | |
fsm = DomainStateMachine(virt, module) | |
state = module.params.get('state') | |
result = getattr(fsm, state)() | |
except Exception as e: | |
module.fail_json(msg=to_native(e), exception=traceback.format_exc()) | |
finally: | |
module.exit_json(**result) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment