mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Remove libvirt/virt content (#14)
This content has been moved to: https://github.com/ansible-collections/libvirt
This commit is contained in:
parent
8532c595fd
commit
0ffe9fa6c4
22 changed files with 0 additions and 2416 deletions
|
@ -1,181 +0,0 @@
|
|||
# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com>
|
||||
# (c) 2013, Michael Scherer <misc@zarb.org>
|
||||
# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
# (c) 2017 Ansible Project
|
||||
# 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 = '''
|
||||
author: Michael Scherer <misc@zarb.org>
|
||||
connection: libvirt_lxc
|
||||
short_description: Run tasks in lxc containers via libvirt
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing lxc container using libvirt
|
||||
options:
|
||||
remote_addr:
|
||||
description:
|
||||
- Container identifier
|
||||
default: The set user as per docker's configuration
|
||||
vars:
|
||||
- name: ansible_host
|
||||
- name: ansible_libvirt_lxc_host
|
||||
'''
|
||||
|
||||
import distutils.spawn
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local lxc based connections '''
|
||||
|
||||
transport = 'community.general.libvirt_lxc'
|
||||
has_pipelining = True
|
||||
# su currently has an undiagnosed issue with calculating the file
|
||||
# checksums (so copy, for instance, doesn't work right)
|
||||
# Have to look into that before re-enabling this
|
||||
default_user = 'root'
|
||||
has_tty = False
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
self.lxc = self._play_context.remote_addr
|
||||
|
||||
self.virsh = self._search_executable('virsh')
|
||||
|
||||
self._check_domain(self.lxc)
|
||||
|
||||
def _search_executable(self, executable):
|
||||
cmd = distutils.spawn.find_executable(executable)
|
||||
if not cmd:
|
||||
raise AnsibleError("%s command not found in PATH") % executable
|
||||
return cmd
|
||||
|
||||
def _check_domain(self, domain):
|
||||
p = subprocess.Popen([self.virsh, '-q', '-c', 'lxc:///', 'dominfo', to_bytes(domain)],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p.communicate()
|
||||
if p.returncode:
|
||||
raise AnsibleError("%s is not a lxc defined in libvirt" % domain)
|
||||
|
||||
def _connect(self):
|
||||
''' connect to the lxc; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
display.vvv("THIS IS A LOCAL LXC DIR", host=self.lxc)
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
''' run a command on the chroot. This is only needed for implementing
|
||||
put_file() get_file() so that we don't have to read the whole file
|
||||
into memory.
|
||||
|
||||
compared to exec_command() it looses some niceties like being able to
|
||||
return the process's exit code immediately.
|
||||
'''
|
||||
executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else '/bin/sh'
|
||||
local_cmd = [self.virsh, '-q', '-c', 'lxc:///', 'lxc-enter-namespace']
|
||||
|
||||
if C.DEFAULT_LIBVIRT_LXC_NOSECLABEL:
|
||||
local_cmd += ['--noseclabel']
|
||||
|
||||
local_cmd += [self.lxc, '--', executable, '-c', cmd]
|
||||
|
||||
display.vvv("EXEC %s" % (local_cmd,), host=self.lxc)
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
return p
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
''' run a command on the chroot '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
exist in any given chroot. So for now we're choosing "/" instead.
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
'''
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
''' transfer a file from local to lxc '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.lxc)
|
||||
|
||||
out_path = shlex_quote(self._prefix_login_path(out_path))
|
||||
try:
|
||||
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
|
||||
if not os.fstat(in_file.fileno()).st_size:
|
||||
count = ' count=0'
|
||||
else:
|
||||
count = ''
|
||||
try:
|
||||
p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file)
|
||||
except OSError:
|
||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
||||
try:
|
||||
stdout, stderr = p.communicate()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
except IOError:
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
''' fetch a file from lxc to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.lxc)
|
||||
|
||||
in_path = shlex_quote(self._prefix_login_path(in_path))
|
||||
try:
|
||||
p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE))
|
||||
except OSError:
|
||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
||||
|
||||
with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file:
|
||||
try:
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
while chunk:
|
||||
out_file.write(chunk)
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
|
||||
def close(self):
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
|
@ -1,602 +0,0 @@
|
|||
#!/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
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: virt
|
||||
short_description: Manages virtual machines supported by libvirt
|
||||
description:
|
||||
- Manages virtual machines supported by I(libvirt).
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the guest VM being managed. Note that VM must be previously
|
||||
defined with xml.
|
||||
- This option is required unless I(command) is C(list_vms) or C(info).
|
||||
state:
|
||||
description:
|
||||
- Note that there may be some lag for state requests like C(shutdown)
|
||||
since these refer only to VM states. After starting a guest, it may not
|
||||
be immediately accessible.
|
||||
state and command are mutually exclusive except when command=list_vms. In
|
||||
this case all VMs in specified state will be listed.
|
||||
choices: [ destroyed, paused, running, shutdown ]
|
||||
command:
|
||||
description:
|
||||
- In addition to state management, various non-idempotent commands are available.
|
||||
choices: [ create, define, destroy, freemem, get_xml, info, list_vms, nodeinfo, pause, shutdown, start, status, stop, undefine, unpause, virttype ]
|
||||
autostart:
|
||||
description:
|
||||
- start VM at host startup.
|
||||
type: bool
|
||||
uri:
|
||||
description:
|
||||
- libvirt connection uri.
|
||||
default: qemu:///system
|
||||
xml:
|
||||
description:
|
||||
- XML document used with the define command.
|
||||
- Must be raw XML content using C(lookup). XML cannot be reference to a file.
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
- libvirt-python
|
||||
author:
|
||||
- Ansible Core Team
|
||||
- Michael DeHaan
|
||||
- Seth Vidal (@skvidal)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# a playbook task line:
|
||||
- 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 vm
|
||||
virt:
|
||||
command: define
|
||||
xml: "{{ lookup('template', 'container-template.xml.j2') }}"
|
||||
uri: 'lxc:///'
|
||||
- name: start vm
|
||||
virt:
|
||||
name: foo
|
||||
state: running
|
||||
uri: 'lxc:///'
|
||||
|
||||
# setting autostart on a qemu VM (default uri)
|
||||
- name: set autostart for a VM
|
||||
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
|
||||
virt:
|
||||
command: define
|
||||
xml: "{{ lookup('template', 'vm_template.xml.j2') }}"
|
||||
autostart: yes
|
||||
|
||||
# Listing VMs
|
||||
- name: list all VMs
|
||||
virt:
|
||||
command: list_vms
|
||||
register: all_vms
|
||||
|
||||
- name: list only running VMs
|
||||
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
|
||||
"""
|
||||
conn = self.conn
|
||||
|
||||
vms = []
|
||||
|
||||
# this block of code borrowed from virt-manager:
|
||||
# get working domain's name
|
||||
ids = conn.listDomainsID()
|
||||
for id in ids:
|
||||
vm = conn.lookupByID(id)
|
||||
vms.append(vm)
|
||||
# get defined domain
|
||||
names = conn.listDefinedDomains()
|
||||
for name in names:
|
||||
vm = conn.lookupByName(name)
|
||||
vms.append(vm)
|
||||
|
||||
if vmid == -1:
|
||||
return vms
|
||||
|
||||
for vm in vms:
|
||||
if vm.name() == vmid:
|
||||
return vm
|
||||
|
||||
raise VMNotFound("virtual machine %s not found" % vmid)
|
||||
|
||||
def shutdown(self, vmid):
|
||||
return self.find_vm(vmid).shutdown()
|
||||
|
||||
def pause(self, vmid):
|
||||
return self.suspend(vmid)
|
||||
|
||||
def unpause(self, vmid):
|
||||
return self.resume(vmid)
|
||||
|
||||
def suspend(self, vmid):
|
||||
return self.find_vm(vmid).suspend()
|
||||
|
||||
def resume(self, vmid):
|
||||
return self.find_vm(vmid).resume()
|
||||
|
||||
def create(self, vmid):
|
||||
return self.find_vm(vmid).create()
|
||||
|
||||
def destroy(self, vmid):
|
||||
return self.find_vm(vmid).destroy()
|
||||
|
||||
def undefine(self, vmid):
|
||||
return self.find_vm(vmid).undefine()
|
||||
|
||||
def get_status2(self, vm):
|
||||
state = vm.info()[0]
|
||||
return VIRT_STATE_NAME_MAP.get(state, "unknown")
|
||||
|
||||
def get_status(self, vmid):
|
||||
state = self.find_vm(vmid).info()[0]
|
||||
return VIRT_STATE_NAME_MAP.get(state, "unknown")
|
||||
|
||||
def nodeinfo(self):
|
||||
return self.conn.getInfo()
|
||||
|
||||
def get_type(self):
|
||||
return self.conn.getType()
|
||||
|
||||
def get_xml(self, vmid):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.XMLDesc(0)
|
||||
|
||||
def get_maxVcpus(self, vmid):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.maxVcpus()
|
||||
|
||||
def get_maxMemory(self, vmid):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.maxMemory()
|
||||
|
||||
def getFreeMemory(self):
|
||||
return self.conn.getFreeMemory()
|
||||
|
||||
def get_autostart(self, vmid):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.autostart()
|
||||
|
||||
def set_autostart(self, vmid, val):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.setAutostart(val)
|
||||
|
||||
def define_from_xml(self, xml):
|
||||
return self.conn.defineXML(xml)
|
||||
|
||||
|
||||
class Virt(object):
|
||||
|
||||
def __init__(self, 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'")
|
||||
found_name = re.search('<name>(.*)</name>', xml).groups()
|
||||
if found_name:
|
||||
domain_name = found_name[0]
|
||||
else:
|
||||
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 = v.get_vm(domain_name)
|
||||
except VMNotFound:
|
||||
existing_domain = None
|
||||
try:
|
||||
domain = v.define(xml)
|
||||
if existing_domain:
|
||||
# if we are here, then libvirt redefined existing domain as the doc promised
|
||||
if existing_domain.XMLDesc() != domain.XMLDesc():
|
||||
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.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', 'pause', '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()
|
|
@ -1,638 +0,0 @@
|
|||
#!/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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: virt_net
|
||||
author: "Maciej Delmanowski (@drybjed)"
|
||||
short_description: Manage libvirt network configuration
|
||||
description:
|
||||
- Manage I(libvirt) networks.
|
||||
options:
|
||||
name:
|
||||
required: true
|
||||
aliases: ['network']
|
||||
description:
|
||||
- name of the network being managed. Note that network must be previously
|
||||
defined with xml.
|
||||
state:
|
||||
required: false
|
||||
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's 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.
|
||||
command:
|
||||
required: false
|
||||
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 version 2.1
|
||||
autostart:
|
||||
required: false
|
||||
type: bool
|
||||
description:
|
||||
- Specify if a given network should be started automatically on system boot.
|
||||
uri:
|
||||
required: false
|
||||
default: "qemu:///system"
|
||||
description:
|
||||
- libvirt connection uri.
|
||||
xml:
|
||||
required: false
|
||||
description:
|
||||
- XML document used with the define command.
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "python-libvirt"
|
||||
- "python-lxml"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Define a new network
|
||||
- virt_net:
|
||||
command: define
|
||||
name: br_nat
|
||||
xml: '{{ lookup("template", "network/bridge.xml.j2") }}'
|
||||
|
||||
# Start a network
|
||||
- virt_net:
|
||||
command: create
|
||||
name: br_nat
|
||||
|
||||
# List available networks
|
||||
- virt_net:
|
||||
command: list_nets
|
||||
|
||||
# Get XML data of a specified network
|
||||
- virt_net:
|
||||
command: get_xml
|
||||
name: br_nat
|
||||
|
||||
# Stop a network
|
||||
- virt_net:
|
||||
command: destroy
|
||||
name: br_nat
|
||||
|
||||
# Undefine a network
|
||||
- virt_net:
|
||||
command: undefine
|
||||
name: br_nat
|
||||
|
||||
# Gather facts about networks
|
||||
# Facts will be available as 'ansible_libvirt_networks'
|
||||
- virt_net:
|
||||
command: facts
|
||||
|
||||
# Gather information about network managed by 'libvirt' remotely using uri
|
||||
- virt_net:
|
||||
command: info
|
||||
uri: '{{ item }}'
|
||||
with_items: '{{ libvirt_uris }}'
|
||||
register: networks
|
||||
|
||||
# Ensure that a network is active (needs to be defined and built first)
|
||||
- virt_net:
|
||||
state: active
|
||||
name: br_nat
|
||||
|
||||
# Ensure that a network is inactive
|
||||
- virt_net:
|
||||
state: inactive
|
||||
name: br_nat
|
||||
|
||||
# Ensure that a given network will be started at boot
|
||||
- virt_net:
|
||||
autostart: yes
|
||||
name: br_nat
|
||||
|
||||
# Disable autostart for a given network
|
||||
- virt_net:
|
||||
autostart: no
|
||||
name: br_nat
|
||||
|
||||
# Add a new host in the dhcp pool
|
||||
- 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:
|
||||
res = network.update(libvirt.VIR_NETWORK_UPDATE_COMMAND_ADD_LAST,
|
||||
libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST,
|
||||
-1, xml, libvirt.VIR_NETWORK_UPDATE_AFFECT_CURRENT)
|
||||
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:
|
||||
res = network.update(libvirt.VIR_NETWORK_UPDATE_COMMAND_MODIFY,
|
||||
libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST,
|
||||
-1, xml, libvirt.VIR_NETWORK_UPDATE_AFFECT_CURRENT)
|
||||
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
|
||||
)
|
||||
|
||||
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()
|
|
@ -1,711 +0,0 @@
|
|||
#!/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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: virt_pool
|
||||
author: "Maciej Delmanowski (@drybjed)"
|
||||
short_description: Manage libvirt storage pools
|
||||
description:
|
||||
- Manage I(libvirt) storage pools.
|
||||
options:
|
||||
name:
|
||||
required: false
|
||||
aliases: [ "pool" ]
|
||||
description:
|
||||
- name of the storage pool being managed. Note that pool must be previously
|
||||
defined with xml.
|
||||
state:
|
||||
required: false
|
||||
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's 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.
|
||||
command:
|
||||
required: false
|
||||
choices: [ "define", "build", "create", "start", "stop", "destroy",
|
||||
"delete", "undefine", "get_xml", "list_pools", "facts",
|
||||
"info", "status" ]
|
||||
description:
|
||||
- in addition to state management, various non-idempotent commands are available.
|
||||
See examples.
|
||||
autostart:
|
||||
required: false
|
||||
type: bool
|
||||
description:
|
||||
- Specify if a given storage pool should be started automatically on system boot.
|
||||
uri:
|
||||
required: false
|
||||
default: "qemu:///system"
|
||||
description:
|
||||
- I(libvirt) connection uri.
|
||||
xml:
|
||||
required: false
|
||||
description:
|
||||
- XML document used with the define command.
|
||||
mode:
|
||||
required: false
|
||||
choices: [ 'new', 'repair', 'resize', 'no_overwrite', 'overwrite', 'normal', 'zeroed' ]
|
||||
description:
|
||||
- Pass additional parameters to 'build' or 'delete' commands.
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "python-libvirt"
|
||||
- "python-lxml"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Define a new storage pool
|
||||
- virt_pool:
|
||||
command: define
|
||||
name: vms
|
||||
xml: '{{ lookup("template", "pool/dir.xml.j2") }}'
|
||||
|
||||
# Build a storage pool if it does not exist
|
||||
- virt_pool:
|
||||
command: build
|
||||
name: vms
|
||||
|
||||
# Start a storage pool
|
||||
- virt_pool:
|
||||
command: create
|
||||
name: vms
|
||||
|
||||
# List available pools
|
||||
- virt_pool:
|
||||
command: list_pools
|
||||
|
||||
# Get XML data of a specified pool
|
||||
- virt_pool:
|
||||
command: get_xml
|
||||
name: vms
|
||||
|
||||
# Stop a storage pool
|
||||
- virt_pool:
|
||||
command: destroy
|
||||
name: vms
|
||||
|
||||
# Delete a storage pool (destroys contents)
|
||||
- virt_pool:
|
||||
command: delete
|
||||
name: vms
|
||||
|
||||
# Undefine a storage pool
|
||||
- virt_pool:
|
||||
command: undefine
|
||||
name: vms
|
||||
|
||||
# Gather facts about storage pools
|
||||
# Facts will be available as 'ansible_libvirt_pools'
|
||||
- virt_pool:
|
||||
command: facts
|
||||
|
||||
# Gather information about pools managed by 'libvirt' remotely using uri
|
||||
- virt_pool:
|
||||
command: info
|
||||
uri: '{{ item }}'
|
||||
with_items: '{{ libvirt_uris }}'
|
||||
register: storage_pools
|
||||
|
||||
# Ensure that a pool is active (needs to be defined and built first)
|
||||
- virt_pool:
|
||||
state: active
|
||||
name: vms
|
||||
|
||||
# Ensure that a pool is inactive
|
||||
- virt_pool:
|
||||
state: inactive
|
||||
name: vms
|
||||
|
||||
# Ensure that a given pool will be started at boot
|
||||
- virt_pool:
|
||||
autostart: yes
|
||||
name: vms
|
||||
|
||||
# Disable autostart for a given pool
|
||||
- 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))
|
||||
return xml.xpath('/pool/target/path')[0].text
|
||||
|
||||
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]["path"] = self.conn.get_path(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]["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()
|
|
@ -1 +0,0 @@
|
|||
./cloud/misc/virt.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/misc/virt_net.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/misc/virt_pool.py
|
|
@ -1,37 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2013, Michael Scherer <misc@zarb.org>
|
||||
#
|
||||
# This file is part of Ansible,
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
import sys
|
||||
import json
|
||||
|
||||
result = {}
|
||||
result['all'] = {}
|
||||
|
||||
pipe = Popen(['virsh', '-q', '-c', 'lxc:///', 'list', '--name', '--all'], stdout=PIPE, universal_newlines=True)
|
||||
result['all']['hosts'] = [x[:-1] for x in pipe.stdout.readlines()]
|
||||
result['all']['vars'] = {}
|
||||
result['all']['vars']['ansible_connection'] = 'libvirt_lxc'
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == '--list':
|
||||
print(json.dumps(result))
|
||||
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
|
||||
print(json.dumps({'ansible_connection': 'libvirt_lxc'}))
|
||||
else:
|
||||
sys.stderr.write("Need an argument, either --list or --host <host>\n")
|
|
@ -1,6 +0,0 @@
|
|||
shippable/posix/group1
|
||||
skip/aix
|
||||
skip/freebsd
|
||||
skip/osx
|
||||
needs/privileged
|
||||
destructive
|
|
@ -1,9 +0,0 @@
|
|||
<network>
|
||||
<name>foobar</name>
|
||||
<forward mode='nat'/>
|
||||
<ip address='192.168.125.1' netmask='255.255.255.0'>
|
||||
<dhcp>
|
||||
<range start='192.168.125.2' end='192.168.125.254'/>
|
||||
</dhcp>
|
||||
</ip>
|
||||
</network>
|
|
@ -1,85 +0,0 @@
|
|||
---
|
||||
- include_vars: '{{ item }}'
|
||||
with_first_found:
|
||||
- "{{ ansible_distribution }}-{{ ansible_distribution_version}}.yml"
|
||||
- "{{ ansible_distribution }}-{{ ansible_distribution_major_version}}.yml"
|
||||
- "{{ ansible_distribution }}.yml"
|
||||
- "default.yml"
|
||||
|
||||
- block:
|
||||
- name: Install libvirt packages
|
||||
package:
|
||||
name: "{{ virt_net_packages }}"
|
||||
|
||||
- name: Start libvirt
|
||||
service:
|
||||
name: libvirtd
|
||||
state: started
|
||||
|
||||
- name: Define the foobar network
|
||||
virt_net:
|
||||
command: define
|
||||
name: foobar
|
||||
xml: '{{ lookup("file", "foobar.xml") }}'
|
||||
|
||||
- name: Define the foobar network (again)
|
||||
virt_net:
|
||||
command: define
|
||||
name: foobar
|
||||
xml: '{{ lookup("file", "foobar.xml") }}'
|
||||
register: second_virt_net_define
|
||||
|
||||
- name: Start the default network
|
||||
virt_net:
|
||||
uri: qemu:///system
|
||||
command: start
|
||||
name: foobar
|
||||
|
||||
- name: Start the default network (again)
|
||||
virt_net:
|
||||
uri: qemu:///system
|
||||
command: start
|
||||
name: foobar
|
||||
register: second_virt_net_start
|
||||
|
||||
- name: Get facts for default network
|
||||
virt_net:
|
||||
uri: qemu:///system
|
||||
command: facts
|
||||
name: foobar
|
||||
register: virt_net_facts
|
||||
|
||||
- name: Destroy the foobar network
|
||||
virt_net:
|
||||
command: destroy
|
||||
name: foobar
|
||||
|
||||
- name: Undefine the foobar network
|
||||
virt_net:
|
||||
command: undefine
|
||||
name: foobar
|
||||
register: second_virt_net_define
|
||||
|
||||
- name: Undefine the foobar network (again)
|
||||
virt_net:
|
||||
command: undefine
|
||||
name: foobar
|
||||
register: second_virt_net_undefine
|
||||
|
||||
- name: Ensure the second calls return "unchanged"
|
||||
assert:
|
||||
that:
|
||||
- "second_virt_net_start is not changed"
|
||||
- "second_virt_net_define is not changed"
|
||||
- "second_virt_net_undefine is not changed"
|
||||
|
||||
always:
|
||||
- name: Stop libvirt
|
||||
service:
|
||||
name: libvirtd
|
||||
state: stopped
|
||||
|
||||
- name: Remove only the libvirt packages
|
||||
package:
|
||||
name: "{{ virt_net_packages|select('match', '.*libvirt.*')|list }}"
|
||||
state: absent
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt-daemon
|
||||
- libvirt-daemon-system
|
||||
- python-libvirt
|
||||
- python-lxml
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt
|
||||
- libvirt-daemon
|
||||
- python3-libvirt
|
||||
- python3-lxml
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt
|
||||
- libvirt-daemon
|
||||
- libvirt-python
|
||||
- python-lxml
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt
|
||||
- libvirt-daemon
|
||||
- python3-libvirt
|
||||
- python3-lxml
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt-daemon
|
||||
- python-libvirt
|
||||
- python-lxml
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt-daemon
|
||||
- python-libvirt
|
||||
- python-lxml
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt-daemon
|
||||
- libvirt-daemon-system
|
||||
- python-libvirt
|
||||
- python-lxml
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
virt_net_packages:
|
||||
- libvirt-daemon
|
||||
- python-libvirt
|
||||
- python-lxml
|
|
@ -1,69 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2019, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
import pytest
|
||||
|
||||
from ansible_collections.community.general.plugins.modules.cloud.misc import virt_net
|
||||
|
||||
from ansible_collections.community.general.tests.unit.compat import mock
|
||||
|
||||
|
||||
virt_net.libvirt = None
|
||||
virt_net.HAS_VIRT = True
|
||||
|
||||
|
||||
class DummyNetwork():
|
||||
def __init__(self, name, isActive=True):
|
||||
self._name = name
|
||||
self._isActive = isActive
|
||||
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
def isActive(self):
|
||||
return self._isActive
|
||||
|
||||
|
||||
class DummyLibvirtConn():
|
||||
def __init__(self):
|
||||
self._network = [
|
||||
DummyNetwork("inactive_net", isActive=False),
|
||||
DummyNetwork("active_net", isActive=True)]
|
||||
|
||||
def listNetworks(self):
|
||||
return [i.name() for i in self._network]
|
||||
|
||||
def networkLookupByName(self, name):
|
||||
for i in self._network:
|
||||
if i.name() == name:
|
||||
return i
|
||||
|
||||
def listDefinedNetworks(self):
|
||||
return []
|
||||
|
||||
|
||||
class DummyLibvirt():
|
||||
VIR_ERR_NETWORK_EXIST = 'VIR_ERR_NETWORK_EXIST'
|
||||
|
||||
@classmethod
|
||||
def open(cls, uri):
|
||||
return DummyLibvirtConn()
|
||||
|
||||
class libvirtError(Exception):
|
||||
def __init__(self):
|
||||
self.error_code
|
||||
|
||||
def get_error_code(self):
|
||||
return self.error_code
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_libvirt(monkeypatch):
|
||||
monkeypatch.setattr(virt_net, 'libvirt', DummyLibvirt)
|
||||
return DummyLibvirt
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def virt_net_obj(dummy_libvirt):
|
||||
return virt_net.VirtNetwork('qemu:///nowhere', mock.MagicMock())
|
|
@ -1,30 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2019, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from ansible_collections.community.general.tests.unit.compat import mock
|
||||
|
||||
|
||||
def test_virt_net_create_already_active(virt_net_obj, dummy_libvirt):
|
||||
virt_net_obj.conn.create = mock.Mock()
|
||||
assert virt_net_obj.create("active_net") is None
|
||||
virt_net_obj.conn.create.assert_not_called()
|
||||
|
||||
|
||||
def test_virt_net_recreate(virt_net_obj, dummy_libvirt):
|
||||
virt_net_obj.conn.create = mock.Mock()
|
||||
dummy_libvirt.libvirtError.error_code = 'VIR_ERR_NETWORK_EXIST'
|
||||
virt_net_obj.conn.create.side_effect = dummy_libvirt.libvirtError
|
||||
assert virt_net_obj.create("active_net") is None
|
||||
|
||||
|
||||
def test_virt_stop_ignore_inactive(virt_net_obj):
|
||||
virt_net_obj.conn.destroy = mock.Mock()
|
||||
virt_net_obj.stop('inactive_net')
|
||||
virt_net_obj.conn.destroy.assert_not_called()
|
||||
|
||||
|
||||
def test_virt_stop_active(virt_net_obj, monkeypatch):
|
||||
virt_net_obj.conn.destroy = mock.Mock()
|
||||
virt_net_obj.stop('active_net')
|
||||
virt_net_obj.conn.destroy.assert_called_with('active_net')
|
Loading…
Reference in a new issue