2020-03-09 10:11:07 +01:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
2022-11-06 20:28:56 +01:00
# Copyright (c) 2017, Milan Ilic <milani@nordeus.com>
# Copyright (c) 2019, Jan Meerkamp <meerkamp@dvv.de>
2022-08-07 13:37:23 +02:00
# 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
2020-03-09 10:11:07 +01:00
# 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
Add attributes to atomic, memset, one, oneview, packet, proxmox, and xenserver modules (#5958)
Add attributes to atomic, memset, one, oneview, packet, proxmox, and xenserver modules.
2023-02-20 17:27:38 +01:00
extends_documentation_fragment :
- community . general . attributes
attributes :
check_mode :
support : full
diff_mode :
support : none
2020-03-09 10:11:07 +01:00
options :
api_url :
description :
- URL of the OpenNebula RPC server .
- It is recommended to use HTTPS so that the username / password are not
2023-06-15 15:48:17 +02:00
transferred over the network unencrypted .
- If not set then the value of the E ( ONE_URL ) environment variable is used .
2020-12-01 21:28:40 +01:00
type : str
2020-03-09 10:11:07 +01:00
api_username :
description :
- Name of the user to login into the OpenNebula RPC server . If not set
2023-06-15 15:48:17 +02:00
then the value of the E ( ONE_USERNAME ) environment variable is used .
2020-12-01 21:28:40 +01:00
type : str
2020-03-09 10:11:07 +01:00
api_password :
description :
- Password of the user to login into OpenNebula RPC server . If not set
2023-06-15 15:48:17 +02:00
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 .
2020-12-01 21:28:40 +01:00
type : str
2020-03-09 10:11:07 +01:00
template_name :
description :
2023-10-25 23:01:32 +02:00
- Name of VM template to use to create a new instance
2020-12-01 21:28:40 +01:00
type : str
2020-03-09 10:11:07 +01:00
template_id :
description :
- ID of a VM template to use to create a new instance
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
vm_start_on_hold :
description :
- Set to true to put vm on hold while creating
2022-09-06 20:42:17 +02:00
default : false
2020-12-01 21:28:40 +01:00
type : bool
2020-03-09 10:11:07 +01:00
instance_ids :
description :
2023-06-15 15:48:17 +02:00
- ' A list of instance ids used for states: V(absent), V(running), V(rebooted), V(poweredoff). '
2020-03-09 10:11:07 +01:00
aliases : [ ' ids ' ]
2020-12-01 21:28:40 +01:00
type : list
2021-03-08 08:35:09 +01:00
elements : int
2020-03-09 10:11:07 +01:00
state :
description :
2023-06-15 15:48:17 +02:00
- 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
2020-03-09 10:11:07 +01:00
choices : [ " present " , " absent " , " running " , " rebooted " , " poweredoff " ]
default : present
2020-12-01 21:28:40 +01:00
type : str
2020-03-09 10:11:07 +01:00
hard :
description :
2023-06-15 15:48:17 +02:00
- Reboot , power - off or terminate instances C ( hard ) .
2022-08-24 20:16:25 +02:00
default : false
2020-03-09 10:11:07 +01:00
type : bool
wait :
description :
- Wait for the instance to reach its desired state before returning . Keep
2023-06-15 15:48:17 +02:00
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 .
2022-08-24 20:16:25 +02:00
default : true
2020-03-09 10:11:07 +01:00
type : bool
wait_timeout :
description :
- How long before wait gives up , in seconds
default : 300
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
attributes :
description :
- A dictionary of key / value attributes to add to new instances , or for
2023-06-15 15:48:17 +02:00
setting C ( state ) of instances with these attributes .
2020-03-09 10:11:07 +01:00
- 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
2023-06-15 15:48:17 +02:00
indexes to the names of VMs .
2020-03-09 10:11:07 +01:00
- For example ' : ' C ( NAME ' : ' foo - ###) would create VMs with names C(foo-000), C(foo-001),...
2023-06-15 15:48:17 +02:00
- When used with O ( count_attributes ) and O ( exact_count ) the module will
match the base name without the index part .
2020-03-09 10:11:07 +01:00
default : { }
2020-12-01 21:28:40 +01:00
type : dict
2020-03-09 10:11:07 +01:00
labels :
description :
- A list of labels to associate with new instances , or for setting
2023-06-15 15:48:17 +02:00
C ( state ) of instances with these labels .
2020-03-09 10:11:07 +01:00
default : [ ]
2020-12-01 21:28:40 +01:00
type : list
2021-03-08 08:35:09 +01:00
elements : str
2020-03-09 10:11:07 +01:00
count_attributes :
description :
- A dictionary of key / value attributes that can only be used with
2023-06-15 15:48:17 +02:00
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 .
2020-12-01 21:28:40 +01:00
type : dict
2020-03-09 10:11:07 +01:00
count_labels :
description :
2023-06-15 15:48:17 +02:00
- 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 .
2020-12-01 21:28:40 +01:00
type : list
2021-03-08 08:35:09 +01:00
elements : str
2020-03-09 10:11:07 +01:00
count :
description :
- Number of instances to launch
default : 1
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
exact_count :
description :
2023-06-15 15:48:17 +02:00
- 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. '
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
mode :
description :
2023-06-15 15:48:17 +02:00
- 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 .
2020-12-01 21:28:40 +01:00
type : str
2020-03-09 10:11:07 +01:00
owner_id :
description :
- ID of the user which will be set as the owner of the instance
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
group_id :
description :
- ID of the group which will be set as the group of the instance
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
memory :
description :
- The size of the memory for new instances ( in MB , GB , . . . )
2020-12-01 21:28:40 +01:00
type : str
2020-03-09 10:11:07 +01:00
disk_size :
description :
- The size of the disk created for new instances ( in MB , GB , TB , . . . ) .
2023-06-15 15:48:17 +02:00
- ' 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 ) . '
2020-12-01 21:28:40 +01:00
type : list
2021-03-08 08:35:09 +01:00
elements : str
2020-03-09 10:11:07 +01:00
cpu :
description :
- Percentage of CPU divided by 100 required for the new instance . Half a
2023-06-15 15:48:17 +02:00
processor is written 0.5 .
2020-12-01 21:28:40 +01:00
type : float
2020-03-09 10:11:07 +01:00
vcpu :
description :
- Number of CPUs ( cores ) new VM will have .
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
networks :
description :
- A list of dictionaries with network parameters . See examples for more details .
default : [ ]
2020-12-01 21:28:40 +01:00
type : list
2021-03-08 08:35:09 +01:00
elements : dict
2020-03-09 10:11:07 +01:00
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.
2023-06-15 15:48:17 +02:00
- ' 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 . '
2020-03-09 10:11:07 +01:00
- Also this operation will fail if an image with specified C ( name ) already exists .
2020-12-01 21:28:40 +01:00
type : dict
2020-03-09 10:11:07 +01:00
persistent :
description :
- Create a private persistent copy of the template plus any image defined in DISK , and instantiate that copy .
2022-08-24 20:16:25 +02:00
default : false
2020-03-09 10:11:07 +01:00
type : bool
2020-06-13 15:01:19 +02:00
version_added : ' 0.2.0 '
2020-03-09 10:11:07 +01:00
datastore_id :
description :
2023-10-25 23:01:32 +02:00
- Name of Datastore to use to create a new instance
2020-06-13 15:01:19 +02:00
version_added : ' 0.2.0 '
2020-12-01 21:28:40 +01:00
type : int
2020-03-09 10:11:07 +01:00
datastore_name :
description :
2023-10-25 23:01:32 +02:00
- Name of Datastore to use to create a new instance
2020-06-13 15:01:19 +02:00
version_added : ' 0.2.0 '
2020-12-01 21:28:40 +01:00
type : str
2023-01-28 11:29:00 +01:00
updateconf :
description :
2023-06-15 15:48:17 +02:00
- When O ( instance_ids ) is provided , updates running VMs with the C ( updateconf ) API call .
2023-01-28 11:29:00 +01:00
- 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
2020-03-09 10:11:07 +01:00
author :
- " Milan Ilic (@ilicmilan) "
- " Jan Meerkamp (@meerkampdvv) "
'''
EXAMPLES = '''
2020-05-15 12:13:45 +02:00
- name : Create a new instance
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 90
register : result
2020-05-15 12:13:45 +02:00
- name : Print VM properties
2020-07-14 17:28:08 +02:00
ansible . builtin . debug :
2020-03-09 10:11:07 +01:00
msg : result
2020-05-15 12:13:45 +02:00
- name : Deploy a new VM on hold
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_name : ' app1_template '
vm_start_on_hold : ' True '
2020-05-15 12:13:45 +02:00
- name : Deploy a new VM and set its name to ' foo '
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_name : ' app1_template '
attributes :
name : foo
2020-05-15 12:13:45 +02:00
- name : Deploy a new VM and set its group_id and mode
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 90
group_id : 16
mode : 660
2020-05-15 12:13:45 +02:00
- name : Deploy a new VM as persistent
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 90
2022-08-24 20:16:25 +02:00
persistent : true
2020-03-09 10:11:07 +01:00
2020-05-15 12:13:45 +02:00
- name : Change VM ' s permissions to 640
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
instance_ids : 5
mode : 640
2020-05-15 12:13:45 +02:00
- name : Deploy 2 new instances and set memory , vcpu , disk_size and 3 networks
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
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 "
2020-05-15 12:13:45 +02:00
- name : Deploy a new instance which uses a Template with two Disks
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 42
disk_size :
- 35.2 GB
- 50 GB
memory : 4 GB
vcpu : 4
count : 1
networks :
- NETWORK_ID : 27
2020-05-15 12:13:45 +02:00
- name : " Deploy an new instance with attribute ' bar: bar1 ' and set its name to ' foo ' "
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 53
attributes :
name : foo
bar : bar1
2020-05-15 12:13:45 +02:00
- name : " Enforce that 2 instances with attributes ' foo1: app1 ' and ' foo2: app2 ' are deployed "
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 53
attributes :
foo1 : app1
foo2 : app2
exact_count : 2
count_attributes :
foo1 : app1
foo2 : app2
2020-05-15 12:13:45 +02:00
- name : Enforce that 4 instances with an attribute ' bar ' are deployed
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
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
2020-05-15 12:13:45 +02:00
- name : Deploy 2 new instances
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
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
2020-05-15 12:13:45 +02:00
- name : Deploy 2 new instances
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
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
2020-05-15 12:13:45 +02:00
- name : Reboot all instances with names in a certain format
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
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
2020-05-15 12:13:45 +02:00
- name : Enforce that only 1 instance with name in a certain format is deployed
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 53
exact_count : 1
count_attributes :
name : fooapp - #
2020-05-15 12:13:45 +02:00
- name : Deploy an new instance with a network
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 53
networks :
- NETWORK_ID : 27
register : vm
2020-05-15 12:13:45 +02:00
- name : Wait for SSH to come up
2020-07-16 13:42:12 +02:00
ansible . builtin . wait_for_connection :
2020-03-09 10:11:07 +01:00
delegate_to : ' {{ vm.instances[0].networks[0].ip }} '
2020-05-15 12:13:45 +02:00
- name : Terminate VMs by ids
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
instance_ids :
- 153
- 160
state : absent
2020-05-15 12:13:45 +02:00
- name : Reboot all VMs that have labels ' foo ' and ' app1 '
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
labels :
- foo
- app1
state : rebooted
2020-05-15 12:13:45 +02:00
- name : " Fetch all VMs that have name ' foo ' and attribute ' app: bar ' "
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
attributes :
name : foo
app : bar
register : results
2020-05-15 12:13:45 +02:00
- name : Deploy 2 new instances with labels ' foo1 ' and ' foo2 '
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_name : app_template
labels :
- foo1
- foo2
count : 2
2020-05-15 12:13:45 +02:00
- name : Enforce that only 1 instance with label ' foo1 ' will be running
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_name : app_template
labels :
- foo1
exact_count : 1
count_labels :
- foo1
2020-05-15 12:13:45 +02:00
- name : Terminate all instances that have attribute foo
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
template_id : 53
exact_count : 0
count_attributes :
foo :
2020-05-15 12:13:45 +02:00
- name : " Power-off the VM and save VM ' s disk with id=0 to the image with name ' foo-image ' "
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
instance_ids : 351
state : poweredoff
disk_saveas :
name : foo - image
2020-05-15 12:13:45 +02:00
- name : " Save VM ' s disk with id=1 to the image with name ' bar-image ' "
2020-07-13 21:50:31 +02:00
community . general . one_vm :
2020-03-09 10:11:07 +01:00
instance_ids : 351
disk_saveas :
name : bar - image
disk_id : 1
2023-01-28 11:29:00 +01:00
- 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 . . .
2020-03-09 10:11:07 +01:00
'''
RETURN = '''
instances_ids :
2023-06-15 15:48:17 +02:00
description : a list of instances ids whose state is changed or which are fetched with O ( instance_ids ) option .
2020-03-09 10:11:07 +01:00
type : list
returned : success
sample : [ 1234 , 1235 ]
instances :
2023-06-15 15:48:17 +02:00
description : a list of instances info whose state is changed or which are fetched with O ( instance_ids ) option .
2020-03-09 10:11:07 +01:00
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
}
2023-01-28 11:29:00 +01:00
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 ... "
}
}
2020-03-09 10:11:07 +01:00
tagged_instances :
description :
- A list of instances info based on a specific attributes and / or
2023-06-15 15:48:17 +02:00
- labels that are specified with O ( count_attributes ) and O ( count_labels )
2020-03-09 10:11:07 +01:00
- 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
}
2023-01-28 11:29:00 +01:00
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 ... "
}
}
2020-03-09 10:11:07 +01:00
'''
try :
import pyone
HAS_PYONE = True
except ImportError :
HAS_PYONE = False
2023-01-28 11:29:00 +01:00
2020-03-09 10:11:07 +01:00
import os
2023-01-28 11:29:00 +01:00
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
2020-03-09 10:11:07 +01:00
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 ' ] :
2021-05-04 12:21:55 +02:00
networks_info . append ( {
' ip ' : nic . get ( ' IP ' , ' ' ) ,
' mac ' : nic . get ( ' MAC ' , ' ' ) ,
' name ' : nic . get ( ' NETWORK ' , ' ' ) ,
' security_groups ' : nic . get ( ' SECURITY_GROUPS ' , ' ' )
} )
2020-03-09 10:11:07 +01:00
else :
2021-05-04 12:21:55 +02:00
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 ' , ' ' )
} )
2020-03-09 10:11:07 +01:00
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 )
2023-01-28 11:29:00 +01:00
updateconf = parse_updateconf ( vm . TEMPLATE )
2020-03-09 10:11:07 +01:00
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 ,
2023-01-28 11:29:00 +01:00
' labels ' : vm_labels ,
' updateconf ' : updateconf ,
2020-03-09 10:11:07 +01:00
}
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
2023-01-28 11:29:00 +01:00
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
2020-03-09 10:11:07 +01:00
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
2023-01-28 11:29:00 +01:00
def create_vm ( module , client , template_id , attributes_dict , labels_list , disk_size , network_attrs_list , vm_start_on_hold , vm_persistent , updateconf_dict ) :
2020-03-09 10:11:07 +01:00
if attributes_dict :
2023-01-28 11:29:00 +01:00
vm_name = attributes_dict . get ( ' NAME ' , ' ' )
2020-03-09 10:11:07 +01:00
2023-01-28 11:29:00 +01:00
template = client . template . info ( template_id ) . TEMPLATE
2020-03-09 10:11:07 +01:00
2023-01-28 11:29:00 +01:00
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 { } )
2020-03-09 10:11:07 +01:00
try :
2023-01-28 11:29:00 +01:00
vm_id = client . template . instantiate ( template_id ,
vm_name ,
vm_start_on_hold ,
render ( vm_extra_template ) ,
vm_persistent )
2020-03-09 10:11:07 +01:00
except pyone . OneException as e :
module . fail_json ( msg = str ( e ) )
2023-01-28 11:29:00 +01:00
vm = get_vm_by_id ( client , vm_id )
2020-03-09 10:11:07 +01:00
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 :
2022-11-08 23:00:05 +01:00
if key is not None and value is not None :
2020-03-09 10:11:07 +01:00
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
2023-01-28 11:29:00 +01:00
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 ) :
2020-03-09 10:11:07 +01:00
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
2023-01-28 11:29:00 +01:00
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 )
2020-03-09 10:11:07 +01:00
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 , [ ]
2023-01-28 11:29:00 +01:00
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 ) :
2020-03-09 10:11:07 +01:00
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 ,
2023-01-28 11:29:00 +01:00
vm_start_on_hold , vm_persistent , updateconf_dict )
2020-03-09 10:11:07 +01:00
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
2022-01-24 19:52:32 +01:00
state = vm . STATE
if state in [ VM_STATES . index ( ' HOLD ' ) ] :
changed = release_vm ( module , client , vm )
return changed
2020-03-09 10:11:07 +01:00
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
2022-01-24 19:52:32 +01:00
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
2020-03-09 10:11:07 +01:00
def check_name_attribute ( module , attributes ) :
if attributes . get ( " NAME " ) :
import re
if re . match ( r ' ^[^#]+#*$ ' , attributes . get ( " NAME " ) ) is None :
2023-10-25 23:01:32 +02:00
module . fail_json ( msg = " Illegal ' NAME ' attribute: ' " + attributes . get ( " NAME " ) +
2020-03-09 10:11:07 +01:00
" ' .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 } ,
2021-03-08 08:35:09 +01:00
" instance_ids " : { " required " : False , " aliases " : [ ' ids ' ] , " type " : " list " , " elements " : " int " } ,
2020-03-09 10:11:07 +01:00
" 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 " } ,
2021-03-08 08:35:09 +01:00
" disk_size " : { " required " : False , " type " : " list " , " elements " : " str " } ,
2020-03-09 10:11:07 +01:00
" datastore_name " : { " required " : False , " type " : " str " } ,
" datastore_id " : { " required " : False , " type " : " int " } ,
2021-03-08 08:35:09 +01:00
" networks " : { " default " : [ ] , " type " : " list " , " elements " : " dict " } ,
2020-03-09 10:11:07 +01:00
" count " : { " default " : 1 , " type " : " int " } ,
" exact_count " : { " required " : False , " type " : " int " } ,
" attributes " : { " default " : { } , " type " : " dict " } ,
" count_attributes " : { " required " : False , " type " : " dict " } ,
2021-03-08 08:35:09 +01:00
" labels " : { " default " : [ ] , " type " : " list " , " elements " : " str " } ,
" count_labels " : { " required " : False , " type " : " list " , " elements " : " str " } ,
2020-03-09 10:11:07 +01:00
" disk_saveas " : { " type " : " dict " } ,
2023-01-28 11:29:00 +01:00
" persistent " : { " default " : False , " type " : " bool " } ,
" updateconf " : { " type " : " dict " } ,
2020-03-09 10:11:07 +01:00
}
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 ' )
2023-01-28 11:29:00 +01:00
updateconf = params . get ( ' updateconf ' )
2020-03-09 10:11:07 +01:00
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 :
2024-09-01 20:22:53 +02:00
attributes = { key . upper ( ) : value for key , value in attributes . items ( ) }
2020-03-09 10:11:07 +01:00
check_attributes ( module , attributes )
if count_attributes :
2024-09-01 20:22:53 +02:00
count_attributes = { key . upper ( ) : value for key , value in count_attributes . items ( ) }
2020-03-09 10:11:07 +01:00
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 )
2023-01-28 11:29:00 +01:00
if updateconf :
check_updateconf ( module , updateconf )
2020-03-09 10:11:07 +01:00
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 ,
2023-01-28 11:29:00 +01:00
networks , hard , wait , wait_timeout , put_vm_on_hold , persistent , updateconf )
2020-03-09 10:11:07 +01:00
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 ,
2023-01-28 11:29:00 +01:00
put_vm_on_hold , persistent , updateconf )
2020-03-09 10:11:07 +01:00
# 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
2023-01-28 11:29:00 +01:00
if template_id is None and updateconf is not None :
changed = update_vms ( module , one_client , vms , updateconf ) or changed
2020-03-09 10:11:07 +01:00
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 ( )