mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
0128022654
* Add OpenNebula one_vm module * `one_vm` - module for managing VM instances instances on OpenNebula * Add integration tests
1368 lines
46 KiB
Python
1368 lines
46 KiB
Python
#!/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_vm
|
|
short_description: Creates or terminates OpenNebula instances
|
|
description:
|
|
- Manages OpenNebula instances
|
|
version_added: "2.6"
|
|
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
|
|
template_name:
|
|
description:
|
|
- Name of VM template to use to create a new instace
|
|
template_id:
|
|
description:
|
|
- ID of a VM template to use to create a new instance
|
|
instance_ids:
|
|
description:
|
|
- A list of instance ids used for states':' C(absent), C(running), C(rebooted), C(poweredoff)
|
|
aliases: ['ids']
|
|
state:
|
|
description:
|
|
- C(present) - create instances from a template specified with C(template_id)/C(template_name).
|
|
- C(running) - run instances
|
|
- C(poweredoff) - power-off instances
|
|
- C(rebooted) - reboot instances
|
|
- C(absent) - terminate instances
|
|
choices: ["present", "absent", "running", "rebooted", "poweredoff"]
|
|
default: present
|
|
hard:
|
|
description:
|
|
- Reboot, power-off or terminate instances C(hard)
|
|
default: no
|
|
type: bool
|
|
wait:
|
|
description:
|
|
- Wait for the instance to reach its desired state before returning. Keep
|
|
- in mind if you are waiting for instance to be in running state it
|
|
- doesn't mean that you will be able to SSH on that machine only that
|
|
- boot process have started on that instance, see 'wait_for' example for
|
|
- details.
|
|
default: yes
|
|
type: bool
|
|
wait_timeout:
|
|
description:
|
|
- How long before wait gives up, in seconds
|
|
default: 300
|
|
attributes:
|
|
description:
|
|
- A dictionary of key/value attributes to add to new instances, or for
|
|
- setting C(state) of instances with these attributes.
|
|
- Keys are case insensitive and OpenNebula automatically converts them to upper case.
|
|
- Be aware C(NAME) is a special attribute which sets the name of the VM when it's deployed.
|
|
- C(#) character(s) can be appended to the C(NAME) and the module will automatically add
|
|
- indexes to the names of VMs.
|
|
- For example':' C(NAME':' foo-###) would create VMs with names C(foo-000), C(foo-001),...
|
|
- When used with C(count_attributes) and C(exact_count) the module will
|
|
- match the base name without the index part.
|
|
default: {}
|
|
labels:
|
|
description:
|
|
- A list of labels to associate with new instances, or for setting
|
|
- C(state) of instances with these labels.
|
|
default: []
|
|
count_attributes:
|
|
description:
|
|
- A dictionary of key/value attributes that can only be used with
|
|
- C(exact_count) to determine how many nodes based on a specific
|
|
- attributes criteria should be deployed. This can be expressed in
|
|
- multiple ways and is shown in the EXAMPLES section.
|
|
count_labels:
|
|
description:
|
|
- A list of labels that can only be used with C(exact_count) to determine
|
|
- how many nodes based on a specific labels criteria should be deployed.
|
|
- This can be expressed in multiple ways and is shown in the EXAMPLES
|
|
- section.
|
|
count:
|
|
description:
|
|
- Number of instances to launch
|
|
default: 1
|
|
exact_count:
|
|
description:
|
|
- Indicates how many instances that match C(count_attributes) and
|
|
- C(count_labels) parameters should be deployed. Instances are either
|
|
- created or terminated based on this value.
|
|
- NOTE':' Instances with the least IDs will be terminated first.
|
|
memory:
|
|
description:
|
|
- The size of the memory for new instances (in MB, GB, ...)
|
|
disk_size:
|
|
description:
|
|
- The size of the disk created for new instances (in MB, GB, TB,...).
|
|
- NOTE':' This option can be used only if the VM template specified with
|
|
- C(template_id)/C(template_name) has exactly one disk.
|
|
cpu:
|
|
description:
|
|
- Percentage of CPU divided by 100 required for the new instance. Half a
|
|
- processor is written 0.5.
|
|
vcpu:
|
|
description:
|
|
- Number of CPUs (cores) new VM will have.
|
|
networks:
|
|
description:
|
|
- A list of dictionaries with network parameters. See examples for more details.
|
|
default: []
|
|
disk_saveas:
|
|
description:
|
|
- Creates an image from a VM disk.
|
|
- It is a dictionary where you have to specife C(name) of the new image.
|
|
- Optionally you can specife C(disk_id) of the disk you want to save. By default C(disk_id) is 0.
|
|
- I(NOTE)':' This operation will only be performed on the first VM (if more than one VM ID is passed)
|
|
- and the VM has to be in the C(poweredoff) state.
|
|
- Also this operation will fail if an image with specified C(name) already exists.
|
|
author:
|
|
- "Milan Ilic (@ilicmilan)"
|
|
'''
|
|
|
|
|
|
EXAMPLES = '''
|
|
# Create a new instance
|
|
- one_vm:
|
|
template_id: 90
|
|
register: result
|
|
|
|
# Print VM properties
|
|
- debug:
|
|
msg: result
|
|
|
|
# Deploy a new VM and set its name to 'foo'
|
|
- one_vm:
|
|
template_name: 'app1_template'
|
|
attributes:
|
|
name: foo
|
|
|
|
# Deploy 2 new instances and set memory, vcpu, disk_size and 3 networks
|
|
- one_vm:
|
|
template_id: 15
|
|
disk_size: 35.2 GB
|
|
memory: 4 GB
|
|
vcpu: 4
|
|
count: 2
|
|
networks:
|
|
- NETWORK_ID: 27
|
|
- NETWORK: "default-network"
|
|
NETWORK_UNAME: "app-user"
|
|
SECURITY_GROUPS: "120,124"
|
|
- NETWORK_ID: 27
|
|
SECURITY_GROUPS: "10"
|
|
|
|
# Deploy an new instance with attribute 'bar: bar1' and set its name to 'foo'
|
|
- one_vm:
|
|
template_id: 53
|
|
attributes:
|
|
name: foo
|
|
bar: bar1
|
|
|
|
# Enforce that 2 instances with attributes 'foo1: app1' and 'foo2: app2' are deployed
|
|
- one_vm:
|
|
template_id: 53
|
|
attributes:
|
|
foo1: app1
|
|
foo2: app2
|
|
exact_count: 2
|
|
count_attributes:
|
|
foo1: app1
|
|
foo2: app2
|
|
|
|
# Enforce that 4 instances with an attribute 'bar' are deployed
|
|
- one_vm:
|
|
template_id: 53
|
|
attributes:
|
|
name: app
|
|
bar: bar2
|
|
exact_count: 4
|
|
count_attributes:
|
|
bar:
|
|
|
|
# Deploy 2 new instances with attribute 'foo: bar' and labels 'app1' and 'app2' and names in format 'fooapp-##'
|
|
# Names will be: fooapp-00 and fooapp-01
|
|
- one_vm:
|
|
template_id: 53
|
|
attributes:
|
|
name: fooapp-##
|
|
foo: bar
|
|
labels:
|
|
- app1
|
|
- app2
|
|
count: 2
|
|
|
|
# Deploy 2 new instances with attribute 'app: app1' and names in format 'fooapp-###'
|
|
# Names will be: fooapp-002 and fooapp-003
|
|
- one_vm:
|
|
template_id: 53
|
|
attributes:
|
|
name: fooapp-###
|
|
app: app1
|
|
count: 2
|
|
|
|
# Reboot all instances with name in format 'fooapp-#'
|
|
# Instances 'fooapp-00', 'fooapp-01', 'fooapp-002' and 'fooapp-003' will be rebooted
|
|
- one_vm:
|
|
attributes:
|
|
name: fooapp-#
|
|
state: rebooted
|
|
|
|
# Enforce that only 1 instance with name in format 'fooapp-#' is deployed
|
|
# The task will delete oldest instances, so only the 'fooapp-003' will remain
|
|
- one_vm:
|
|
template_id: 53
|
|
exact_count: 1
|
|
count_attributes:
|
|
name: fooapp-#
|
|
|
|
# Deploy an new instance with a network
|
|
- one_vm:
|
|
template_id: 53
|
|
networks:
|
|
- NETWORK_ID: 27
|
|
register: vm
|
|
|
|
# Wait for SSH to come up
|
|
- wait_for_connection:
|
|
delegate_to: '{{ vm.instances[0].networks[0].ip }}'
|
|
|
|
# Terminate VMs by ids
|
|
- one_vm:
|
|
instance_ids:
|
|
- 153
|
|
- 160
|
|
state: absent
|
|
|
|
# Reboot all VMs that have labels 'foo' and 'app1'
|
|
- one_vm:
|
|
labels:
|
|
- foo
|
|
- app1
|
|
state: rebooted
|
|
|
|
# Fetch all VMs that have name 'foo' and attribute 'app: bar'
|
|
- one_vm:
|
|
attributes:
|
|
name: foo
|
|
app: bar
|
|
register: results
|
|
|
|
# Deploy 2 new instances with labels 'foo1' and 'foo2'
|
|
- one_vm:
|
|
template_name: app_template
|
|
labels:
|
|
- foo1
|
|
- foo2
|
|
count: 2
|
|
|
|
# Enforce that only 1 instance with label 'foo1' will be running
|
|
- one_vm:
|
|
template_name: app_template
|
|
labels:
|
|
- foo1
|
|
exact_count: 1
|
|
count_labels:
|
|
- foo1
|
|
|
|
# Terminate all instances that have attribute foo
|
|
- one_vm:
|
|
template_id: 53
|
|
exact_count: 0
|
|
count_attributes:
|
|
foo:
|
|
|
|
# Power-off the VM and save VM's disk with id=0 to the image with name 'foo-image'
|
|
- one_vm:
|
|
instance_ids: 351
|
|
state: powered-off
|
|
disk_saveas:
|
|
name: foo-image
|
|
|
|
# Save VM's disk with id=1 to the image with name 'bar-image'
|
|
- one_vm:
|
|
instance_ids: 351
|
|
disk_saveas:
|
|
name: bar-image
|
|
disk_id: 1
|
|
'''
|
|
|
|
RETURN = '''
|
|
instances_ids:
|
|
description: a list of instances ids whose state is changed or which are fetched with C(instance_ids) option.
|
|
type: list
|
|
returned: success
|
|
sample: [ 1234, 1235 ]
|
|
instances:
|
|
description: a list of instances info whose state is changed or which are fetched with C(instance_ids) option.
|
|
type: complex
|
|
returned: success
|
|
contains:
|
|
vm_id:
|
|
description: vm id
|
|
type: integer
|
|
sample: 153
|
|
vm_name:
|
|
description: vm name
|
|
type: string
|
|
sample: foo
|
|
template_id:
|
|
description: vm's template id
|
|
type: integer
|
|
sample: 153
|
|
group_id:
|
|
description: vm's group id
|
|
type: integer
|
|
sample: 1
|
|
group_name:
|
|
description: vm's group name
|
|
type: string
|
|
sample: one-users
|
|
user_id:
|
|
description: vm's user id
|
|
type: integer
|
|
sample: 143
|
|
user_name:
|
|
description: vm's user name
|
|
type: string
|
|
sample: app-user
|
|
state:
|
|
description: state of an instance
|
|
type: string
|
|
sample: ACTIVE
|
|
lcm_state:
|
|
description: lcm state of an instance that is only relevant when the state is ACTIVE
|
|
type: string
|
|
sample: RUNNING
|
|
cpu:
|
|
description: Percentage of CPU divided by 100
|
|
type: float
|
|
sample: 0.2
|
|
vcpu:
|
|
description: Number of CPUs (cores)
|
|
type: int
|
|
sample: 2
|
|
memory:
|
|
description: The size of the memory in MB
|
|
type: string
|
|
sample: 4096 MB
|
|
disk_size:
|
|
description: The size of the disk in MB
|
|
type: string
|
|
sample: 20480 MB
|
|
networks:
|
|
description: a list of dictionaries with info about IP, NAME, MAC, SECURITY_GROUPS for each NIC
|
|
type: list
|
|
sample: [
|
|
{
|
|
"ip": "10.120.5.33",
|
|
"mac": "02:00:0a:78:05:21",
|
|
"name": "default-test-private",
|
|
"security_groups": "0,10"
|
|
},
|
|
{
|
|
"ip": "10.120.5.34",
|
|
"mac": "02:00:0a:78:05:22",
|
|
"name": "default-test-private",
|
|
"security_groups": "0"
|
|
}
|
|
]
|
|
uptime_h:
|
|
description: Uptime of the instance in hours
|
|
type: integer
|
|
sample: 35
|
|
labels:
|
|
description: A list of string labels that are associated with the instance
|
|
type: list
|
|
sample: [
|
|
"foo",
|
|
"spec-label"
|
|
]
|
|
attributes:
|
|
description: A dictionary of key/values attributes that are associated with the instance
|
|
type: dict
|
|
sample: {
|
|
"HYPERVISOR": "kvm",
|
|
"LOGO": "images/logos/centos.png",
|
|
"TE_GALAXY": "bar",
|
|
"USER_INPUTS": null
|
|
}
|
|
tagged_instances:
|
|
description:
|
|
- A list of instances info based on a specific attributes and/or
|
|
- labels that are specified with C(count_attributes) and C(count_labels)
|
|
- options.
|
|
type: complex
|
|
returned: success
|
|
contains:
|
|
vm_id:
|
|
description: vm id
|
|
type: integer
|
|
sample: 153
|
|
vm_name:
|
|
description: vm name
|
|
type: string
|
|
sample: foo
|
|
template_id:
|
|
description: vm's template id
|
|
type: integer
|
|
sample: 153
|
|
group_id:
|
|
description: vm's group id
|
|
type: integer
|
|
sample: 1
|
|
group_name:
|
|
description: vm's group name
|
|
type: string
|
|
sample: one-users
|
|
user_id:
|
|
description: vm's user id
|
|
type: integer
|
|
sample: 143
|
|
user_name:
|
|
description: vm's user name
|
|
type: string
|
|
sample: app-user
|
|
state:
|
|
description: state of an instance
|
|
type: string
|
|
sample: ACTIVE
|
|
lcm_state:
|
|
description: lcm state of an instance that is only relevant when the state is ACTIVE
|
|
type: string
|
|
sample: RUNNING
|
|
cpu:
|
|
description: Percentage of CPU divided by 100
|
|
type: float
|
|
sample: 0.2
|
|
vcpu:
|
|
description: Number of CPUs (cores)
|
|
type: int
|
|
sample: 2
|
|
memory:
|
|
description: The size of the memory in MB
|
|
type: string
|
|
sample: 4096 MB
|
|
disk_size:
|
|
description: The size of the disk in MB
|
|
type: string
|
|
sample: 20480 MB
|
|
networks:
|
|
description: a list of dictionaries with info about IP, NAME, MAC, SECURITY_GROUPS for each NIC
|
|
type: list
|
|
sample: [
|
|
{
|
|
"ip": "10.120.5.33",
|
|
"mac": "02:00:0a:78:05:21",
|
|
"name": "default-test-private",
|
|
"security_groups": "0,10"
|
|
},
|
|
{
|
|
"ip": "10.120.5.34",
|
|
"mac": "02:00:0a:78:05:22",
|
|
"name": "default-test-private",
|
|
"security_groups": "0"
|
|
}
|
|
]
|
|
uptime_h:
|
|
description: Uptime of the instance in hours
|
|
type: integer
|
|
sample: 35
|
|
labels:
|
|
description: A list of string labels that are associated with the instance
|
|
type: list
|
|
sample: [
|
|
"foo",
|
|
"spec-label"
|
|
]
|
|
attributes:
|
|
description: A dictionary of key/values attributes that are associated with the instance
|
|
type: dict
|
|
sample: {
|
|
"HYPERVISOR": "kvm",
|
|
"LOGO": "images/logos/centos.png",
|
|
"TE_GALAXY": "bar",
|
|
"USER_INPUTS": null
|
|
}
|
|
'''
|
|
|
|
|
|
try:
|
|
import oca
|
|
HAS_OCA = True
|
|
except ImportError:
|
|
HAS_OCA = False
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
import os
|
|
|
|
|
|
def get_template(module, client, predicate):
|
|
pool = oca.VmTemplatePool(client)
|
|
# Filter -2 means fetch all templates user can Use
|
|
pool.info(filter=-2)
|
|
found = 0
|
|
found_template = None
|
|
template_name = ''
|
|
|
|
for template in pool:
|
|
if predicate(template):
|
|
found = found + 1
|
|
found_template = template
|
|
template_name = template.name
|
|
|
|
if found == 0:
|
|
return None
|
|
elif found > 1:
|
|
module.fail_json(msg='There are more templates with name: ' + template_name)
|
|
return found_template
|
|
|
|
|
|
def get_template_by_name(module, client, template_name):
|
|
return get_template(module, client, lambda template: (template.name == template_name))
|
|
|
|
|
|
def get_template_by_id(module, client, template_id):
|
|
return get_template(module, client, lambda template: (template.id == template_id))
|
|
|
|
|
|
def get_template_id(module, client, requested_id, requested_name):
|
|
template = get_template_by_id(module, client, requested_id) if requested_id else get_template_by_name(module, client, requested_name)
|
|
if template:
|
|
return template.id
|
|
else:
|
|
return None
|
|
|
|
|
|
def get_vm_by_id(client, vm_id):
|
|
pool = oca.VirtualMachinePool(client)
|
|
# Retrieves information for all or part of the vms pool
|
|
# -4: Vms belonging to the user's primary group
|
|
# -3: Vms belonging to the user
|
|
# -2: All vms user can Use
|
|
# -1: Vms belonging to the user and any of his groups - default
|
|
# >= 0: UID User's vms
|
|
pool.info(filter=-2)
|
|
|
|
for vm in pool:
|
|
if str(vm.id) == str(vm_id):
|
|
return vm
|
|
return None
|
|
|
|
|
|
def get_vms_by_ids(module, client, state, ids):
|
|
vms = []
|
|
|
|
for vm_id in ids:
|
|
vm = get_vm_by_id(client, vm_id)
|
|
if vm is None and state != 'absent':
|
|
module.fail_json(msg='There is no VM with id=' + str(vm_id))
|
|
vms.append(vm)
|
|
|
|
return vms
|
|
|
|
|
|
def get_vm_info(client, vm):
|
|
vm.info()
|
|
|
|
networks_info = []
|
|
|
|
disk_size = ''
|
|
if hasattr(vm.template, 'disks'):
|
|
disk_size = vm.template.disks[0].size + ' MB'
|
|
|
|
if hasattr(vm.template, 'nics'):
|
|
for nic in vm.template.nics:
|
|
networks_info.append({'ip': nic.ip, 'mac': nic.mac, 'name': nic.network, 'security_groups': nic.security_groups})
|
|
import time
|
|
|
|
current_time = time.localtime()
|
|
vm_start_time = time.localtime(vm.stime)
|
|
|
|
vm_uptime = time.mktime(current_time) - time.mktime(vm_start_time)
|
|
vm_uptime /= (60 * 60)
|
|
|
|
# LCM_STATE is VM's sub-state that is relevant only when STATE is ACTIVE
|
|
vm_lcm_state = None
|
|
if vm.state == VM_STATES.index('ACTIVE'):
|
|
vm_lcm_state = LCM_STATES[vm.lcm_state]
|
|
|
|
vm_labels, vm_attributes = get_vm_labels_and_attributes_dict(client, vm.id)
|
|
info = {
|
|
'template_id': int(vm.template.template_id),
|
|
'vm_id': vm.id,
|
|
'vm_name': vm.name,
|
|
'state': VM_STATES[vm.state],
|
|
'lcm_state': vm_lcm_state,
|
|
'user_name': vm.uname,
|
|
'user_id': vm.uid,
|
|
'networks': networks_info,
|
|
'disk_size': disk_size,
|
|
'memory': vm.template.memory + ' MB',
|
|
'vcpu': vm.template.vcpu,
|
|
'cpu': vm.template.cpu,
|
|
'group_name': vm.gname,
|
|
'group_id': vm.gid,
|
|
'uptime_h': int(vm_uptime),
|
|
'attributes': vm_attributes,
|
|
'labels': vm_labels
|
|
}
|
|
|
|
return info
|
|
|
|
|
|
def get_size_in_MB(module, size_str):
|
|
|
|
SYMBOLS = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
|
|
s = size_str
|
|
init = size_str
|
|
num = ""
|
|
while s and s[0:1].isdigit() or s[0:1] == '.':
|
|
num += s[0]
|
|
s = s[1:]
|
|
num = float(num)
|
|
symbol = s.strip()
|
|
|
|
if symbol not in SYMBOLS:
|
|
module.fail_json(msg="Cannot interpret %r %r %d" % (init, symbol, num))
|
|
|
|
prefix = {'B': 1}
|
|
|
|
for i, s in enumerate(SYMBOLS[1:]):
|
|
prefix[s] = 1 << (i + 1) * 10
|
|
|
|
size_in_bytes = int(num * prefix[symbol])
|
|
size_in_MB = size_in_bytes / (1024 * 1024)
|
|
|
|
return size_in_MB
|
|
|
|
|
|
def create_disk_str(module, client, template_id, disk_size_str):
|
|
|
|
if not disk_size_str:
|
|
return ''
|
|
|
|
import xml.etree.ElementTree as ET
|
|
|
|
template_XML = client.call('template.info', template_id)
|
|
root = ET.fromstring(template_XML)
|
|
|
|
disks_num = 0
|
|
disk = None
|
|
|
|
for child in root.find('TEMPLATE').findall('DISK'):
|
|
disks_num += 1
|
|
root = child
|
|
|
|
if disks_num != 1:
|
|
module.fail_json(msg='You can pass disk_size only if template has exact one disk. This template has ' + str(disks_num) + ' disks.')
|
|
|
|
disk = {}
|
|
# Get all info about existed disk e.g. IMAGE_ID,...
|
|
for child in root:
|
|
disk[child.tag] = child.text
|
|
|
|
result = 'DISK = [' + ','.join('{key}="{val}"'.format(key=key, val=val) for key, val in disk.items() if key != 'SIZE')
|
|
result += ', SIZE=' + str(get_size_in_MB(module, disk_size_str)) + ']\n'
|
|
|
|
return result
|
|
|
|
|
|
def create_attributes_str(attributes_dict, labels_list):
|
|
|
|
attributes_str = ''
|
|
|
|
if labels_list:
|
|
attributes_str += 'LABELS="' + ','.join('{label}'.format(label=label) for label in labels_list) + '"\n'
|
|
if attributes_dict:
|
|
attributes_str += '\n'.join('{key}="{val}"'.format(key=key.upper(), val=val) for key, val in attributes_dict.items()) + '\n'
|
|
|
|
return attributes_str
|
|
|
|
|
|
def create_nics_str(network_attrs_list):
|
|
nics_str = ''
|
|
|
|
for network in network_attrs_list:
|
|
# Packing key-value dict in string with format key="value", key="value"
|
|
network_str = ','.join('{key}="{val}"'.format(key=key, val=val) for key, val in network.items())
|
|
nics_str = nics_str + 'NIC = [' + network_str + ']\n'
|
|
|
|
return nics_str
|
|
|
|
|
|
def create_vm(module, client, template_id, attributes_dict, labels_list, disk_size, network_attrs_list):
|
|
|
|
if attributes_dict:
|
|
vm_name = attributes_dict.get('NAME', '')
|
|
|
|
disk_str = create_disk_str(module, client, template_id, disk_size)
|
|
vm_extra_template_str = create_attributes_str(attributes_dict, labels_list) + create_nics_str(network_attrs_list) + disk_str
|
|
vm_id = client.call('template.instantiate', template_id, vm_name, False, vm_extra_template_str)
|
|
vm = get_vm_by_id(client, vm_id)
|
|
|
|
return get_vm_info(client, vm)
|
|
|
|
|
|
def generate_next_index(vm_filled_indexes_list, num_sign_cnt):
|
|
counter = 0
|
|
cnt_str = str(counter).zfill(num_sign_cnt)
|
|
|
|
while cnt_str in vm_filled_indexes_list:
|
|
counter = counter + 1
|
|
cnt_str = str(counter).zfill(num_sign_cnt)
|
|
|
|
return cnt_str
|
|
|
|
|
|
def get_vm_labels_and_attributes_dict(client, vm_id):
|
|
import xml.etree.ElementTree as ET
|
|
vm_XML = client.call('vm.info', vm_id)
|
|
root = ET.fromstring(vm_XML)
|
|
|
|
attrs_dict = {}
|
|
labels_list = []
|
|
|
|
root = root.find('USER_TEMPLATE')
|
|
|
|
for child in root:
|
|
if child.tag != 'LABELS':
|
|
attrs_dict[child.tag] = child.text
|
|
else:
|
|
if child.text is not None:
|
|
labels_list = child.text.split(',')
|
|
|
|
return labels_list, attrs_dict
|
|
|
|
|
|
def get_all_vms_by_attributes(client, attributes_dict, labels_list):
|
|
pool = oca.VirtualMachinePool(client)
|
|
# Retrieves information for all or part of the vms pool
|
|
# -4: Vms belonging to the user's primary group
|
|
# -3: Vms belonging to the user
|
|
# -2: All vms user can Use
|
|
# -1: Vms belonging to the user and any of his groups - default
|
|
# >= 0: UID User's vms
|
|
pool.info(filter=-2)
|
|
vm_list = []
|
|
name = ''
|
|
if attributes_dict:
|
|
name = attributes_dict.pop('NAME', '')
|
|
|
|
if name != '':
|
|
base_name = name[:len(name) - name.count('#')]
|
|
# Check does the name have indexed format
|
|
with_hash = name.endswith('#')
|
|
|
|
for vm in pool:
|
|
if vm.name.startswith(base_name):
|
|
if with_hash and vm.name[len(base_name):].isdigit():
|
|
# If the name has indexed format and after base_name it has only digits it'll be matched
|
|
vm_list.append(vm)
|
|
elif not with_hash and vm.name == name:
|
|
# If the name is not indexed it has to be same
|
|
vm_list.append(vm)
|
|
pool = vm_list
|
|
|
|
import copy
|
|
|
|
vm_list = copy.copy(pool)
|
|
|
|
for vm in pool:
|
|
vm_labels_list, vm_attributes_dict = get_vm_labels_and_attributes_dict(client, vm.id)
|
|
|
|
if attributes_dict and len(attributes_dict) > 0:
|
|
for key, val in attributes_dict.items():
|
|
if key in vm_attributes_dict:
|
|
if val and vm_attributes_dict[key] != val and vm in vm_list:
|
|
vm_list.remove(vm)
|
|
break
|
|
else:
|
|
if vm in vm_list:
|
|
vm_list.remove(vm)
|
|
break
|
|
if labels_list and len(labels_list) > 0:
|
|
for label in labels_list:
|
|
if label not in vm_labels_list and vm in vm_list:
|
|
vm_list.remove(vm)
|
|
break
|
|
|
|
return vm_list
|
|
|
|
|
|
def create_count_of_vms(module, client, template_id, count, attributes_dict, labels_list, disk_size, network_attrs_list, wait, wait_timeout):
|
|
new_vms_list = []
|
|
instances_ids = []
|
|
instances = []
|
|
|
|
vm_name = ''
|
|
if attributes_dict:
|
|
vm_name = attributes_dict.get('NAME', '')
|
|
|
|
if module.check_mode:
|
|
return {'changed': True}
|
|
|
|
# Create list of used indexes
|
|
vm_filled_indexes_list = None
|
|
num_sign_cnt = vm_name.count('#')
|
|
if vm_name != '' and num_sign_cnt > 0:
|
|
vm_list = get_all_vms_by_attributes(client, {'NAME': vm_name}, None)
|
|
base_name = vm_name[:len(vm_name) - num_sign_cnt]
|
|
vm_name = base_name
|
|
# Make list which contains used indexes in format ['000', '001',...]
|
|
vm_filled_indexes_list = list((vm.name[len(base_name):].zfill(num_sign_cnt)) for vm in vm_list)
|
|
|
|
while count > 0:
|
|
new_vm_name = vm_name
|
|
# Create indexed name
|
|
if vm_filled_indexes_list is not None:
|
|
next_index = generate_next_index(vm_filled_indexes_list, num_sign_cnt)
|
|
vm_filled_indexes_list.append(next_index)
|
|
new_vm_name += next_index
|
|
# Update NAME value in the attributes in case there is index
|
|
attributes_dict['NAME'] = new_vm_name
|
|
new_vm_dict = create_vm(module, client, template_id, attributes_dict, labels_list, disk_size, network_attrs_list)
|
|
new_vm_id = new_vm_dict.get('vm_id')
|
|
new_vm = get_vm_by_id(client, new_vm_id)
|
|
new_vms_list.append(new_vm)
|
|
count -= 1
|
|
|
|
if wait:
|
|
for vm in new_vms_list:
|
|
wait_for_running(module, vm, wait_timeout)
|
|
|
|
for vm in new_vms_list:
|
|
vm_info = get_vm_info(client, vm)
|
|
instances.append(vm_info)
|
|
instances_ids.append(vm.id)
|
|
|
|
return {'changed': True, 'instances_ids': instances_ids, 'instances': instances, 'tagged_instances': instances}
|
|
|
|
|
|
def create_exact_count_of_vms(module, client, template_id, exact_count, attributes_dict, count_attributes_dict,
|
|
labels_list, count_labels_list, disk_size, network_attrs_list, hard, wait, wait_timeout):
|
|
|
|
vm_list = get_all_vms_by_attributes(client, count_attributes_dict, count_labels_list)
|
|
|
|
vm_count_diff = exact_count - len(vm_list)
|
|
changed = vm_count_diff != 0
|
|
|
|
result = {}
|
|
new_vms_list = []
|
|
instances_ids = []
|
|
instances = []
|
|
tagged_instances = list(get_vm_info(client, vm) for vm in vm_list)
|
|
|
|
if module.check_mode:
|
|
return {'changed': changed, 'instances_ids': instances_ids, 'instances': instances, 'tagged_instances': tagged_instances}
|
|
|
|
if vm_count_diff > 0:
|
|
# Add more VMs
|
|
result = create_count_of_vms(module, client, template_id, vm_count_diff, attributes_dict,
|
|
labels_list, disk_size, network_attrs_list, wait, wait_timeout)
|
|
|
|
result['tagged_instances'] += tagged_instances
|
|
return result
|
|
|
|
elif vm_count_diff < 0:
|
|
# Delete surplus VMs
|
|
old_vms_list = []
|
|
|
|
while vm_count_diff < 0:
|
|
old_vm = vm_list.pop(0)
|
|
old_vms_list.append(old_vm)
|
|
terminate_vm(module, client, old_vm, hard)
|
|
vm_count_diff += 1
|
|
|
|
if wait:
|
|
for vm in old_vms_list:
|
|
wait_for_done(module, vm, wait_timeout)
|
|
|
|
for vm in old_vms_list:
|
|
vm_info = get_vm_info(client, vm)
|
|
instances.append(vm_info)
|
|
instances_ids.append(vm.id)
|
|
tagged_instances[:] = [dct for dct in tagged_instances if dct.get('vm_id') != vm.id]
|
|
|
|
return {'changed': changed, 'instances_ids': instances_ids, 'instances': instances, 'tagged_instances': tagged_instances}
|
|
|
|
VM_STATES = ['INIT', 'PENDING', 'HOLD', 'ACTIVE', 'STOPPED', 'SUSPENDED', 'DONE', '', 'POWEROFF', 'UNDEPLOYED', 'CLONING', 'CLONING_FAILURE']
|
|
LCM_STATES = ['LCM_INIT', 'PROLOG', 'BOOT', 'RUNNING', 'MIGRATE', 'SAVE_STOP',
|
|
'SAVE_SUSPEND', 'SAVE_MIGRATE', 'PROLOG_MIGRATE', 'PROLOG_RESUME',
|
|
'EPILOG_STOP', 'EPILOG', 'SHUTDOWN', 'STATE13', 'STATE14', 'CLEANUP_RESUBMIT', 'UNKNOWN', 'HOTPLUG', 'SHUTDOWN_POWEROFF',
|
|
'BOOT_UNKNOWN', 'BOOT_POWEROFF', 'BOOT_SUSPENDED', 'BOOT_STOPPED', 'CLEANUP_DELETE', 'HOTPLUG_SNAPSHOT', 'HOTPLUG_NIC',
|
|
'HOTPLUG_SAVEAS', 'HOTPLUG_SAVEAS_POWEROFF', 'HOTPULG_SAVEAS_SUSPENDED', 'SHUTDOWN_UNDEPLOY']
|
|
|
|
|
|
def wait_for_state(module, vm, wait_timeout, state_predicate):
|
|
import time
|
|
start_time = time.time()
|
|
|
|
while (time.time() - start_time) < wait_timeout:
|
|
vm.info()
|
|
state = vm.state
|
|
lcm_state = vm.lcm_state
|
|
|
|
if state_predicate(state, lcm_state):
|
|
return vm
|
|
elif state not in [VM_STATES.index('INIT'), VM_STATES.index('PENDING'), VM_STATES.index('HOLD'),
|
|
VM_STATES.index('ACTIVE'), VM_STATES.index('POWEROFF')]:
|
|
module.fail_json(msg='Action is unsuccessful. VM state: ' + VM_STATES[state])
|
|
|
|
time.sleep(1)
|
|
|
|
module.fail_json(msg="Wait timeout has expired!")
|
|
|
|
|
|
def wait_for_running(module, vm, wait_timeout):
|
|
return wait_for_state(module, vm, wait_timeout, lambda state,
|
|
lcm_state: (state in [VM_STATES.index('ACTIVE')] and lcm_state in [LCM_STATES.index('RUNNING')]))
|
|
|
|
|
|
def wait_for_done(module, vm, wait_timeout):
|
|
return wait_for_state(module, vm, wait_timeout, lambda state, lcm_state: (state in [VM_STATES.index('DONE')]))
|
|
|
|
|
|
def wait_for_poweroff(module, vm, wait_timeout):
|
|
return wait_for_state(module, vm, wait_timeout, lambda state, lcm_state: (state in [VM_STATES.index('POWEROFF')]))
|
|
|
|
|
|
def terminate_vm(module, client, vm, hard=False):
|
|
changed = False
|
|
|
|
if not vm:
|
|
return changed
|
|
|
|
changed = True
|
|
|
|
if not module.check_mode:
|
|
if hard:
|
|
client.call('vm.action', 'terminate-hard', vm.id)
|
|
else:
|
|
client.call('vm.action', 'terminate', vm.id)
|
|
|
|
return changed
|
|
|
|
|
|
def terminate_vms(module, client, vms, wait, wait_timeout, hard, tagged):
|
|
changed = False
|
|
instances_ids = []
|
|
instances = []
|
|
|
|
if tagged:
|
|
module.fail_json(msg='Option `instance_ids` is required when state is `absent`.')
|
|
|
|
for vm in vms:
|
|
changed = terminate_vm(module, client, vm, hard) or changed
|
|
|
|
if wait and not module.check_mode:
|
|
for vm in vms:
|
|
if vm is not None:
|
|
wait_for_done(module, vm, wait_timeout)
|
|
|
|
for vm in vms:
|
|
if vm is not None:
|
|
instances_ids.append(vm.id)
|
|
instances.append(get_vm_info(client, vm))
|
|
|
|
return {'changed': changed, 'instances': instances, 'instances_ids': instances_ids, 'tagged_instances': []}
|
|
|
|
|
|
def poweroff_vm(module, vm, hard):
|
|
vm.info()
|
|
changed = False
|
|
|
|
lcm_state = vm.lcm_state
|
|
state = vm.state
|
|
|
|
if lcm_state not in [LCM_STATES.index('SHUTDOWN'), LCM_STATES.index('SHUTDOWN_POWEROFF')] and state not in [VM_STATES.index('POWEROFF')]:
|
|
changed = True
|
|
|
|
if changed and not module.check_mode:
|
|
if not hard:
|
|
vm.poweroff()
|
|
else:
|
|
vm.poweroff_hard()
|
|
|
|
return changed
|
|
|
|
|
|
def poweroff_vms(module, client, vms, wait, wait_timeout, hard, tagged):
|
|
instances_ids = []
|
|
instances = []
|
|
tagged_instances = []
|
|
changed = False
|
|
|
|
for vm in vms:
|
|
changed = poweroff_vm(module, vm, hard) or changed
|
|
|
|
if wait and not module.check_mode:
|
|
for vm in vms:
|
|
wait_for_poweroff(module, vm, wait_timeout)
|
|
|
|
for vm in vms:
|
|
instances_ids.append(vm.id)
|
|
instances.append(get_vm_info(client, vm))
|
|
if tagged:
|
|
tagged_instances.append(get_vm_info(client, vm))
|
|
|
|
return {'changed': changed, 'instances_ids': instances_ids, 'instances': instances, 'tagged_instances': tagged_instances}
|
|
|
|
|
|
def reboot_vms(module, client, vms, wait, wait_timeout, hard, tagged):
|
|
instances_ids = []
|
|
instances = []
|
|
tagged_instances = []
|
|
|
|
if not module.check_mode:
|
|
# Firstly, power-off all instances
|
|
for vm in vms:
|
|
vm.info()
|
|
lcm_state = vm.lcm_state
|
|
state = vm.state
|
|
if lcm_state not in [LCM_STATES.index('SHUTDOWN_POWEROFF')] and state not in [VM_STATES.index('POWEROFF')]:
|
|
poweroff_vm(module, vm, hard)
|
|
|
|
# Wait for all to be power-off
|
|
for vm in vms:
|
|
wait_for_poweroff(module, vm, wait_timeout)
|
|
|
|
for vm in vms:
|
|
resume_vm(module, vm)
|
|
|
|
if wait:
|
|
for vm in vms:
|
|
wait_for_running(module, vm, wait_timeout)
|
|
|
|
for vm in vms:
|
|
instances_ids.append(vm.id)
|
|
instances.append(get_vm_info(client, vm))
|
|
if tagged:
|
|
tagged_instances.append(get_vm_info(client, vm))
|
|
|
|
return {'changed': True, 'instances_ids': instances_ids, 'instances': instances, 'tagged_instances': tagged_instances}
|
|
|
|
|
|
def resume_vm(module, vm):
|
|
vm.info()
|
|
changed = False
|
|
|
|
lcm_state = vm.lcm_state
|
|
if lcm_state == LCM_STATES.index('SHUTDOWN_POWEROFF'):
|
|
module.fail_json(msg="Cannot perform action 'resume' because this action is not available " +
|
|
"for LCM_STATE: 'SHUTDOWN_POWEROFF'. Wait for the VM to shutdown properly")
|
|
if lcm_state not in [LCM_STATES.index('RUNNING')]:
|
|
changed = True
|
|
|
|
if changed and not module.check_mode:
|
|
vm.resume()
|
|
|
|
return changed
|
|
|
|
|
|
def resume_vms(module, client, vms, wait, wait_timeout, tagged):
|
|
instances_ids = []
|
|
instances = []
|
|
tagged_instances = []
|
|
|
|
changed = False
|
|
|
|
for vm in vms:
|
|
changed = resume_vm(module, vm) or changed
|
|
|
|
if wait and changed and not module.check_mode:
|
|
for vm in vms:
|
|
wait_for_running(module, vm, wait_timeout)
|
|
|
|
for vm in vms:
|
|
instances_ids.append(vm.id)
|
|
instances.append(get_vm_info(client, vm))
|
|
if tagged:
|
|
tagged_instances.append(get_vm_info(client, vm))
|
|
|
|
return {'changed': changed, 'instances_ids': instances_ids, 'instances': instances, 'tagged_instances': tagged_instances}
|
|
|
|
|
|
def check_name_attribute(module, attributes):
|
|
if attributes.get("NAME"):
|
|
import re
|
|
if re.match(r'^[^#]+#*$', attributes.get("NAME")) is None:
|
|
module.fail_json(msg="Ilegal 'NAME' attribute: '" + attributes.get("NAME") +
|
|
"' .Signs '#' are allowed only at the end of the name and the name cannot contain only '#'.")
|
|
|
|
TEMPLATE_RESTRICTED_ATTRIBUTES = ["CPU", "VCPU", "OS", "FEATURES", "MEMORY", "DISK", "NIC", "INPUT", "GRAPHICS",
|
|
"CONTEXT", "CREATED_BY", "CPU_COST", "DISK_COST", "MEMORY_COST",
|
|
"TEMPLATE_ID", "VMID", "AUTOMATIC_DS_REQUIREMENTS", "DEPLOY_FOLDER", "LABELS"]
|
|
|
|
|
|
def check_attributes(module, attributes):
|
|
for key in attributes.keys():
|
|
if key in TEMPLATE_RESTRICTED_ATTRIBUTES:
|
|
module.fail_json(msg='Restricted attribute `' + key + '` cannot be used when filtering VMs.')
|
|
# Check the format of the name attribute
|
|
check_name_attribute(module, attributes)
|
|
|
|
|
|
def disk_save_as(module, client, vm, disk_saveas, wait_timeout):
|
|
if not disk_saveas.get('name'):
|
|
module.fail_json(msg="Key 'name' is required for 'disk_saveas' option")
|
|
|
|
image_name = disk_saveas.get('name')
|
|
disk_id = disk_saveas.get('disk_id', 0)
|
|
|
|
if not module.check_mode:
|
|
if vm.state != VM_STATES.index('POWEROFF'):
|
|
module.fail_json(msg="'disksaveas' option can be used only when the VM is in 'POWEROFF' state")
|
|
client.call('vm.disksaveas', vm.id, disk_id, image_name, 'OS', -1)
|
|
wait_for_poweroff(module, vm, wait_timeout)
|
|
|
|
|
|
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},
|
|
"instance_ids": {"required": False, "aliases": ['ids'], "type": "list"},
|
|
"template_name": {"required": False, "type": "str"},
|
|
"template_id": {"required": False, "type": "int"},
|
|
"state": {
|
|
"default": "present",
|
|
"choices": ['present', 'absent', 'rebooted', 'poweredoff', 'running'],
|
|
"type": "str"
|
|
},
|
|
"wait": {"default": True, "type": "bool"},
|
|
"wait_timeout": {"default": 300, "type": "int"},
|
|
"hard": {"default": False, "type": "bool"},
|
|
"memory": {"required": False, "type": "str"},
|
|
"cpu": {"required": False, "type": "float"},
|
|
"vcpu": {"required": False, "type": "int"},
|
|
"disk_size": {"required": False, "type": "str"},
|
|
"networks": {"default": [], "type": "list"},
|
|
"count": {"default": 1, "type": "int"},
|
|
"exact_count": {"required": False, "type": "int"},
|
|
"attributes": {"default": {}, "type": "dict"},
|
|
"count_attributes": {"required": False, "type": "dict"},
|
|
"labels": {"default": [], "type": "list"},
|
|
"count_labels": {"required": False, "type": "list"},
|
|
"disk_saveas": {"type": "dict"}
|
|
}
|
|
|
|
module = AnsibleModule(argument_spec=fields,
|
|
mutually_exclusive=[
|
|
['template_id', 'template_name', 'instance_ids'],
|
|
['instance_ids', 'count_attributes', 'count'],
|
|
['instance_ids', 'count_labels', 'count'],
|
|
['instance_ids', 'exact_count'],
|
|
['instance_ids', 'attributes'],
|
|
['instance_ids', 'labels'],
|
|
['disk_saveas', 'attributes'],
|
|
['disk_saveas', 'labels'],
|
|
['exact_count', 'count'],
|
|
['count', 'hard'],
|
|
['instance_ids', 'cpu'], ['instance_ids', 'vcpu'],
|
|
['instance_ids', 'memory'], ['instance_ids', 'disk_size'],
|
|
['instance_ids', 'networks']
|
|
],
|
|
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
|
|
instance_ids = params.get('instance_ids')
|
|
requested_template_name = params.get('template_name')
|
|
requested_template_id = params.get('template_id')
|
|
state = params.get('state')
|
|
wait = params.get('wait')
|
|
wait_timeout = params.get('wait_timeout')
|
|
hard = params.get('hard')
|
|
memory = params.get('memory')
|
|
cpu = params.get('cpu')
|
|
vcpu = params.get('vcpu')
|
|
disk_size = params.get('disk_size')
|
|
networks = params.get('networks')
|
|
count = params.get('count')
|
|
exact_count = params.get('exact_count')
|
|
attributes = params.get('attributes')
|
|
count_attributes = params.get('count_attributes')
|
|
labels = params.get('labels')
|
|
count_labels = params.get('count_labels')
|
|
disk_saveas = params.get('disk_saveas')
|
|
|
|
client = oca.Client(auth.username + ':' + auth.password, auth.url)
|
|
|
|
if attributes:
|
|
attributes = dict((key.upper(), value) for key, value in attributes.items())
|
|
check_attributes(module, attributes)
|
|
|
|
if count_attributes:
|
|
count_attributes = dict((key.upper(), value) for key, value in count_attributes.items())
|
|
if not attributes:
|
|
import copy
|
|
module.warn('When you pass `count_attributes` without `attributes` option when deploying, `attributes` option will have same values implicitly.')
|
|
attributes = copy.copy(count_attributes)
|
|
check_attributes(module, count_attributes)
|
|
|
|
if count_labels and not labels:
|
|
module.warn('When you pass `count_labels` without `labels` option when deploying, `labels` option will have same values implicitly.')
|
|
labels = count_labels
|
|
|
|
# Fetch template
|
|
template_id = None
|
|
if requested_template_id or requested_template_name:
|
|
template_id = get_template_id(module, client, 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 exact_count and not template_id:
|
|
module.fail_json(msg='Option `exact_count` needs template_id or template_name')
|
|
|
|
if exact_count is not None and not (count_attributes or count_labels):
|
|
module.fail_json(msg='Either `count_attributes` or `count_labels` has to be specified with option `exact_count`.')
|
|
if (count_attributes or count_labels) and exact_count is None:
|
|
module.fail_json(msg='Option `exact_count` has to be specified when either `count_attributes` or `count_labels` is used.')
|
|
if template_id and state != 'present':
|
|
module.fail_json(msg="Only state 'present' is valid for the template")
|
|
|
|
if memory:
|
|
attributes['MEMORY'] = str(int(get_size_in_MB(module, memory)))
|
|
if cpu:
|
|
attributes['CPU'] = str(cpu)
|
|
if vcpu:
|
|
attributes['VCPU'] = str(vcpu)
|
|
|
|
if exact_count is not None and state != 'present':
|
|
module.fail_json(msg='The `exact_count` option is valid only for the `present` state')
|
|
if exact_count is not None and exact_count < 0:
|
|
module.fail_json(msg='`exact_count` cannot be less than 0')
|
|
if count <= 0:
|
|
module.fail_json(msg='`count` has to be grater than 0')
|
|
|
|
if exact_count is not None:
|
|
# Deploy an exact count of VMs
|
|
result = create_exact_count_of_vms(module, client, template_id, exact_count, attributes, count_attributes,
|
|
labels, count_labels, disk_size, networks, hard, wait, wait_timeout)
|
|
elif template_id and state == 'present':
|
|
# Deploy count VMs
|
|
result = create_count_of_vms(module, client, template_id, count, attributes, labels, disk_size, networks, wait, wait_timeout)
|
|
else:
|
|
# Fetch data of instances, or change their state
|
|
if not (instance_ids or attributes or labels):
|
|
module.fail_json(msg="At least one of `instance_ids`,`attributes`,`labels` must be passed!")
|
|
|
|
if memory or cpu or vcpu or disk_size or networks:
|
|
module.fail_json(msg="Parameters as `memory`, `cpu`, `vcpu`, `disk_size` and `networks` you can only set when deploying a VM!")
|
|
|
|
if hard and state not in ['rebooted', 'poweredoff', 'absent', 'present']:
|
|
module.fail_json(msg="The 'hard' option can be used only for one of these states: 'rebooted', 'poweredoff', 'absent' and 'present'")
|
|
|
|
vms = None
|
|
tagged = False
|
|
|
|
if instance_ids:
|
|
vms = get_vms_by_ids(module, client, state, instance_ids)
|
|
else:
|
|
tagged = True
|
|
vms = get_all_vms_by_attributes(client, attributes, labels)
|
|
|
|
instances = list(get_vm_info(client, vm) for vm in vms if vm is not None)
|
|
instances_ids = list(vm.id for vm in vms if vm is not None)
|
|
|
|
if tagged:
|
|
result = {'changed': False, 'instances': instances, 'instances_ids': instances_ids, 'tagged_instances': instances}
|
|
else:
|
|
result = {'changed': False, 'instances': instances, 'instances_ids': instances_ids, 'tagged_instances': []}
|
|
|
|
if len(vms) == 0 and state != 'absent' and state != 'present':
|
|
module.fail_json(msg='There are no instances with specified `instance_ids`, `attributes` and/or `labels`')
|
|
|
|
if len(vms) == 0 and state == 'present' and not tagged:
|
|
module.fail_json(msg='There are no instances with specified `instance_ids`.')
|
|
|
|
if state == 'absent':
|
|
result = terminate_vms(module, client, vms, wait, wait_timeout, hard, tagged)
|
|
elif state == 'rebooted':
|
|
result = reboot_vms(module, client, vms, wait, wait_timeout, hard, tagged)
|
|
elif state == 'poweredoff':
|
|
result = poweroff_vms(module, client, vms, wait, wait_timeout, hard, tagged)
|
|
elif state == 'running':
|
|
result = resume_vms(module, client, vms, wait, wait_timeout, tagged)
|
|
|
|
if disk_saveas is not None:
|
|
if len(vms) == 0:
|
|
module.fail_json(msg="There is no VM whose disk will be saved.")
|
|
disk_save_as(module, client, vms[0], disk_saveas, wait_timeout)
|
|
result['changed'] = True
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|