Added xen_orchestra module

This commit is contained in:
Samori Gorse 2024-06-06 15:31:58 +02:00
commit 1e12684eb9

View file

@ -8,48 +8,26 @@ __metaclass__ = type
DOCUMENTATION = ''' DOCUMENTATION = '''
name: xen_orchestra name: xen_orchestra
short_description: Xen Orchestra inventory source short_description: Management of instances on Xen Orchestra
version_added: 4.1.0 version_added: 4.1.0
author: author:
- Dom Del Nano (@ddelnano) <ddelnano@gmail.com>
- Samori Gorse (@shinuza) <samorigorse@gmail.com> - Samori Gorse (@shinuza) <samorigorse@gmail.com>
requirements: requirements:
- websocket-client >= 1.0.0 - websocket-client >= 1.0.0
description: description:
- Get inventory hosts from a Xen Orchestra deployment. - Allows you to create/delete/restart/stop instances on Xen Orchestra
- 'Uses a configuration file as an inventory source, it must end in C(.xen_orchestra.yml) or C(.xen_orchestra.yaml).'
extends_documentation_fragment:
- constructed
- inventory_cache
options: options:
plugin:
description: The name of this plugin, it should always be set to V(community.general.xen_orchestra) for this plugin to recognize it as its own.
required: true
choices: ['community.general.xen_orchestra']
type: str
api_host: api_host:
description: description: API host to XOA API.
- API host to XOA API.
- If the value is not specified in the inventory configuration, the value of environment variable E(ANSIBLE_XO_HOST) will be used instead.
type: str type: str
env:
- name: ANSIBLE_XO_HOST
user: user:
description: description: Xen Orchestra user.
- Xen Orchestra user.
- If the value is not specified in the inventory configuration, the value of environment variable E(ANSIBLE_XO_USER) will be used instead.
required: true required: true
type: str type: str
env:
- name: ANSIBLE_XO_USER
password: password:
description: description: Xen Orchestra password.
- Xen Orchestra password.
- If the value is not specified in the inventory configuration, the value of environment variable E(ANSIBLE_XO_PASSWORD) will be used instead.
required: true required: true
type: str type: str
env:
- name: ANSIBLE_XO_PASSWORD
validate_certs: validate_certs:
description: Verify TLS certificate if using HTTPS. description: Verify TLS certificate if using HTTPS.
type: boolean type: boolean
@ -58,22 +36,69 @@ DOCUMENTATION = '''
description: Use wss when connecting to the Xen Orchestra API description: Use wss when connecting to the Xen Orchestra API
type: boolean type: boolean
default: true default: true
state:
description: State in which the Virtual Machine should be
choices: ['present', 'started', 'absent', 'stopped', 'restarted']
default: present
label:
description: Label of the Virtual Machine to create, can be used when O(state=present)
type: boolean
default: false
description:
description: Description of the Virtual Machine to create, can be used when O(state=present)
type: boolean
default: false
boot_after_create:
description: Boot Virtual Machine after creation, can be used when O(state=present)
type: boolean
default: false
''' '''
EXAMPLES = ''' EXAMPLES = r'''
# file must be named xen_orchestra.yaml or xen_orchestra.yml - name: Create a new virtual machine
plugin: community.general.xen_orchestra community.general.xen_orchestra:
api_host: 192.168.1.255 api_host: xen-orchestra.lab
user: xo user: user
password: xo_pwd password: passw0rd
validate_certs: true validate_certs: no
use_ssl: true state: present
groups: template: 355ee47d-ff4c-4924-3db2-fd86ae629676-a3d70e4d-c5ac-4dfb-999b-30a0a7efe546
kube_nodes: "'kube_node' in tags" label: This is a test from ansible
compose: description: This is a test from ansible
ansible_port: 2222 boot_after_create: no
- name: Start an existing virtual machine
community.general.xen_orchestra:
api_host: xen-orchestra.lab
user: user
password: passw0rd
validate_certs: no
state: started
- name: Stop an existing virtual machine
community.general.xen_orchestra:
api_host: xen-orchestra.lab
user: user
password: passw0rd
validate_certs: no
state: stop
- name: Restart an existing virtual machine
community.general.xen_orchestra:
api_host: xen-orchestra.lab
user: user
password: passw0rd
validate_certs: no
state: stopped
- name: Delete a virtual machine
community.general.xen_orchestra:
api_host: xen-orchestra.lab
user: user
password: passw0rd
validate_certs: no
state: absent
''' '''
import json import json
@ -96,18 +121,8 @@ try:
except ImportError as e: except ImportError as e:
HAS_WEBSOCKET = False HAS_WEBSOCKET = False
OBJECT_NOT_FOUND = 1
HALTED = 'Halted' VM_STATE_ERROR = 13
PAUSED = 'Paused'
RUNNING = 'Running'
SUSPENDED = 'Suspended'
POWER_STATES = [RUNNING, HALTED, SUSPENDED, PAUSED]
HOST_GROUP = 'xo_hosts'
POOL_GROUP = 'xo_pools'
def clean_group_name(label):
return label.lower().replace(' ', '-').replace('-', '_')
class XenOrchestra(object): class XenOrchestra(object):
@ -115,7 +130,7 @@ class XenOrchestra(object):
NAME = 'community.general.xen_orchestra' NAME = 'community.general.xen_orchestra'
CALL_TIMEOUT = 100 CALL_TIMEOUT = 100
"""Number of 1/10ths of a second to wait before method call times out.""" '''Number of 1/10ths of a second to wait before method call times out.'''
def __init__(self, module): def __init__(self, module):
@ -124,8 +139,8 @@ class XenOrchestra(object):
self.con = None self.con = None
self.module = module self.module = module
self.create_connection(module['api_host']) self.create_connection(module.params['api_host'])
self.login(module['user'], module['password']) self.login(module.params['user'], module.params['password'])
@property @property
def pointer(self): def pointer(self):
@ -133,8 +148,8 @@ class XenOrchestra(object):
return self.counter return self.counter
def create_connection(self, xoa_api_host): def create_connection(self, xoa_api_host):
validate_certs = self.module['validate_certs'] validate_certs = self.module.params['validate_certs']
use_ssl = self.module['use_ssl'] use_ssl = self.module.params['use_ssl']
proto = 'wss' if use_ssl else 'ws' proto = 'wss' if use_ssl else 'ws'
sslopt = None if validate_certs else {'cert_reqs': ssl.CERT_NONE} sslopt = None if validate_certs else {'cert_reqs': ssl.CERT_NONE}
@ -142,7 +157,7 @@ class XenOrchestra(object):
'{0}://{1}/api/'.format(proto, xoa_api_host), sslopt=sslopt) '{0}://{1}/api/'.format(proto, xoa_api_host), sslopt=sslopt)
def call(self, method, params): def call(self, method, params):
"""Calls a method on the XO server with the provided parameters.""" '''Calls a method on the XO server with the provided parameters.'''
id = self.pointer id = self.pointer
self.conn.send(json.dumps({ self.conn.send(json.dumps({
'id': id, 'id': id,
@ -169,15 +184,48 @@ class XenOrchestra(object):
}) })
if 'error' in answer: if 'error' in answer:
raise AnsibleError( raise self.module.fail_json(
'Could not connect: {0}'.format(answer['error'])) 'Could not connect: {0}'.format(answer['error']))
def stop_vm(self, vm_uid): return answer['result']
answer = self.call('vm.stop', {'id': vm_uid})
def create_vm(self):
params = {
'template': self.module.params['template'],
'name_label': self.module.params['label'],
'bootAfterCreate': self.module.params.get('boot_after_create', False)
}
description = self.module.params.get('description')
if description:
params['name_description'] = description
answer = self.call('vm.create', params)
if 'error' in answer: if 'error' in answer:
raise AnsibleError( raise self.module.fail_json(
'Could not request: {0}'.format(answer['error'])) 'Could not create vm: {0}'.format(answer['error']))
return answer['result']
def restart_vm(self, vm_uid):
answer = self.call('vm.restart', {'id': vm_uid, 'force': True })
if 'error' in answer:
raise self.module.fail_json(
'Could not restart vm: {0}'.format(answer['error']))
return answer['result']
def stop_vm(self, vm_uid):
answer = self.call('vm.stop', {'id': vm_uid, 'force': True})
if 'error' in answer:
# VM is not paused, suspended or running
if answer['error']['code'] == VM_STATE_ERROR:
return False
raise self.module.fail_json(
'Could not stop vm: {0}'.format(answer['error']))
return answer['result'] return answer['result']
@ -185,22 +233,30 @@ class XenOrchestra(object):
answer = self.call('vm.start', {'id': vm_uid}) answer = self.call('vm.start', {'id': vm_uid})
if 'error' in answer: if 'error' in answer:
raise AnsibleError( # VM is already started, nothing to do
'Could not request: {0}'.format(answer['error'])) if answer['error']['code'] == VM_STATE_ERROR:
return False
raise self.module.fail_json(
'Could not start vm: {0}'.format(answer['error']))
return answer['result'] return answer['result']
def get_object(self, name): def delete_vm(self, vm_uid):
answer = self.call('xo.getAllObjects', {'filter': {'type': name}}) answer = self.call('vm.delete', {'id': vm_uid})
if 'error' in answer: if 'error' in answer:
raise AnsibleError( if answer['error']['code'] == OBJECT_NOT_FOUND:
'Could not request: {0}'.format(answer['error'])) return False
raise self.module.fail_json(
'Could not delete vm: {0}'.format(answer['error']))
return answer['result'] return answer['result']
def main(): def main():
if not HAS_WEBSOCKET:
raise AnsibleError('This module requires websocket-client 1.0.0 or higher: '
'https://github.com/websocket-client/websocket-client.')
module_args = dict( module_args = dict(
api_host=dict(type='str', required=True), api_host=dict(type='str', required=True),
user=dict(type='str', required=True), user=dict(type='str', required=True),
@ -208,25 +264,49 @@ def main():
validate_certs=dict(type='bool', default=True), validate_certs=dict(type='bool', default=True),
use_ssl=dict(type='bool', default=True), use_ssl=dict(type='bool', default=True),
vm_uid=dict(type='str'), vm_uid=dict(type='str'),
state=dict(default='present', choices=['absent', 'stopped', 'started', 'restarted']), template=dict(type='str'),
label=dict(type='str'),
description=dict(type='str'),
boot_after_create=dict(type='bool', default=False),
state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted']),
) )
xen_orchestra = XenOrchestra()
module = AnsibleModule( module = AnsibleModule(
argument_spec=module_args, argument_spec=module_args,
required_one_of=[('api_password', 'api_token_id')], required_if=[
('state', 'present', ['template', 'label']),
('state', 'absent', ('vm_uid')),
('state', 'started', ('vm_uid')),
('state', 'restarted', ('vm_uid')),
('state', 'stopped', ('vm_uid')),
],
) )
xen_orchestra = XenOrchestra(module)
state = module.params['state'] state = module.params['state']
vm_uid = module.params['vm_uid'] vm_uid = module.params['vm_uid']
if state == 'stopped': if state == 'stopped':
xen_orchestra.stop_vm(vm_uid) result = xen_orchestra.stop_vm(vm_uid)
module.exit_json(changed=True) module.exit_json(changed=result)
if state == 'started': if state == 'started':
xen_orchestra.start_vm(vm_uid) result = xen_orchestra.start_vm(vm_uid)
module.exit_json(changed=True) module.exit_json(changed=result)
if state == 'restarted':
result = xen_orchestra.restart_vm(vm_uid)
module.exit_json(changed=result)
if state == 'absent':
result = xen_orchestra.delete_vm(vm_uid)
module.exit_json(changed=result)
if state == 'present':
result = xen_orchestra.create_vm()
module.exit_json(changed=True, vm_uid=result)
if __name__ == '__main__': if __name__ == '__main__':
main() main()