You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
decort-ansible/library/decort_kvmvm.py

864 lines
38 KiB

#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_kvmvm
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_kvmvm(DecortController):
def __init__(self, arg_amodule):
# call superclass constructor first
super(decort_kvmvm, self).__init__(arg_amodule)
self.check_amodule_args()
self.comp_should_exist = False
# This following flag is used to avoid extra (and unnecessary) get of compute details prior to
# packaging facts before the module completes. As ""
self.skip_final_get = False
self.comp_id = 0
self.comp_info = None
self.acc_id = 0
self.rg_id = 0
validated_acc_id =0
validated_rg_id = 0
validated_rg_facts = None
# Analyze Compute name & ID, RG name & ID and build arguments to compute_find accordingly.
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot manage Compute when its ID is 0 and name is empty."
self.fail_json(**self.result)
# fail the module - exit
if not arg_amodule.params['id']: # manage Compute by name -> need RG identity
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
# fail the module - exit
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
# at this point we are ready to locate Compute, and if anything fails now, then it must be
# because this Compute does not exist or something goes wrong in the upstream API
# We call compute_find with check_state=False as we also consider the case when a Compute
# specified by account / RG / compute name never existed and will be created for the first time.
self.comp_id, self.comp_info, self.rg_id = self.compute_find(comp_id=arg_amodule.params['id'],
comp_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False,
need_custom_fields=True)
if self.comp_id:
self.comp_should_exist = True
self.acc_id = self.comp_info['accountId']
check_error = False
params_to_check = {
'chipset': 'chipset',
'cpu_pin': 'cpupin',
'hp_backed': 'hpBacked',
'numa_affinity': 'numaAffinity',
}
for param_name, comp_field_name in params_to_check.items():
if (
self.aparams[param_name] is not None
and self.comp_info[comp_field_name] != self.aparams[param_name]
and self.aparams['state'] not in ('halted', 'poweredoff')
):
self.message(
f'Cannot change "{param_name}" for compute '
f'{self.comp_id} if parameter "state" is not '
f'halted or poweredoff.'
)
check_error = True
if check_error:
self.exit(fail=True)
else:
if self.aparams['chipset'] is None:
self.message(
'Check for parameter "chipset" failed: '
'chipset must be specified for a new compute.'
)
self.exit(fail=True)
return
def check_amodule_args(self):
"""
Additional Ansible Module arguments validation that
cannot be implemented using Ansible Argument spec.
"""
# Check parameter "networks" for DPDK type
aparam_nets = self.aparams['networks']
if aparam_nets:
net_types = {net['type'] for net in aparam_nets}
DPDK = 'DPDK'
if DPDK in net_types and not net_types.issubset({'DPDK', 'EMPTY'}):
self.message(
'Check for parameter "networks" failed: a compute cannot'
' be connected to a DPDK network and a network of another'
' type at the same time.'
)
self.exit(fail=True)
aparam_custom_fields = self.aparams['custom_fields']
if aparam_custom_fields is not None:
if (
aparam_custom_fields['disable']
and aparam_custom_fields['fields'] is not None
):
self.message(
'Check for parameter "custom_fields" failed: '
'"fields" cannot be set if "disable" is True.'
)
self.exit(fail=True)
def nop(self):
"""No operation (NOP) handler for Compute management by decort_kvmvm module.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.comp_id:
self.result['msg'] = ("No state change required for Compute ID {} because of its "
"current status '{}'.").format(self.comp_id, self.comp_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent Compute instance.").format(self.amodule.params['state'])
return
def error(self):
"""Error handler for Compute instance management by decort_kvmvm module.
This function is intended to be called when an invalid state change is requested.
Invalid means that the current is invalid for any operations on the Compute or the
transition from current to desired state is not technically possible.
"""
self.result['failed'] = True
self.result['changed'] = False
if self.comp_id:
self.result['msg'] = ("Invalid target state '{}' requested for Compute ID {} in the "
"current status '{}'.").format(self.comp_id,
self.amodule.params['state'],
self.comp_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent Compute name '{}' "
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['rg_name'])
return
def create(self):
"""New Compute instance creation handler for decort_kvmvm module.
This function checks for the presence of required parameters and deploys a new KVM VM
Compute instance with the specified characteristics into the target Resource Group.
The target RG must exist.
"""
# the following parameters must be present: cpu, ram, image_id or image_name
# each of the following calls will abort if argument is missing
self.check_amodule_argument('cpu')
self.check_amodule_argument('ram')
validated_bdisk_size = self.amodule.params['boot_disk'] or 0
image_id, image_facts = None, None
if (
self.amodule.params['image_id'] is None
and self.amodule.params['image_name'] is None
):
if self.amodule.params['state'] not in ('poweredoff', 'halted'):
self.result['msg'] = (
'"state" parameter for a blank Compute must be either '
'"poweredoff" or "halted".'
)
self.exit(fail=True)
else:
# either image_name or image_id must be present
if (
self.check_amodule_argument('image_id', abort=False)
and self.amodule.params['image_id'] > 0
):
# find image by image ID and account ID
# image_find(self, image_id, image_name, account_id, rg_id=0, sepid=0, pool=""):
image_id, image_facts = self.image_find(
image_id=self.amodule.params['image_id'],
image_name="",
account_id=self.acc_id)
elif (
self.check_amodule_argument('image_name', abort=False)
and self.amodule.params['image_name'] != ""
):
# find image by image name and account ID
image_id, image_facts = self.image_find(
image_id=0,
image_name=self.amodule.params['image_name'],
account_id=self.acc_id,
)
if validated_bdisk_size <= image_facts['size']:
# adjust disk size to the minimum allowed by OS image, which will be used to spin off this Compute
validated_bdisk_size = image_facts['size']
# NOTE: due to a libvirt "feature", that impacts management of a VM created without any network interfaces,
# we create KVM VM in HALTED state.
# Consequently, if desired state is different from 'halted' or 'porewedoff", we should explicitly start it
# in the upstream code.
# See corresponding NOTE below for another place where this "feature" is redressed for.
#
# Once this "feature" is fixed, make sure VM is created according to the actual desired state
#
start_compute = False # change this once a workaround for the aforementioned libvirt "feature" is implemented
if self.amodule.params['state'] in ('halted', 'poweredoff'):
start_compute = False
if self.amodule.params['ssh_key'] and self.amodule.params['ssh_key_user'] and not self.amodule.params['ci_user_data']:
cloud_init_params = {'users': [
{"name": self.amodule.params['ssh_key_user'],
"ssh-authorized-keys": [self.amodule.params['ssh_key']],
"shell": '/bin/bash'}
]}
elif self.amodule.params['ci_user_data']:
cloud_init_params = self.amodule.params['ci_user_data']
else:
cloud_init_params = None
cpu_pin = self.aparams['cpu_pin']
if cpu_pin is None:
cpu_pin = False
hp_backed = self.aparams['hp_backed']
if hp_backed is None:
hp_backed = False
numa_affinity = self.aparams['numa_affinity']
if numa_affinity is None:
numa_affinity = 'none'
# if we get through here, all parameters required to create new Compute instance should be at hand
# NOTE: KVM VM is created in HALTED state and must be explicitly started
self.comp_id = self.kvmvm_provision(rg_id=self.rg_id,
comp_name=self.amodule.params['name'],
cpu=self.amodule.params['cpu'], ram=self.amodule.params['ram'],
boot_disk=validated_bdisk_size,
image_id=image_id,
description=self.amodule.params['description'],
userdata=cloud_init_params,
sep_id=self.amodule.params['sep_id' ] if "sep_id" in self.amodule.params else None,
pool_name=self.amodule.params['pool'] if "pool" in self.amodule.params else None,
start_on_create=start_compute,
chipset=self.amodule.params['chipset'],
cpu_pin=cpu_pin,
hp_backed=hp_backed,
numa_affinity=numa_affinity)
self.comp_should_exist = True
# Originally we would have had to re-read comp_info after VM was provisioned
# _, self.comp_info, _ = self.compute_find(self.comp_id)
# However, to avoid extra call to compute/get API we need to construct comp_info so that
# the below calls to compute_networks and compute_data_disks work properly.
#
# Here we are imitating comp_info structure as if it has been returned by a real call
# to API compute/get
self.comp_info = {
'id': self.comp_id,
'accountId': self.acc_id,
'status': "ENABLED",
'techStatus': "STOPPED",
'interfaces': [], # new compute instance is created network-less
'disks': [], # new compute instance is created without any data disks attached
'tags': {},
'affinityLabel': "",
'affinityRules': [],
'antiAffinityRules': [],
}
#
# Compute was created
#
# Setup network connections
if self.amodule.params['networks'] is not None:
self.compute_networks(
comp_dict=self.comp_info,
new_networks=self.amodule.params['networks'],
)
# Next manage data disks
if self.amodule.params['data_disks'] is not None:
self.compute_data_disks(
comp_dict=self.comp_info,
new_data_disks=self.amodule.params['data_disks'],
)
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'],)
# NOTE: see NOTE above regarding libvirt "feature" and new VMs created in HALTED state
if self.amodule.params['state'] not in ('halted', 'poweredoff'):
self.compute_powerstate(self.comp_info, 'started')
if self.aparams['custom_fields'] is None:
custom_fields_disable = True
custom_fields_fields = None
else:
custom_fields_disable = self.aparams['custom_fields']['disable']
custom_fields_fields = self.aparams['custom_fields']['fields']
if not custom_fields_disable:
self.compute_set_custom_fields(
compute_id=self.comp_info['id'],
custom_fields=custom_fields_fields,
)
# read in Compute facts once more after all initial setup is complete
_, self.comp_info, _ = self.compute_find(
comp_id=self.comp_id,
need_custom_fields=True,
)
self.skip_final_get = True
return
def destroy(self):
"""Compute destroy handler for VM management by decort_kvmvm module.
Note that this handler deletes the VM permanently together with all assigned disk resources.
"""
self.compute_delete(comp_id=self.comp_id, permanently=True)
self.comp_info['status'] = 'DESTROYED'
self.comp_should_exist = False
return
def restore(self):
"""Compute restore handler for Compute instance management by decort_kvmvm module.
Note that restoring Compute is only possible if it is in DELETED state. If called on a
Compute instance in any other state, the method will throw an error and abort the execution
of the module.
"""
self.compute_restore(comp_id=self.comp_id)
# TODO - do we need updated comp_info to manage port forwards and size after VM is restored?
_, self.comp_info, _ = self.compute_find(
comp_id=self.comp_id,
need_custom_fields=True,
)
self.modify()
self.comp_should_exist = True
return
def modify(self, arg_wait_cycles=0):
"""Compute modify handler for KVM VM management by decort_kvmvm module.
This method is a convenience wrapper that calls individual Compute modification functions from
DECORT utility library (module).
Note that it does not modify power state of KVM VM.
"""
if self.amodule.params['networks'] is not None:
self.compute_networks(
comp_dict=self.comp_info,
new_networks=self.aparams['networks'],
order_changing=self.aparams['network_order_changing'],
)
boot_disk_new_size = self.amodule.params['boot_disk']
if boot_disk_new_size:
self.compute_bootdisk_size(self.comp_info, boot_disk_new_size)
if self.amodule.params['data_disks'] is not None:
self.compute_data_disks(self.comp_info, self.amodule.params['data_disks'])
self.compute_resize(self.comp_info,
self.amodule.params['cpu'], self.amodule.params['ram'],
wait_for_state_change=arg_wait_cycles)
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'])
if self.compute_update_args:
self.compute_update(
compute_id=self.comp_info['id'],
**self.compute_update_args,
)
aparam_custom_fields = self.amodule.params['custom_fields']
if aparam_custom_fields is not None:
compute_custom_fields = self.comp_info['custom_fields']
if aparam_custom_fields['disable']:
if compute_custom_fields is not None:
self.compute_disable_custom_fields(
compute_id=self.comp_info['id'],
)
else:
if compute_custom_fields != aparam_custom_fields['fields']:
self.compute_set_custom_fields(
compute_id=self.comp_info['id'],
custom_fields=aparam_custom_fields['fields'],
)
return
@property
def compute_update_args(self) -> dict:
result_args = {}
params_to_check = {
'name': 'name',
'chipset': 'chipset',
'cpu_pin': 'cpupin',
'hp_backed': 'hpBacked',
'numa_affinity': 'numaAffinity',
'description': 'desc',
}
for param_name, comp_field_name in params_to_check.items():
aparam_value = self.amodule.params[param_name]
if (
aparam_value is not None
and aparam_value != self.comp_info[comp_field_name]
):
result_args[param_name] = aparam_value
return result_args
def package_facts(self, check_mode=False):
"""Package a dictionary of KVM VM facts according to the decort_kvmvm module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of decort_kvmvm
module run.
@param check_mode: boolean that tells if this Ansible module is run in check mode
@return: dictionary of KVM VM facts, containing suffucient information to manage the KVM VM in
subsequent Ansible tasks.
"""
ret_dict = dict(id=0,
name="",
arch="",
cpu="",
ram="",
disk_size=0,
data_disks=[], # IDs of attached data disks; this list can be emty
state="CHECK_MODE",
tech_status="",
account_id=0,
rg_id=0,
username="",
password="",
public_ips=[], # direct IPs; this list can be empty
private_ips=[], # IPs on ViNSes; usually, at least one IP is listed
nat_ip="", # IP of the external ViNS interface; can be empty.
tags={},
chipset="",
interfaces=[],
cpu_pin="",
hp_backed="",
numa_affinity="",
custom_fields={},
)
if check_mode or self.comp_info is None:
# if in check mode (or void facts provided) return immediately with the default values
return ret_dict
# if not self.comp_should_exist:
# ret_dict['state'] = "ABSENT"
# return ret_dict
ret_dict['id'] = self.comp_info['id']
ret_dict['name'] = self.comp_info['name']
ret_dict['arch'] = self.comp_info['arch']
ret_dict['state'] = self.comp_info['status']
ret_dict['tech_status'] = self.comp_info['techStatus']
ret_dict['account_id'] = self.comp_info['accountId']
ret_dict['rg_id'] = self.comp_info['rgId']
if self.comp_info['tags']:
ret_dict['tags'] = self.comp_info['tags']
# if the VM is an imported VM, then the 'accounts' list may be empty,
# so check for this case before trying to access login and passowrd values
if len(self.comp_info['osUsers']):
ret_dict['username'] = self.comp_info['osUsers'][0]['login']
ret_dict['password'] = self.comp_info['osUsers'][0]['password']
if self.comp_info['interfaces']:
# We need a list of all ViNSes in the account, which owns this Compute
# to find a ViNS, which may have active external connection. Then
# we will save external IP address of that connection in ret_dict['nat_ip']
for iface in self.comp_info['interfaces']:
if iface['connType'] == "VXLAN": # This is ViNS connection
ret_dict['private_ips'].append(iface['ipAddress'])
# if iface['connId']
# Now we need to check if this ViNS has GW function and external connection.
# If it does - save public IP address of GW VNF in ret_dict['nat_ip']
elif iface['connType'] == "VLAN": # This is direct external network connection
ret_dict['public_ips'].append(iface['ipAddress'])
ret_dict['cpu'] = self.comp_info['cpus']
ret_dict['ram'] = self.comp_info['ram']
ret_dict['image_id'] = self.comp_info['imageId']
for ddisk in self.comp_info['disks']:
if ddisk['type'] == 'B':
# if it is a boot disk - store its size
ret_dict['disk_size'] = ddisk['sizeMax']
elif ddisk['type'] == 'D':
# if it is a data disk - append its ID to the list of data disks IDs
ret_dict['data_disks'].append(ddisk['id'])
ret_dict['chipset'] = self.comp_info['chipset']
ret_dict['interfaces'] = self.comp_info['interfaces']
ret_dict['cpu_pin'] = self.comp_info['cpupin']
ret_dict['hp_backed'] = self.comp_info['hpBacked']
ret_dict['numa_affinity'] = self.comp_info['numaAffinity']
ret_dict['custom_fields'] = self.comp_info['custom_fields']
return ret_dict
def check_amodule_args_for_create(self):
# Check for unacceptable parameters for a blank Compute
if (
self.aparams['image_id'] is None
and self.aparams['image_name'] is None
):
for parameter in (
'ssh_key',
'ssh_key_user',
'ci_user_data',
):
if self.aparams[parameter] is not None:
self.message(
f'Check for parameter "{parameter}" failed: '
f'"image_id" or "image_name" must be specified '
f'to set {parameter}.'
)
self.exit(fail=True)
if (
self.aparams['sep_id'] is not None
and self.aparams['boot_disk'] is None
):
self.message(
'Check for parameter "sep_id" failed: '
'"image_id" or "image_name" or "boot_disk" '
'must be specified to set sep_id.'
)
self.exit(fail=True)
@staticmethod
def build_parameters():
"""Build and return a dictionary of parameters expected by decort_kvmvm module in a form
accepted by AnsibleModule utility class.
This dictionary is then used y AnsibleModule class instance to parse and validate parameters
passed to the module from the playbook.
"""
return dict(
account_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=False, default=''),
description=dict(type='str', required=False),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
boot_disk=dict(type='int', required=False),
sep_id=dict(type='int', required=False),
pool=dict(type='str', required=False),
controller_url=dict(type='str', required=True),
# count=dict(type='int', required=False, default=1),
cpu=dict(type='int', required=False),
# datacenter=dict(type='str', required=False, default=''),
data_disks=dict(type='list', required=False), # list of integer disk IDs
id=dict(type='int', required=False, default=0),
image_id=dict(type='int', required=False),
image_name=dict(type='str', required=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
name=dict(type='str'),
networks=dict(
type='list',
elements='dict',
options=dict(
type=dict(
type='str',
required=True,
choices=[
'VINS',
'EXTNET',
'VFNIC',
'DPDK',
'EMPTY',
],
),
id=dict(
type='int',
),
ip_addr=dict(
type='str',
),
),
required_if=[
('type', 'VINS', ('id',)),
('type', 'EXTNET', ('id',)),
('type', 'VFNIC', ('id',)),
('type', 'DPDK', ('id',)),
],
),
network_order_changing=dict(
type='bool',
default=False,
),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
ram=dict(type='int', required=False),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str', default=""),
ssh_key=dict(type='str', required=False),
ssh_key_user=dict(type='str', required=False),
tag=dict(type='dict', required=False),
affinity_label=dict(type='str', required=False),
aff_rule=dict(type='list', required=False),
aaff_rule=dict(type='list', required=False),
ci_user_data=dict(type='dict', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'paused', 'poweredoff', 'halted', 'poweredon', 'present', 'check']),
tags=dict(type='str', required=False),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
# wait_for_ip_address=dict(type='bool', required=False, default=False),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
chipset=dict(
type='str',
choices=['Q35', 'i440fx']
),
cpu_pin=dict(
type='bool',
),
hp_backed=dict(
type='bool',
),
numa_affinity=dict(
type='str',
choices=['strict', 'loose', 'none'],
),
custom_fields=dict(
type='dict',
options=dict(
fields=dict(
type='dict',
),
disable=dict(
type='bool',
),
),
),
)
def check_amodule_args_for_change(self):
new_boot_disk_size = self.amodule.params['boot_disk']
if new_boot_disk_size is not None:
for disk in self.comp_info['disks']:
if disk['type'] == 'B':
boot_disk_size = disk['sizeMax']
break
else:
self.message(
f'Can\'t set boot disk size for Compute '
f'{self.comp_info["id"]}, because it doesn\'t have a '
f'boot disk.'
)
self.exit(fail=True)
if new_boot_disk_size < boot_disk_size:
self.message(
f'New boot disk size {new_boot_disk_size} is less than '
f'current {boot_disk_size} for Compute ID '
f'{self.comp_info["id"]}'
)
self.exit(fail=True)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECSController
# 2) check if the VM with the specified id or rg_name:name exists
# 3) if VM does not exist, check if there is enough resources to deploy it in the target account / vdc
# 4) if VM exists: check desired state, desired configuration -> initiate action accordingly
# 5) VM does not exist: check desired state -> initiate action accordingly
# - create VM: check if target VDC exists, create VDC as necessary, create VM
# - delete VM: delete VM
# - change power state: change as required
# - change guest OS state: change as required
# 6) report result to Ansible
def main():
module_parameters = decort_kvmvm.build_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
],
)
# Initialize DECORT KVM VM instance object
# This object does not necessarily represent an existing KVM VM
subj = decort_kvmvm(amodule)
# handle state=check before any other logic
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.comp_id:
# Compute is found - package facts and report success to Ansible
subj.result['failed'] = False
# _, subj.comp_info, _ = subj.compute_find(comp_id=subj.comp_id)
# _, rg_facts = subj.rg_find(arg_account_id=0, arg_rg_id=subj.rg_id)
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate Compute name '{}'. Other arguments are: Compute ID {}, "
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
amodule.params['id'],
amodule.params['rg_name'],
amodule.params['rg_id'],
amodule.params['account_name'])
amodule.fail_json(**subj.result)
pass
if subj.comp_id:
subj.check_amodule_args_for_change()
if subj.comp_info['status'] in ("DISABLED", "MIGRATING", "DELETING", "DESTROYING", "ERROR", "REDEPLOYING"):
# cannot do anything on the existing Compute in the listed states
subj.error() # was subj.nop()
elif subj.comp_info['status'] in ("ENABLED", "DISABLED"):
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present', 'paused', 'poweredon', 'poweredoff', 'halted'):
subj.compute_powerstate(subj.comp_info, amodule.params['state'])
subj.modify(arg_wait_cycles=7)
elif subj.comp_info['status'] == "DELETED":
if amodule.params['state'] in ('present', 'poweredon'):
# TODO - check if restore API returns VM ID (similarly to VM create API)
subj.compute_restore(comp_id=subj.comp_id)
# TODO - do we need updated comp_info to manage port forwards and size after VM is restored?
_, subj.comp_info, _ = subj.compute_find(
comp_id=subj.comp_id,
need_custom_fields=True,
)
subj.modify()
elif amodule.params['state'] == 'absent':
# subj.nop()
# subj.comp_should_exist = False
subj.destroy()
elif amodule.params['state'] in ('paused', 'poweredoff', 'halted'):
subj.error()
elif subj.comp_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'poweredon', 'poweredoff', 'halted'):
subj.create() # this call will also handle data disk & network connection
elif amodule.params['state'] == 'absent':
subj.nop()
subj.comp_should_exist = False
elif amodule.params['state'] == 'paused':
subj.error()
else:
subj.check_amodule_args_for_create()
# Preexisting Compute of specified identity was not found.
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
subj.nop()
elif amodule.params['state'] in ('present', 'poweredon', 'poweredoff', 'halted'):
subj.create() # this call will also handle data disk & network connection
elif amodule.params['state'] == 'paused':
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
# prepare Compute facts to be returned as part of decon.result and then call exit_json(...)
rg_facts = None
if subj.comp_should_exist:
if subj.result['changed'] and not subj.skip_final_get:
# There were changes to the Compute - refresh Compute facts.
_, subj.comp_info, _ = subj.compute_find(
comp_id=subj.comp_id,
need_custom_fields=True,
)
#
# We no longer need to re-read RG facts, as all network info is now available inside
# compute structure
# _, rg_facts = subj.rg_find(arg_account_id=0, arg_rg_id=subj.rg_id)
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()