diff options
Diffstat (limited to 'ansible_collections/community/libvirt/plugins/modules')
3 files changed, 1917 insertions, 0 deletions
diff --git a/ansible_collections/community/libvirt/plugins/modules/virt.py b/ansible_collections/community/libvirt/plugins/modules/virt.py new file mode 100644 index 000000000..bbc2add06 --- /dev/null +++ b/ansible_collections/community/libvirt/plugins/modules/virt.py @@ -0,0 +1,563 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2007, 2012 Red Hat, Inc +# Michael DeHaan <michael.dehaan@gmail.com> +# Seth Vidal <skvidal@fedoraproject.org> +# 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 + + +DOCUMENTATION = ''' +--- +module: virt +short_description: Manages virtual machines supported by libvirt +description: + - Manages virtual machines supported by I(libvirt). +extends_documentation_fragment: + - community.libvirt.virt.options_uri + - community.libvirt.virt.options_xml + - community.libvirt.virt.options_guest + - community.libvirt.virt.options_autostart + - community.libvirt.virt.options_state + - community.libvirt.virt.options_command + - community.libvirt.requirements +author: + - Ansible Core Team + - Michael DeHaan + - Seth Vidal (@skvidal) +''' + +EXAMPLES = ''' +# a playbook task line: +- name: Start a VM + community.libvirt.virt: + name: alpha + state: running + +# /usr/bin/ansible invocations +# ansible host -m virt -a "name=alpha command=status" +# ansible host -m virt -a "name=alpha command=get_xml" +# ansible host -m virt -a "name=alpha command=create uri=lxc:///" + +# defining and launching an LXC guest +- name: Define a VM + community.libvirt.virt: + command: define + xml: "{{ lookup('template', 'container-template.xml.j2') }}" + uri: 'lxc:///' +- name: start vm + community.libvirt.virt: + name: foo + state: running + uri: 'lxc:///' + +# setting autostart on a qemu VM (default uri) +- name: Set autostart for a VM + community.libvirt.virt: + name: foo + autostart: yes + +# Defining a VM and making is autostart with host. VM will be off after this task +- name: Define vm from xml and set autostart + community.libvirt.virt: + command: define + xml: "{{ lookup('template', 'vm_template.xml.j2') }}" + autostart: yes + +# Listing VMs +- name: List all VMs + community.libvirt.virt: + command: list_vms + register: all_vms + +- name: List only running VMs + community.libvirt.virt: + command: list_vms + state: running + register: running_vms +''' + +RETURN = ''' +# for list_vms command +list_vms: + description: The list of vms defined on the remote system. + type: list + 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: str + sample: "success" + returned: success +''' + +import traceback + +try: + import libvirt + from libvirt import libvirtError +except ImportError: + HAS_VIRT = False +else: + HAS_VIRT = True + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +VIRT_FAILED = 1 +VIRT_SUCCESS = 0 +VIRT_UNAVAILABLE = 2 + +ALL_COMMANDS = [] +VM_COMMANDS = ['create', 'define', 'destroy', 'get_xml', 'pause', 'shutdown', 'status', 'start', 'stop', 'undefine', 'unpause'] +HOST_COMMANDS = ['freemem', 'info', 'list_vms', 'nodeinfo', 'virttype'] +ALL_COMMANDS.extend(VM_COMMANDS) +ALL_COMMANDS.extend(HOST_COMMANDS) + +VIRT_STATE_NAME_MAP = { + 0: 'running', + 1: 'running', + 2: 'running', + 3: 'paused', + 4: 'shutdown', + 5: 'shutdown', + 6: 'crashed', +} + + +class VMNotFound(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 + """ + + vms = self.conn.listAllDomains() + + 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, uri, module): + self.module = module + self.uri = 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 Exception: + 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() + self.conn.shutdown(vmid) + return 0 + + 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) + + +def core(module): + + state = module.params.get('state', None) + autostart = module.params.get('autostart', None) + guest = module.params.get('name', None) + command = module.params.get('command', None) + uri = module.params.get('uri', None) + xml = module.params.get('xml', None) + + v = Virt(uri, module) + res = dict() + + if state and command == 'list_vms': + res = v.list_vms(state=state) + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + if autostart is not None and command != 'define': + if not guest: + module.fail_json(msg="autostart requires 1 argument: name") + try: + v.get_vm(guest) + except VMNotFound: + module.fail_json(msg="domain %s not found" % guest) + res['changed'] = v.autostart(guest, autostart) + if not command and not state: + return VIRT_SUCCESS, res + + if state: + if not guest: + module.fail_json(msg="state change requires a guest specified") + + if state == 'running': + if v.status(guest) == 'paused': + res['changed'] = True + res['msg'] = v.unpause(guest) + elif v.status(guest) != 'running': + res['changed'] = True + res['msg'] = v.start(guest) + elif state == 'shutdown': + if v.status(guest) != 'shutdown': + res['changed'] = True + res['msg'] = v.shutdown(guest) + elif state == 'destroyed': + if v.status(guest) != 'shutdown': + res['changed'] = True + res['msg'] = v.destroy(guest) + elif state == 'paused': + if v.status(guest) == 'running': + res['changed'] = True + res['msg'] = v.pause(guest) + else: + module.fail_json(msg="unexpected state") + + return VIRT_SUCCESS, res + + if command: + if command in VM_COMMANDS: + if command == 'define': + if not xml: + module.fail_json(msg="define requires xml argument") + if guest: + # there might be a mismatch between quest 'name' in the module and in the xml + module.warn("'xml' is given - ignoring 'name'") + try: + domain_name = re.search('<name>(.*)</name>', xml).groups()[0] + except AttributeError: + module.fail_json(msg="Could not find domain 'name' in xml") + + # From libvirt docs (https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainDefineXML): + # -- A previous definition for this domain would be overridden if it already exists. + # + # In real world testing with libvirt versions 1.2.17-13, 2.0.0-10 and 3.9.0-14 + # on qemu and lxc domains results in: + # operation failed: domain '<name>' already exists with <uuid> + # + # In case a domain would be indeed overwritten, we should protect idempotency: + try: + existing_domain_xml = v.get_vm(domain_name).XMLDesc( + libvirt.VIR_DOMAIN_XML_INACTIVE + ) + except VMNotFound: + existing_domain_xml = None + try: + domain = v.define(xml) + if existing_domain_xml: + # if we are here, then libvirt redefined existing domain as the doc promised + if existing_domain_xml != domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE): + res = {'changed': True, 'change_reason': 'config changed'} + else: + res = {'changed': True, 'created': domain.name()} + except libvirtError as e: + if e.get_error_code() != 9: # 9 means 'domain already exists' error + module.fail_json(msg='libvirtError: %s' % e.get_error_message()) + if autostart is not None and v.autostart(domain_name, autostart): + res = {'changed': True, 'change_reason': 'autostart'} + + elif not guest: + module.fail_json(msg="%s requires 1 argument: guest" % command) + else: + res = getattr(v, command)(guest) + if not isinstance(res, dict): + res = {command: res} + + return VIRT_SUCCESS, res + + elif hasattr(v, command): + res = getattr(v, command)() + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + else: + module.fail_json(msg="Command %s not recognized" % command) + + module.fail_json(msg="expected state or command parameter to be specified") + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', aliases=['guest']), + state=dict(type='str', choices=['destroyed', 'paused', 'running', 'shutdown']), + autostart=dict(type='bool'), + command=dict(type='str', choices=ALL_COMMANDS), + uri=dict(type='str', default='qemu:///system'), + xml=dict(type='str'), + ), + ) + + if not HAS_VIRT: + module.fail_json(msg='The `libvirt` module is not importable. Check the requirements.') + + rc = VIRT_SUCCESS + try: + rc, result = core(module) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + if rc != 0: # something went wrong emit the msg + module.fail_json(rc=rc, msg=result) + else: + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/libvirt/plugins/modules/virt_net.py b/ansible_collections/community/libvirt/plugins/modules/virt_net.py new file mode 100644 index 000000000..7492cac79 --- /dev/null +++ b/ansible_collections/community/libvirt/plugins/modules/virt_net.py @@ -0,0 +1,647 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Maciej Delmanowski <drybjed@gmail.com> +# 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 + + +DOCUMENTATION = ''' +--- +module: virt_net +author: "Maciej Delmanowski (@drybjed)" +short_description: Manage libvirt network configuration +description: + - Manage I(libvirt) networks. +options: + name: + aliases: ['network'] + description: + - Name of the network being managed. Note that network must be previously + defined with xml. + type: str + state: + choices: [ "active", "inactive", "present", "absent" ] + description: + - Specify which state you want a network to be in. + If 'active', network will be started. + If 'present', ensure that network is present but do not change its + state; if it is missing, you need to specify xml argument. + If 'inactive', network will be stopped. + If 'undefined' or 'absent', network will be removed from I(libvirt) configuration. + type: str + command: + choices: [ "define", "create", "start", "stop", "destroy", + "undefine", "get_xml", "list_nets", "facts", + "info", "status", "modify"] + description: + - In addition to state management, various non-idempotent commands are available. + See examples. + Modify was added in Ansible version 2.1. + type: str + autostart: + type: bool + description: + - Specify if a given network should be started automatically on system boot. +extends_documentation_fragment: + - community.libvirt.virt.options_uri + - community.libvirt.virt.options_xml + - community.libvirt.requirements +requirements: + - "python-lxml" +''' + +EXAMPLES = ''' +- name: Define a new network + community.libvirt.virt_net: + command: define + name: br_nat + xml: '{{ lookup("template", "network/bridge.xml.j2") }}' + +- name: Start a network + community.libvirt.virt_net: + command: create + name: br_nat + +- name: List available networks + community.libvirt.virt_net: + command: list_nets + +- name: Get XML data of a specified network + community.libvirt.virt_net: + command: get_xml + name: br_nat + +- name: Stop a network + community.libvirt.virt_net: + command: destroy + name: br_nat + +- name: Undefine a network + community.libvirt.virt_net: + command: undefine + name: br_nat + +# Gather facts about networks +# Facts will be available as 'ansible_libvirt_networks' +- name: Gather facts about networks + community.libvirt.virt_net: + command: facts + +- name: Gather information about network managed by 'libvirt' remotely using uri + community.libvirt.virt_net: + command: info + uri: '{{ item }}' + with_items: '{{ libvirt_uris }}' + register: networks + +- name: Ensure that a network is active (needs to be defined and built first) + community.libvirt.virt_net: + state: active + name: br_nat + +- name: Ensure that a network is inactive + community.libvirt.virt_net: + state: inactive + name: br_nat + +- name: Ensure that a given network will be started at boot + community.libvirt.virt_net: + autostart: yes + name: br_nat + +- name: Disable autostart for a given network + community.libvirt.virt_net: + autostart: no + name: br_nat + +- name: Add a new host in the dhcp pool + community.libvirt.virt_net: + name: br_nat + command: modify + xml: "<host mac='FC:C2:33:00:6c:3c' name='my_vm' ip='192.168.122.30'/>" +''' + +try: + import libvirt +except ImportError: + HAS_VIRT = False +else: + HAS_VIRT = True + +try: + from lxml import etree +except ImportError: + HAS_XML = False +else: + HAS_XML = 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 + +ALL_COMMANDS = [] +ENTRY_COMMANDS = ['create', 'status', 'start', 'stop', + 'undefine', 'destroy', 'get_xml', 'define', + 'modify'] +HOST_COMMANDS = ['list_nets', 'facts', 'info'] +ALL_COMMANDS.extend(ENTRY_COMMANDS) +ALL_COMMANDS.extend(HOST_COMMANDS) + +ENTRY_STATE_ACTIVE_MAP = { + 0: "inactive", + 1: "active" +} + +ENTRY_STATE_AUTOSTART_MAP = { + 0: "no", + 1: "yes" +} + +ENTRY_STATE_PERSISTENT_MAP = { + 0: "no", + 1: "yes" +} + + +class EntryNotFound(Exception): + pass + + +class LibvirtConnection(object): + + def __init__(self, uri, module): + + self.module = module + + conn = libvirt.open(uri) + + if not conn: + raise Exception("hypervisor connection failure") + + self.conn = conn + + def find_entry(self, entryid): + if entryid == -1: # Get active entries + names = self.conn.listNetworks() + self.conn.listDefinedNetworks() + return [self.conn.networkLookupByName(n) for n in names] + + try: + return self.conn.networkLookupByName(entryid) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_NETWORK: + raise EntryNotFound("network %s not found" % entryid) + raise + + def create(self, entryid): + if not self.module.check_mode: + return self.find_entry(entryid).create() + else: + try: + state = self.find_entry(entryid).isActive() + except Exception: + return self.module.exit_json(changed=True) + if not state: + return self.module.exit_json(changed=True) + + def modify(self, entryid, xml): + network = self.find_entry(entryid) + # identify what type of entry is given in the xml + new_data = etree.fromstring(xml) + old_data = etree.fromstring(network.XMLDesc(0)) + if new_data.tag == 'host': + mac_addr = new_data.get('mac') + hosts = old_data.xpath('/network/ip/dhcp/host') + # find the one mac we're looking for + host = None + for h in hosts: + if h.get('mac') == mac_addr: + host = h + break + if host is None: + # add the host + if not self.module.check_mode: + if network.isActive(): + res = network.update(libvirt.VIR_NETWORK_UPDATE_COMMAND_ADD_LAST, + libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST, + -1, xml, libvirt.VIR_NETWORK_UPDATE_AFFECT_LIVE | libvirt.VIR_NETWORK_UPDATE_AFFECT_CONFIG) + else: + res = network.update(libvirt.VIR_NETWORK_UPDATE_COMMAND_ADD_LAST, + libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST, + -1, xml, libvirt.VIR_NETWORK_UPDATE_AFFECT_CONFIG) + else: + # pretend there was a change + res = 0 + if res == 0: + return True + else: + # change the host + if host.get('name') == new_data.get('name') and host.get('ip') == new_data.get('ip'): + return False + else: + if not self.module.check_mode: + if network.isActive(): + res = network.update(libvirt.VIR_NETWORK_UPDATE_COMMAND_MODIFY, + libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST, + -1, xml, libvirt.VIR_NETWORK_UPDATE_AFFECT_LIVE | libvirt.VIR_NETWORK_UPDATE_AFFECT_CONFIG) + else: + res = network.update(libvirt.VIR_NETWORK_UPDATE_COMMAND_MODIFY, + libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST, + -1, xml, libvirt.VIR_NETWORK_UPDATE_AFFECT_CONFIG) + else: + # pretend there was a change + res = 0 + if res == 0: + return True + # command, section, parentIndex, xml, flags=0 + self.module.fail_json(msg='updating this is not supported yet %s' % to_native(xml)) + + def destroy(self, entryid): + if not self.module.check_mode: + return self.find_entry(entryid).destroy() + else: + if self.find_entry(entryid).isActive(): + return self.module.exit_json(changed=True) + + def undefine(self, entryid): + entry = None + try: + entry = self.find_entry(entryid) + found = True + except EntryNotFound: + found = False + + if found: + return self.find_entry(entryid).undefine() + + if self.module.check_mode: + return self.module.exit_json(changed=found) + + def get_status2(self, entry): + state = entry.isActive() + return ENTRY_STATE_ACTIVE_MAP.get(state, "unknown") + + def get_status(self, entryid): + if not self.module.check_mode: + state = self.find_entry(entryid).isActive() + return ENTRY_STATE_ACTIVE_MAP.get(state, "unknown") + else: + try: + state = self.find_entry(entryid).isActive() + return ENTRY_STATE_ACTIVE_MAP.get(state, "unknown") + except Exception: + return ENTRY_STATE_ACTIVE_MAP.get("inactive", "unknown") + + def get_uuid(self, entryid): + return self.find_entry(entryid).UUIDString() + + def get_xml(self, entryid): + return self.find_entry(entryid).XMLDesc(0) + + def get_forward(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + try: + result = xml.xpath('/network/forward')[0].get('mode') + except Exception: + raise ValueError('Forward mode not specified') + return result + + def get_domain(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + try: + result = xml.xpath('/network/domain')[0].get('name') + except Exception: + raise ValueError('Domain not specified') + return result + + def get_macaddress(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + try: + result = xml.xpath('/network/mac')[0].get('address') + except Exception: + raise ValueError('MAC address not specified') + return result + + def get_autostart(self, entryid): + state = self.find_entry(entryid).autostart() + return ENTRY_STATE_AUTOSTART_MAP.get(state, "unknown") + + def get_autostart2(self, entryid): + if not self.module.check_mode: + return self.find_entry(entryid).autostart() + else: + try: + return self.find_entry(entryid).autostart() + except Exception: + return self.module.exit_json(changed=True) + + def set_autostart(self, entryid, val): + if not self.module.check_mode: + return self.find_entry(entryid).setAutostart(val) + else: + try: + state = self.find_entry(entryid).autostart() + except Exception: + return self.module.exit_json(changed=True) + if bool(state) != val: + return self.module.exit_json(changed=True) + + def get_bridge(self, entryid): + return self.find_entry(entryid).bridgeName() + + def get_persistent(self, entryid): + state = self.find_entry(entryid).isPersistent() + return ENTRY_STATE_PERSISTENT_MAP.get(state, "unknown") + + def get_dhcp_leases(self, entryid): + network = self.find_entry(entryid) + return network.DHCPLeases() + + def define_from_xml(self, entryid, xml): + if not self.module.check_mode: + return self.conn.networkDefineXML(xml) + else: + try: + self.find_entry(entryid) + except Exception: + return self.module.exit_json(changed=True) + + +class VirtNetwork(object): + + def __init__(self, uri, module): + self.module = module + self.uri = uri + self.conn = LibvirtConnection(self.uri, self.module) + + def get_net(self, entryid): + return self.conn.find_entry(entryid) + + def list_nets(self, state=None): + results = [] + for entry in self.conn.find_entry(-1): + if state: + if state == self.conn.get_status2(entry): + results.append(entry.name()) + else: + results.append(entry.name()) + return results + + def state(self): + results = [] + for entry in self.list_nets(): + state_blurb = self.conn.get_status(entry) + results.append("%s %s" % (entry, state_blurb)) + return results + + def autostart(self, entryid): + return self.conn.set_autostart(entryid, True) + + def get_autostart(self, entryid): + return self.conn.get_autostart2(entryid) + + def set_autostart(self, entryid, state): + return self.conn.set_autostart(entryid, state) + + def create(self, entryid): + if self.conn.get_status(entryid) == "active": + return + try: + return self.conn.create(entryid) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NETWORK_EXIST: + return None + raise + + def modify(self, entryid, xml): + return self.conn.modify(entryid, xml) + + def start(self, entryid): + return self.create(entryid) + + def stop(self, entryid): + if self.conn.get_status(entryid) == "active": + return self.conn.destroy(entryid) + + def destroy(self, entryid): + return self.stop(entryid) + + def undefine(self, entryid): + return self.conn.undefine(entryid) + + def status(self, entryid): + return self.conn.get_status(entryid) + + def get_xml(self, entryid): + return self.conn.get_xml(entryid) + + def define(self, entryid, xml): + return self.conn.define_from_xml(entryid, xml) + + def info(self): + return self.facts(facts_mode='info') + + def facts(self, name=None, facts_mode='facts'): + results = dict() + if name: + entries = [name] + else: + entries = self.list_nets() + for entry in entries: + results[entry] = dict() + results[entry]["autostart"] = self.conn.get_autostart(entry) + results[entry]["persistent"] = self.conn.get_persistent(entry) + results[entry]["state"] = self.conn.get_status(entry) + results[entry]["bridge"] = self.conn.get_bridge(entry) + results[entry]["uuid"] = self.conn.get_uuid(entry) + try: + results[entry]["dhcp_leases"] = self.conn.get_dhcp_leases(entry) + # not supported on RHEL 6 + except AttributeError: + pass + + try: + results[entry]["forward_mode"] = self.conn.get_forward(entry) + except ValueError: + pass + + try: + results[entry]["domain"] = self.conn.get_domain(entry) + except ValueError: + pass + + try: + results[entry]["macaddress"] = self.conn.get_macaddress(entry) + except ValueError: + pass + + facts = dict() + if facts_mode == 'facts': + facts["ansible_facts"] = dict() + facts["ansible_facts"]["ansible_libvirt_networks"] = results + elif facts_mode == 'info': + facts['networks'] = results + return facts + + +def core(module): + + state = module.params.get('state', None) + name = module.params.get('name', None) + command = module.params.get('command', None) + uri = module.params.get('uri', None) + xml = module.params.get('xml', None) + autostart = module.params.get('autostart', None) + + v = VirtNetwork(uri, module) + res = {} + + if state and command == 'list_nets': + res = v.list_nets(state=state) + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + if state: + if not name: + module.fail_json(msg="state change requires a specified name") + + res['changed'] = False + if state in ['active']: + if v.status(name) != 'active': + res['changed'] = True + res['msg'] = v.start(name) + elif state in ['present']: + try: + v.get_net(name) + except EntryNotFound: + if not xml: + module.fail_json(msg="network '" + name + "' not present, but xml not specified") + v.define(name, xml) + res = {'changed': True, 'created': name} + elif state in ['inactive']: + entries = v.list_nets() + if name in entries: + if v.status(name) != 'inactive': + res['changed'] = True + res['msg'] = v.destroy(name) + elif state in ['undefined', 'absent']: + entries = v.list_nets() + if name in entries: + if v.status(name) != 'inactive': + v.destroy(name) + res['changed'] = True + res['msg'] = v.undefine(name) + else: + module.fail_json(msg="unexpected state") + + return VIRT_SUCCESS, res + + if command: + if command in ENTRY_COMMANDS: + if not name: + module.fail_json(msg="%s requires 1 argument: name" % command) + if command in ('define', 'modify'): + if not xml: + module.fail_json(msg=command + " requires xml argument") + try: + v.get_net(name) + except EntryNotFound: + v.define(name, xml) + res = {'changed': True, 'created': name} + else: + if command == 'modify': + mod = v.modify(name, xml) + res = {'changed': mod, 'modified': name} + return VIRT_SUCCESS, res + res = getattr(v, command)(name) + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + elif hasattr(v, command): + if command == 'facts' and name: + res = v.facts(name) + else: + res = getattr(v, command)() + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + else: + module.fail_json(msg="Command %s not recognized" % command) + + if autostart is not None: + if not name: + module.fail_json(msg="state change requires a specified name") + + res['changed'] = False + if autostart: + if not v.get_autostart(name): + res['changed'] = True + res['msg'] = v.set_autostart(name, True) + else: + if v.get_autostart(name): + res['changed'] = True + res['msg'] = v.set_autostart(name, False) + + return VIRT_SUCCESS, res + + module.fail_json(msg="expected state or command parameter to be specified") + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + name=dict(aliases=['network']), + state=dict(choices=['active', 'inactive', 'present', 'absent']), + command=dict(choices=ALL_COMMANDS), + uri=dict(default='qemu:///system'), + xml=dict(), + autostart=dict(type='bool') + ), + supports_check_mode=True, + required_if=[ + ('command', 'create', ['name']), + ('command', 'status', ['name']), + ('command', 'start', ['name']), + ('command', 'stop', ['name']), + ('command', 'undefine', ['name']), + ('command', 'destroy', ['name']), + ('command', 'get_xml', ['name']), + ('command', 'define', ['name']), + ('command', 'modify', ['name']), + ] + ) + + if not HAS_VIRT: + module.fail_json( + msg='The `libvirt` module is not importable. Check the requirements.' + ) + + if not HAS_XML: + module.fail_json( + msg='The `lxml` module is not importable. Check the requirements.' + ) + + rc = VIRT_SUCCESS + try: + rc, result = core(module) + except Exception as e: + module.fail_json(msg=str(e)) + + if rc != 0: # something went wrong emit the msg + module.fail_json(rc=rc, msg=result) + else: + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/libvirt/plugins/modules/virt_pool.py b/ansible_collections/community/libvirt/plugins/modules/virt_pool.py new file mode 100644 index 000000000..70145c617 --- /dev/null +++ b/ansible_collections/community/libvirt/plugins/modules/virt_pool.py @@ -0,0 +1,707 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Maciej Delmanowski <drybjed@gmail.com> +# 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 + + +DOCUMENTATION = ''' +--- +module: virt_pool +author: "Maciej Delmanowski (@drybjed)" +short_description: Manage libvirt storage pools +description: + - Manage I(libvirt) storage pools. +options: + name: + aliases: [ "pool" ] + description: + - Name of the storage pool being managed. Note that pool must be previously + defined with xml. + type: str + state: + choices: [ "active", "inactive", "present", "absent", "undefined", "deleted" ] + description: + - Specify which state you want a storage pool to be in. + If 'active', pool will be started. + If 'present', ensure that pool is present but do not change its + state; if it is missing, you need to specify xml argument. + If 'inactive', pool will be stopped. + If 'undefined' or 'absent', pool will be removed from I(libvirt) configuration. + If 'deleted', pool contents will be deleted and then pool undefined. + type: str + command: + choices: [ "define", "build", "create", "start", "stop", "destroy", + "delete", "undefine", "get_xml", "list_pools", "facts", + "info", "status", "refresh" ] + description: + - In addition to state management, various non-idempotent commands are available. + See examples. + type: str + autostart: + type: bool + description: + - Specify if a given storage pool should be started automatically on system boot. + mode: + choices: [ 'new', 'repair', 'resize', 'no_overwrite', 'overwrite', 'normal', 'zeroed' ] + description: + - Pass additional parameters to 'build' or 'delete' commands. + type: str +extends_documentation_fragment: + - community.libvirt.virt.options_uri + - community.libvirt.virt.options_xml + - community.libvirt.requirements +requirements: + - "python-lxml" +''' + +EXAMPLES = ''' +- name: Define a new storage pool + community.libvirt.virt_pool: + command: define + name: vms + xml: '{{ lookup("template", "pool/dir.xml.j2") }}' + +- name: Build a storage pool if it does not exist + community.libvirt.virt_pool: + command: build + name: vms + +- name: Start a storage pool + community.libvirt.virt_pool: + command: create + name: vms + +- name: List available pools + community.libvirt.virt_pool: + command: list_pools + +- name: Get XML data of a specified pool + community.libvirt.virt_pool: + command: get_xml + name: vms + +- name: Stop a storage pool + community.libvirt.virt_pool: + command: destroy + name: vms + +- name: Delete a storage pool (destroys contents) + community.libvirt.virt_pool: + command: delete + name: vms + +- name: Undefine a storage pool + community.libvirt.virt_pool: + command: undefine + name: vms + +# Gather facts about storage pools +# Facts will be available as 'ansible_libvirt_pools' +- name: Gather facts about storage pools + community.libvirt.virt_pool: + command: facts + +- name: Gather information about pools managed by 'libvirt' remotely using uri + community.libvirt.virt_pool: + command: info + uri: '{{ item }}' + with_items: '{{ libvirt_uris }}' + register: storage_pools + +- name: Ensure that a pool is active (needs to be defined and built first) + community.libvirt.virt_pool: + state: active + name: vms + +- name: Ensure that a pool is inactive + community.libvirt.virt_pool: + state: inactive + name: vms + +- name: Ensure that a given pool will be started at boot + community.libvirt.virt_pool: + autostart: yes + name: vms + +- name: Disable autostart for a given pool + community.libvirt.virt_pool: + autostart: no + name: vms +''' + +try: + import libvirt +except ImportError: + HAS_VIRT = False +else: + HAS_VIRT = True + +try: + from lxml import etree +except ImportError: + HAS_XML = False +else: + HAS_XML = True + +from ansible.module_utils.basic import AnsibleModule + + +VIRT_FAILED = 1 +VIRT_SUCCESS = 0 +VIRT_UNAVAILABLE = 2 + +ALL_COMMANDS = [] +ENTRY_COMMANDS = ['create', 'status', 'start', 'stop', 'build', 'delete', + 'undefine', 'destroy', 'get_xml', 'define', 'refresh'] +HOST_COMMANDS = ['list_pools', 'facts', 'info'] +ALL_COMMANDS.extend(ENTRY_COMMANDS) +ALL_COMMANDS.extend(HOST_COMMANDS) + +ENTRY_STATE_ACTIVE_MAP = { + 0: "inactive", + 1: "active" +} + +ENTRY_STATE_AUTOSTART_MAP = { + 0: "no", + 1: "yes" +} + +ENTRY_STATE_PERSISTENT_MAP = { + 0: "no", + 1: "yes" +} + +ENTRY_STATE_INFO_MAP = { + 0: "inactive", + 1: "building", + 2: "running", + 3: "degraded", + 4: "inaccessible" +} + +ENTRY_BUILD_FLAGS_MAP = { + "new": 0, + "repair": 1, + "resize": 2, + "no_overwrite": 4, + "overwrite": 8 +} + +ENTRY_DELETE_FLAGS_MAP = { + "normal": 0, + "zeroed": 1 +} + +ALL_MODES = [] +ALL_MODES.extend(ENTRY_BUILD_FLAGS_MAP.keys()) +ALL_MODES.extend(ENTRY_DELETE_FLAGS_MAP.keys()) + + +class EntryNotFound(Exception): + pass + + +class LibvirtConnection(object): + + def __init__(self, uri, module): + + self.module = module + + conn = libvirt.open(uri) + + if not conn: + raise Exception("hypervisor connection failure") + + self.conn = conn + + def find_entry(self, entryid): + # entryid = -1 returns a list of everything + + results = [] + + # Get active entries + for name in self.conn.listStoragePools(): + entry = self.conn.storagePoolLookupByName(name) + results.append(entry) + + # Get inactive entries + for name in self.conn.listDefinedStoragePools(): + entry = self.conn.storagePoolLookupByName(name) + results.append(entry) + + if entryid == -1: + return results + + for entry in results: + if entry.name() == entryid: + return entry + + raise EntryNotFound("storage pool %s not found" % entryid) + + def create(self, entryid): + if not self.module.check_mode: + return self.find_entry(entryid).create() + else: + try: + state = self.find_entry(entryid).isActive() + except Exception: + return self.module.exit_json(changed=True) + if not state: + return self.module.exit_json(changed=True) + + def destroy(self, entryid): + if not self.module.check_mode: + return self.find_entry(entryid).destroy() + else: + if self.find_entry(entryid).isActive(): + return self.module.exit_json(changed=True) + + def undefine(self, entryid): + if not self.module.check_mode: + return self.find_entry(entryid).undefine() + else: + if not self.find_entry(entryid): + return self.module.exit_json(changed=True) + + def get_status2(self, entry): + state = entry.isActive() + return ENTRY_STATE_ACTIVE_MAP.get(state, "unknown") + + def get_status(self, entryid): + if not self.module.check_mode: + state = self.find_entry(entryid).isActive() + return ENTRY_STATE_ACTIVE_MAP.get(state, "unknown") + else: + try: + state = self.find_entry(entryid).isActive() + return ENTRY_STATE_ACTIVE_MAP.get(state, "unknown") + except Exception: + return ENTRY_STATE_ACTIVE_MAP.get("inactive", "unknown") + + def get_uuid(self, entryid): + return self.find_entry(entryid).UUIDString() + + def get_xml(self, entryid): + return self.find_entry(entryid).XMLDesc(0) + + def get_info(self, entryid): + return self.find_entry(entryid).info() + + def get_volume_count(self, entryid): + return self.find_entry(entryid).numOfVolumes() + + def get_volume_names(self, entryid): + return self.find_entry(entryid).listVolumes() + + def get_devices(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + if xml.xpath('/pool/source/device'): + result = [] + for device in xml.xpath('/pool/source/device'): + result.append(device.get('path')) + try: + return result + except Exception: + raise ValueError('No devices specified') + + def get_format(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + try: + result = xml.xpath('/pool/source/format')[0].get('type') + except Exception: + raise ValueError('Format not specified') + return result + + def get_host(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + try: + result = xml.xpath('/pool/source/host')[0].get('name') + except Exception: + raise ValueError('Host not specified') + return result + + def get_source_path(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + try: + result = xml.xpath('/pool/source/dir')[0].get('path') + except Exception: + raise ValueError('Source path not specified') + return result + + def get_path(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + try: + result = xml.xpath('/pool/target/path')[0].text + except Exception: + raise ValueError('Target path not specified') + return result + + def get_type(self, entryid): + xml = etree.fromstring(self.find_entry(entryid).XMLDesc(0)) + return xml.get('type') + + def build(self, entryid, flags): + if not self.module.check_mode: + return self.find_entry(entryid).build(flags) + else: + try: + state = self.find_entry(entryid) + except Exception: + return self.module.exit_json(changed=True) + if not state: + return self.module.exit_json(changed=True) + + def delete(self, entryid, flags): + if not self.module.check_mode: + return self.find_entry(entryid).delete(flags) + else: + try: + state = self.find_entry(entryid) + except Exception: + return self.module.exit_json(changed=True) + if state: + return self.module.exit_json(changed=True) + + def get_autostart(self, entryid): + state = self.find_entry(entryid).autostart() + return ENTRY_STATE_AUTOSTART_MAP.get(state, "unknown") + + def get_autostart2(self, entryid): + if not self.module.check_mode: + return self.find_entry(entryid).autostart() + else: + try: + return self.find_entry(entryid).autostart() + except Exception: + return self.module.exit_json(changed=True) + + def set_autostart(self, entryid, val): + if not self.module.check_mode: + return self.find_entry(entryid).setAutostart(val) + else: + try: + state = self.find_entry(entryid).autostart() + except Exception: + return self.module.exit_json(changed=True) + if bool(state) != val: + return self.module.exit_json(changed=True) + + def refresh(self, entryid): + return self.find_entry(entryid).refresh() + + def get_persistent(self, entryid): + state = self.find_entry(entryid).isPersistent() + return ENTRY_STATE_PERSISTENT_MAP.get(state, "unknown") + + def define_from_xml(self, entryid, xml): + if not self.module.check_mode: + return self.conn.storagePoolDefineXML(xml) + else: + try: + self.find_entry(entryid) + except Exception: + return self.module.exit_json(changed=True) + + +class VirtStoragePool(object): + + def __init__(self, uri, module): + self.module = module + self.uri = uri + self.conn = LibvirtConnection(self.uri, self.module) + + def get_pool(self, entryid): + return self.conn.find_entry(entryid) + + def list_pools(self, state=None): + results = [] + for entry in self.conn.find_entry(-1): + if state: + if state == self.conn.get_status2(entry): + results.append(entry.name()) + else: + results.append(entry.name()) + return results + + def state(self): + results = [] + for entry in self.list_pools(): + state_blurb = self.conn.get_status(entry) + results.append("%s %s" % (entry, state_blurb)) + return results + + def autostart(self, entryid): + return self.conn.set_autostart(entryid, True) + + def get_autostart(self, entryid): + return self.conn.get_autostart2(entryid) + + def set_autostart(self, entryid, state): + return self.conn.set_autostart(entryid, state) + + def create(self, entryid): + return self.conn.create(entryid) + + def start(self, entryid): + return self.conn.create(entryid) + + def stop(self, entryid): + return self.conn.destroy(entryid) + + def destroy(self, entryid): + return self.conn.destroy(entryid) + + def undefine(self, entryid): + return self.conn.undefine(entryid) + + def status(self, entryid): + return self.conn.get_status(entryid) + + def get_xml(self, entryid): + return self.conn.get_xml(entryid) + + def define(self, entryid, xml): + return self.conn.define_from_xml(entryid, xml) + + def build(self, entryid, flags): + return self.conn.build(entryid, ENTRY_BUILD_FLAGS_MAP.get(flags, 0)) + + def delete(self, entryid, flags): + return self.conn.delete(entryid, ENTRY_DELETE_FLAGS_MAP.get(flags, 0)) + + def refresh(self, entryid): + return self.conn.refresh(entryid) + + def info(self): + return self.facts(facts_mode='info') + + def facts(self, facts_mode='facts'): + results = dict() + for entry in self.list_pools(): + results[entry] = dict() + if self.conn.find_entry(entry): + data = self.conn.get_info(entry) + # 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. + results[entry] = { + "status": ENTRY_STATE_INFO_MAP.get(data[0], "unknown"), + "size_total": str(data[1]), + "size_used": str(data[2]), + "size_available": str(data[3]), + } + results[entry]["autostart"] = self.conn.get_autostart(entry) + results[entry]["persistent"] = self.conn.get_persistent(entry) + results[entry]["state"] = self.conn.get_status(entry) + results[entry]["type"] = self.conn.get_type(entry) + results[entry]["uuid"] = self.conn.get_uuid(entry) + if self.conn.find_entry(entry).isActive(): + results[entry]["volume_count"] = self.conn.get_volume_count(entry) + results[entry]["volumes"] = list() + for volume in self.conn.get_volume_names(entry): + results[entry]["volumes"].append(volume) + else: + results[entry]["volume_count"] = -1 + + try: + results[entry]["path"] = self.conn.get_path(entry) + except ValueError: + pass + + try: + results[entry]["host"] = self.conn.get_host(entry) + except ValueError: + pass + + try: + results[entry]["source_path"] = self.conn.get_source_path(entry) + except ValueError: + pass + + try: + results[entry]["format"] = self.conn.get_format(entry) + except ValueError: + pass + + try: + devices = self.conn.get_devices(entry) + results[entry]["devices"] = devices + except ValueError: + pass + + else: + results[entry]["state"] = self.conn.get_status(entry) + + facts = dict() + if facts_mode == 'facts': + facts["ansible_facts"] = dict() + facts["ansible_facts"]["ansible_libvirt_pools"] = results + elif facts_mode == 'info': + facts['pools'] = results + return facts + + +def core(module): + + state = module.params.get('state', None) + name = module.params.get('name', None) + command = module.params.get('command', None) + uri = module.params.get('uri', None) + xml = module.params.get('xml', None) + autostart = module.params.get('autostart', None) + mode = module.params.get('mode', None) + + v = VirtStoragePool(uri, module) + res = {} + + if state and command == 'list_pools': + res = v.list_pools(state=state) + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + if state: + if not name: + module.fail_json(msg="state change requires a specified name") + + res['changed'] = False + if state in ['active']: + if v.status(name) != 'active': + res['changed'] = True + res['msg'] = v.start(name) + elif state in ['present']: + try: + v.get_pool(name) + except EntryNotFound: + if not xml: + module.fail_json(msg="storage pool '" + name + "' not present, but xml not specified") + v.define(name, xml) + res = {'changed': True, 'created': name} + elif state in ['inactive']: + entries = v.list_pools() + if name in entries: + if v.status(name) != 'inactive': + res['changed'] = True + res['msg'] = v.destroy(name) + elif state in ['undefined', 'absent']: + entries = v.list_pools() + if name in entries: + if v.status(name) != 'inactive': + v.destroy(name) + res['changed'] = True + res['msg'] = v.undefine(name) + elif state in ['deleted']: + entries = v.list_pools() + if name in entries: + if v.status(name) != 'inactive': + v.destroy(name) + v.delete(name, mode) + res['changed'] = True + res['msg'] = v.undefine(name) + else: + module.fail_json(msg="unexpected state") + + return VIRT_SUCCESS, res + + if command: + if command in ENTRY_COMMANDS: + if not name: + module.fail_json(msg="%s requires 1 argument: name" % command) + if command == 'define': + if not xml: + module.fail_json(msg="define requires xml argument") + try: + v.get_pool(name) + except EntryNotFound: + v.define(name, xml) + res = {'changed': True, 'created': name} + return VIRT_SUCCESS, res + elif command == 'build': + res = v.build(name, mode) + if not isinstance(res, dict): + res = {'changed': True, command: res} + return VIRT_SUCCESS, res + elif command == 'delete': + res = v.delete(name, mode) + if not isinstance(res, dict): + res = {'changed': True, command: res} + return VIRT_SUCCESS, res + res = getattr(v, command)(name) + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + elif hasattr(v, command): + res = getattr(v, command)() + if not isinstance(res, dict): + res = {command: res} + return VIRT_SUCCESS, res + + else: + module.fail_json(msg="Command %s not recognized" % command) + + if autostart is not None: + if not name: + module.fail_json(msg="state change requires a specified name") + + res['changed'] = False + if autostart: + if not v.get_autostart(name): + res['changed'] = True + res['msg'] = v.set_autostart(name, True) + else: + if v.get_autostart(name): + res['changed'] = True + res['msg'] = v.set_autostart(name, False) + + return VIRT_SUCCESS, res + + module.fail_json(msg="expected state or command parameter to be specified") + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + name=dict(aliases=['pool']), + state=dict(choices=['active', 'inactive', 'present', 'absent', 'undefined', 'deleted']), + command=dict(choices=ALL_COMMANDS), + uri=dict(default='qemu:///system'), + xml=dict(), + autostart=dict(type='bool'), + mode=dict(choices=ALL_MODES), + ), + supports_check_mode=True + ) + + if not HAS_VIRT: + module.fail_json( + msg='The `libvirt` module is not importable. Check the requirements.' + ) + + if not HAS_XML: + module.fail_json( + msg='The `lxml` module is not importable. Check the requirements.' + ) + + rc = VIRT_SUCCESS + try: + rc, result = core(module) + except Exception as e: + module.fail_json(msg=str(e)) + + if rc != 0: # something went wrong emit the msg + module.fail_json(rc=rc, msg=result) + else: + module.exit_json(**result) + + +if __name__ == '__main__': + main() |