1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/modules/one_vm.py
2023-10-25 23:01:32 +02:00

1725 lines
60 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Milan Ilic <milani@nordeus.com>
# Copyright (c) 2019, Jan Meerkamp <meerkamp@dvv.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
module: one_vm
short_description: Creates or terminates OpenNebula instances
description:
- Manages OpenNebula instances
requirements:
- pyone
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
diff_mode:
support: none
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 E(ONE_URL) environment variable is used.
type: str
api_username:
description:
- Name of the user to login into the OpenNebula RPC server. If not set
then the value of the E(ONE_USERNAME) environment variable is used.
type: str
api_password:
description:
- Password of the user to login into OpenNebula RPC server. If not set
then the value of the E(ONE_PASSWORD) environment variable is used.
if both O(api_username) or O(api_password) are not set, then it will try
authenticate with ONE auth file. Default path is "~/.one/one_auth".
- Set environment variable E(ONE_AUTH) to override this path.
type: str
template_name:
description:
- Name of VM template to use to create a new instance
type: str
template_id:
description:
- ID of a VM template to use to create a new instance
type: int
vm_start_on_hold:
description:
- Set to true to put vm on hold while creating
default: false
type: bool
instance_ids:
description:
- 'A list of instance ids used for states: V(absent), V(running), V(rebooted), V(poweredoff).'
aliases: ['ids']
type: list
elements: int
state:
description:
- V(present) - create instances from a template specified with C(template_id)/C(template_name).
- V(running) - run instances
- V(poweredoff) - power-off instances
- V(rebooted) - reboot instances
- V(absent) - terminate instances
choices: ["present", "absent", "running", "rebooted", "poweredoff"]
default: present
type: str
hard:
description:
- Reboot, power-off or terminate instances C(hard).
default: false
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: true
type: bool
wait_timeout:
description:
- How long before wait gives up, in seconds
default: 300
type: int
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 O(count_attributes) and O(exact_count) the module will
match the base name without the index part.
default: {}
type: dict
labels:
description:
- A list of labels to associate with new instances, or for setting
C(state) of instances with these labels.
default: []
type: list
elements: str
count_attributes:
description:
- A dictionary of key/value attributes that can only be used with
O(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.
type: dict
count_labels:
description:
- A list of labels that can only be used with O(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.
type: list
elements: str
count:
description:
- Number of instances to launch
default: 1
type: int
exact_count:
description:
- Indicates how many instances that match O(count_attributes) and
O(count_labels) parameters should be deployed. Instances are either
created or terminated based on this value.
- 'B(NOTE:) Instances with the least IDs will be terminated first.'
type: int
mode:
description:
- Set permission mode of the instance in octet format, for example V(0600) to give owner C(use) and C(manage) and nothing to group and others.
type: str
owner_id:
description:
- ID of the user which will be set as the owner of the instance
type: int
group_id:
description:
- ID of the group which will be set as the group of the instance
type: int
memory:
description:
- The size of the memory for new instances (in MB, GB, ...)
type: str
disk_size:
description:
- The size of the disk created for new instances (in MB, GB, TB,...).
- 'B(NOTE:) If The Template hats Multiple Disks the Order of the Sizes is
matched against the order specified in O(template_id)/O(template_name).'
type: list
elements: str
cpu:
description:
- Percentage of CPU divided by 100 required for the new instance. Half a
processor is written 0.5.
type: float
vcpu:
description:
- Number of CPUs (cores) new VM will have.
type: int
networks:
description:
- A list of dictionaries with network parameters. See examples for more details.
default: []
type: list
elements: dict
disk_saveas:
description:
- Creates an image from a VM disk.
- It is a dictionary where you have to specify C(name) of the new image.
- Optionally you can specify C(disk_id) of the disk you want to save. By default C(disk_id) is 0.
- 'B(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.
type: dict
persistent:
description:
- Create a private persistent copy of the template plus any image defined in DISK, and instantiate that copy.
default: false
type: bool
version_added: '0.2.0'
datastore_id:
description:
- Name of Datastore to use to create a new instance
version_added: '0.2.0'
type: int
datastore_name:
description:
- Name of Datastore to use to create a new instance
version_added: '0.2.0'
type: str
updateconf:
description:
- When O(instance_ids) is provided, updates running VMs with the C(updateconf) API call.
- When new VMs are being created, emulates the C(updateconf) API call via direct template merge.
- Allows for complete modifications of the C(CONTEXT) attribute.
type: dict
version_added: 6.3.0
author:
- "Milan Ilic (@ilicmilan)"
- "Jan Meerkamp (@meerkampdvv)"
'''
EXAMPLES = '''
- name: Create a new instance
community.general.one_vm:
template_id: 90
register: result
- name: Print VM properties
ansible.builtin.debug:
msg: result
- name: Deploy a new VM on hold
community.general.one_vm:
template_name: 'app1_template'
vm_start_on_hold: 'True'
- name: Deploy a new VM and set its name to 'foo'
community.general.one_vm:
template_name: 'app1_template'
attributes:
name: foo
- name: Deploy a new VM and set its group_id and mode
community.general.one_vm:
template_id: 90
group_id: 16
mode: 660
- name: Deploy a new VM as persistent
community.general.one_vm:
template_id: 90
persistent: true
- name: Change VM's permissions to 640
community.general.one_vm:
instance_ids: 5
mode: 640
- name: Deploy 2 new instances and set memory, vcpu, disk_size and 3 networks
community.general.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"
- name: Deploy a new instance which uses a Template with two Disks
community.general.one_vm:
template_id: 42
disk_size:
- 35.2 GB
- 50 GB
memory: 4 GB
vcpu: 4
count: 1
networks:
- NETWORK_ID: 27
- name: "Deploy an new instance with attribute 'bar: bar1' and set its name to 'foo'"
community.general.one_vm:
template_id: 53
attributes:
name: foo
bar: bar1
- name: "Enforce that 2 instances with attributes 'foo1: app1' and 'foo2: app2' are deployed"
community.general.one_vm:
template_id: 53
attributes:
foo1: app1
foo2: app2
exact_count: 2
count_attributes:
foo1: app1
foo2: app2
- name: Enforce that 4 instances with an attribute 'bar' are deployed
community.general.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
- name: Deploy 2 new instances
community.general.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
- name: Deploy 2 new instances
community.general.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
- name: Reboot all instances with names in a certain format
community.general.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
- name: Enforce that only 1 instance with name in a certain format is deployed
community.general.one_vm:
template_id: 53
exact_count: 1
count_attributes:
name: fooapp-#
- name: Deploy an new instance with a network
community.general.one_vm:
template_id: 53
networks:
- NETWORK_ID: 27
register: vm
- name: Wait for SSH to come up
ansible.builtin.wait_for_connection:
delegate_to: '{{ vm.instances[0].networks[0].ip }}'
- name: Terminate VMs by ids
community.general.one_vm:
instance_ids:
- 153
- 160
state: absent
- name: Reboot all VMs that have labels 'foo' and 'app1'
community.general.one_vm:
labels:
- foo
- app1
state: rebooted
- name: "Fetch all VMs that have name 'foo' and attribute 'app: bar'"
community.general.one_vm:
attributes:
name: foo
app: bar
register: results
- name: Deploy 2 new instances with labels 'foo1' and 'foo2'
community.general.one_vm:
template_name: app_template
labels:
- foo1
- foo2
count: 2
- name: Enforce that only 1 instance with label 'foo1' will be running
community.general.one_vm:
template_name: app_template
labels:
- foo1
exact_count: 1
count_labels:
- foo1
- name: Terminate all instances that have attribute foo
community.general.one_vm:
template_id: 53
exact_count: 0
count_attributes:
foo:
- name: "Power-off the VM and save VM's disk with id=0 to the image with name 'foo-image'"
community.general.one_vm:
instance_ids: 351
state: poweredoff
disk_saveas:
name: foo-image
- name: "Save VM's disk with id=1 to the image with name 'bar-image'"
community.general.one_vm:
instance_ids: 351
disk_saveas:
name: bar-image
disk_id: 1
- name: "Deploy 2 new instances with a custom 'start script'"
community.general.one_vm:
template_name: app_template
count: 2
updateconf:
CONTEXT:
START_SCRIPT: ip r r 169.254.16.86/32 dev eth0
- name: "Add a custom 'start script' to a running VM"
community.general.one_vm:
instance_ids: 351
updateconf:
CONTEXT:
START_SCRIPT: ip r r 169.254.16.86/32 dev eth0
- name: "Update SSH public keys inside the VM's context"
community.general.one_vm:
instance_ids: 351
updateconf:
CONTEXT:
SSH_PUBLIC_KEY: |-
ssh-rsa ...
ssh-ed25519 ...
'''
RETURN = '''
instances_ids:
description: a list of instances ids whose state is changed or which are fetched with O(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 O(instance_ids) option.
type: complex
returned: success
contains:
vm_id:
description: vm id
type: int
sample: 153
vm_name:
description: vm name
type: str
sample: foo
template_id:
description: vm's template id
type: int
sample: 153
group_id:
description: vm's group id
type: int
sample: 1
group_name:
description: vm's group name
type: str
sample: one-users
owner_id:
description: vm's owner id
type: int
sample: 143
owner_name:
description: vm's owner name
type: str
sample: app-user
mode:
description: vm's mode
type: str
returned: success
sample: 660
state:
description: state of an instance
type: str
sample: ACTIVE
lcm_state:
description: lcm state of an instance that is only relevant when the state is ACTIVE
type: str
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: str
sample: 4096 MB
disk_size:
description: The size of the disk in MB
type: str
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: int
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
}
updateconf:
description: A dictionary of key/values attributes that are set with the updateconf API call.
type: dict
version_added: 6.3.0
sample: {
"OS": { "ARCH": "x86_64" },
"CONTEXT": {
"START_SCRIPT": "ip r r 169.254.16.86/32 dev eth0",
"SSH_PUBLIC_KEY": "ssh-rsa ...\\nssh-ed25519 ..."
}
}
tagged_instances:
description:
- A list of instances info based on a specific attributes and/or
- labels that are specified with O(count_attributes) and O(count_labels)
- options.
type: complex
returned: success
contains:
vm_id:
description: vm id
type: int
sample: 153
vm_name:
description: vm name
type: str
sample: foo
template_id:
description: vm's template id
type: int
sample: 153
group_id:
description: vm's group id
type: int
sample: 1
group_name:
description: vm's group name
type: str
sample: one-users
owner_id:
description: vm's user id
type: int
sample: 143
owner_name:
description: vm's user name
type: str
sample: app-user
mode:
description: vm's mode
type: str
returned: success
sample: 660
state:
description: state of an instance
type: str
sample: ACTIVE
lcm_state:
description: lcm state of an instance that is only relevant when the state is ACTIVE
type: str
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: str
sample: 4096 MB
disk_size:
description: The size of the disk in MB
type: list
sample: [
"20480 MB",
"10240 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: int
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
}
updateconf:
description: A dictionary of key/values attributes that are set with the updateconf API call
type: dict
version_added: 6.3.0
sample: {
"OS": { "ARCH": "x86_64" },
"CONTEXT": {
"START_SCRIPT": "ip r r 169.254.16.86/32 dev eth0",
"SSH_PUBLIC_KEY": "ssh-rsa ...\\nssh-ed25519 ..."
}
}
'''
try:
import pyone
HAS_PYONE = True
except ImportError:
HAS_PYONE = False
import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.dict_transformations import dict_merge
from ansible_collections.community.general.plugins.module_utils.opennebula import flatten, render
UPDATECONF_ATTRIBUTES = {
"OS": ["ARCH", "MACHINE", "KERNEL", "INITRD", "BOOTLOADER", "BOOT", "SD_DISK_BUS", "UUID"],
"FEATURES": ["ACPI", "PAE", "APIC", "LOCALTIME", "HYPERV", "GUEST_AGENT"],
"INPUT": ["TYPE", "BUS"],
"GRAPHICS": ["TYPE", "LISTEN", "PASSWD", "KEYMAP"],
"RAW": ["DATA", "DATA_VMX", "TYPE"],
"CONTEXT": [],
}
def check_updateconf(module, to_check):
'''Checks if attributes are compatible with one.vm.updateconf API call.'''
for attr, subattributes in to_check.items():
if attr not in UPDATECONF_ATTRIBUTES:
module.fail_json(msg="'{0:}' is not a valid VM attribute.".format(attr))
if not UPDATECONF_ATTRIBUTES[attr]:
continue
for subattr in subattributes:
if subattr not in UPDATECONF_ATTRIBUTES[attr]:
module.fail_json(msg="'{0:}' is not a valid VM subattribute of '{1:}'".format(subattr, attr))
def parse_updateconf(vm_template):
'''Extracts 'updateconf' attributes from a VM template.'''
updateconf = {}
for attr, subattributes in vm_template.items():
if attr not in UPDATECONF_ATTRIBUTES:
continue
tmp = {}
for subattr, value in subattributes.items():
if UPDATECONF_ATTRIBUTES[attr] and subattr not in UPDATECONF_ATTRIBUTES[attr]:
continue
tmp[subattr] = value
if tmp:
updateconf[attr] = tmp
return updateconf
def get_template(module, client, predicate):
pool = client.templatepool.info(-2, -1, -1, -1)
# Filter -2 means fetch all templates user can Use
found = 0
found_template = None
template_name = ''
for template in pool.VMTEMPLATE:
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 is not None else get_template_by_name(module, client, requested_name)
if template:
return template.ID
else:
return None
def get_datastore(module, client, predicate):
pool = client.datastorepool.info()
found = 0
found_datastore = None
datastore_name = ''
for datastore in pool.DATASTORE:
if predicate(datastore):
found = found + 1
found_datastore = datastore
datastore_name = datastore.NAME
if found == 0:
return None
elif found > 1:
module.fail_json(msg='There are more datastores with name: ' + datastore_name)
return found_datastore
def get_datastore_by_name(module, client, datastore_name):
return get_datastore(module, client, lambda datastore: (datastore.NAME == datastore_name))
def get_datastore_by_id(module, client, datastore_id):
return get_datastore(module, client, lambda datastore: (datastore.ID == datastore_id))
def get_datastore_id(module, client, requested_id, requested_name):
datastore = get_datastore_by_id(module, client, requested_id) if requested_id else get_datastore_by_name(module, client, requested_name)
if datastore:
return datastore.ID
else:
return None
def get_vm_by_id(client, vm_id):
try:
vm = client.vm.info(int(vm_id))
except BaseException:
return None
return vm
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 = client.vm.info(vm.ID)
networks_info = []
disk_size = []
if 'DISK' in vm.TEMPLATE:
if isinstance(vm.TEMPLATE['DISK'], list):
for disk in vm.TEMPLATE['DISK']:
disk_size.append(disk['SIZE'] + ' MB')
else:
disk_size.append(vm.TEMPLATE['DISK']['SIZE'] + ' MB')
if 'NIC' in vm.TEMPLATE:
if isinstance(vm.TEMPLATE['NIC'], list):
for nic in vm.TEMPLATE['NIC']:
networks_info.append({
'ip': nic.get('IP', ''),
'mac': nic.get('MAC', ''),
'name': nic.get('NETWORK', ''),
'security_groups': nic.get('SECURITY_GROUPS', '')
})
else:
networks_info.append({
'ip': vm.TEMPLATE['NIC'].get('IP', ''),
'mac': vm.TEMPLATE['NIC'].get('MAC', ''),
'name': vm.TEMPLATE['NIC'].get('NETWORK', ''),
'security_groups':
vm.TEMPLATE['NIC'].get('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)
permissions_str = parse_vm_permissions(client, vm)
# 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)
updateconf = parse_updateconf(vm.TEMPLATE)
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,
'owner_name': vm.UNAME,
'owner_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,
'mode': permissions_str,
'labels': vm_labels,
'updateconf': updateconf,
}
return info
def parse_vm_permissions(client, vm):
vm_PERMISSIONS = client.vm.info(vm.ID).PERMISSIONS
owner_octal = int(vm_PERMISSIONS.OWNER_U) * 4 + int(vm_PERMISSIONS.OWNER_M) * 2 + int(vm_PERMISSIONS.OWNER_A)
group_octal = int(vm_PERMISSIONS.GROUP_U) * 4 + int(vm_PERMISSIONS.GROUP_M) * 2 + int(vm_PERMISSIONS.GROUP_A)
other_octal = int(vm_PERMISSIONS.OTHER_U) * 4 + int(vm_PERMISSIONS.OTHER_M) * 2 + int(vm_PERMISSIONS.OTHER_A)
permissions = str(owner_octal) + str(group_octal) + str(other_octal)
return permissions
def set_vm_permissions(module, client, vms, permissions):
changed = False
for vm in vms:
vm = client.vm.info(vm.ID)
old_permissions = parse_vm_permissions(client, vm)
changed = changed or old_permissions != permissions
if not module.check_mode and old_permissions != permissions:
permissions_str = bin(int(permissions, base=8))[2:] # 600 -> 110000000
mode_bits = [int(d) for d in permissions_str]
try:
client.vm.chmod(
vm.ID, mode_bits[0], mode_bits[1], mode_bits[2], mode_bits[3], mode_bits[4], mode_bits[5], mode_bits[6], mode_bits[7], mode_bits[8])
except pyone.OneAuthorizationException:
module.fail_json(msg="Permissions changing is unsuccessful, but instances are present if you deployed them.")
return changed
def set_vm_ownership(module, client, vms, owner_id, group_id):
changed = False
for vm in vms:
vm = client.vm.info(vm.ID)
if owner_id is None:
owner_id = vm.UID
if group_id is None:
group_id = vm.GID
changed = changed or owner_id != vm.UID or group_id != vm.GID
if not module.check_mode and (owner_id != vm.UID or group_id != vm.GID):
try:
client.vm.chown(vm.ID, owner_id, group_id)
except pyone.OneAuthorizationException:
module.fail_json(msg="Ownership changing is unsuccessful, but instances are present if you deployed them.")
return changed
def update_vm(module, client, vm, updateconf_dict):
changed = False
if not updateconf_dict:
return changed
before = client.vm.info(vm.ID).TEMPLATE
client.vm.updateconf(vm.ID, render(updateconf_dict), 1) # 1: Merge new template with the existing one.
after = client.vm.info(vm.ID).TEMPLATE
changed = before != after
return changed
def update_vms(module, client, vms, *args):
changed = False
for vm in vms:
changed = update_vm(module, client, vm, *args) or changed
return changed
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_vm(module, client, template_id, attributes_dict, labels_list, disk_size, network_attrs_list, vm_start_on_hold, vm_persistent, updateconf_dict):
if attributes_dict:
vm_name = attributes_dict.get('NAME', '')
template = client.template.info(template_id).TEMPLATE
disk_count = len(flatten(template.get('DISK', [])))
if disk_size:
size_count = len(flatten(disk_size))
# check if the number of disks is correct
if disk_count != size_count:
module.fail_json(msg='This template has ' + str(disk_count) + ' disks but you defined ' + str(size_count))
vm_extra_template = dict_merge(template or {}, attributes_dict or {})
vm_extra_template = dict_merge(vm_extra_template, {
'LABELS': ','.join(labels_list),
'NIC': flatten(network_attrs_list, extract=True),
'DISK': flatten([
disk if not size else dict_merge(disk, {
'SIZE': str(int(get_size_in_MB(module, size))),
})
for disk, size in zip(
flatten(template.get('DISK', [])),
flatten(disk_size or [None] * disk_count),
)
if disk is not None
], extract=True)
})
vm_extra_template = dict_merge(vm_extra_template, updateconf_dict or {})
try:
vm_id = client.template.instantiate(template_id,
vm_name,
vm_start_on_hold,
render(vm_extra_template),
vm_persistent)
except pyone.OneException as e:
module.fail_json(msg=str(e))
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):
vm_USER_TEMPLATE = client.vm.info(vm_id).USER_TEMPLATE
attrs_dict = {}
labels_list = []
for key, value in vm_USER_TEMPLATE.items():
if key != 'LABELS':
attrs_dict[key] = value
else:
if key is not None and value is not None:
labels_list = value.split(',')
return labels_list, attrs_dict
def get_all_vms_by_attributes(client, attributes_dict, labels_list):
pool = client.vmpool.info(-2, -1, -1, -1).VM
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:
remove_list = []
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:
remove_list.append(vm)
break
else:
remove_list.append(vm)
break
vm_list = list(set(vm_list).difference(set(remove_list)))
remove_list = []
if labels_list and len(labels_list) > 0:
for label in labels_list:
if label not in vm_labels_list:
remove_list.append(vm)
break
vm_list = list(set(vm_list).difference(set(remove_list)))
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, vm_start_on_hold, vm_persistent, updateconf_dict):
new_vms_list = []
vm_name = ''
if attributes_dict:
vm_name = attributes_dict.get('NAME', '')
if module.check_mode:
return 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,
vm_start_on_hold, vm_persistent, updateconf_dict)
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 vm_start_on_hold:
if wait:
for vm in new_vms_list:
wait_for_hold(module, client, vm, wait_timeout)
else:
if wait:
for vm in new_vms_list:
wait_for_running(module, client, vm, wait_timeout)
return True, new_vms_list, []
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_start_on_hold, vm_persistent, updateconf_dict):
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
new_vms_list = []
instances_list = []
tagged_instances_list = vm_list
if module.check_mode:
return changed, instances_list, tagged_instances_list
if vm_count_diff > 0:
# Add more VMs
changed, instances_list, tagged_instances = create_count_of_vms(module, client, template_id, vm_count_diff, attributes_dict,
labels_list, disk_size, network_attrs_list, wait, wait_timeout,
vm_start_on_hold, vm_persistent, updateconf_dict)
tagged_instances_list += instances_list
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, client, vm, wait_timeout)
instances_list = old_vms_list
# store only the remaining instances
old_vms_set = set(old_vms_list)
tagged_instances_list = [vm for vm in vm_list if vm not in old_vms_set]
return changed, instances_list, tagged_instances_list
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, client, vm, wait_timeout, state_predicate):
import time
start_time = time.time()
while (time.time() - start_time) < wait_timeout:
vm = client.vm.info(vm.ID)
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('CLONING'), 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, client, vm, wait_timeout):
return wait_for_state(module, client, 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, client, vm, wait_timeout):
return wait_for_state(module, client, vm, wait_timeout, lambda state, lcm_state: (state in [VM_STATES.index('DONE')]))
def wait_for_hold(module, client, vm, wait_timeout):
return wait_for_state(module, client, vm, wait_timeout, lambda state, lcm_state: (state in [VM_STATES.index('HOLD')]))
def wait_for_poweroff(module, client, vm, wait_timeout):
return wait_for_state(module, client, 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.vm.action('terminate-hard', vm.ID)
else:
client.vm.action('terminate', vm.ID)
return changed
def terminate_vms(module, client, vms, hard):
changed = False
for vm in vms:
changed = terminate_vm(module, client, vm, hard) or changed
return changed
def poweroff_vm(module, client, vm, hard):
vm = client.vm.info(vm.ID)
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:
client.vm.action('poweroff', vm.ID)
else:
client.vm.action('poweroff-hard', vm.ID)
return changed
def poweroff_vms(module, client, vms, hard):
changed = False
for vm in vms:
changed = poweroff_vm(module, client, vm, hard) or changed
return changed
def reboot_vms(module, client, vms, wait_timeout, hard):
if not module.check_mode:
# Firstly, power-off all instances
for vm in vms:
vm = client.vm.info(vm.ID)
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, client, vm, hard)
# Wait for all to be power-off
for vm in vms:
wait_for_poweroff(module, client, vm, wait_timeout)
for vm in vms:
resume_vm(module, client, vm)
return True
def resume_vm(module, client, vm):
vm = client.vm.info(vm.ID)
changed = False
state = vm.STATE
if state in [VM_STATES.index('HOLD')]:
changed = release_vm(module, client, vm)
return changed
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:
client.vm.action('resume', vm.ID)
return changed
def resume_vms(module, client, vms):
changed = False
for vm in vms:
changed = resume_vm(module, client, vm) or changed
return changed
def release_vm(module, client, vm):
vm = client.vm.info(vm.ID)
changed = False
state = vm.STATE
if state != VM_STATES.index('HOLD'):
module.fail_json(msg="Cannot perform action 'release' because this action is not available " +
"because VM is not in state 'HOLD'.")
else:
changed = True
if changed and not module.check_mode:
client.vm.action('release', vm.ID)
return changed
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="Illegal '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")
try:
client.vm.disksaveas(vm.ID, disk_id, image_name, 'OS', -1)
except pyone.OneException as e:
module.fail_json(msg=str(e))
wait_for_poweroff(module, client, vm, wait_timeout) # wait for VM to leave the hotplug_saveas_poweroff state
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 username:
if not password:
authfile = os.environ.get('ONE_AUTH')
if authfile is None:
authfile = os.path.join(os.environ.get("HOME"), ".one", "one_auth")
try:
with open(authfile, "r") as fp:
authstring = fp.read().rstrip()
username = authstring.split(":")[0]
password = authstring.split(":")[1]
except (OSError, IOError):
module.fail_json(msg=("Could not find or read ONE_AUTH file at '%s'" % authfile))
except Exception:
module.fail_json(msg=("Error occurs when read ONE_AUTH file at '%s'" % authfile))
if not url:
module.fail_json(msg="Opennebula API url (api_url) is 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", "elements": "int"},
"template_name": {"required": False, "type": "str"},
"template_id": {"required": False, "type": "int"},
"vm_start_on_hold": {"default": False, "type": "bool"},
"state": {
"default": "present",
"choices": ['present', 'absent', 'rebooted', 'poweredoff', 'running'],
"type": "str"
},
"mode": {"required": False, "type": "str"},
"owner_id": {"required": False, "type": "int"},
"group_id": {"required": False, "type": "int"},
"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": "list", "elements": "str"},
"datastore_name": {"required": False, "type": "str"},
"datastore_id": {"required": False, "type": "int"},
"networks": {"default": [], "type": "list", "elements": "dict"},
"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", "elements": "str"},
"count_labels": {"required": False, "type": "list", "elements": "str"},
"disk_saveas": {"type": "dict"},
"persistent": {"default": False, "type": "bool"},
"updateconf": {"type": "dict"},
}
module = AnsibleModule(argument_spec=fields,
mutually_exclusive=[
['template_id', 'template_name', 'instance_ids'],
['template_id', 'template_name', 'disk_saveas'],
['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'],
['persistent', 'disk_size']
],
supports_check_mode=True)
if not HAS_PYONE:
module.fail_json(msg='This module requires pyone 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')
put_vm_on_hold = params.get('vm_start_on_hold')
state = params.get('state')
permissions = params.get('mode')
owner_id = params.get('owner_id')
group_id = params.get('group_id')
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')
requested_datastore_id = params.get('datastore_id')
requested_datastore_name = params.get('datastore_name')
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')
persistent = params.get('persistent')
updateconf = params.get('updateconf')
if not (auth.username and auth.password):
module.warn("Credentials missing")
else:
one_client = pyone.OneServer(auth.url, session=auth.username + ':' + auth.password)
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 updateconf:
check_updateconf(module, updateconf)
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 is not None or requested_template_name:
template_id = get_template_id(module, one_client, requested_template_id, requested_template_name)
if template_id is None:
if requested_template_id is not None:
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)
# Fetch datastore
datastore_id = None
if requested_datastore_id or requested_datastore_name:
datastore_id = get_datastore_id(module, one_client, requested_datastore_id, requested_datastore_name)
if datastore_id is None:
if requested_datastore_id:
module.fail_json(msg='There is no datastore with datastore_id: ' + str(requested_datastore_id))
elif requested_datastore_name:
module.fail_json(msg="There is no datastore with name: " + requested_datastore_name)
else:
attributes['SCHED_DS_REQUIREMENTS'] = 'ID=' + str(datastore_id)
if exact_count and template_id is None:
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 is not None 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 greater than 0')
if permissions is not None:
import re
if re.match("^[0-7]{3}$", permissions) is None:
module.fail_json(msg="Option `mode` has to have exactly 3 digits and be in the octet format e.g. 600")
if exact_count is not None:
# Deploy an exact count of VMs
changed, instances_list, tagged_instances_list = create_exact_count_of_vms(module, one_client, template_id, exact_count, attributes,
count_attributes, labels, count_labels, disk_size,
networks, hard, wait, wait_timeout, put_vm_on_hold, persistent, updateconf)
vms = tagged_instances_list
elif template_id is not None and state == 'present':
# Deploy count VMs
changed, instances_list, tagged_instances_list = create_count_of_vms(module, one_client, template_id, count,
attributes, labels, disk_size, networks, wait, wait_timeout,
put_vm_on_hold, persistent, updateconf)
# instances_list - new instances
# tagged_instances_list - all instances with specified `count_attributes` and `count_labels`
vms = instances_list
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 = []
tagged = False
changed = False
if instance_ids:
vms = get_vms_by_ids(module, one_client, state, instance_ids)
else:
tagged = True
vms = get_all_vms_by_attributes(one_client, attributes, labels)
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 tagged and state == 'absent':
module.fail_json(msg='Option `instance_ids` is required when state is `absent`.')
if state == 'absent':
changed = terminate_vms(module, one_client, vms, hard)
elif state == 'rebooted':
changed = reboot_vms(module, one_client, vms, wait_timeout, hard)
elif state == 'poweredoff':
changed = poweroff_vms(module, one_client, vms, hard)
elif state == 'running':
changed = resume_vms(module, one_client, vms)
instances_list = vms
tagged_instances_list = []
if permissions is not None:
changed = set_vm_permissions(module, one_client, vms, permissions) or changed
if owner_id is not None or group_id is not None:
changed = set_vm_ownership(module, one_client, vms, owner_id, group_id) or changed
if template_id is None and updateconf is not None:
changed = update_vms(module, one_client, vms, updateconf) or changed
if wait and not module.check_mode and state != 'present':
wait_for = {
'absent': wait_for_done,
'rebooted': wait_for_running,
'poweredoff': wait_for_poweroff,
'running': wait_for_running
}
for vm in vms:
if vm is not None:
wait_for[state](module, one_client, vm, wait_timeout)
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, one_client, vms[0], disk_saveas, wait_timeout)
changed = True
# instances - a list of instances info whose state is changed or which are fetched with C(instance_ids) option
instances = list(get_vm_info(one_client, vm) for vm in instances_list if vm is not None)
instances_ids = list(vm.ID for vm in instances_list if vm is not None)
# tagged_instances - A list of instances info based on a specific attributes and/or labels that are specified with C(count_attributes) and C(count_labels)
tagged_instances = list(get_vm_info(one_client, vm) for vm in tagged_instances_list if vm is not None)
result = {'changed': changed, 'instances': instances, 'instances_ids': instances_ids, 'tagged_instances': tagged_instances}
module.exit_json(**result)
if __name__ == '__main__':
main()