mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-24 22:00:22 -07:00
Initial commit
This commit is contained in:
commit
aebc1b03fd
4861 changed files with 812621 additions and 0 deletions
281
plugins/modules/cloud/opennebula/one_host.py
Normal file
281
plugins/modules/cloud/opennebula/one_host.py
Normal file
|
@ -0,0 +1,281 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2018 www.privaz.io Valletech AB
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: one_host
|
||||
|
||||
short_description: Manages OpenNebula Hosts
|
||||
|
||||
|
||||
requirements:
|
||||
- pyone
|
||||
|
||||
description:
|
||||
- "Manages OpenNebula Hosts"
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Hostname of the machine to manage.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Takes the host to the desired lifecycle state.
|
||||
- If C(absent) the host will be deleted from the cluster.
|
||||
- If C(present) the host will be created in the cluster (includes C(enabled), C(disabled) and C(offline) states).
|
||||
- If C(enabled) the host is fully operational.
|
||||
- C(disabled), e.g. to perform maintenance operations.
|
||||
- C(offline), host is totally offline.
|
||||
choices:
|
||||
- absent
|
||||
- present
|
||||
- enabled
|
||||
- disabled
|
||||
- offline
|
||||
default: present
|
||||
im_mad_name:
|
||||
description:
|
||||
- The name of the information manager, this values are taken from the oned.conf with the tag name IM_MAD (name)
|
||||
default: kvm
|
||||
vmm_mad_name:
|
||||
description:
|
||||
- The name of the virtual machine manager mad name, this values are taken from the oned.conf with the tag name VM_MAD (name)
|
||||
default: kvm
|
||||
cluster_id:
|
||||
description:
|
||||
- The cluster ID.
|
||||
default: 0
|
||||
cluster_name:
|
||||
description:
|
||||
- The cluster specified by name.
|
||||
labels:
|
||||
description:
|
||||
- The labels for this host.
|
||||
template:
|
||||
description:
|
||||
- The template or attribute changes to merge into the host template.
|
||||
aliases:
|
||||
- attributes
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.opennebula
|
||||
|
||||
|
||||
author:
|
||||
- Rafael del Valle (@rvalle)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a new host in OpenNebula
|
||||
one_host:
|
||||
name: host1
|
||||
cluster_id: 1
|
||||
api_url: http://127.0.0.1:2633/RPC2
|
||||
|
||||
- name: Create a host and adjust its template
|
||||
one_host:
|
||||
name: host2
|
||||
cluster_name: default
|
||||
template:
|
||||
LABELS:
|
||||
- gold
|
||||
- ssd
|
||||
RESERVED_CPU: -100
|
||||
'''
|
||||
|
||||
# TODO: pending setting guidelines on returned values
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
# TODO: Documentation on valid state transitions is required to properly implement all valid cases
|
||||
# TODO: To be coherent with CLI this module should also provide "flush" functionality
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.opennebula import OpenNebulaModule
|
||||
|
||||
try:
|
||||
from pyone import HOST_STATES, HOST_STATUS
|
||||
except ImportError:
|
||||
pass # handled at module utils
|
||||
|
||||
|
||||
# Pseudo definitions...
|
||||
|
||||
HOST_ABSENT = -99 # the host is absent (special case defined by this module)
|
||||
|
||||
|
||||
class HostModule(OpenNebulaModule):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
argument_spec = dict(
|
||||
name=dict(type='str', required=True),
|
||||
state=dict(choices=['present', 'absent', 'enabled', 'disabled', 'offline'], default='present'),
|
||||
im_mad_name=dict(type='str', default="kvm"),
|
||||
vmm_mad_name=dict(type='str', default="kvm"),
|
||||
cluster_id=dict(type='int', default=0),
|
||||
cluster_name=dict(type='str'),
|
||||
labels=dict(type='list'),
|
||||
template=dict(type='dict', aliases=['attributes']),
|
||||
)
|
||||
|
||||
mutually_exclusive = [
|
||||
['cluster_id', 'cluster_name']
|
||||
]
|
||||
|
||||
OpenNebulaModule.__init__(self, argument_spec, mutually_exclusive=mutually_exclusive)
|
||||
|
||||
def allocate_host(self):
|
||||
"""
|
||||
Creates a host entry in OpenNebula
|
||||
Returns: True on success, fails otherwise.
|
||||
|
||||
"""
|
||||
if not self.one.host.allocate(self.get_parameter('name'),
|
||||
self.get_parameter('vmm_mad_name'),
|
||||
self.get_parameter('im_mad_name'),
|
||||
self.get_parameter('cluster_id')):
|
||||
self.fail(msg="could not allocate host")
|
||||
else:
|
||||
self.result['changed'] = True
|
||||
return True
|
||||
|
||||
def wait_for_host_state(self, host, target_states):
|
||||
"""
|
||||
Utility method that waits for a host state.
|
||||
Args:
|
||||
host:
|
||||
target_states:
|
||||
|
||||
"""
|
||||
return self.wait_for_state('host',
|
||||
lambda: self.one.host.info(host.ID).STATE,
|
||||
lambda s: HOST_STATES(s).name, target_states,
|
||||
invalid_states=[HOST_STATES.ERROR, HOST_STATES.MONITORING_ERROR])
|
||||
|
||||
def run(self, one, module, result):
|
||||
|
||||
# Get the list of hosts
|
||||
host_name = self.get_parameter("name")
|
||||
host = self.get_host_by_name(host_name)
|
||||
|
||||
# manage host state
|
||||
desired_state = self.get_parameter('state')
|
||||
if bool(host):
|
||||
current_state = host.STATE
|
||||
current_state_name = HOST_STATES(host.STATE).name
|
||||
else:
|
||||
current_state = HOST_ABSENT
|
||||
current_state_name = "ABSENT"
|
||||
|
||||
# apply properties
|
||||
if desired_state == 'present':
|
||||
if current_state == HOST_ABSENT:
|
||||
self.allocate_host()
|
||||
host = self.get_host_by_name(host_name)
|
||||
self.wait_for_host_state(host, [HOST_STATES.MONITORED])
|
||||
elif current_state in [HOST_STATES.ERROR, HOST_STATES.MONITORING_ERROR]:
|
||||
self.fail(msg="invalid host state %s" % current_state_name)
|
||||
|
||||
elif desired_state == 'enabled':
|
||||
if current_state == HOST_ABSENT:
|
||||
self.allocate_host()
|
||||
host = self.get_host_by_name(host_name)
|
||||
self.wait_for_host_state(host, [HOST_STATES.MONITORED])
|
||||
elif current_state in [HOST_STATES.DISABLED, HOST_STATES.OFFLINE]:
|
||||
if one.host.status(host.ID, HOST_STATUS.ENABLED):
|
||||
self.wait_for_host_state(host, [HOST_STATES.MONITORED])
|
||||
result['changed'] = True
|
||||
else:
|
||||
self.fail(msg="could not enable host")
|
||||
elif current_state in [HOST_STATES.MONITORED]:
|
||||
pass
|
||||
else:
|
||||
self.fail(msg="unknown host state %s, cowardly refusing to change state to enable" % current_state_name)
|
||||
|
||||
elif desired_state == 'disabled':
|
||||
if current_state == HOST_ABSENT:
|
||||
self.fail(msg='absent host cannot be put in disabled state')
|
||||
elif current_state in [HOST_STATES.MONITORED, HOST_STATES.OFFLINE]:
|
||||
if one.host.status(host.ID, HOST_STATUS.DISABLED):
|
||||
self.wait_for_host_state(host, [HOST_STATES.DISABLED])
|
||||
result['changed'] = True
|
||||
else:
|
||||
self.fail(msg="could not disable host")
|
||||
elif current_state in [HOST_STATES.DISABLED]:
|
||||
pass
|
||||
else:
|
||||
self.fail(msg="unknown host state %s, cowardly refusing to change state to disable" % current_state_name)
|
||||
|
||||
elif desired_state == 'offline':
|
||||
if current_state == HOST_ABSENT:
|
||||
self.fail(msg='absent host cannot be placed in offline state')
|
||||
elif current_state in [HOST_STATES.MONITORED, HOST_STATES.DISABLED]:
|
||||
if one.host.status(host.ID, HOST_STATUS.OFFLINE):
|
||||
self.wait_for_host_state(host, [HOST_STATES.OFFLINE])
|
||||
result['changed'] = True
|
||||
else:
|
||||
self.fail(msg="could not set host offline")
|
||||
elif current_state in [HOST_STATES.OFFLINE]:
|
||||
pass
|
||||
else:
|
||||
self.fail(msg="unknown host state %s, cowardly refusing to change state to offline" % current_state_name)
|
||||
|
||||
elif desired_state == 'absent':
|
||||
if current_state != HOST_ABSENT:
|
||||
if one.host.delete(host.ID):
|
||||
result['changed'] = True
|
||||
else:
|
||||
self.fail(msg="could not delete host from cluster")
|
||||
|
||||
# if we reach this point we can assume that the host was taken to the desired state
|
||||
|
||||
if desired_state != "absent":
|
||||
# manipulate or modify the template
|
||||
desired_template_changes = self.get_parameter('template')
|
||||
|
||||
if desired_template_changes is None:
|
||||
desired_template_changes = dict()
|
||||
|
||||
# complete the template with specific ansible parameters
|
||||
if self.is_parameter('labels'):
|
||||
desired_template_changes['LABELS'] = self.get_parameter('labels')
|
||||
|
||||
if self.requires_template_update(host.TEMPLATE, desired_template_changes):
|
||||
# setup the root element so that pyone will generate XML instead of attribute vector
|
||||
desired_template_changes = {"TEMPLATE": desired_template_changes}
|
||||
if one.host.update(host.ID, desired_template_changes, 1): # merge the template
|
||||
result['changed'] = True
|
||||
else:
|
||||
self.fail(msg="failed to update the host template")
|
||||
|
||||
# the cluster
|
||||
if host.CLUSTER_ID != self.get_parameter('cluster_id'):
|
||||
if one.cluster.addhost(self.get_parameter('cluster_id'), host.ID):
|
||||
result['changed'] = True
|
||||
else:
|
||||
self.fail(msg="failed to update the host cluster")
|
||||
|
||||
# return
|
||||
self.exit()
|
||||
|
||||
|
||||
def main():
|
||||
HostModule().run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
423
plugins/modules/cloud/opennebula/one_image.py
Normal file
423
plugins/modules/cloud/opennebula/one_image.py
Normal file
|
@ -0,0 +1,423 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
"""
|
||||
(c) 2018, Milan Ilic <milani@nordeus.com>
|
||||
|
||||
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 clone of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'metadata_version': '1.1'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: one_image
|
||||
short_description: Manages OpenNebula images
|
||||
description:
|
||||
- Manages OpenNebula images
|
||||
requirements:
|
||||
- python-oca
|
||||
options:
|
||||
api_url:
|
||||
description:
|
||||
- URL of the OpenNebula RPC server.
|
||||
- It is recommended to use HTTPS so that the username/password are not
|
||||
- transferred over the network unencrypted.
|
||||
- If not set then the value of the C(ONE_URL) environment variable is used.
|
||||
api_username:
|
||||
description:
|
||||
- Name of the user to login into the OpenNebula RPC server. If not set
|
||||
- then the value of the C(ONE_USERNAME) environment variable is used.
|
||||
api_password:
|
||||
description:
|
||||
- Password of the user to login into OpenNebula RPC server. If not set
|
||||
- then the value of the C(ONE_PASSWORD) environment variable is used.
|
||||
id:
|
||||
description:
|
||||
- A C(id) of the image you would like to manage.
|
||||
name:
|
||||
description:
|
||||
- A C(name) of the image you would like to manage.
|
||||
state:
|
||||
description:
|
||||
- C(present) - state that is used to manage the image
|
||||
- C(absent) - delete the image
|
||||
- C(cloned) - clone the image
|
||||
- C(renamed) - rename the image to the C(new_name)
|
||||
choices: ["present", "absent", "cloned", "renamed"]
|
||||
default: present
|
||||
enabled:
|
||||
description:
|
||||
- Whether the image should be enabled or disabled.
|
||||
type: bool
|
||||
new_name:
|
||||
description:
|
||||
- A name that will be assigned to the existing or new image.
|
||||
- In the case of cloning, by default C(new_name) will take the name of the origin image with the prefix 'Copy of'.
|
||||
author:
|
||||
- "Milan Ilic (@ilicmilan)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Fetch the IMAGE by id
|
||||
- one_image:
|
||||
id: 45
|
||||
register: result
|
||||
|
||||
# Print the IMAGE properties
|
||||
- debug:
|
||||
msg: result
|
||||
|
||||
# Rename existing IMAGE
|
||||
- one_image:
|
||||
id: 34
|
||||
state: renamed
|
||||
new_name: bar-image
|
||||
|
||||
# Disable the IMAGE by id
|
||||
- one_image:
|
||||
id: 37
|
||||
enabled: no
|
||||
|
||||
# Enable the IMAGE by name
|
||||
- one_image:
|
||||
name: bar-image
|
||||
enabled: yes
|
||||
|
||||
# Clone the IMAGE by name
|
||||
- one_image:
|
||||
name: bar-image
|
||||
state: cloned
|
||||
new_name: bar-image-clone
|
||||
register: result
|
||||
|
||||
# Delete the IMAGE by id
|
||||
- one_image:
|
||||
id: '{{ result.id }}'
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
id:
|
||||
description: image id
|
||||
type: int
|
||||
returned: success
|
||||
sample: 153
|
||||
name:
|
||||
description: image name
|
||||
type: str
|
||||
returned: success
|
||||
sample: app1
|
||||
group_id:
|
||||
description: image's group id
|
||||
type: int
|
||||
returned: success
|
||||
sample: 1
|
||||
group_name:
|
||||
description: image's group name
|
||||
type: str
|
||||
returned: success
|
||||
sample: one-users
|
||||
owner_id:
|
||||
description: image's owner id
|
||||
type: int
|
||||
returned: success
|
||||
sample: 143
|
||||
owner_name:
|
||||
description: image's owner name
|
||||
type: str
|
||||
returned: success
|
||||
sample: ansible-test
|
||||
state:
|
||||
description: state of image instance
|
||||
type: str
|
||||
returned: success
|
||||
sample: READY
|
||||
used:
|
||||
description: is image in use
|
||||
type: bool
|
||||
returned: success
|
||||
sample: true
|
||||
running_vms:
|
||||
description: count of running vms that use this image
|
||||
type: int
|
||||
returned: success
|
||||
sample: 7
|
||||
'''
|
||||
|
||||
try:
|
||||
import oca
|
||||
HAS_OCA = True
|
||||
except ImportError:
|
||||
HAS_OCA = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import os
|
||||
|
||||
|
||||
def get_image(module, client, predicate):
|
||||
pool = oca.ImagePool(client)
|
||||
# Filter -2 means fetch all images user can Use
|
||||
pool.info(filter=-2)
|
||||
|
||||
for image in pool:
|
||||
if predicate(image):
|
||||
return image
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_image_by_name(module, client, image_name):
|
||||
return get_image(module, client, lambda image: (image.name == image_name))
|
||||
|
||||
|
||||
def get_image_by_id(module, client, image_id):
|
||||
return get_image(module, client, lambda image: (image.id == image_id))
|
||||
|
||||
|
||||
def get_image_instance(module, client, requested_id, requested_name):
|
||||
if requested_id:
|
||||
return get_image_by_id(module, client, requested_id)
|
||||
else:
|
||||
return get_image_by_name(module, client, requested_name)
|
||||
|
||||
|
||||
IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS', 'LOCKED_USED', 'LOCKED_USED_PERS']
|
||||
|
||||
|
||||
def get_image_info(image):
|
||||
image.info()
|
||||
|
||||
info = {
|
||||
'id': image.id,
|
||||
'name': image.name,
|
||||
'state': IMAGE_STATES[image.state],
|
||||
'running_vms': image.running_vms,
|
||||
'used': bool(image.running_vms),
|
||||
'user_name': image.uname,
|
||||
'user_id': image.uid,
|
||||
'group_name': image.gname,
|
||||
'group_id': image.gid,
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def wait_for_state(module, image, wait_timeout, state_predicate):
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
while (time.time() - start_time) < wait_timeout:
|
||||
image.info()
|
||||
state = image.state
|
||||
|
||||
if state_predicate(state):
|
||||
return image
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
module.fail_json(msg="Wait timeout has expired!")
|
||||
|
||||
|
||||
def wait_for_ready(module, image, wait_timeout=60):
|
||||
return wait_for_state(module, image, wait_timeout, lambda state: (state in [IMAGE_STATES.index('READY')]))
|
||||
|
||||
|
||||
def wait_for_delete(module, image, wait_timeout=60):
|
||||
return wait_for_state(module, image, wait_timeout, lambda state: (state in [IMAGE_STATES.index('DELETE')]))
|
||||
|
||||
|
||||
def enable_image(module, client, image, enable):
|
||||
image.info()
|
||||
changed = False
|
||||
|
||||
state = image.state
|
||||
|
||||
if state not in [IMAGE_STATES.index('READY'), IMAGE_STATES.index('DISABLED'), IMAGE_STATES.index('ERROR')]:
|
||||
if enable:
|
||||
module.fail_json(msg="Cannot enable " + IMAGE_STATES[state] + " image!")
|
||||
else:
|
||||
module.fail_json(msg="Cannot disable " + IMAGE_STATES[state] + " image!")
|
||||
|
||||
if ((enable and state != IMAGE_STATES.index('READY')) or
|
||||
(not enable and state != IMAGE_STATES.index('DISABLED'))):
|
||||
changed = True
|
||||
|
||||
if changed and not module.check_mode:
|
||||
client.call('image.enable', image.id, enable)
|
||||
|
||||
result = get_image_info(image)
|
||||
result['changed'] = changed
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def clone_image(module, client, image, new_name):
|
||||
if new_name is None:
|
||||
new_name = "Copy of " + image.name
|
||||
|
||||
tmp_image = get_image_by_name(module, client, new_name)
|
||||
if tmp_image:
|
||||
result = get_image_info(tmp_image)
|
||||
result['changed'] = False
|
||||
return result
|
||||
|
||||
if image.state == IMAGE_STATES.index('DISABLED'):
|
||||
module.fail_json(msg="Cannot clone DISABLED image")
|
||||
|
||||
if not module.check_mode:
|
||||
new_id = client.call('image.clone', image.id, new_name)
|
||||
image = get_image_by_id(module, client, new_id)
|
||||
wait_for_ready(module, image)
|
||||
|
||||
result = get_image_info(image)
|
||||
result['changed'] = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def rename_image(module, client, image, new_name):
|
||||
if new_name is None:
|
||||
module.fail_json(msg="'new_name' option has to be specified when the state is 'renamed'")
|
||||
|
||||
if new_name == image.name:
|
||||
result = get_image_info(image)
|
||||
result['changed'] = False
|
||||
return result
|
||||
|
||||
tmp_image = get_image_by_name(module, client, new_name)
|
||||
if tmp_image:
|
||||
module.fail_json(msg="Name '" + new_name + "' is already taken by IMAGE with id=" + str(tmp_image.id))
|
||||
|
||||
if not module.check_mode:
|
||||
client.call('image.rename', image.id, new_name)
|
||||
|
||||
result = get_image_info(image)
|
||||
result['changed'] = True
|
||||
return result
|
||||
|
||||
|
||||
def delete_image(module, client, image):
|
||||
|
||||
if not image:
|
||||
return {'changed': False}
|
||||
|
||||
if image.running_vms > 0:
|
||||
module.fail_json(msg="Cannot delete image. There are " + str(image.running_vms) + " VMs using it.")
|
||||
|
||||
if not module.check_mode:
|
||||
client.call('image.delete', image.id)
|
||||
wait_for_delete(module, image)
|
||||
|
||||
return {'changed': True}
|
||||
|
||||
|
||||
def get_connection_info(module):
|
||||
|
||||
url = module.params.get('api_url')
|
||||
username = module.params.get('api_username')
|
||||
password = module.params.get('api_password')
|
||||
|
||||
if not url:
|
||||
url = os.environ.get('ONE_URL')
|
||||
|
||||
if not username:
|
||||
username = os.environ.get('ONE_USERNAME')
|
||||
|
||||
if not password:
|
||||
password = os.environ.get('ONE_PASSWORD')
|
||||
|
||||
if not(url and username and password):
|
||||
module.fail_json(msg="One or more connection parameters (api_url, api_username, api_password) were not specified")
|
||||
from collections import namedtuple
|
||||
|
||||
auth_params = namedtuple('auth', ('url', 'username', 'password'))
|
||||
|
||||
return auth_params(url=url, username=username, password=password)
|
||||
|
||||
|
||||
def main():
|
||||
fields = {
|
||||
"api_url": {"required": False, "type": "str"},
|
||||
"api_username": {"required": False, "type": "str"},
|
||||
"api_password": {"required": False, "type": "str", "no_log": True},
|
||||
"id": {"required": False, "type": "int"},
|
||||
"name": {"required": False, "type": "str"},
|
||||
"state": {
|
||||
"default": "present",
|
||||
"choices": ['present', 'absent', 'cloned', 'renamed'],
|
||||
"type": "str"
|
||||
},
|
||||
"enabled": {"required": False, "type": "bool"},
|
||||
"new_name": {"required": False, "type": "str"},
|
||||
}
|
||||
|
||||
module = AnsibleModule(argument_spec=fields,
|
||||
mutually_exclusive=[['id', 'name']],
|
||||
supports_check_mode=True)
|
||||
|
||||
if not HAS_OCA:
|
||||
module.fail_json(msg='This module requires python-oca to work!')
|
||||
|
||||
auth = get_connection_info(module)
|
||||
params = module.params
|
||||
id = params.get('id')
|
||||
name = params.get('name')
|
||||
state = params.get('state')
|
||||
enabled = params.get('enabled')
|
||||
new_name = params.get('new_name')
|
||||
client = oca.Client(auth.username + ':' + auth.password, auth.url)
|
||||
|
||||
result = {}
|
||||
|
||||
if not id and state == 'renamed':
|
||||
module.fail_json(msg="Option 'id' is required when the state is 'renamed'")
|
||||
|
||||
image = get_image_instance(module, client, id, name)
|
||||
if not image and state != 'absent':
|
||||
if id:
|
||||
module.fail_json(msg="There is no image with id=" + str(id))
|
||||
else:
|
||||
module.fail_json(msg="There is no image with name=" + name)
|
||||
|
||||
if state == 'absent':
|
||||
result = delete_image(module, client, image)
|
||||
else:
|
||||
result = get_image_info(image)
|
||||
changed = False
|
||||
result['changed'] = False
|
||||
|
||||
if enabled is not None:
|
||||
result = enable_image(module, client, image, enabled)
|
||||
if state == "cloned":
|
||||
result = clone_image(module, client, image, new_name)
|
||||
elif state == "renamed":
|
||||
result = rename_image(module, client, image, new_name)
|
||||
|
||||
changed = changed or result['changed']
|
||||
result['changed'] = changed
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1
plugins/modules/cloud/opennebula/one_image_facts.py
Symbolic link
1
plugins/modules/cloud/opennebula/one_image_facts.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
one_image_info.py
|
291
plugins/modules/cloud/opennebula/one_image_info.py
Normal file
291
plugins/modules/cloud/opennebula/one_image_info.py
Normal file
|
@ -0,0 +1,291 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
"""
|
||||
(c) 2018, Milan Ilic <milani@nordeus.com>
|
||||
|
||||
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 clone of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'metadata_version': '1.1'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: one_image_info
|
||||
short_description: Gather information on OpenNebula images
|
||||
description:
|
||||
- Gather information on OpenNebula images.
|
||||
- This module was called C(one_image_facts) before Ansible 2.9. The usage did not change.
|
||||
requirements:
|
||||
- pyone
|
||||
options:
|
||||
api_url:
|
||||
description:
|
||||
- URL of the OpenNebula RPC server.
|
||||
- It is recommended to use HTTPS so that the username/password are not
|
||||
- transferred over the network unencrypted.
|
||||
- If not set then the value of the C(ONE_URL) environment variable is used.
|
||||
api_username:
|
||||
description:
|
||||
- Name of the user to login into the OpenNebula RPC server. If not set
|
||||
- then the value of the C(ONE_USERNAME) environment variable is used.
|
||||
api_password:
|
||||
description:
|
||||
- Password of the user to login into OpenNebula RPC server. If not set
|
||||
- then the value of the C(ONE_PASSWORD) environment variable is used.
|
||||
ids:
|
||||
description:
|
||||
- A list of images ids whose facts you want to gather.
|
||||
aliases: ['id']
|
||||
name:
|
||||
description:
|
||||
- A C(name) of the image whose facts will be gathered.
|
||||
- If the C(name) begins with '~' the C(name) will be used as regex pattern
|
||||
- which restricts the list of images (whose facts will be returned) whose names match specified regex.
|
||||
- Also, if the C(name) begins with '~*' case-insensitive matching will be performed.
|
||||
- See examples for more details.
|
||||
author:
|
||||
- "Milan Ilic (@ilicmilan)"
|
||||
- "Jan Meerkamp (@meerkampdvv)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Gather facts about all images
|
||||
- one_image_info:
|
||||
register: result
|
||||
|
||||
# Print all images facts
|
||||
- debug:
|
||||
msg: result
|
||||
|
||||
# Gather facts about an image using ID
|
||||
- one_image_info:
|
||||
ids:
|
||||
- 123
|
||||
|
||||
# Gather facts about an image using the name
|
||||
- one_image_info:
|
||||
name: 'foo-image'
|
||||
register: foo_image
|
||||
|
||||
# Gather facts about all IMAGEs whose name matches regex 'app-image-.*'
|
||||
- one_image_info:
|
||||
name: '~app-image-.*'
|
||||
register: app_images
|
||||
|
||||
# Gather facts about all IMAGEs whose name matches regex 'foo-image-.*' ignoring cases
|
||||
- one_image_info:
|
||||
name: '~*foo-image-.*'
|
||||
register: foo_images
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
images:
|
||||
description: A list of images info
|
||||
type: complex
|
||||
returned: success
|
||||
contains:
|
||||
id:
|
||||
description: image id
|
||||
type: int
|
||||
sample: 153
|
||||
name:
|
||||
description: image name
|
||||
type: str
|
||||
sample: app1
|
||||
group_id:
|
||||
description: image's group id
|
||||
type: int
|
||||
sample: 1
|
||||
group_name:
|
||||
description: image's group name
|
||||
type: str
|
||||
sample: one-users
|
||||
owner_id:
|
||||
description: image's owner id
|
||||
type: int
|
||||
sample: 143
|
||||
owner_name:
|
||||
description: image's owner name
|
||||
type: str
|
||||
sample: ansible-test
|
||||
state:
|
||||
description: state of image instance
|
||||
type: str
|
||||
sample: READY
|
||||
used:
|
||||
description: is image in use
|
||||
type: bool
|
||||
sample: true
|
||||
running_vms:
|
||||
description: count of running vms that use this image
|
||||
type: int
|
||||
sample: 7
|
||||
'''
|
||||
|
||||
try:
|
||||
import pyone
|
||||
HAS_PYONE = True
|
||||
except ImportError:
|
||||
HAS_PYONE = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import os
|
||||
|
||||
|
||||
def get_all_images(client):
|
||||
pool = client.imagepool.info(-2, -1, -1, -1)
|
||||
# Filter -2 means fetch all images user can Use
|
||||
|
||||
return pool
|
||||
|
||||
|
||||
IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS', 'LOCKED_USED', 'LOCKED_USED_PERS']
|
||||
|
||||
|
||||
def get_image_info(image):
|
||||
info = {
|
||||
'id': image.ID,
|
||||
'name': image.NAME,
|
||||
'state': IMAGE_STATES[image.STATE],
|
||||
'running_vms': image.RUNNING_VMS,
|
||||
'used': bool(image.RUNNING_VMS),
|
||||
'user_name': image.UNAME,
|
||||
'user_id': image.UID,
|
||||
'group_name': image.GNAME,
|
||||
'group_id': image.GID,
|
||||
}
|
||||
return info
|
||||
|
||||
|
||||
def get_images_by_ids(module, client, ids):
|
||||
images = []
|
||||
pool = get_all_images(client)
|
||||
|
||||
for image in pool.IMAGE:
|
||||
if str(image.ID) in ids:
|
||||
images.append(image)
|
||||
ids.remove(str(image.ID))
|
||||
if len(ids) == 0:
|
||||
break
|
||||
|
||||
if len(ids) > 0:
|
||||
module.fail_json(msg='There is no IMAGE(s) with id(s)=' + ', '.join('{id}'.format(id=str(image_id)) for image_id in ids))
|
||||
|
||||
return images
|
||||
|
||||
|
||||
def get_images_by_name(module, client, name_pattern):
|
||||
|
||||
images = []
|
||||
pattern = None
|
||||
|
||||
pool = get_all_images(client)
|
||||
|
||||
if name_pattern.startswith('~'):
|
||||
import re
|
||||
if name_pattern[1] == '*':
|
||||
pattern = re.compile(name_pattern[2:], re.IGNORECASE)
|
||||
else:
|
||||
pattern = re.compile(name_pattern[1:])
|
||||
|
||||
for image in pool.IMAGE:
|
||||
if pattern is not None:
|
||||
if pattern.match(image.NAME):
|
||||
images.append(image)
|
||||
elif name_pattern == image.NAME:
|
||||
images.append(image)
|
||||
break
|
||||
|
||||
# if the specific name is indicated
|
||||
if pattern is None and len(images) == 0:
|
||||
module.fail_json(msg="There is no IMAGE with name=" + name_pattern)
|
||||
|
||||
return images
|
||||
|
||||
|
||||
def get_connection_info(module):
|
||||
|
||||
url = module.params.get('api_url')
|
||||
username = module.params.get('api_username')
|
||||
password = module.params.get('api_password')
|
||||
|
||||
if not url:
|
||||
url = os.environ.get('ONE_URL')
|
||||
|
||||
if not username:
|
||||
username = os.environ.get('ONE_USERNAME')
|
||||
|
||||
if not password:
|
||||
password = os.environ.get('ONE_PASSWORD')
|
||||
|
||||
if not(url and username and password):
|
||||
module.fail_json(msg="One or more connection parameters (api_url, api_username, api_password) were not specified")
|
||||
from collections import namedtuple
|
||||
|
||||
auth_params = namedtuple('auth', ('url', 'username', 'password'))
|
||||
|
||||
return auth_params(url=url, username=username, password=password)
|
||||
|
||||
|
||||
def main():
|
||||
fields = {
|
||||
"api_url": {"required": False, "type": "str"},
|
||||
"api_username": {"required": False, "type": "str"},
|
||||
"api_password": {"required": False, "type": "str", "no_log": True},
|
||||
"ids": {"required": False, "aliases": ['id'], "type": "list"},
|
||||
"name": {"required": False, "type": "str"},
|
||||
}
|
||||
|
||||
module = AnsibleModule(argument_spec=fields,
|
||||
mutually_exclusive=[['ids', 'name']],
|
||||
supports_check_mode=True)
|
||||
if module._name == 'one_image_facts':
|
||||
module.deprecate("The 'one_image_facts' module has been renamed to 'one_image_info'", version='2.13')
|
||||
|
||||
if not HAS_PYONE:
|
||||
module.fail_json(msg='This module requires pyone to work!')
|
||||
|
||||
auth = get_connection_info(module)
|
||||
params = module.params
|
||||
ids = params.get('ids')
|
||||
name = params.get('name')
|
||||
client = pyone.OneServer(auth.url, session=auth.username + ':' + auth.password)
|
||||
|
||||
result = {'images': []}
|
||||
images = []
|
||||
|
||||
if ids:
|
||||
images = get_images_by_ids(module, client, ids)
|
||||
elif name:
|
||||
images = get_images_by_name(module, client, name)
|
||||
else:
|
||||
images = get_all_images(client).IMAGE
|
||||
|
||||
for image in images:
|
||||
result['images'].append(get_image_info(image))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
757
plugins/modules/cloud/opennebula/one_service.py
Normal file
757
plugins/modules/cloud/opennebula/one_service.py
Normal file
|
@ -0,0 +1,757 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
"""
|
||||
(c) 2017, Milan Ilic <milani@nordeus.com>
|
||||
|
||||
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/>.
|
||||
"""
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'metadata_version': '1.1'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: one_service
|
||||
short_description: Deploy and manage OpenNebula services
|
||||
description:
|
||||
- Manage OpenNebula services
|
||||
options:
|
||||
api_url:
|
||||
description:
|
||||
- URL of the OpenNebula OneFlow API server.
|
||||
- It is recommended to use HTTPS so that the username/password are not transferred over the network unencrypted.
|
||||
- If not set then the value of the ONEFLOW_URL environment variable is used.
|
||||
api_username:
|
||||
description:
|
||||
- Name of the user to login into the OpenNebula OneFlow API server. If not set then the value of the C(ONEFLOW_USERNAME) environment variable is used.
|
||||
api_password:
|
||||
description:
|
||||
- Password of the user to login into OpenNebula OneFlow API server. If not set then the value of the C(ONEFLOW_PASSWORD) environment variable is used.
|
||||
template_name:
|
||||
description:
|
||||
- Name of service template to use to create a new instance of a service
|
||||
template_id:
|
||||
description:
|
||||
- ID of a service template to use to create a new instance of a service
|
||||
service_id:
|
||||
description:
|
||||
- ID of a service instance that you would like to manage
|
||||
service_name:
|
||||
description:
|
||||
- Name of a service instance that you would like to manage
|
||||
unique:
|
||||
description:
|
||||
- Setting C(unique=yes) will make sure that there is only one service instance running with a name set with C(service_name) when
|
||||
- instantiating a service from a template specified with C(template_id)/C(template_name). Check examples below.
|
||||
type: bool
|
||||
default: no
|
||||
state:
|
||||
description:
|
||||
- C(present) - instantiate a service from a template specified with C(template_id)/C(template_name).
|
||||
- C(absent) - terminate an instance of a service specified with C(service_id)/C(service_name).
|
||||
choices: ["present", "absent"]
|
||||
default: present
|
||||
mode:
|
||||
description:
|
||||
- Set permission mode of a service instance in octet format, e.g. C(600) to give owner C(use) and C(manage) and nothing to group and others.
|
||||
owner_id:
|
||||
description:
|
||||
- ID of the user which will be set as the owner of the service
|
||||
group_id:
|
||||
description:
|
||||
- ID of the group which will be set as the group of the service
|
||||
wait:
|
||||
description:
|
||||
- Wait for the instance to reach RUNNING state after DEPLOYING or COOLDOWN state after SCALING
|
||||
type: bool
|
||||
default: no
|
||||
wait_timeout:
|
||||
description:
|
||||
- How long before wait gives up, in seconds
|
||||
default: 300
|
||||
custom_attrs:
|
||||
description:
|
||||
- Dictionary of key/value custom attributes which will be used when instantiating a new service.
|
||||
default: {}
|
||||
role:
|
||||
description:
|
||||
- Name of the role whose cardinality should be changed
|
||||
cardinality:
|
||||
description:
|
||||
- Number of VMs for the specified role
|
||||
force:
|
||||
description:
|
||||
- Force the new cardinality even if it is outside the limits
|
||||
type: bool
|
||||
default: no
|
||||
author:
|
||||
- "Milan Ilic (@ilicmilan)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Instantiate a new service
|
||||
- one_service:
|
||||
template_id: 90
|
||||
register: result
|
||||
|
||||
# Print service properties
|
||||
- debug:
|
||||
msg: result
|
||||
|
||||
# Instantiate a new service with specified service_name, service group and mode
|
||||
- one_service:
|
||||
template_name: 'app1_template'
|
||||
service_name: 'app1'
|
||||
group_id: 1
|
||||
mode: '660'
|
||||
|
||||
# Instantiate a new service with template_id and pass custom_attrs dict
|
||||
- one_service:
|
||||
template_id: 90
|
||||
custom_attrs:
|
||||
public_network_id: 21
|
||||
private_network_id: 26
|
||||
|
||||
# Instantiate a new service 'foo' if the service doesn't already exist, otherwise do nothing
|
||||
- one_service:
|
||||
template_id: 53
|
||||
service_name: 'foo'
|
||||
unique: yes
|
||||
|
||||
# Delete a service by ID
|
||||
- one_service:
|
||||
service_id: 153
|
||||
state: absent
|
||||
|
||||
# Get service info
|
||||
- one_service:
|
||||
service_id: 153
|
||||
register: service_info
|
||||
|
||||
# Change service owner, group and mode
|
||||
- one_service:
|
||||
service_name: 'app2'
|
||||
owner_id: 34
|
||||
group_id: 113
|
||||
mode: '600'
|
||||
|
||||
# Instantiate service and wait for it to become RUNNING
|
||||
- one_service:
|
||||
template_id: 43
|
||||
service_name: 'foo1'
|
||||
|
||||
# Wait service to become RUNNING
|
||||
- one_service:
|
||||
service_id: 112
|
||||
wait: yes
|
||||
|
||||
# Change role cardinality
|
||||
- one_service:
|
||||
service_id: 153
|
||||
role: bar
|
||||
cardinality: 5
|
||||
|
||||
# Change role cardinality and wait for it to be applied
|
||||
- one_service:
|
||||
service_id: 112
|
||||
role: foo
|
||||
cardinality: 7
|
||||
wait: yes
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
service_id:
|
||||
description: service id
|
||||
type: int
|
||||
returned: success
|
||||
sample: 153
|
||||
service_name:
|
||||
description: service name
|
||||
type: str
|
||||
returned: success
|
||||
sample: app1
|
||||
group_id:
|
||||
description: service's group id
|
||||
type: int
|
||||
returned: success
|
||||
sample: 1
|
||||
group_name:
|
||||
description: service's group name
|
||||
type: str
|
||||
returned: success
|
||||
sample: one-users
|
||||
owner_id:
|
||||
description: service's owner id
|
||||
type: int
|
||||
returned: success
|
||||
sample: 143
|
||||
owner_name:
|
||||
description: service's owner name
|
||||
type: str
|
||||
returned: success
|
||||
sample: ansible-test
|
||||
state:
|
||||
description: state of service instance
|
||||
type: str
|
||||
returned: success
|
||||
sample: RUNNING
|
||||
mode:
|
||||
description: service's mode
|
||||
type: int
|
||||
returned: success
|
||||
sample: 660
|
||||
roles:
|
||||
description: list of dictionaries of roles, each role is described by name, cardinality, state and nodes ids
|
||||
type: list
|
||||
returned: success
|
||||
sample: '[{"cardinality": 1,"name": "foo","state": "RUNNING","ids": [ 123, 456 ]},
|
||||
{"cardinality": 2,"name": "bar","state": "RUNNING", "ids": [ 452, 567, 746 ]}]'
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
STATES = ("PENDING", "DEPLOYING", "RUNNING", "UNDEPLOYING", "WARNING", "DONE",
|
||||
"FAILED_UNDEPLOYING", "FAILED_DEPLOYING", "SCALING", "FAILED_SCALING", "COOLDOWN")
|
||||
|
||||
|
||||
def get_all_templates(module, auth):
|
||||
try:
|
||||
all_templates = open_url(url=(auth.url + "/service_template"), method="GET", force_basic_auth=True, url_username=auth.user, url_password=auth.password)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
return module.from_json(all_templates.read())
|
||||
|
||||
|
||||
def get_template(module, auth, pred):
|
||||
all_templates_dict = get_all_templates(module, auth)
|
||||
|
||||
found = 0
|
||||
found_template = None
|
||||
template_name = ''
|
||||
|
||||
if "DOCUMENT_POOL" in all_templates_dict and "DOCUMENT" in all_templates_dict["DOCUMENT_POOL"]:
|
||||
for template in all_templates_dict["DOCUMENT_POOL"]["DOCUMENT"]:
|
||||
if pred(template):
|
||||
found = found + 1
|
||||
found_template = template
|
||||
template_name = template["NAME"]
|
||||
|
||||
if found <= 0:
|
||||
return None
|
||||
elif found > 1:
|
||||
module.fail_json(msg="There is no template with unique name: " + template_name)
|
||||
else:
|
||||
return found_template
|
||||
|
||||
|
||||
def get_all_services(module, auth):
|
||||
try:
|
||||
response = open_url(auth.url + "/service", method="GET", force_basic_auth=True, url_username=auth.user, url_password=auth.password)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
return module.from_json(response.read())
|
||||
|
||||
|
||||
def get_service(module, auth, pred):
|
||||
all_services_dict = get_all_services(module, auth)
|
||||
|
||||
found = 0
|
||||
found_service = None
|
||||
service_name = ''
|
||||
|
||||
if "DOCUMENT_POOL" in all_services_dict and "DOCUMENT" in all_services_dict["DOCUMENT_POOL"]:
|
||||
for service in all_services_dict["DOCUMENT_POOL"]["DOCUMENT"]:
|
||||
if pred(service):
|
||||
found = found + 1
|
||||
found_service = service
|
||||
service_name = service["NAME"]
|
||||
|
||||
# fail if there are more services with same name
|
||||
if found > 1:
|
||||
module.fail_json(msg="There are multiple services with a name: '" +
|
||||
service_name + "'. You have to use a unique service name or use 'service_id' instead.")
|
||||
elif found <= 0:
|
||||
return None
|
||||
else:
|
||||
return found_service
|
||||
|
||||
|
||||
def get_service_by_id(module, auth, service_id):
|
||||
return get_service(module, auth, lambda service: (int(service["ID"]) == int(service_id))) if service_id else None
|
||||
|
||||
|
||||
def get_service_by_name(module, auth, service_name):
|
||||
return get_service(module, auth, lambda service: (service["NAME"] == service_name))
|
||||
|
||||
|
||||
def get_service_info(module, auth, service):
|
||||
|
||||
result = {
|
||||
"service_id": int(service["ID"]),
|
||||
"service_name": service["NAME"],
|
||||
"group_id": int(service["GID"]),
|
||||
"group_name": service["GNAME"],
|
||||
"owner_id": int(service["UID"]),
|
||||
"owner_name": service["UNAME"],
|
||||
"state": STATES[service["TEMPLATE"]["BODY"]["state"]]
|
||||
}
|
||||
|
||||
roles_status = service["TEMPLATE"]["BODY"]["roles"]
|
||||
roles = []
|
||||
for role in roles_status:
|
||||
nodes_ids = []
|
||||
if "nodes" in role:
|
||||
for node in role["nodes"]:
|
||||
nodes_ids.append(node["deploy_id"])
|
||||
roles.append({"name": role["name"], "cardinality": role["cardinality"], "state": STATES[int(role["state"])], "ids": nodes_ids})
|
||||
|
||||
result["roles"] = roles
|
||||
result["mode"] = int(parse_service_permissions(service))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def create_service(module, auth, template_id, service_name, custom_attrs, unique, wait, wait_timeout):
|
||||
# make sure that the values in custom_attrs dict are strings
|
||||
custom_attrs_with_str = dict((k, str(v)) for k, v in custom_attrs.items())
|
||||
|
||||
data = {
|
||||
"action": {
|
||||
"perform": "instantiate",
|
||||
"params": {
|
||||
"merge_template": {
|
||||
"custom_attrs_values": custom_attrs_with_str,
|
||||
"name": service_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = open_url(auth.url + "/service_template/" + str(template_id) + "/action", method="POST",
|
||||
data=module.jsonify(data), force_basic_auth=True, url_username=auth.user, url_password=auth.password)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
service_result = module.from_json(response.read())["DOCUMENT"]
|
||||
|
||||
return service_result
|
||||
|
||||
|
||||
def wait_for_service_to_become_ready(module, auth, service_id, wait_timeout):
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
while (time.time() - start_time) < wait_timeout:
|
||||
try:
|
||||
status_result = open_url(auth.url + "/service/" + str(service_id), method="GET",
|
||||
force_basic_auth=True, url_username=auth.user, url_password=auth.password)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Request for service status has failed. Error message: " + str(e))
|
||||
|
||||
status_result = module.from_json(status_result.read())
|
||||
service_state = status_result["DOCUMENT"]["TEMPLATE"]["BODY"]["state"]
|
||||
|
||||
if service_state in [STATES.index("RUNNING"), STATES.index("COOLDOWN")]:
|
||||
return status_result["DOCUMENT"]
|
||||
elif service_state not in [STATES.index("PENDING"), STATES.index("DEPLOYING"), STATES.index("SCALING")]:
|
||||
log_message = ''
|
||||
for log_info in status_result["DOCUMENT"]["TEMPLATE"]["BODY"]["log"]:
|
||||
if log_info["severity"] == "E":
|
||||
log_message = log_message + log_info["message"]
|
||||
break
|
||||
|
||||
module.fail_json(msg="Deploying is unsuccessful. Service state: " + STATES[service_state] + ". Error message: " + log_message)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
module.fail_json(msg="Wait timeout has expired")
|
||||
|
||||
|
||||
def change_service_permissions(module, auth, service_id, permissions):
|
||||
|
||||
data = {
|
||||
"action": {
|
||||
"perform": "chmod",
|
||||
"params": {"octet": permissions}
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
status_result = open_url(auth.url + "/service/" + str(service_id) + "/action", method="POST", force_basic_auth=True,
|
||||
url_username=auth.user, url_password=auth.password, data=module.jsonify(data))
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
def change_service_owner(module, auth, service_id, owner_id):
|
||||
data = {
|
||||
"action": {
|
||||
"perform": "chown",
|
||||
"params": {"owner_id": owner_id}
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
status_result = open_url(auth.url + "/service/" + str(service_id) + "/action", method="POST", force_basic_auth=True,
|
||||
url_username=auth.user, url_password=auth.password, data=module.jsonify(data))
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
def change_service_group(module, auth, service_id, group_id):
|
||||
|
||||
data = {
|
||||
"action": {
|
||||
"perform": "chgrp",
|
||||
"params": {"group_id": group_id}
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
status_result = open_url(auth.url + "/service/" + str(service_id) + "/action", method="POST", force_basic_auth=True,
|
||||
url_username=auth.user, url_password=auth.password, data=module.jsonify(data))
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
def change_role_cardinality(module, auth, service_id, role, cardinality, force):
|
||||
|
||||
data = {
|
||||
"cardinality": cardinality,
|
||||
"force": force
|
||||
}
|
||||
|
||||
try:
|
||||
status_result = open_url(auth.url + "/service/" + str(service_id) + "/role/" + role, method="PUT",
|
||||
force_basic_auth=True, url_username=auth.user, url_password=auth.password, data=module.jsonify(data))
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
if status_result.getcode() != 204:
|
||||
module.fail_json(msg="Failed to change cardinality for role: " + role + ". Return code: " + str(status_result.getcode()))
|
||||
|
||||
|
||||
def check_change_service_owner(module, service, owner_id):
|
||||
old_owner_id = int(service["UID"])
|
||||
|
||||
return old_owner_id != owner_id
|
||||
|
||||
|
||||
def check_change_service_group(module, service, group_id):
|
||||
old_group_id = int(service["GID"])
|
||||
|
||||
return old_group_id != group_id
|
||||
|
||||
|
||||
def parse_service_permissions(service):
|
||||
perm_dict = service["PERMISSIONS"]
|
||||
'''
|
||||
This is the structure of the 'PERMISSIONS' dictionary:
|
||||
|
||||
"PERMISSIONS": {
|
||||
"OWNER_U": "1",
|
||||
"OWNER_M": "1",
|
||||
"OWNER_A": "0",
|
||||
"GROUP_U": "0",
|
||||
"GROUP_M": "0",
|
||||
"GROUP_A": "0",
|
||||
"OTHER_U": "0",
|
||||
"OTHER_M": "0",
|
||||
"OTHER_A": "0"
|
||||
}
|
||||
'''
|
||||
|
||||
owner_octal = int(perm_dict["OWNER_U"]) * 4 + int(perm_dict["OWNER_M"]) * 2 + int(perm_dict["OWNER_A"])
|
||||
group_octal = int(perm_dict["GROUP_U"]) * 4 + int(perm_dict["GROUP_M"]) * 2 + int(perm_dict["GROUP_A"])
|
||||
other_octal = int(perm_dict["OTHER_U"]) * 4 + int(perm_dict["OTHER_M"]) * 2 + int(perm_dict["OTHER_A"])
|
||||
|
||||
permissions = str(owner_octal) + str(group_octal) + str(other_octal)
|
||||
|
||||
return permissions
|
||||
|
||||
|
||||
def check_change_service_permissions(module, service, permissions):
|
||||
old_permissions = parse_service_permissions(service)
|
||||
|
||||
return old_permissions != permissions
|
||||
|
||||
|
||||
def check_change_role_cardinality(module, service, role_name, cardinality):
|
||||
roles_list = service["TEMPLATE"]["BODY"]["roles"]
|
||||
|
||||
for role in roles_list:
|
||||
if role["name"] == role_name:
|
||||
return int(role["cardinality"]) != cardinality
|
||||
|
||||
module.fail_json(msg="There is no role with name: " + role_name)
|
||||
|
||||
|
||||
def create_service_and_operation(module, auth, template_id, service_name, owner_id, group_id, permissions, custom_attrs, unique, wait, wait_timeout):
|
||||
if not service_name:
|
||||
service_name = ''
|
||||
changed = False
|
||||
service = None
|
||||
|
||||
if unique:
|
||||
service = get_service_by_name(module, auth, service_name)
|
||||
|
||||
if not service:
|
||||
if not module.check_mode:
|
||||
service = create_service(module, auth, template_id, service_name, custom_attrs, unique, wait, wait_timeout)
|
||||
changed = True
|
||||
|
||||
# if check_mode=true and there would be changes, service doesn't exist and we can not get it
|
||||
if module.check_mode and changed:
|
||||
return {"changed": True}
|
||||
|
||||
result = service_operation(module, auth, owner_id=owner_id, group_id=group_id, wait=wait,
|
||||
wait_timeout=wait_timeout, permissions=permissions, service=service)
|
||||
|
||||
if result["changed"]:
|
||||
changed = True
|
||||
|
||||
result["changed"] = changed
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def service_operation(module, auth, service_id=None, owner_id=None, group_id=None, permissions=None,
|
||||
role=None, cardinality=None, force=None, wait=False, wait_timeout=None, service=None):
|
||||
|
||||
changed = False
|
||||
|
||||
if not service:
|
||||
service = get_service_by_id(module, auth, service_id)
|
||||
else:
|
||||
service_id = service["ID"]
|
||||
|
||||
if not service:
|
||||
module.fail_json(msg="There is no service with id: " + str(service_id))
|
||||
|
||||
if owner_id:
|
||||
if check_change_service_owner(module, service, owner_id):
|
||||
if not module.check_mode:
|
||||
change_service_owner(module, auth, service_id, owner_id)
|
||||
changed = True
|
||||
if group_id:
|
||||
if check_change_service_group(module, service, group_id):
|
||||
if not module.check_mode:
|
||||
change_service_group(module, auth, service_id, group_id)
|
||||
changed = True
|
||||
if permissions:
|
||||
if check_change_service_permissions(module, service, permissions):
|
||||
if not module.check_mode:
|
||||
change_service_permissions(module, auth, service_id, permissions)
|
||||
changed = True
|
||||
|
||||
if role:
|
||||
if check_change_role_cardinality(module, service, role, cardinality):
|
||||
if not module.check_mode:
|
||||
change_role_cardinality(module, auth, service_id, role, cardinality, force)
|
||||
changed = True
|
||||
|
||||
if wait and not module.check_mode:
|
||||
service = wait_for_service_to_become_ready(module, auth, service_id, wait_timeout)
|
||||
|
||||
# if something has changed, fetch service info again
|
||||
if changed:
|
||||
service = get_service_by_id(module, auth, service_id)
|
||||
|
||||
service_info = get_service_info(module, auth, service)
|
||||
service_info["changed"] = changed
|
||||
|
||||
return service_info
|
||||
|
||||
|
||||
def delete_service(module, auth, service_id):
|
||||
service = get_service_by_id(module, auth, service_id)
|
||||
if not service:
|
||||
return {"changed": False}
|
||||
|
||||
service_info = get_service_info(module, auth, service)
|
||||
|
||||
service_info["changed"] = True
|
||||
|
||||
if module.check_mode:
|
||||
return service_info
|
||||
|
||||
try:
|
||||
result = open_url(auth.url + '/service/' + str(service_id), method="DELETE", force_basic_auth=True, url_username=auth.user, url_password=auth.password)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Service deletion has failed. Error message: " + str(e))
|
||||
|
||||
return service_info
|
||||
|
||||
|
||||
def get_template_by_name(module, auth, template_name):
|
||||
return get_template(module, auth, lambda template: (template["NAME"] == template_name))
|
||||
|
||||
|
||||
def get_template_by_id(module, auth, template_id):
|
||||
return get_template(module, auth, lambda template: (int(template["ID"]) == int(template_id))) if template_id else None
|
||||
|
||||
|
||||
def get_template_id(module, auth, requested_id, requested_name):
|
||||
template = get_template_by_id(module, auth, requested_id) if requested_id else get_template_by_name(module, auth, requested_name)
|
||||
|
||||
if template:
|
||||
return template["ID"]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_service_id_by_name(module, auth, service_name):
|
||||
service = get_service_by_name(module, auth, service_name)
|
||||
|
||||
if service:
|
||||
return service["ID"]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_connection_info(module):
|
||||
|
||||
url = module.params.get('api_url')
|
||||
username = module.params.get('api_username')
|
||||
password = module.params.get('api_password')
|
||||
|
||||
if not url:
|
||||
url = os.environ.get('ONEFLOW_URL')
|
||||
|
||||
if not username:
|
||||
username = os.environ.get('ONEFLOW_USERNAME')
|
||||
|
||||
if not password:
|
||||
password = os.environ.get('ONEFLOW_PASSWORD')
|
||||
|
||||
if not(url and username and password):
|
||||
module.fail_json(msg="One or more connection parameters (api_url, api_username, api_password) were not specified")
|
||||
from collections import namedtuple
|
||||
|
||||
auth_params = namedtuple('auth', ('url', 'user', 'password'))
|
||||
|
||||
return auth_params(url=url, user=username, password=password)
|
||||
|
||||
|
||||
def main():
|
||||
fields = {
|
||||
"api_url": {"required": False, "type": "str"},
|
||||
"api_username": {"required": False, "type": "str"},
|
||||
"api_password": {"required": False, "type": "str", "no_log": True},
|
||||
"service_name": {"required": False, "type": "str"},
|
||||
"service_id": {"required": False, "type": "int"},
|
||||
"template_name": {"required": False, "type": "str"},
|
||||
"template_id": {"required": False, "type": "int"},
|
||||
"state": {
|
||||
"default": "present",
|
||||
"choices": ['present', 'absent'],
|
||||
"type": "str"
|
||||
},
|
||||
"mode": {"required": False, "type": "str"},
|
||||
"owner_id": {"required": False, "type": "int"},
|
||||
"group_id": {"required": False, "type": "int"},
|
||||
"unique": {"default": False, "type": "bool"},
|
||||
"wait": {"default": False, "type": "bool"},
|
||||
"wait_timeout": {"default": 300, "type": "int"},
|
||||
"custom_attrs": {"default": {}, "type": "dict"},
|
||||
"role": {"required": False, "type": "str"},
|
||||
"cardinality": {"required": False, "type": "int"},
|
||||
"force": {"default": False, "type": "bool"}
|
||||
}
|
||||
|
||||
module = AnsibleModule(argument_spec=fields,
|
||||
mutually_exclusive=[
|
||||
['template_id', 'template_name', 'service_id'],
|
||||
['service_id', 'service_name'],
|
||||
['template_id', 'template_name', 'role'],
|
||||
['template_id', 'template_name', 'cardinality'],
|
||||
['service_id', 'custom_attrs']
|
||||
],
|
||||
required_together=[['role', 'cardinality']],
|
||||
supports_check_mode=True)
|
||||
|
||||
auth = get_connection_info(module)
|
||||
params = module.params
|
||||
service_name = params.get('service_name')
|
||||
service_id = params.get('service_id')
|
||||
|
||||
requested_template_id = params.get('template_id')
|
||||
requested_template_name = params.get('template_name')
|
||||
state = params.get('state')
|
||||
permissions = params.get('mode')
|
||||
owner_id = params.get('owner_id')
|
||||
group_id = params.get('group_id')
|
||||
unique = params.get('unique')
|
||||
wait = params.get('wait')
|
||||
wait_timeout = params.get('wait_timeout')
|
||||
custom_attrs = params.get('custom_attrs')
|
||||
role = params.get('role')
|
||||
cardinality = params.get('cardinality')
|
||||
force = params.get('force')
|
||||
|
||||
template_id = None
|
||||
|
||||
if requested_template_id or requested_template_name:
|
||||
template_id = get_template_id(module, auth, requested_template_id, requested_template_name)
|
||||
if not template_id:
|
||||
if requested_template_id:
|
||||
module.fail_json(msg="There is no template with template_id: " + str(requested_template_id))
|
||||
elif requested_template_name:
|
||||
module.fail_json(msg="There is no template with name: " + requested_template_name)
|
||||
|
||||
if unique and not service_name:
|
||||
module.fail_json(msg="You cannot use unique without passing service_name!")
|
||||
|
||||
if template_id and state == 'absent':
|
||||
module.fail_json(msg="State absent is not valid for template")
|
||||
|
||||
if template_id and state == 'present': # Instantiate a service
|
||||
result = create_service_and_operation(module, auth, template_id, service_name, owner_id,
|
||||
group_id, permissions, custom_attrs, unique, wait, wait_timeout)
|
||||
else:
|
||||
if not (service_id or service_name):
|
||||
module.fail_json(msg="To manage the service at least the service id or service name should be specified!")
|
||||
if custom_attrs:
|
||||
module.fail_json(msg="You can only set custom_attrs when instantiate service!")
|
||||
|
||||
if not service_id:
|
||||
service_id = get_service_id_by_name(module, auth, service_name)
|
||||
# The task should be failed when we want to manage a non-existent service identified by its name
|
||||
if not service_id and state == 'present':
|
||||
module.fail_json(msg="There is no service with name: " + service_name)
|
||||
|
||||
if state == 'absent':
|
||||
result = delete_service(module, auth, service_id)
|
||||
else:
|
||||
result = service_operation(module, auth, service_id, owner_id, group_id, permissions, role, cardinality, force, wait, wait_timeout)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1570
plugins/modules/cloud/opennebula/one_vm.py
Normal file
1570
plugins/modules/cloud/opennebula/one_vm.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue