summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/libvirt/plugins/modules
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/libvirt/plugins/modules')
-rw-r--r--ansible_collections/community/libvirt/plugins/modules/virt.py563
-rw-r--r--ansible_collections/community/libvirt/plugins/modules/virt_net.py647
-rw-r--r--ansible_collections/community/libvirt/plugins/modules/virt_pool.py707
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()