Skip to content

Instantly share code, notes, and snippets.

@frock81
Last active August 24, 2020 13:36
Show Gist options
  • Save frock81/16265f9c40399d96b4bebe942ca78969 to your computer and use it in GitHub Desktop.
Save frock81/16265f9c40399d96b4bebe942ca78969 to your computer and use it in GitHub Desktop.
Ansible module libvirt_domain
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Fabio Rocha <frock81@yahoo.com.br>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: libvirt_domain
short_description: Manages a Libvirt domain
version_added: "2.9"
description:
- "Manages domain resources (cpus, memory, etc), persistence, state and
autostart."
options:
name:
description:
- Name of the Libvirt managed domain.
required: true
type: str
domain_type:
description:
- Type of domain.
required: False
default: kvm
choices:
- kvm
- qemu
- xen
- lxc
type: str
state:
description:
- The desired state for the domain.
- For I(state=stopped) see I(stop_method).
- For I(state=restarted) see I(restart_method).
required: false
default: running
choices:
- running
- stopped
- restarted
- paused
- saved
type: str
stop_method:
description:
- "The method used to stop the domain. Shutdown and destroy
are analogous to Libvirt. It may succeed or not
depending on the guest."
- "Module (default) will try to shutdown and if it do not
succeed until a timeout it will destroy."
required: false
default: module
choices:
- module
- shutdown
- destroy
type: str
restart_method:
description:
- The method used to restart the domain.
- "For I(persistent=no) the valid methods are
I(reboot_method=reboot) or I(reboot_method=reset)."
- "Module (default) will try to shutdown and if it do not
succeed until a timeout it will destroy and start|create
accordingly to the desired state."
required: false
default: module
choices:
- module
- reboot
- reset
type: str
apply_changes:
description:
- "Some domain modifications are not applied until the next
restart of the domain. Restart calling restart (either
with reboot or reset Libvirt command) is not enough. If
this parameter is set to yes, the module will try a
shutdown followed by a destroy if a timeout expires."
required: false
default: yes
type: bool
poll:
description:
- Poll interval to watch changes to take effect in seconds.
required: false
default: 1
type: int
delay:
description:
- Delay interval before polling for changes in seconds.
required: false
default: 1
type: int
timeout:
description:
- "Timeout for operations (stop|restart|apply_changes) to complete
in seconds."
required: false
default: 120
type: int
save_file:
description:
- The file where to save domain state.
- "Required when I(state=saved) or when one want to restore a saved
domain with I(state=running)."
required: false
type: 'str'
persistent:
description:
- The domain should be persisted or not.
required: false
default: yes
type: bool
autostart:
description:
- The domain should be started with the host or not.
required: false
default: yes
type: bool
resources:
description:
- The domain resources like cpus, memory, etc.
required: false
default:
uuid: "{{ name | to_uuid }}"
cpu: host
vcpus: 1
maxcpus: 1
maxmemory: 512
memory: 512
memory_unit: MiB
console: yes
type: dict
suboptions:
uuid: "{{ name | to_uuid }}"
cpu: host
vcpus: 1
maxcpus: 1
maxmemory: 512
memory: 512
memory_unit: MiB
console: yes
extends_documentation_fragment: []
requirements:
- python >= 2.6
- libvirt-python
author:
- Fabio Rocha (@frock81)
'''
EXAMPLES = '''
# Make a persistent domain with default resources and start it.
- name: Create a domain with default options
libvirt_domain:
name: vm-foo
'''
RETURN = ''' # '''
from ansible.module_utils.basic import AnsibleModule
import re
import libvirt
class LibvirtConnector:
"""Connector to communicate with Libvirt
Attributes:
_conn (libvirt.virConnect): libvirt connection
Methods:
close_connection
"""
def __init__(self, connection_uri=None):
self._conn = libvirt.open(connection_uri)
def close_connection(self):
"""Close Libvirt connection"""
self._conn.close()
class LibvirtDomain():
"""Libvirt domain class
Attributes:
exists (bool): if the domain exists in the hypervisor
vir_domain (libvirt.virDomain|None): libvirt domain returned by
lookup.
"""
def __init__(self, libvirt_connector, domain_name):
try:
# If the domain is not found an error will be raised.
self.vir_domain = libvirt_connector._conn.lookupByName(domain_name)
self.exists = True
except libvirt.libvirtError as e:
if str(e).find('Domain not found') != -1:
self.vir_domain = None
self.exists = False
return
raise
class ParameterValidationError(Exception):
"""Parameters validatiorn errors
Parameters:
parameter (str): parameter name
message (str): custom message
"""
def __init__(self, parameter, message=''):
self.parameter = parameter
if not message:
self.message = "The parameter '{0}' could not be validated."\
.format(parameter)
else:
self.message = message
super().__init__(self.message)
def validate_user_input(domain_name, **opt_args):
"""Validate user input
Parameters:
domain_name (str): domain name
Other Parameters:
Returns:
(bool, str, str): Returns true or false depdending if validation
succeeded or not, the attribute which failed to validade, in
the case of validation failure, and an error message.
"""
domain_name_validation = validate_user_input_domain_name(domain_name)
if not domain_name_validation[0]:
raise ParameterValidationError(parameter='name',
message=domain_name_validation[1])
return (True, '', '')
def validate_user_input_domain_name(domain_name):
"""Validate domain name
Parameters:
name (str): the value of the domain name to be validated
Returns:
(bool, str): wheter the name was validated or not together with an
error message in the case it did not validate.
"""
# Libvirt documentation stats that the domain name must consist of
# alpha-numeric characters only, but it works with dashes and
# underscores.
regexp = '^[a-zA-Z0-9-_]+$'
if not re.search(regexp, domain_name):
return (False, "The domain name must contain only alphanumeric, dashes "
"or underscores characters, got: '{0}'".format(domain_name))
return (True, '')
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
name=dict(
type='str',
required=True
),
domain_type=dict(
required=False,
default='kvm',
choices=[
'kvm',
'qemu',
'xen',
'lxc'
],
type=str
),
state=dict(
required=False,
default='running',
choices=[
'running',
'stopped',
'restarted',
'paused',
'saved'
],
type='str'
),
stop_method=dict(
required=False,
default='module',
choices=[
'module',
'shutdown',
'destroy'
],
type='str'
),
restart_method=dict(
required=False,
default='module',
choices=[
'module',
'reboot',
'reset'
],
type='str'
),
apply_changes=dict(
required=False,
default=True,
type='bool'
),
poll=dict(
required=False,
default=1,
type='int'
),
delay=dict(
required=False,
default=1,
type='int'
),
timeout=dict(
required=False,
default=240,
type='int'
),
save_file=dict(
required=False,
type='str'
),
persistent=dict(
required=False,
default=True,
type='bool'
),
autostart=dict(
required=False,
default=True,
type='bool'
),
resources=dict(
required=False,
default=dict(
uuid="",
cpu='host',
vcpus=1,
maxcpus=1,
maxmemory=512,
memory=512,
memory_unit='MiB',
console=True
),
type='dict',
suboptions=dict(
uuid="",
cpu='host',
vcpus=1,
maxcpus=1,
maxmemory=512,
memory=512,
memory_unit='MiB',
console=True
)
)
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=False
)
domain_name = module.params['name']
# seed the result dict in the object
# we primarily care about changed and state
# change is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
name=domain_name,
meta=module.params
)
try:
#=======================================================================
# Early user input validation layer.
#=======================================================================
# The part not handled by the module.
validate_user_input(domain_name=domain_name)
#=======================================================================
# Information gathering layer.
#=======================================================================
try:
libvirt_connector = LibvirtConnector()
except libvirt.libvirtError as e:
module.fail_json(msg=str(e), **result)
# Check if domain exists.
# domain_exists = check_domain_existance(
# libvirt_connection=libvirt_connector._conn,
# domain_name=domain_name
# )
libvirt_domain = LibvirtDomain(libvirt_connector=libvirt_connector,
domain_name=domain_name)
module.exit_json(vir_domain=str(libvirt_domain.vir_domain), **result)
#=======================================================================
# Validation for transitions.
#=======================================================================
#=======================================================================
# Couser of action layer.
#=======================================================================
#---------------------------------------------------------------
# Persistence
#---------------------------------------------------------------
#---------------------------------------------------------------
# State
#---------------------------------------------------------------
#---------------------------------------------------------------
# Resources
#---------------------------------------------------------------
#---------------------------------------------------------------
# Autostart
#---------------------------------------------------------------
#=======================================================================
# Operations validation, last layer.
#=======================================================================
#=======================================================================
# Apply operations.
#=======================================================================
except ParameterValidationError as e:
module.fail_json(msg=str(e), **result)
finally:
if libvirt_connector:
libvirt_connector.close_connection()
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment