From 9a2c909961a737d4a4c3ef8e388f7c63f0f61ad1 Mon Sep 17 00:00:00 2001 From: Sergey Shubin svs1370 Date: Fri, 26 Jun 2020 20:42:32 +0300 Subject: [PATCH] Debug and fix for disk and KVM VM handling --- library/decort_disk.py | 2 +- library/decort_kvmvm.py | 41 +++++++++++++----------------------- module_utils/decort_utils.py | 41 ++++++++++++++++++++---------------- 3 files changed, 39 insertions(+), 45 deletions(-) diff --git a/library/decort_disk.py b/library/decort_disk.py index da76073..5638256 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -361,7 +361,7 @@ def main(): decon.result['msg'] = "Specified Disk ID {} not found.".format(amodule.params['id']) amodule.fail_json(**decon.result) validated_acc_id =disk_facts['accountId'] - elif (amodule.params['account_id'] or amodule.params['account_name'] != "") and amodule.params['name'] != "": + elif amodule.params['account_id'] > 0 or amodule.params['account_name'] != "": # Make sure disk name is specified, if not - fail the module if amodule.params['name'] == "": decon.result['failed'] = True diff --git a/library/decort_kvmvm.py b/library/decort_kvmvm.py index 8a210d1..635f674 100644 --- a/library/decort_kvmvm.py +++ b/library/decort_kvmvm.py @@ -556,6 +556,9 @@ class decort_kvmvm(DecortController): userdata=cloud_init_params, start_on_create=start_compute) self.comp_should_exist = True + + # Need to re-read comp_info after VM was provisioned + _, self.comp_info, _ = self.compute_find(self.comp_id) # # Compute was created @@ -595,6 +598,8 @@ class decort_kvmvm(DecortController): """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. """ self.compute_networks(self.comp_info, self.amodule.params['networks']) self.compute_bootdisk_size(self.comp_info, self.amodule.params['boot_disk']) @@ -674,7 +679,7 @@ class decort_kvmvm(DecortController): 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['maxSize'] + 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']) @@ -804,27 +809,15 @@ def main(): pass if subj.comp_id: - if subj.comp_info['status'] in ("MIGRATING", "DESTROYING", "ERROR"): - # nothing to do for an existing Compute in the listed states regardless of the requested state - subj.nop() - elif subj.comp_info['status'] == "RUNNING": + 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'] == "ENABLED": if amodule.params['state'] == 'absent': subj.destroy() - elif amodule.params['state'] in ('present', 'poweredon'): - # check port forwards / check size / nop - subj.modify() - elif amodule.params['state'] in ('paused', 'poweredoff', 'halted'): - # pause or power off the vm, then check port forwards / check size + 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'] in ("PAUSED", "HALTED"): - if amodule.params['state'] == 'absent': - subj.destroy() - elif amodule.params['state'] in ('present', 'paused', 'poweredoff', 'halted'): - subj.modify() - elif amodule.params['state'] == 'poweredon': - subj.modify() - subj.compute_powerstate(subj.comp_info, amodule.params['state']) 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) @@ -839,9 +832,7 @@ def main(): subj.error() elif subj.comp_info['status'] == "DESTROYED": if amodule.params['state'] in ('present', 'poweredon', 'poweredoff', 'halted'): - subj.create() - # TODO: Network setup? - # TODO: Data disks setup? + subj.create() # this call will also handle data disk & network connection elif amodule.params['state'] == 'absent': subj.nop() subj.comp_should_exist = False @@ -853,9 +844,7 @@ def main(): if amodule.params['state'] == 'absent': subj.nop() elif amodule.params['state'] in ('present', 'poweredon', 'poweredoff', 'halted'): - subj.create() - # TODO: Network setup? - # TODO: Data disks setup? + subj.create() # this call will also handle data disk & network connection elif amodule.params['state'] == 'paused': subj.error() @@ -872,8 +861,8 @@ def main(): # TODO: check if we really need to get RG facts here in view of DNF implementation # we need to extract RG facts regardless of 'changed' flag, as it is our source of information on # the VDC external IP address - _, rg_facts = subj.rg_find(arg_rg_id=subj.rg_id) - subj.result['facts'] = subj.package_facts(rg_facts, amodule.check_mode) + _, 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) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 9f270c6..8c87b7f 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -661,7 +661,8 @@ class DecortController(object): # @param wait_for_change: integer number that tells how many 5 seconds intervals to wait for the power state # change before returning from this method. - NOP_STATES_FOR_POWER_CHANGE = ["MIGRATING", "DELETED", "DESTROYING", "DESTROYED", "ERROR"] + INVALID_MODEL_STATES = ["MIGRATING", "DELETED", "DESTROYING", "DESTROYED", "ERROR", "REDEPLOYING"] + INVALID_TECH_STATES = ["STOPPING", "STARTING", "PAUSING"] self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "compute_powerstate") @@ -671,48 +672,51 @@ class DecortController(object): "to '{}' was requested.").format(comp_facts['id'], target_state) return - if comp_facts['status'] in NOP_STATES_FOR_POWER_CHANGE: + if comp_facts['status'] in INVALID_MODEL_STATES: self.result['failed'] = False self.result['msg'] = ("compute_powerstate(): no power state change possible for Compute ID {} " "in its current state '{}'.").format(comp_facts['id'], comp_facts['status']) return + if comp_facts['techStatus'] in INVALID_TECH_STATES: + self.result['failed'] = False + self.result['msg'] = ("compute_powerstate(): no power state change possible for Compute ID {} " + "in its current techState '{}'.").format(comp_facts['id'], comp_facts['techStatus']) + return + powerstate_api = "" # this string will also be used as a flag to indicate that API call is necessary api_params = dict(compId=comp_facts['id']) expected_state = "" - if comp_facts['status'] == "RUNNING": + if comp_facts['techStatus'] == "STARTED": if target_state == 'paused': powerstate_api = "/restmachine/cloudapi/compute/pause" - expected_state = "PAUSED" + expected_techState = "PAUSED" elif target_state in ('poweredoff', 'halted', 'stopped'): powerstate_api = "/restmachine/cloudapi/compute/stop" params['force'] = force_change - expected_state = "HALTED" + expected_techState = "STOPPED" elif target_state == 'restarted': powerstate_api = "/restmachine/cloudapi/compute/reboot" - expected_state = "RUNNING" - elif comp_facts['status'] == "PAUSED" and target_state in ('poweredon', 'restarted', 'started'): + expected_techState = "STARTED" + elif comp_facts['techStatus'] == "PAUSED" and target_state in ('poweredon', 'restarted', 'started'): powerstate_api = "/restmachine/cloudapi/compute/resume" - expected_state = "RUNNING" - elif comp_facts['status'] == "HALTED" and target_state in ('poweredon', 'restarted', 'started'): + expected_techState = "STARTED" + elif comp_facts['techStatus'] == "STOPPED" and target_state in ('poweredon', 'restarted', 'started'): powerstate_api = "/restmachine/cloudapi/compute/start" - expected_state = "RUNNING" - else: - # This Compute instance seems to be in the desired power state already - do not call API - pass + expected_techState = "STARTED" if powerstate_api != "": self.decort_api_call(requests.post, powerstate_api, api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. self.result['failed'] = False self.result['changed'] = True - comp_facts['status'] = expected_state + comp_facts['techStatus'] = expected_techState else: self.result['failed'] = False self.result['warning'] = ("compute_powerstate(): no power state change required for Compute ID {} from its " "current state '{}' to desired state '{}'.").format(comp_facts['id'], - comp_facts['status'], + comp_facts['techStatus'], target_state) return @@ -766,7 +770,8 @@ class DecortController(object): cpu=cpu, ram=ram, imageId=image_id, bootDisk=boot_disk, - start=start_on_create) # start_machine parameter requires DECORT API ver 3.3.1 or higher + start=start_on_create, # start_machine parameter requires DECORT API ver 3.3.1 or higher + netType="NONE") # we create VM without any network connections if userdata: api_params['userdata'] = json.dumps(userdata) # we need to pass a string object as "userdata" @@ -816,7 +821,7 @@ class DecortController(object): "Compute ID {}.").format(comp_dict['accountId'], comp_dict['id']) return - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/externalnetworks/list", api_params) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/externalnetwork/list", api_params) extnet_list = json.loads(api_resp.content.decode('utf8')) # list of dicts: "name" holds "NET_ADDR/NETMASK", "id" is ID if not len(vins_list): self.result['failed'] = True @@ -2217,7 +2222,7 @@ class DecortController(object): api_params = dict(accountId=account_id, name=disk_name, showAll=False) # we do not want to see disks in DESTROYED, PURGED or invalid states - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/list", api_params) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/search", api_params) # the above call may return more than one matching disk disks_list = json.loads(api_resp.content.decode('utf8')) for runner in disks_list: