mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 21:59:38 -07:00 
			
		
		
		
	Update one_vm.py (#9959)
* Update one_vm.py
Update updateconf attributes to match latest acceptable values.
* Add changelog fragment
* Update plugins/modules/one_vm.py
Co-authored-by: Nicola Soranzo <nicola.soranzo@gmail.com>
* Update plugins/modules/one_vm.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update changelogs/fragments/9959-update-opennebula-onevm-updateconf-params.yml
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update one_vm.py
Add updateconf values to documentation
* Update one_vm.py
* Update one_vm.py
* Update one_vm.py
* Update one_vm.py
Fix doc formatting
* Update plugins/modules/one_vm.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update one_vm.py
Colon causing test failure
* Add colon.
---------
Co-authored-by: Nicola Soranzo <nicola.soranzo@gmail.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 5ef6f1ebd2)
Co-authored-by: Tom Paine <github@aioue.net>
		
	
			
		
			
				
	
	
		
			1706 lines
		
	
	
	
		
			59 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1706 lines
		
	
	
	
		
			59 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| # Copyright (c) 2017, Milan Ilic <milani@nordeus.com>
 | |
| # Copyright (c) 2019, Jan Meerkamp <meerkamp@dvv.de>
 | |
| # Copyright (c) 2025, Tom Paine <github@aioue.net>
 | |
| # 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 = r"""
 | |
| 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 C(~/.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 does not mean that you will be able to SSH on that machine only that boot process have started
 | |
|         on that instance. See the example using the M(ansible.builtin.wait_for) module 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 V(NAME) is a special attribute which sets the name of the VM when it is 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: V(NAME: foo-###) would create VMs with names V(foo-000), V(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 using direct template merge.
 | |
|       - Allows for complete modifications of the C(CONTEXT) attribute.
 | |
|       - "Supported attributes include:"
 | |
|       - B(BACKUP_CONFIG:) V(BACKUP_VOLATILE), V(FS_FREEZE), V(INCREMENT_MODE), V(KEEP_LAST), V(MODE);
 | |
|       - B(CONTEXT:) (Any value, except V(ETH*). Variable substitution will be made);
 | |
|       - B(CPU_MODEL:) V(FEATURES), V(MODEL);
 | |
|       - B(FEATURES:) V(ACPI), V(APIC), V(GUEST_AGENT), V(HYPERV), V(IOTHREADS), V(LOCALTIME), V(PAE), V(VIRTIO_BLK_QUEUES), V(VIRTIO_SCSI_QUEUES);
 | |
|       - B(GRAPHICS:) V(COMMAND), V(KEYMAP), V(LISTEN), V(PASSWD), V(PORT), V(TYPE);
 | |
|       - B(INPUT:) V(BUS), V(TYPE);
 | |
|       - B(OS:) V(ARCH), V(BOOT), V(BOOTLOADER), V(FIRMWARE), V(INITRD), V(KERNEL), V(KERNEL_CMD), V(MACHINE), V(ROOT), V(SD_DISK_BUS), V(UUID);
 | |
|       - B(RAW:) V(DATA), V(DATA_VMX), V(TYPE), V(VALIDATE);
 | |
|       - B(VIDEO:) V(ATS), V(IOMMU), V(RESOLUTION), V(TYPE), V(VRAM).
 | |
|     type: dict
 | |
|     version_added: 6.3.0
 | |
| author:
 | |
|   - "Milan Ilic (@ilicmilan)"
 | |
|   - "Jan Meerkamp (@meerkampdvv)"
 | |
| """
 | |
| 
 | |
| 
 | |
| EXAMPLES = r"""
 | |
| - 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:
 | |
|     port: 22
 | |
|     host: '{{ 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 = r"""
 | |
| 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 documentation: https://docs.opennebula.io/6.10/integration_and_development/system_interfaces/api.html#one-vm-updateconf
 | |
| UPDATECONF_ATTRIBUTES = {
 | |
|     "OS": ["ARCH", "MACHINE", "KERNEL", "INITRD", "BOOTLOADER", "BOOT", "SD_DISK_BUS", "UUID", "FIRMWARE"],
 | |
|     "CPU_MODEL": ["MODEL", "FEATURES"],
 | |
|     "FEATURES": ["ACPI", "PAE", "APIC", "LOCALTIME", "HYPERV", "GUEST_AGENT", "VIRTIO_BLK_QUEUES", "VIRTIO_SCSI_QUEUES", "IOTHREADS"],
 | |
|     "INPUT": ["TYPE", "BUS"],
 | |
|     "GRAPHICS": ["TYPE", "LISTEN", "PORT", "PASSWD", "KEYMAP", "COMMAND"],
 | |
|     "VIDEO": ["ATS", "IOMMU", "RESOLUTION", "TYPE", "VRAM"],
 | |
|     "RAW": ["DATA", "DATA_VMX", "TYPE", "VALIDATE"],
 | |
|     "CONTEXT": [],
 | |
|     "BACKUP_CONFIG": ["FS_FREEZE", "KEEP_LAST", "BACKUP_VOLATILE", "MODE", "INCREMENT_MODE"],
 | |
| }
 | |
| 
 | |
| 
 | |
| 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 = {key.upper(): value for key, value in attributes.items()}
 | |
|         check_attributes(module, attributes)
 | |
| 
 | |
|     if count_attributes:
 | |
|         count_attributes = {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()
 |