From 3797ed9814dad5d5e99026ede5d811e057848f21 Mon Sep 17 00:00:00 2001 From: Aleksandr Malyavin Date: Wed, 5 Oct 2022 12:22:05 +0300 Subject: [PATCH] Add decort_lb management. decort_disk: Change limitIO, rename disk. --- library/decort_disk.py | 625 ++++++++++++++------------ library/decort_lb.py | 328 ++++++++++++++ library/decort_vins.py | 14 +- module_utils/decort_utils.py | 821 ++++++++++++++++++++++++++++++++--- 4 files changed, 1466 insertions(+), 322 deletions(-) create mode 100644 library/decort_lb.py diff --git a/library/decort_disk.py b/library/decort_disk.py index 1302969..5167d96 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -157,6 +157,16 @@ options: - `If specified for an existing disk, and it is greater than current disk size, platform will try to resize the disk on the fly. Downsizing disk is not allowed.` required: no + limitIO: + description: + - Disk input / output limit, used to limit the speed of interaction with the disk. + required: no + type: + description: + - Type of the disk. + - `Disks can be of the following types: "D"-Data, "B"-Boot, "T"-Tmp.` + default: "D" + required: no state: description: - Specify the desired state of the disk at the exit of the module. @@ -234,103 +244,277 @@ facts: gid: 1001 ''' + + from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback from ansible.module_utils.decort_utils import * +class decort_disk(DecortController): + def __init__(self,amodule): + super(decort_disk, self).__init__(amodule) -def decort_disk_package_facts(disk_facts, check_mode=False): - """Package a dictionary of disk facts according to the decort_disk module specification. - This dictionary will be returned to the upstream Ansible engine at the completion of - the module run. - - @param (dict) disk_facts: dictionary with Disk facts as returned by API call to .../disks/get - @param (bool) check_mode: boolean that tells if this Ansible module is run in check mode - """ - - ret_dict = dict(id=0, - name="none", - state="CHECK_MODE", - size=0, - account_id=0, - sep_id=0, - pool="none", - attached_to=0, - gid=0 - ) - - if check_mode: - # in check mode return immediately with the default values - return ret_dict + self.validated_account_id = 0 + self.validated_disk_id = 0 + self.disk_facts = None # will hold Disk facts + self.acc_facts = None # will hold Account facts + + # limitIO check for exclusive parameters + if amodule.params['limitIO']: + limit = amodule.params['limitIO'] + if limit['total_bytes_sec'] > 0 and limit['read_bytes_sec'] > 0 or \ + limit['total_bytes_sec'] > 0 and limit['write_bytes_sec'] > 0: + self.result['failed'] = True + self.result['msg'] = ("total and read/write of bytes_sec cannot be set at the same time.") + amodule.fail_json(**self.result) + elif limit['total_iops_sec'] > 0 and limit['read_iops_sec'] > 0 or \ + limit['total_iops_sec'] > 0 and limit['write_iops_sec'] > 0: + self.result['failed'] = True + self.result['msg'] = ("total and read/write of iops_sec cannot be set at the same time.") + amodule.fail_json(**self.result) + elif limit['total_bytes_sec_max'] > 0 and limit['read_bytes_sec_max'] > 0 or \ + limit['total_bytes_sec_max'] > 0 and limit['write_bytes_sec_max'] > 0: + self.result['failed'] = True + self.result['msg'] = ("total and read/write of bytes_sec_max cannot be set at the same time.") + amodule.fail_json(**self.result) + elif limit['total_iops_sec_max'] > 0 and limit['read_iops_sec_max'] > 0 or \ + limit['total_iops_sec_max'] > 0 and limit['write_iops_sec_max'] > 0: + self.result['failed'] = True + self.result['msg'] = ("total and read/write of iops_sec_max cannot be set at the same time.") + amodule.fail_json(**self.result) + + + if amodule.params['account_id']: + self.validated_account_id = amodule.params['account_id'] + elif amodule.params['account_name']: + self.validated_account_id, _ = self.account_find(amodule.params['account_name']) + elif not amodule.params['id'] and not amodule.params['account_name']: + self.result['failed'] = True + self.result['msg'] = ("Cannot found disk without account id or name.") + amodule.fail_json(**self.result) + + if self.validated_account_id == 0 and not amodule.params['id']: + # we failed either to find or access the specified account - fail the module + self.result['failed'] = True + self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name']) + amodule.fail_json(**self.result) + + if amodule.params['id'] or amodule.params['name']: + self.validated_disk_id, self.disk_facts = self.decort_disk_find(amodule) + + else: + self.result['failed'] = True + self.result['msg'] = ("Cannot find or create disk without disk name or disk id") + amodule.fail_json(**self.result) + + if amodule.params['place_with'] > 0: + image_id, image_facts = self.image_find(amodule.params['place_with'], "", 0) + amodule.params['sep_id']= image_facts['sepId'] + + + + + def decort_disk_create(self, amodule): + if not self.disk_facts: + self.disk_id = self.disk_create(accountId=self.validated_account_id, gid=amodule.params['gid'], + name=amodule.params['name'], description=amodule.params['description'], + size=amodule.params['size'], type=amodule.params['type'], + iops=amodule.params['iops'], + sep_id=amodule.params['sep_id'], pool=amodule.params['pool']) + self.result['msg'] = ("Disk with id '{}' successfully created.").format(self.disk_id) + + elif self.disk_facts['status'] in ["DESTROYED", "PURGED"]: + if not amodule.params['limitIO']: + amodule.params['limitIO'] = self.disk_facts['iotune'] + if amodule.params['sep_id'] == 0: + validated_sep_id = self.disk_facts['sepId'] + else: + validated_sep_id = amodule.params['sep_id'] + + if amodule.params['pool'] == 0: + validated_pool = self.disk_facts['pool'] + else: + validated_pool = amodule.params['pool'] + + if amodule.params['size'] == 0: + validated_size = self.disk_facts['sizeMax'] + else: + validated_size = amodule.params['size'] + + if amodule.params['gid'] == 0: + validated_gid = self.disk_facts['gid'] + else: + validated_gid = amodule.params['gid'] + + self.disk_id = self.disk_create(accountId=self.validated_account_id, gid=validated_gid, + name=self.disk_facts['name'], description=amodule.params['description'], + size=validated_size, type=self.disk_facts['type'], + iops=self.disk_facts['iotune']['total_iops_sec'], + sep_id=validated_sep_id, pool=validated_pool) + if not amodule.params['limitIO']: + amodule.params['limitIO'] = self.disk_facts['iotune'] + self.result['msg'] = ("Disk with id '{}' successfully recreated.").format(self.disk_id) + + self.result['failed'] = False + self.result['changed'] = True + return self.disk_id + + def decort_disk_delete(self, amodule): + self.disk_id = self.disk_delete(disk_id=self.validated_disk_id, + detach=amodule.params['force_detach'], + permanently=amodule.params['permanently'], + reason=amodule.params['reason']) + return + + + def decort_disk_find(self, amodule): + if amodule.params['name'] and not amodule.params['id']: + self.disk_id, self.disk_facts = self.disk_find(disk_id=self.validated_disk_id, + name=amodule.params['name'], + account_id=self.validated_account_id) + elif self.validated_disk_id > 0: + self.disk_id, self.disk_facts = self.disk_find(disk_id=self.validated_disk_id, + name=self.disk_facts['name'], + account_id=0) + elif amodule.params['id']: + self.disk_id, self.disk_facts = self.disk_find(disk_id=amodule.params['id'], + name=amodule.params['name'], + account_id=0) + + if not self.disk_id and not amodule.params['name']: + self.result['failed'] = True + self.result['msg'] = "Specified Disk ID {} not found.".format(amodule.params['id']) + amodule.fail_json(**self.result) + self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts) + return self.disk_id, self.disk_facts + + def decort_disk_limitIO(self, amodule): + self.limits = amodule.params['limitIO'] + + self.disk_limitIO(limits = self.limits, + diskId = self.validated_disk_id) + self.disk_facts['iotune'] = amodule.params['limitIO'] + self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts) + return + + def decort_disk_rename(self, amodule): + self.disk_rename(diskId = self.validated_disk_id, + name = amodule.params['name']) + self.disk_facts['name'] = amodule.params['name'] + self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts) + self.result['msg'] = ("Disk with id '{}',successfully renamed to '{}'.").format(self.validated_disk_id, amodule.params['name']) + return + + def decort_disk_package_facts(disk_facts, check_mode=False): + ret_dict = dict(id=0, + name="none", + state="CHECK_MODE", + size=0, + account_id=0, + sep_id=0, + pool="none", + attached_to=0, + gid=0 + ) + + if check_mode: + # in check mode return immediately with the default values + return ret_dict + + if disk_facts is None: + # if void facts provided - change state value to ABSENT and return + ret_dict['state'] = "ABSENT" + return ret_dict + + ret_dict['id'] = disk_facts['id'] + ret_dict['name'] = disk_facts['name'] + ret_dict['size'] = disk_facts['sizeMax'] + ret_dict['state'] = disk_facts['status'] + ret_dict['account_id'] = disk_facts['accountId'] + ret_dict['sep_id'] = disk_facts['sepId'] + ret_dict['pool'] = disk_facts['pool'] + ret_dict['attached_to'] = disk_facts['vmid'] + ret_dict['gid'] = disk_facts['gid'] + ret_dict['iotune'] = disk_facts['iotune'] - if disk_facts is None: - # if void facts provided - change state value to ABSENT and return - ret_dict['state'] = "ABSENT" return ret_dict - ret_dict['id'] = disk_facts['id'] - ret_dict['name'] = disk_facts['name'] - ret_dict['size'] = disk_facts['sizeMax'] - ret_dict['state'] = disk_facts['status'] - ret_dict['account_id'] = disk_facts['accountId'] - ret_dict['sep_id'] = disk_facts['sepId'] - ret_dict['pool'] = disk_facts['pool'] - ret_dict['attached_to'] = disk_facts['vmid'] - ret_dict['gid'] = disk_facts['gid'] - - return ret_dict - -def decort_disk_parameters(): - """Build and return a dictionary of parameters expected by decort_disk module in a form accepted - by AnsibleModule utility class.""" - - return dict( - account_id=dict(type='int', required=False, default=0), - account_name=dict(type='str', required=False, default=''), - annotation=dict(type='str', required=False, default='Disk by decort_disk'), - 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']), - controller_url=dict(type='str', required=True), - id=dict(type='int', required=False, default=0), - name=dict(type='str', required=False), - force_detach=dict(type='bool', required=False, default=False), - jwt=dict(type='str', - required=False, - fallback=(env_fallback, ['DECORT_JWT']), - no_log=True), - oauth2_url=dict(type='str', + def decort_disk_parameters(): + """Build and return a dictionary of parameters expected by decort_disk module in a form accepted + by AnsibleModule utility class.""" + + return dict( + account_id=dict(type='int', required=False, default=0), + account_name=dict(type='str', required=False, default=''), + annotation=dict(type='str', required=False, default='Disk by decort_disk'), + app_id=dict(type='str', required=False, - fallback=(env_fallback, ['DECORT_OAUTH2_URL'])), - password=dict(type='str', + 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']), + controller_url=dict(type='str', required=True), + id=dict(type='int', required=False, default=0), + name=dict(type='str', required=False), + force_detach=dict(type='bool', required=False, default=False), + jwt=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_JWT']), + no_log=True), + 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), + place_with=dict(type='int', default=0), + pool=dict(type='str', default=''), + sep_id=dict(type='int', default=0), + gid=dict(type='int', default=0), + size=dict(type='int', default=0), + type=dict(type='str', + required=False, + default="D", + choices=['B', 'D', 'T']), + iops=dict(type='int', default=2000), + limitIO=dict(type='dict', + options=dict( + total_bytes_sec=dict(default=0,type='int'), + read_bytes_sec=dict(default=0,type='int'), + write_bytes_sec=dict(default=0,type='int'), + total_iops_sec=dict(default=0,type='int'), + read_iops_sec=dict(default=0,type='int'), + write_iops_sec=dict(default=0,type='int'), + total_bytes_sec_max=dict(default=0,type='int'), + read_bytes_sec_max=dict(default=0,type='int'), + write_bytes_sec_max=dict(default=0,type='int'), + total_iops_sec_max=dict(default=0,type='int'), + read_iops_sec_max=dict(default=0,type='int'), + write_iops_sec_max=dict(default=0,type='int'), + size_iops_sec=dict(default=0,type='int'),)), + permanently=dict(type='bool', required=False, default=False), + reason=dict(type='int', required=False), + description=dict(type='str', required=False, + default="Disk created with Ansible Decort_disk module."), + state=dict(type='str', + default='present', + choices=['absent', 'present']), + user=dict(type='str', required=False, - fallback=(env_fallback, ['DECORT_PASSWORD']), - no_log=True), - place_with=dict(type='int', required=False, default=0), - pool=dict(type='str', required=False, default=''), - sep_id=dict(type='int', required=False, default=0), - size=dict(type='int', required=False), - state=dict(type='str', - default='present', - choices=['absent', 'present']), - user=dict(type='str', - required=False, - fallback=(env_fallback, ['DECORT_USER'])), - verify_ssl=dict(type='bool', required=False, default=True), - workflow_callback=dict(type='str', required=False), - workflow_context=dict(type='str', required=False), - ) + fallback=(env_fallback, ['DECORT_USER'])), + verify_ssl=dict(type='bool', required=False, default=True), + workflow_callback=dict(type='str', required=False), + workflow_context=dict(type='str', required=False), + ) def main(): - module_parameters = decort_disk_parameters() + module_parameters = decort_disk.decort_disk_parameters() amodule = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True, @@ -345,197 +529,106 @@ def main(): ], ) - decon = DecortController(amodule) + decon = decort_disk(amodule) - disk_id = 0 - disk_facts = None # will hold Disk facts - validated_acc_id = 0 - acc_facts = None # will hold Account facts - - if amodule.params['id']: - # expect existing Disk with the specified ID - # This call to disk_find will abort the module if no Disk with such ID is present - disk_id, disk_facts = decon.disk_find(amodule.params['id']) - if not disk_id: - decon.result['failed'] = True - 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'] > 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 - decon.result['msg'] = ("Cannot manage disk if both ID is 0 and disk name is empty.") + if decon.validated_disk_id == 0 and amodule.params['state'] == 'present': + # if sep_id or place_with not specified, then exit with error + if amodule.params['sep_id'] == 0 and amodule.params['place_with'] == 0: + decon.result['msg'] = ("To create a disk, you must specify sep_id or place_with.")\ + .format(decon.validated_disk_id) amodule.fail_json(**decon.result) - # Specified account must be present and accessible by the user, otherwise abort the module - validated_acc_id, acc_facts = decon.account_find(amodule.params['account_name'], amodule.params['account_id']) - if not validated_acc_id: - decon.result['failed'] = True - decon.result['msg'] = ("Current user does not have access to the requested account " - "or non-existent account specified.") - amodule.fail_json(**decon.result) - # This call to disk_find may return disk_id=0 if no Disk with this name found in - disk_id, disk_facts = decon.disk_find(disk_id=0, disk_name=amodule.params['name'], - account_id=validated_acc_id, - check_state=False) - else: - # this is "invalid arguments combination" sink - # if we end up here, it means that module was invoked with disk_id=0 and undefined account - decon.result['failed'] = True - if amodule.params['account_id'] == 0 and amodule.params['account_name'] == "": - decon.result['msg'] = "Cannot find Disk by name when account name is empty and account ID is 0." - if amodule.params['name'] == "": - decon.result['msg'] = "Cannot find Disk by empty name." + # if id cannot cannot be found and have a state 'present', then create a new disk + decon.validated_disk_id = decon.decort_disk_create(amodule) + _, decon.disk_facts = decon.decort_disk_find(amodule) + decon.result['changed'] = True + decon.result['msg'] = ("Disk with id '{}' successfully created.").format(decon.validated_disk_id) + + elif decon.validated_disk_id == 0 and amodule.params['state'] == 'absent' and amodule.params['name']: + # if disk with specified name cannot be found and have a state 'absent', then nothing to do, + # specified disk already deleted + decon.result['msg'] = ("Disk with name '{}' has already been deleted or your account does not have" + " access to it.")\ + .format(amodule.params['name']) + amodule.exit_json(**decon.result) + + elif decon.validated_disk_id == 0 and amodule.params['state'] == 'absent' and amodule.params['id']: + # if disk with specified id cannot be found and have a state 'absent', then nothing to do, + # specified disk already deleted + decon.result['msg'] = ("Disk with name '{}' has already been deleted or your account does not have" + " access to it.")\ + .format(decon.validated_disk_id) + amodule.exit_json(**decon.result) + + elif decon.disk_facts['status'] == "CREATED": + if amodule.params['state'] == 'present': + # if disk status in condition "CREATED" and state "present", nothing to do, + # specified disk already created + decon.result['msg'] = "Specified Disk ID {} already created.".format(decon.validated_disk_id) + + if amodule.params['state'] == 'absent': + # if disk status in condition "CREATED" and state "absent", delete the disk + decon.validated_disk_id = decon.decort_disk_delete(amodule) + decon.disk_facts['status'] = "DESTROYED" + decon.result['msg'] = ("Disk with id '{}' successfully deleted.").format(decon.disk_facts['id']) + decon.result['facts'] = decon.decort_disk_package_facts(decon.disk_facts) + amodule.exit_json(**decon.result) + + elif decon.disk_facts['status'] in ["MODELED", "CREATING" ]: + # if disk in status "MODELED" or "CREATING", + # then we cannot do anything, while disk in this status + decon.result['changed'] = False + decon.result['msg'] = ("Cannot do anything with disk id '{}',please wait until disk will be created.")\ + .format(decon.validated_disk_id) amodule.fail_json(**decon.result) - # - # Initial validation of module arguments is complete - # - # At this point non-zero disk_id means that we will be managing pre-existing Disk - # Otherwise we are about to create a new disk - # - # Valid Disk model statii are as follows: - # - # "CREATED", "ASSIGNED", DELETED", "DESTROYED", "PURGED" - # - - disk_should_exist = False - target_sep_id = 0 - # target_pool = "" + elif decon.disk_facts['status'] == "DELETED": + if amodule.params['state'] == 'present': + # if disk in "DELETED" status and "present" state, restore + decon.disk_restore(decon.validated_disk_id) + _, decon.disk_facts = decon.decort_disk_find(amodule) + decon.result['changed'] = True + decon.result['msg'] = ("Disk with id '{}',restored successfully.").format(decon.validated_disk_id) + + elif amodule.params['state'] == 'absent': + # if disk in "DELETED" status and "absent" state, nothing to do + decon.result['msg'] = "Specified Disk ID {} already destroyed.".format(decon.validated_disk_id) + amodule.exit_json(**decon.result) - if disk_id: - disk_should_exist = True - if disk_facts['status'] in ["MODELED", "CREATING" ]: - # error: nothing can be done to existing Disk in the listed statii regardless of - # the requested state - decon.result['failed'] = True - decon.result['changed'] = False - decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current " - "status '{}'").format(disk_id, disk_facts['status']) - elif disk_facts['status'] in ["CREATED", "ASSIGNED"]: - if amodule.params['state'] == 'absent': - decon.disk_delete(disk_id, True, amodule.params['force_detach']) # delete permanently - disk_facts['status'] = 'DESTROYED' - disk_should_exist = False - elif amodule.params['state'] == 'present': - # resize Disk as necessary & if possible - if decon.check_amodule_argument('size', False): - decon.disk_resize(disk_facts, amodule.params['size']) - elif disk_facts['status'] == "DELETED": - if amodule.params['state'] == 'present': - # restore - decon.disk_restore(disk_id) - _, disk_facts = decon.disk_find(disk_id) - decon.disk_resize(disk_facts, amodule.params['size']) - disk_should_exist = True - elif amodule.params['state'] == 'absent': - # destroy permanently - decon.disk_delete(disk_id, permanently=True) - disk_facts['status'] = 'DESTROYED' - disk_should_exist = False - elif disk_facts['status'] in ["DESTROYED", "PURGED"]: - if amodule.params['state'] == 'present': - # Need to re-provision this Disk. - # Some attributes may change, some must stay the same: - # - disk name - stays, take from disk_facts - # - account ID - stays, take from validated account ID - # - size - may change, take from module arguments - # - SEP ID - may change, build based on module arguments - # - pool - may change, take from module arguments - # - annotation - may change, take from module arguments - # - # First validate required parameters: - decon.check_amodule_argument('size') # this will fail the module if size is not specified - target_sep_id = 0 - if decon.check_amodule_argument('sep_id', False) and amodule.params['sep_id'] > 0: - # non-zero sep_id is explicitly passed in module arguments - target_sep_id = amodule.params['sep_id'] - elif decon.check_amodule_argument('place_with', False) and amodule.params['place_with'] > 0: - # request to place this disk on the same SEP as the specified OS image - # validate specified OS image and assign SEP ID accordingly - image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0) - target_sep_id = image_facts['sepId'] - else: - # no new SEP ID is explicitly specified, and no place_with option - use sepId from the disk_facts - target_sep_id = disk_facts['sepId'] - disk_id = decon.disk_provision(disk_name=disk_facts['name'], # as this disk was found, its name is in the facts - size=amodule.params['size'], - account_id=validated_acc_id, - sep_id=target_sep_id, - pool=amodule.params['pool'], - desc=amodule.params['annotation'], - location="") - disk_should_exist = True - elif amodule.params['state'] == 'absent': - # nop - decon.result['failed'] = False - decon.result['changed'] = False - decon.result['msg'] = ("No state change required for Disk ID {} because of its " - "current status '{}'").format(disk_id, - disk_facts['status']) - disk_should_exist = False - else: - # disk_id =0 -> pre-existing Disk was not found. - disk_should_exist = False # we will change it back to True if Disk is created successfully - # If requested state is 'absent' - nothing to do - if amodule.params['state'] == 'absent': - decon.result['failed'] = False - decon.result['changed'] = False - decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for " - "non-existent Disk name '{}'").format(amodule.params['name']) - elif amodule.params['state'] == 'present': - decon.check_amodule_argument('name') # if disk name not specified, fail the module - decon.check_amodule_argument('size') # if disk size not specified, fail the module - - # as we already have account ID, we can create Disk and get disk id on success - if decon.check_amodule_argument('sep_id', False) and amodule.params['sep_id'] > 0: - # non-zero sep_id is explicitly passed in module arguments - target_sep_id = amodule.params['sep_id'] - elif decon.check_amodule_argument('place_with', False) and amodule.params['place_with'] > 0: - # request to place this disk on the same SEP as the specified OS image - # validate specified OS image and assign SEP ID accordingly - image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0) - target_sep_id = image_facts['sepId'] - else: - # no SEP ID is explicitly specified, and no place_with option - we do not know where - # to place the new disk - fail the module + elif decon.disk_facts['status'] in ["DESTROYED", "PURGED"]: + if amodule.params['state'] == 'present': + decon.validated_disk_id = decon.decort_disk_create(amodule) + _, decon.disk_facts = decon.decort_disk_find(amodule) + + elif amodule.params['state'] == 'absent': + decon.result['msg'] = "Specified Disk ID {} already destroyed.".format(decon.validated_disk_id) + amodule.exit_json(**decon.result) + + if amodule.params['state'] == "present": + if decon.disk_facts['sizeMax'] != amodule.params['size']: + if decon.disk_facts['sizeMax'] > amodule.params['size'] and amodule.params['size'] != 0: decon.result['failed'] = True - decon.result['msg'] = ("Cannot create new Disk name '{}': no SEP ID specified and " - "no 'place_with' option used.").format(amodule.params['name']) + decon.result['msg'] = ("Disk id '{}', cannot reduce disk size.").format(decon.validated_disk_id) amodule.fail_json(**decon.result) - - disk_id = decon.disk_provision(disk_name=amodule.params['name'], - size=amodule.params['size'], - account_id=validated_acc_id, - sep_id=target_sep_id, - pool_name=amodule.params['pool'], - desc=amodule.params['annotation'], - location="") - disk_should_exist = True - elif amodule.params['state'] == 'disabled': - decon.result['failed'] = True - decon.result['changed'] = False - decon.result['msg'] = ("Invalid target state '{}' requested for non-existent " - "Disk name '{}'").format(amodule.params['state'], - amodule.params['name']) + elif decon.disk_facts['sizeMax'] < amodule.params['size']: + decon.disk_resize(disk_facts=decon.disk_facts, + new_size=amodule.params['size']) + decon.result['changed'] = True + decon.disk_facts['size'] = amodule.params['size'] + decon.result['msg'] = ("Disk with id '{}',resized successfully.").format(decon.validated_disk_id) + + if amodule.params['limitIO'] and amodule.params['limitIO'] != decon.disk_facts['iotune']: + decon.decort_disk_limitIO(amodule) + decon.result['changed'] = True + decon.result['msg'] = ("Disk with id '{}',limited successfully.").format(decon.validated_disk_id) - # - # conditional switch end - complete module run - # - if decon.result['failed']: - amodule.fail_json(**decon.result) - else: - # prepare Disk facts to be returned as part of decon.result and then call exit_json(...) - if disk_should_exist: - if decon.result['changed']: - # If we arrive here, there is a good chance that the Disk is present - get fresh Disk - # facts by Disk ID. - # Otherwise, Disk facts from previous call (when the Disk was still in existence) will - # be returned. - _, disk_facts = decon.disk_find(disk_id) - decon.result['facts'] = decort_disk_package_facts(disk_facts, amodule.check_mode) - amodule.exit_json(**decon.result) + if amodule.params['name'] and amodule.params['id']: + if amodule.params['name'] != decon.disk_facts['name']: + decon.decort_disk_rename(amodule) + decon.result['changed'] = True + decon.result['msg'] = ("Disk with id '{}',renamed successfully from '{}' to '{}'.")\ + .format(decon.validated_disk_id, decon.disk_facts['name'], amodule.params['name']) + amodule.exit_json(**decon.result) if __name__ == "__main__": main() diff --git a/library/decort_lb.py b/library/decort_lb.py new file mode 100644 index 0000000..3296e79 --- /dev/null +++ b/library/decort_lb.py @@ -0,0 +1,328 @@ +#!/usr/bin/python +# +# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible +# Copyright: (c) 2018-2022 Digital Energy Cloud Solutions LLC +# +# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) +# + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' + TODO +''' + +EXAMPLES = ''' + TODO +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback + +from ansible.module_utils.decort_utils import * + +class decort_lb(DecortController): + def __init__(self,arg_amodule) -> None: + super(decort_lb,self).__init__(arg_amodule) + + self.lb_id = 0 + self.lb_facts = None + self.vins_id = 0 + self.vins_facts = None + self.rg_id = 0 + self.rg_facts = None + self.acc_id = 0 + self.acc_facts = None + self.default_server_check = "enabled" + self.default_alg = "roundrobin" + self.default_settings = { + "downinter": 10000, + "fall": 2, + "inter": 5000, + "maxconn": 250, + "maxqueue": 256, + "rise": 2, + "slowstart": 60000, + "weight": 100, + } + if arg_amodule.params['lb_id']: + self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id']) + if not self.lb_id: + self.result['failed'] = True + self.result['msg'] = "Specified LB ID {} not found."\ + .format(arg_amodule.params['lb _id']) + self.fail_json(**self.result) + self.acc_id = self.lb_facts['accountId'] + self.rg_id = self.lb_facts['rgId'] + self.vins_id = self.lb_facts['vinsId'] + return + + if arg_amodule.params['rg_id']: + self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="") + if not self.rg_id: + self.result['failed'] = True + self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id']) + self.fail_json(**self.result) + + if arg_amodule.params['vins_id']: + self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id']) + if not self.vins_id: + self.result['failed'] = True + self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) + self.fail_json(**self.result) + + elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": + + if arg_amodule.params['rg_name']: + self.result['failed'] = True + self.result['msg'] = ("RG name must be specified with account present") + self.fail_json(**self.result) + self.acc_id, self.acc_facts = self.account_find(arg_amodule.params['account_name'], + arg_amodule.params['account_id']) + if not self.acc_id: + self.result['failed'] = True + self.result['msg'] = ("Current user does not have access to the requested account " + "or non-existent account specified.") + self.fail_json(**self.result) + self.rg_id, self.rg_facts = self.rg_find(self._acc_id,0, arg_rg_name=arg_amodule.params['rg_name']) + + if self.rg_id and self.vins_id: + self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id) + return + + def create(self): + self.lb_id = self.lb_provision(self.amodule.params['lb_name'], + self.rg_id,self.vins_id, + self.amodule.params['ext_net_id'], + self.amodule.params['annotation']) + if self.amodule.params['backends'] or self.amodule.params['frontends']: + self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id) + self.lb_update( + self.lb_facts['backends'], + self.lb_facts['frontends'], + self.amodule.params['backends'], + self.amodule.params['servers'], + self.amodule.params['frontends'] + ) + return + + def action(self,d_state='',restore=False): + if restore == True: + self.lb_restore(arg_vins_id=self.lb_id) + self.lb_state(self.vins_facts, 'enabled') + self.lb_facts['status'] = "ENABLED" + self.lb_facts['techStatus'] = "STARTED" + + self.lb_update( + self.lb_facts['backends'], + self.lb_facts['frontends'], + self.amodule.params['backends'], + self.amodule.params['servers'], + self.amodule.params['frontends'] + ) + + if d_state != '': + self.lb_state(self.lb_facts, d_state) + return + + def delete(self): + self.lb_delete(self.lb_id, self.amodule.params['permanently']) + self.lb_facts['status'] = 'DESTROYED' + return + def nop(self): + """No operation (NOP) handler for LB management by decort_lb 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 LB state. + """ + self.result['failed'] = False + self.result['changed'] = False + if self.lb_id: + self.result['msg'] = ("No state change required for LB ID {} because of its " + "current status '{}'.").format(self.lb_id, self.vins_facts['status']) + else: + self.result['msg'] = ("No state change to '{}' can be done for " + "non-existent LB instance.").format(self.amodule.params['state']) + return + def error(self): + self.result['failed'] = True + self.result['changed'] = False + if self.vins_id: + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = ("Invalid target state '{}' requested for LB ID {} in the " + "current status '{}'").format(self.lb_id, + self.amodule.params['state'], + self.lb_facts['status']) + else: + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = ("Invalid target state '{}' requested for non-existent " + "LB name '{}'").format(self.amodule.params['state'], + self.amodule.params['lb_name']) + return + def package_facts(self, arg_check_mode=False): + """Package a dictionary of LB facts according to the decort_lb module specification. + This dictionary will be returned to the upstream Ansible engine at the completion of + the module run. + + @param arg_check_mode: boolean that tells if this Ansible module is run in check mode + """ + + ret_dict = dict(id=0, + name="none", + state="CHECK_MODE", + ) + + if arg_check_mode: + # in check mode return immediately with the default values + return ret_dict + + if self.vins_facts is None: + # if void facts provided - change state value to ABSENT and return + ret_dict['state'] = "ABSENT" + return ret_dict + + ret_dict['id'] = self.lb_facts['id'] + ret_dict['name'] = self.lb_facts['name'] + ret_dict['state'] = self.lb_facts['status'] + #ret_dict['account_id'] = self.lb_facts['accountId'] + ret_dict['rg_id'] = self.lb_facts['rgId'] + ret_dict['gid'] = self.lb_facts['gid'] + if self.amodule.params['state']!="absent": + ret_dict['backends'] = self.lb_facts['backends'] + ret_dict['frontends'] = self.lb_facts['frontends'] + return ret_dict + @staticmethod + def build_parameters(): + """Build and return a dictionary of parameters expected by decort_vins module in a form accepted + by AnsibleModule utility class.""" + + return dict( + account_id=dict(type='int', required=False), + account_name=dict(type='str', required=False, default=''), + annotation=dict(type='str', required=False, default='Managed by Ansible module decort_lb'), + 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']), + controller_url=dict(type='str', required=True), + # datacenter=dict(type='str', required=False, default=''), + ext_net_id=dict(type='int', required=False, default=-1), + ext_ip_addr=dict(type='str', required=False, default=''), + jwt=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_JWT']), + no_log=True), + 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), + state=dict(type='str', + default='present', + choices=['absent', 'disabled', 'enabled', 'present','restart']), + user=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_USER'])), + rg_id=dict(type='int', required=False, default=0), + rg_name=dict(type='str', required=False, default=''), + vins_name=dict(type='str', required=False, default=''), + vins_id=dict(type='int', required=False, default=0), + verify_ssl=dict(type='bool', required=False, default=True), + lb_id=dict(type='int', required=False, default=0), + lb_name=dict(type='str', required=True), + backends=dict(type='list',required=False,default=[]), + frontends=dict(type='list',required=False,default=[]), + servers=dict(type='list',required=False,default=[]), + permanently=dict(type='bool', required=False, default=False), + workflow_callback=dict(type='str', required=False), + workflow_context=dict(type='str', required=False), + ) + +def main(): + module_parameters = decort_lb.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=[ + ['rg_id','rg_name'], + ['lb_id','lb_name'], + ['vins_id','vins_name'] + ] + ) + decon = decort_lb(amodule) + if decon.lb_id: + if decon.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]: + decon.result['failed'] = True + decon.result['changed'] = False + decon.result['msg'] = ("No change can be done for existing LB ID {} because of its current " + "status '{}'").format(decon.lb_id, decon.lb_facts['status']) + elif decon.lb_facts['status'] == "DISABLED": + if amodule.params['state'] == 'absent': + decon.delete() + elif amodule.params['state'] in ('present', 'disabled'): + decon.action() + elif amodule.params['state'] == 'enabled': + decon.action('enabled') + elif decon.lb_facts['status'] in ["CREATED", "ENABLED"]: + if amodule.params['state'] == 'absent': + decon.delete() + elif amodule.params['state'] in ('present', 'enabled'): + decon.action() + elif amodule.params['state'] == 'disabled': + decon.action('disabled') + elif amodule.params['state'] in ('stopped', 'started','restart'): + decon.action(amodule.params['state']) + elif decon.lb_facts['status'] == "DELETED": + if amodule.params['state'] in ['present', 'enabled']: + decon.action(restore=True) + elif amodule.params['state'] == 'absent': + decon.delete() + elif amodule.params['state'] == 'disabled': + decon.error() + elif decon.lb_facts['status'] == "DESTROYED": + if amodule.params['state'] in ('present', 'enabled'): + decon.create() + elif amodule.params['state'] == 'absent': + decon.nop() + elif amodule.params['state'] == 'disabled': + decon.error() + else: + if amodule.params['state'] == 'absent': + decon.nop() + elif amodule.params['state'] in ('present', 'enabled'): + decon.create() + elif amodule.params['state'] == 'disabled': + decon.error() + + if decon.result['failed']: + amodule.fail_json(**decon.result) + else: + if decon.result['changed'] and amodule.params['state'] != 'absent': + _, decon.lb_facts = decon.lb_find(decon.lb_id) + if decon.lb_id: + decon.result['facts'] = decon.package_facts(amodule.check_mode) + amodule.exit_json(**decon.result) +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/library/decort_vins.py b/library/decort_vins.py index f94058e..a50faa1 100644 --- a/library/decort_vins.py +++ b/library/decort_vins.py @@ -249,8 +249,8 @@ class decort_vins(DecortController): def __init__(self,arg_amodule): super(decort_vins, self).__init__(arg_amodule) - vins_id = 0 - vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level + self.vins_id = 0 + self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level vins_facts = None # will hold ViNS facts validated_rg_id = 0 rg_facts = None # will hold RG facts @@ -261,17 +261,17 @@ class decort_vins(DecortController): # expect existing ViNS with the specified ID # This call to vins_find will abort the module if no ViNS with such ID is present self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id']) - if not vins_id: + if not self.vins_id: self.result['failed'] = True self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) self.fail_json(**self.result) - vins_level = "ID" + self.vins_level = "ID" validated_acc_id = vins_facts['accountId'] validated_rg_id = vins_facts['rgId'] elif arg_amodule.params['rg_id']: # expect ViNS @ RG level in the RG with specified ID - vins_level = "RG" + self.vins_level = "RG" # This call to rg_find will abort the module if no RG with such ID is present validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID arg_amodule.params['rg_id'], arg_rg_name="") @@ -307,7 +307,7 @@ class decort_vins(DecortController): rg_id=validated_rg_id, rg_facts=rg_facts, check_state=False) - vins_level = "RG" + self.vins_level = "RG" # TODO: add checks and setup ViNS presence flags accordingly else: # At this point we know for sure that rg_name="" and rg_id=0 # So we expect ViNS @ account level @@ -317,7 +317,7 @@ class decort_vins(DecortController): rg_id=0, rg_facts=rg_facts, check_state=False) - vins_level = "ACC" + self.vins_level = "ACC" # TODO: add checks and setup ViNS presence flags accordingly else: # this is "invalid arguments combination" sink diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 918cbee..db5613a 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -31,7 +31,6 @@ Requirements: - DECORT cloud platform version 3.6.1 or higher """ -import copy import json import jwt import netaddr @@ -785,7 +784,7 @@ class DecortController(object): Note: when Ansible is run in check mode method will return 0. """ - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "kvmvm_provision") + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "kvmvm_provision2") if self.amodule.check_mode: self.result['failed'] = False @@ -1207,7 +1206,7 @@ class DecortController(object): affinityLabel=label,) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params) if aff: - if len(aff[0])>0: + if len(aff)>0: for rule in aff: api_params = dict(computeId=comp_dict['id'], key=rule['key'], @@ -1217,7 +1216,7 @@ class DecortController(object): policy=rule['policy'],) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params) if aaff: - if len(aaff[0])>0: + if len(aaff)>0: for rule in aaff: api_params = dict(computeId=comp_dict['id'], key=rule['key'], @@ -1975,7 +1974,7 @@ class DecortController(object): if api_resp.status_code == 200: locations = json.loads(api_resp.content.decode('utf8')) if location_code == "" and locations: - ret_gid = locations[0]['gid'] + ret_gid = locations['gid'] else: for runner in locations: if runner['locationCode'] == location_code: @@ -2569,7 +2568,7 @@ class DecortController(object): # ############################## - def disk_delete(self, disk_id, permanently=False, force_detach=False): + def disk_delete(self, disk_id, permanently, detach, reason): """Deletes specified Disk. @param (int) disk_id: ID of the Disk to be deleted. @@ -2586,8 +2585,9 @@ class DecortController(object): return api_params = dict(diskId=disk_id, - detach=force_detach, - permanently=permanently, ) + detach=detach, + permanently=permanently, + reason=reason) self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/delete", api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. self.result['failed'] = False @@ -2626,7 +2626,7 @@ class DecortController(object): return ret_disk_id, ret_disk_dict - def disk_find(self, disk_id, disk_name="", account_id=0, check_state=False): + def disk_find(self, disk_id, name, account_id, check_state=False): """Find specified Disk. @param (int) disk_id: ID of the Disk. If non-zero disk_id is specified, all other arguments @@ -2662,11 +2662,11 @@ class DecortController(object): self.result['failed'] = True self.result['msg'] = "disk_find(): cannot find Disk by ID {}.".format(disk_id) self.amodule.fail_json(**self.result) - if not check_state or ret_disk_facts['status'] not in DISK_INVALID_STATES: + if not check_state or ret_disk_facts['status']: return ret_disk_id, ret_disk_facts else: return 0, None - elif disk_name != "": + elif name != "": if account_id > 0: api_params = dict(accountId=account_id) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/list", api_params) @@ -2674,15 +2674,15 @@ class DecortController(object): disks_list = json.loads(api_resp.content.decode('utf8')) for runner in disks_list: # return the first disk of the specified name that fulfills status matching rule - if runner['name'] == disk_name: - if not check_state or runner['status'] not in DISK_INVALID_STATES: + if runner['name'] == name: + if not check_state or runner['status']: return runner['id'], runner else: return 0, None else: # we are missing meaningful account_id - fail the module self.result['failed'] = True self.result['msg'] = ("disk_find(): cannot find Disk by name '{}' " - "when no account ID specified.").format(disk_name) + "when no account ID specified.").format(name) self.amodule.fail_json(**self.result) else: # Disk ID is 0 and Disk name is emtpy - fail the module self.result['failed'] = True @@ -2691,55 +2691,40 @@ class DecortController(object): return 0, None - def disk_provision(self, disk_name, size, account_id, sep_id, pool_name="", desc="", location=""): + def disk_create(self, accountId, gid, name, description, size, type, iops, sep_id, pool): """Provision Disk according to the specified arguments. Note that disks created by this method will be of type 'D' (data disks). If critical error occurs the embedded call to API function will abort further execution of the script and relay error to Ansible. - @param (string) disk_name: name to assign to the Disk. + @param (string) name: name to assign to the Disk. @param (int) size: size of the disk in GB. - @param (int) account_id: ID of the account where disk will belong. + @param (int) accountId: ID of the account where disk will belong. @param (int) sep_id: ID of the SEP (Storage Endpoint Provider), where disk will be created. @param (string) pool: optional name of the pool, where this disk will be created. - @param (string) desc: optional text description of this disk. - @param (string) location: optional location, where disk resources will be provided. If empty - string is specified the disk will be created in the default location under DECORT controller. + @param (string) description: optional text description of this disk. + @param (int) gid: optional Grid id, if specified the disk will be created in selected + location. @return: ID of the newly created Disk (in Ansible check mode 0 is returned). """ - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_provision") - - if self.amodule.check_mode: - self.result['failed'] = False - self.result['msg'] = "disk_provision() in check mode: create Disk name '{}' was requested.".format( - disk_name) - return 0 - - target_gid = self.gid_get(location) - if not target_gid: - self.result['failed'] = True - self.result['msg'] = "disk_provision() failed to obtain Grid ID for default location." - self.amodule.fail_json(**self.result) - - ret_disk_id = 0 - api_params = dict(accountId=account_id, - gid=target_gid, - name=disk_name, - desc=desc, + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation") + api_params = dict(accountId=accountId, + gid=gid, + name=name, + description=description, size=size, - type='D', - sepId=sep_id, ) - if pool_name != "": - api_params['pool'] = pool_name + type=type, + iops=iops, + sepId=sep_id, + pool=pool ) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params) if api_resp.status_code == 200: ret_disk_id = json.loads(api_resp.content.decode('utf8')) self.result['failed'] = False self.result['changed'] = True - return ret_disk_id def disk_resize(self, disk_facts, new_size): @@ -2794,6 +2779,48 @@ class DecortController(object): return + def disk_limitIO(self, limits, diskId): + """Limits already created Disk identified by its ID. + @param (dict) limits: Dictionary with limits. + @param (int) diskId: ID of the Disk to limit. + @returns: nothing on success. On error this method will abort module execution. + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_limitIO") + api_params = dict(diskId=diskId, + total_bytes_sec=limits['total_bytes_sec'], + read_bytes_sec=limits['read_bytes_sec'], + write_bytes_sec=limits['write_bytes_sec'], + total_iops_sec=limits['total_iops_sec'], + read_iops_sec=limits['read_iops_sec'], + write_iops_sec=limits['write_iops_sec'], + total_bytes_sec_max=limits['total_bytes_sec_max'], + read_bytes_sec_max=limits['read_bytes_sec_max'], + write_bytes_sec_max=limits['write_bytes_sec_max'], + total_iops_sec_max=limits['total_iops_sec_max'], + read_iops_sec_max=limits['read_iops_sec_max'], + write_iops_sec_max=limits['write_iops_sec_max'], + size_iops_sec=limits['size_iops_sec']) + self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/limitIO", api_params) + self.result['msg'] = "Specified Disk ID {} limited successfully.".format(self.validated_disk_id) + return + + def disk_rename(self, diskId, name): + """Renames disk to the specified new name. + + @param disk_id: ID of the Disk to rename. + @param name: New name. + + @returns: nothing on success. On error this method will abort module execution. + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_rename") + api_params = dict(diskId=diskId, + name=name) + self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/rename", 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 + return + def disk_restore(self, disk_id): """Restores previously deleted Disk identified by its ID. For restore to succeed the Disk must be in 'DELETED' state. @@ -3270,7 +3297,7 @@ class DecortController(object): self.result['failed'] = True return elif ret_info['status'] == "OK": - k8s_id = ret_info['result'][0] + k8s_id = ret_info['result'] self.result['changed'] = True return k8s_id else: @@ -3373,10 +3400,10 @@ class DecortController(object): k8ci_id_present = True break - if k8ci_id_present == False: - self.result['failed'] = True - self.result['msg'] = ("Cannot find k8ci id: {}.").format(arg_k8ci_id) - self.amodule.fail_json(**self.result) + else: + self.result['failed'] = True + self.result['msg'] = ("Cannot find k8ci id: {}.").format(arg_k8ci_id) + self.amodule.fail_json(**self.result) else: self.result['failed'] = True self.result['msg'] = ("Failed to get k8ci list HTTP code {}.").format(api_resp.status_code) @@ -3736,3 +3763,699 @@ class DecortController(object): self.result['msg'] = "group_delete() Group ID {} was deleted.".format(gr_id) self.result['changed'] = True return +#################### +### LB MANAGMENT ### +#################### + def _lb_get_by_id(self,lb_id): + """Helper function that locates LB by ID and returns LB facts. This function + expects that the ViNS exists (albeit in DELETED or DESTROYED state) and will return + 0 LB ID if not found. + + @param (int) vins_id: ID of the LB to find and return facts for. + + @return: LB ID and a dictionary of LB facts as provided by LB/get API call. + """ + ret_lb_id = 0 + ret_lb_dict = dict() + + if not lb_id: + self.result['failed'] = True + self.result['msg'] = "lb_get_by_id(): zero LB ID specified." + self.amodule.fail_json(**self.result) + + api_params = dict(lbId=lb_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/get", api_params) + if api_resp.status_code == 200: + ret_lb_id = lb_id + ret_lb_dict = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("lb_get_by_id(): failed to get LB by ID {}. HTTP code {}, " + "response {}.").format(lb_id, api_resp.status_code, api_resp.reason) + + return ret_lb_id, ret_lb_dict + def _rg_listlb(self,rg_id): + """List all LB in the resource group + @param (int) rg_id: id onr resource group + """ + if not rg_id: + self.result['failed'] = True + self.result['msg'] = "_rg_listlb(): zero RG ID specified." + self.amodule.fail_json(**self.result) + + api_params = dict(rgId=rg_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/listLb", api_params) + if api_resp.status_code == 200: + ret_rg_vins_list = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("rg_listlb(): failed to get RG by ID {}. HTTP code {}, " + "response {}.").format(rg_id, api_resp.status_code, api_resp.reason) + return [] + + return ret_rg_vins_list + def lb_find(self,lb_id=0,lb_name="",rg_id=0): + """Find specified LB. + + @returns: LB ID and dictionary with LB facts. + """ + LB_INVALID_STATES = ["ENABLING", "DISABLING", "DELETING", "DELETED", "DESTROYING", "DESTROYED"] + + ret_lb_id = 0 + ret_lb_facts = None + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_find") + + if lb_id > 0: + ret_lb_id, ret_lb_facts = self._lb_get_by_id(lb_id) + if not ret_lb_id: + self.result['failed'] = True + self.result['msg'] = "lb_find(): cannot find LB by ID {}.".format(lb_id) + self.amodule.fail_json(**self.result) + if not self.amodule.check_mode or ret_lb_facts['status'] not in LB_INVALID_STATES: + return ret_lb_id, ret_lb_facts + else: + return 0, None + elif lb_name != "": + if rg_id > 0: + list_lb = self._rg_listlb(rg_id) + for lb in list_lb: + if lb['name'] == lb_name: + ret_lb_id, ret_lb_facts = self._lb_get_by_id(lb['id']) + if not self.amodule.check_mode or ret_lb_facts['status'] not in LB_INVALID_STATES: + return ret_lb_id, ret_lb_facts + else: + return 0, None + else: + self.result['failed'] = True + self.result['msg'] = ("vins_lb(): cannot find LB by name '{}' " + "when no account ID or RG ID is specified.").format(lb_name) + self.amodule.fail_json(**self.result) + else: + self.result['failed'] = True + self.result['msg'] = "vins_find(): cannot find LB by zero ID and empty name." + self.amodule.fail_json(**self.result) + + return 0, None + def lb_provision(self,lb_name,rg_id,vins_id,ext_net_id,annotation,start=True): + """Provision LB according to the specified arguments. + If critical error occurs the embedded call to API function will abort further execution of + the script and relay error to Ansible. + Note, that when creating ViNS at account level, default location under DECORT controller + will be selected automatically. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_provision") + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = ("vins_lb() in check mode: provision LB name '{}' was " + "requested in RG with id: {}.").format(lb_name,rg_id) + return 0 + + if lb_name == "": + self.result['failed'] = True + self.result['msg'] = "lb_provision(): LB name cannot be empty." + self.amodule.fail_json(**self.result) + + api_url = "/restmachine/cloudapi/lb/create" + api_params = dict( + name=lb_name, + rgId=rg_id, + extnetId=ext_net_id, + vinsId=vins_id, + start=start, + decs=annotation + ) + api_resp = self.decort_api_call(requests.post, api_url, 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 + ret_lb_id = int(api_resp.content.decode('utf8')) + return ret_lb_id + def lb_delete(self,lb_id,permanently=False): + """Deletes specified LB. + + @param (int) lb_id: integer value that identifies the ViNS to be deleted. + @param (bool) permanently: a bool that tells if deletion should be permanent. If False, the LB will be + marked as DELETED and placed into a trash bin for predefined period of time (usually, a few days). Until + this period passes this LB can be restored by calling the corresponding 'restore' method. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_delete") + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = "lb_delete() in check mode: delete ViNS ID {} was requested.".format(lb_id) + return + + api_params = dict(lbId=lb_id, + permanently=permanently) + self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/delete", 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 + return + def lb_state(self, lb_dict, desired_state): + """Change state for LB. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_state") + + NOP_STATES_FOR_LB_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING", + "DESTROYED"] + VALID_TARGET_STATES = ["enabled", "disabled","restart"] + VALID_TARGET_TSTATES = ["STARTED","STOPPED"] + + if lb_dict['status'] in NOP_STATES_FOR_LB_CHANGE: + self.result['failed'] = False + self.result['msg'] = ("lb_state(): no state change possible for LB ID {} " + "in its current state '{}'.").format(lb_dict['id'], lb_dict['status']) + return + + if desired_state not in VALID_TARGET_STATES: + self.result['failed'] = False + self.result['warning'] = ("lb_state(): unrecognized desired state '{}' requested " + "for LB ID {}. No LB state change will be done.").format(desired_state, + lb_dict['id']) + return + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = ("lb_state() in check mode: setting state of LB ID {}, name '{}' to " + "'{}' was requested.").format(lb_dict['id'], lb_dict['name'], + desired_state) + return + + state_api = "" + api_params = dict(lbId=lb_dict['id']) + expected_state = "" + if lb_dict['status'] in ["CREATED", "ENABLED"]: + if desired_state == 'disabled': + state_api = "/restmachine/cloudapi/lb/disable" + expected_state = "DISABLED" + if lb_dict['techStatus'] == "STARTED": + if desired_state == 'stopped': + state_api = "/restmachine/cloudapi/lb/stop" + if desired_state == 'restart': + state_api = "/restmachine/cloudapi/lb/restart" + elif lb_dict['techStatus'] == "STOPPED": + if desired_state == 'started': + state_api = "/restmachine/cloudapi/lb/start" + elif lb_dict['status'] == "DISABLED" and desired_state == 'enabled': + state_api = "/restmachine/cloudapi/lb/enable" + expected_state = "ENABLED" + + if state_api != "": + self.decort_api_call(requests.post, state_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 + lb_dict['status'] = expected_state + else: + self.result['failed'] = False + self.result['msg'] = ("lb_state(): no state change required for LB ID {} from current " + "state '{}' to desired state '{}'.").format(lb_dict['id'], + lb_dict['status'], + desired_state) + return + def lb_restore(self, lb_id): + """Restores previously deleted LB identified by its ID. + + @param lb_id: ID of the LB to restore. + + @returns: nothing on success. On error this method will abort module execution. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_restore") + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = "lb_restore() in check mode: restore LB ID {} was requested.".format(lb_id) + return + + api_params = dict(lbId=lb_id) + self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/restore", 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 + return + + def lb_update_backends(self,lb_backends,mod_backends,mod_servers): + """ + backends + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_update_backends") + + if mod_backends: + backs_out = [rec['name'] for rec in mod_backends] + else: + backs_out="" + backs_in = [rec['name'] for rec in lb_backends] + del_backs = set(backs_in).difference(backs_out) + add_becks = set(backs_out).difference(backs_in) + upd_becks = set(backs_in).intersection(backs_out) + + for item in del_backs: + #need delete frontend + api_params = dict(lbId=self.lb_id,backendName = item) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendDelete", api_params) + self.result['changed'] = True + for item in add_becks: + backend, = list(filter(lambda i: i['name'] == item,mod_backends)) + api_params = dict( + lbId=self.lb_id, + backendName = backend['name'], + algorithm = backend['algorithm'] if "algorithm" in backend else None, + **backend['default_settings'] if "default_settings" in backend else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendCreate", api_params) + self.result['changed'] = True + for item in upd_becks.union(add_becks): + + backend, = list(filter(lambda i: i['name'] == item,lb_backends)) + mod_backend, = list(filter(lambda i: i['name'] == item,mod_backends)) + servers_in = [rec['name'] for rec in backend['servers']] + servers_out = [] + #oO rework + if mod_servers: + for serv in mod_servers: + for bend in serv['backends']: + if bend['name'] == item: + servers_out.append(serv['name']) + + del_srv = set(servers_in).difference(servers_out) + add_srv = set(servers_out).difference(servers_in) + upd_srv = set(servers_in).intersection(servers_out) + + del backend['serverDefaultSettings']['guid'] + + if "default_settings" not in mod_backend: + mod_backend["default_settings"] = self.default_settings + + if "algorithm" not in mod_backend: + mod_backend["algorithm"] = self.default_alg + + if backend['serverDefaultSettings'] != mod_backend["default_settings"] or\ + mod_backend["algorithm"] != backend['algorithm']: + api_params = dict( + lbId=self.lb_id, + backendName = item, + algorithm = mod_backend['algorithm'], + **mod_backend['default_settings'] if "default_settings" in mod_backend else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendUpdate", api_params) + self.result['changed'] = True + + for srv in del_srv: + api_params = dict(lbId=self.lb_id,backendName = item,serverName=srv) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerDelete", api_params) + self.result['changed'] = True + for srv in add_srv: + server, = list(filter(lambda i: i['name'] == srv,mod_servers)) + back, = list(filter(lambda i: i['name'] == item,server['backends'])) + api_params = dict( + lbId=self.lb_id, + backendName = item, + serverName = server['name'], + address = server['address'], + port = back['port'], + check = server['check'] if "check" in server else None, + **server['server_settings'] if "server_settings" in server else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerAdd", api_params) + self.result['changed'] = True + for srv in upd_srv: + mod_server, = list(filter(lambda i: i['name'] == srv,mod_servers)) + mod_server_back, = list(filter(lambda i: i['name'] == item,mod_server['backends'])) + server, = list(filter(lambda i: i['name'] == srv, backend['servers'])) + + del server['serverSettings']['guid'] + + if "server_settings" not in mod_server_back: + mod_server_back['server_settings'] = self.default_settings + if "check" not in mod_server: + mod_server['check'] = self.default_server_check + + if (mod_server['address'] != server['address'] or\ + server['check']!=mod_server["check"]) or\ + mod_server_back['server_settings'] != server['serverSettings']: + api_params = dict( + lbId=self.lb_id, + backendName = item, + serverName = mod_server['name'], + address = mod_server['address'], + port = mod_server_back['port'], + check = mod_server_back['check'] if "check" in mod_server_back else None, + **mod_server_back['server_settings'] if "server_settings" in mod_server_back else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerUpdate", api_params) + self.result['changed'] = True + + return + def lb_update_frontends(self,lb_frontends,mod_frontends): + """ + frontends + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_update_frontends") + if mod_frontends: + front_out = [rec['name'] for rec in mod_frontends] + else: + front_out="" + front_in = [rec['name'] for rec in lb_frontends] + + del_front = set(front_in).difference(front_out) + add_front = set(front_out).difference(front_in) + upd_front = set(front_in).intersection(front_out) + + for front in del_front: + delete_front, = list(filter(lambda i: i['name'] == front,lb_frontends)) + api_params = dict( + lbId=self.lb_id, + frontendName=delete_front['name'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendDelete", api_params) + self.result['changed'] = True + for front in add_front: + create_front, = list(filter(lambda i: i['name'] == front,mod_frontends)) + api_params = dict( + lbId=self.lb_id, + frontendName=create_front['name'], + backendName=create_front['backend'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendCreate", api_params) + self.result['changed'] = True + if "bindings" in create_front: + for bind in create_front['bindings']: + self._lb_bind_frontend( + create_front['name'], + bind['name'], + bind['address'] if "address" in bind else None, + bind['port'] if "port" in bind else None + ) + for front in upd_front: + update_front, = list(filter(lambda i: i['name'] == front,mod_frontends)) + lb_front, = list(filter(lambda i: i['name'] == front,lb_frontends)) + mod_bind = [rec['name'] for rec in update_front['bindings']] + lb_bind = [rec['name'] for rec in lb_front['bindings']] + + del_bind_list = set(lb_bind).difference(mod_bind) + add_bind_list = set(mod_bind).difference(lb_bind) + upd_bind_list = set(lb_bind).intersection(mod_bind) + + for bind_name in del_bind_list: + del_bind, = list(filter(lambda i: i['name'] == bind_name,lb_front['bindings'])) + api_params = dict( + lbId=self.lb_id, + frontendName=update_front['name'], + bindingName=del_bind['name'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendBindDelete", api_params) + self.result['changed'] = True + for bind_name in add_bind_list: + add_bind, = list(filter(lambda i: i['name'] == bind_name,update_front['bindings'])) + self._lb_bind_frontend( + update_front['name'], + add_bind['name'], + add_bind['address'] if "address" in add_bind else None, + add_bind['port'] if "port" in add_bind else None + ) + for bind_name in upd_bind_list: + lb_act_bind, = list(filter(lambda i: i['name'] == bind_name,lb_front['bindings'])) + mod_act_bind, = list(filter(lambda i: i['name'] == bind_name,update_front['bindings'])) + del lb_act_bind['guid'] + if lb_act_bind != mod_act_bind: + self._lb_bind_frontend( + update_front['name'], + mod_act_bind['name'], + mod_act_bind['address'] if "address" in mod_act_bind else None, + mod_act_bind['port'] if "port" in mod_act_bind else None, + update=True + ) + return + + def _lb_bind_frontend(self,front_name,bind_name,bind_addr=None,bind_port=None,update=False): + api_params = dict( + lbId=self.lb_id, + frontendName=front_name, + bindingName=bind_name, + bindingAddress=bind_addr, + bindingPort=bind_port, + ) + if update == True: + api_url = "/restmachine/cloudapi/lb/frontendBindingUpdate" + else: + api_url = "/restmachine/cloudapi/lb/frontendBind" + api_resp = self.decort_api_call(requests.post, api_url, api_params) + self.result['changed'] = True + + def lb_update(self,lb_backends=[],lb_frontends=[],mod_backends=[],mod_servers=[],mod_frontends=[]): + #lists from module and cloud + mod_backs_list = [back['name'] for back in mod_backends] + lb_backs_list = [back['name'] for back in lb_backends] + #ADD\DEL\UPDATE LISTS OF BACKENDS + del_list_backs = set(lb_backs_list).difference(mod_backs_list) + add_back_list = set(mod_backs_list).difference(lb_backs_list) + upd_back_list = set(lb_backs_list).intersection(mod_backs_list) + + #FE + + mod_front_list = [front['name'] for front in mod_frontends] + lb_front_list = [front['name'] for front in lb_frontends] + + if del_list_backs: + + self._lb_delete_backends( + del_list_backs, + lb_frontends + ) + + if add_back_list: + self._lb_create_backends( + add_back_list, + mod_backends, + mod_servers + ) + + if upd_back_list: + self._lb_update_backends( + upd_back_list, + lb_backends, + mod_backends, + mod_servers + ) + + del_list_fronts = set(lb_front_list).difference(mod_front_list) + add_list_fronts = set(mod_front_list).difference(lb_front_list) + upd_front_list = set(lb_front_list).intersection(mod_front_list) + + if del_list_fronts: + self._lb_delete_fronts(del_list_fronts) + + if add_list_fronts: + self._lb_add_fronts(add_list_fronts,mod_frontends) + if upd_front_list: + self._lb_update_fronts(upd_front_list,lb_frontends,mod_frontends) + + return + + def _lb_delete_backends(self,back_list,lb_fronts): + + #delete frontends with that backend + for back in back_list: + fronts = list(filter(lambda i: i['backend'] == back,lb_fronts)) + if fronts: + self._lb_delete_fronts(fronts) + api_params = dict( + lbId=self.lb_id, + backendName = back + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendDelete", api_params) + self.result['changed'] = True + return + def _lb_delete_fronts(self,d_fronts): + for front in d_fronts: + api_params = dict( + lbId=self.lb_id, + frontendName=front['name'] if "name" in front else front, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendDelete", api_params) + #del from cloud dict + if type(front)==dict: + del self.lb_facts['frontends'][front['name']] + self.result['changed'] = True + + return + def _lb_add_fronts(self,front_list,mod_fronts): + for front in front_list: + add_front, = list(filter(lambda i: i['name'] == front,mod_fronts)) + api_params = dict( + lbId=self.lb_id, + frontendName=add_front['name'], + backendName=add_front['backend'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendCreate", api_params) + for bind in add_front['bindings']: + self._lb_bind_frontend( + add_front['name'], + bind['name'], + bind['address']if "address" in bind else None, + bind['port'] if "port" in bind else None, + ) + return + def _lb_create_backends(self,back_list,mod_backs,mod_serv): + ''' + Create backends and add servers to them + ''' + for back in back_list: + backend, = list(filter(lambda i: i['name'] == back,mod_backs)) + api_params = dict( + lbId=self.lb_id, + backendName = back, + algorithm = backend['algorithm'] if "algorithm" in backend else None, + **backend['default_settings'] if "default_settings" in backend else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendCreate", api_params) + + for server in mod_serv: + try: + srv_back, = list(filter(lambda i: i['name'] == back,server['backends'])) + except: + continue + api_params = dict( + lbId=self.lb_id, + backendName = back, + serverName = server['name'], + address = server['address'], + port = srv_back['port'], + check = srv_back['check'] if "check" in srv_back else None, + **srv_back['server_settings'] if "server_settings" in srv_back else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerAdd", api_params) + + self.result['changed'] = True + + return + + def _lb_update_backends(self,back_list,lb_backs,mod_backs,mod_serv): + + lb_backs = list(filter(lambda i: i['name'] in back_list,lb_backs)) + #mod_back = list(filter(lambda i: i['name'] in back_list,mod_backs)) + + for back in lb_backs: + + del back['serverDefaultSettings']['guid'] + mod_back, = list(filter(lambda i: i['name']==back['name'],mod_backs)) + #mod_servers = list(filter(lambda i: i['name']==back['name'],mod_serv)) + #raise Exception(mod_servers) + if "default_settings" not in mod_back: + mod_back["default_settings"] = self.default_settings + else: + for param,value in self.default_settings.items(): + if param not in mod_back["default_settings"]: + mod_back["default_settings"].update(param,value) + + + if "algorithm" not in mod_back: + mod_back["algorithm"] = self.default_alg + + if back['serverDefaultSettings'] != mod_back["default_settings"] or\ + mod_back["algorithm"] != back['algorithm']: + api_params = dict( + lbId=self.lb_id, + backendName = back['name'], + algorithm = mod_back['algorithm'], + **mod_back['default_settings'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendUpdate", api_params) + self.result['changed'] = True + + lb_servers_list = [srv['name'] for srv in back['servers']] + for server in mod_serv: + try: + mod_back, = list(filter(lambda i: i['name'] == back['name'],server['backends'])) + except: + continue + if server['name'] not in lb_servers_list: + api_params = dict( + lbId=self.lb_id, + backendName = mod_back['name'], + serverName = server['name'], + address = server['address'], + port = mod_back['port'], + check = server['check'] if "check" in server else None, + **server['server_settings'] if "server_settings" in server else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerAdd", api_params) + self.result['changed'] = True + else: + lb_server, = list(filter(lambda i: i['name'] == server['name'],back['servers'])) + del lb_server['serverSettings']['guid'] + + if "server_settings" not in mod_back: + mod_back['server_settings'] = self.default_settings + else: + for param,value in self.default_settings.items(): + if param not in mod_back["server_settings"]: + mod_back["server_settings"].update(param,value) + + if "check" not in mod_back: + mod_back['check'] = self.default_server_check + + if (server['address'] != lb_server['address'] or\ + lb_server['check']!=mod_back['check']) or\ + mod_back['server_settings'] != lb_server['serverSettings']: + api_params = dict( + lbId=self.lb_id, + backendName = mod_back['name'], + serverName = server['name'], + address = server['address'], + port = mod_back['port'], + check = mod_back['check'], + **mod_back['server_settings'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerUpdate", api_params) + self.result['changed'] = True + lb_servers_list.remove(server['name']) + for server in lb_servers_list: + api_params = dict(lbId=self.lb_id,backendName = back['name'],serverName=server) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerDelete", api_params) + self.result['changed'] = True + return + def _lb_update_fronts(self,upd_front_list,lb_frontends,mod_frontends): + + for front in upd_front_list: + mod_front, = list(filter(lambda i: i['name'] == front,mod_frontends)) + lb_front, = list(filter(lambda i: i['name'] == front,lb_frontends)) + lb_binds_list = [bind['name'] for bind in lb_front['bindings']] + for bind in mod_front['bindings']: + if bind['name'] not in lb_binds_list: + pass + self._lb_bind_frontend( + front, + bind['name'], + bind['address']if "address" in bind else None, + bind['port'] if "port" in bind else None, + ) + else: + lb_bind, = list(filter(lambda i: i['name'] == bind['name'],lb_front['bindings'])) + del lb_bind['guid'] + + if dict(sorted(bind.items())) != dict(sorted(lb_bind.items())): + self._lb_bind_frontend( + front, + bind['name'], + bind['address']if "address" in bind else None, + bind['port'] if "port" in bind else None, + update=True, + ) + lb_binds_list.remove(bind['name']) + + for lb_bind in lb_binds_list: + api_params = dict( + lbId=self.lb_id, + frontendName=front, + bindingName=lb_bind, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendBindDelete", api_params) + self.result['changed'] = True + + return