From 8d6ed618abcba7a169d2bbd605097505bf67191e Mon Sep 17 00:00:00 2001 From: Alex_geth Date: Fri, 2 Sep 2022 14:56:58 +0300 Subject: [PATCH 1/2] decort_vins update & k8 wg fix --- library/decort_k8s.py | 6 +- library/decort_vins.py | 550 +++++++++++++++++++---------------- module_utils/decort_utils.py | 164 ++++++----- 3 files changed, 394 insertions(+), 326 deletions(-) diff --git a/library/decort_k8s.py b/library/decort_k8s.py index 3abb4e4..11291ac 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -114,10 +114,10 @@ class decort_k8s(DecortController): return ret_dict def nop(self): - """No operation (NOP) handler for Compute management by decort_kvmvm module. + """No operation (NOP) handler for k8s cluster management by decort_k8s 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. + the actual k8s cluster state. """ self.result['failed'] = False self.result['changed'] = False @@ -319,7 +319,7 @@ def main(): if amodule.params['state'] == 'absent': subj.nop() if amodule.params['state'] in ('present','started'): - subj.create() + subj.create() elif amodule.params['state'] in ('stopped', 'disabled','enabled'): subj.error() diff --git a/library/decort_vins.py b/library/decort_vins.py index 749cfd3..f94058e 100644 --- a/library/decort_vins.py +++ b/library/decort_vins.py @@ -242,110 +242,289 @@ facts: from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback -import paramiko from ansible.module_utils.decort_utils import * +class decort_vins(DecortController): + def __init__(self,arg_amodule): + super(decort_vins, self).__init__(arg_amodule) -def decort_vins_package_facts(arg_vins_facts, arg_check_mode=False): - """Package a dictionary of ViNS facts according to the decort_vins module specification. - This dictionary will be returned to the upstream Ansible engine at the completion of - the module run. + vins_id = 0 + 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 + validated_acc_id = 0 + acc_facts = None # will hold Account facts - @param arg_vins_facts: dictionary with viNS facts as returned by API call to .../vins/get - @param arg_check_mode: boolean that tells if this Ansible module is run in check mode - """ + if arg_amodule.params['vins_id']: + # 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: + 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" + 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" + # 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="") + + # This call to vins_find may return vins_id=0 if no ViNS found + self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], + account_id=0, + rg_id=arg_amodule.params['rg_id'], + rg_facts=rg_facts, + check_state=False) + # TODO: add checks and setup ViNS presence flags accordingly + pass + elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": + # Specified account must be present and accessible by the user, otherwise abort the module + validated_acc_id, acc_facts = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id']) + if not validated_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) + if arg_amodule.params['rg_name'] != "": # at this point we know that rg_id=0 + # expect ViNS @ RG level in the RG with specified name under specified account + # RG with the specified name must be present under the account, otherwise abort the module + validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name']) + if (not validated_rg_id or + rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): + self.result['failed'] = True + self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name']) + self.fail_json(**self.result) + # This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG + self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], + account_id=0, # set to 0, as we are looking for ViNS under RG + rg_id=validated_rg_id, + rg_facts=rg_facts, + check_state=False) + 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 + # This call to vins_find may return vins_id=0 if no ViNS found + self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], + account_id=validated_acc_id, + rg_id=0, + rg_facts=rg_facts, + check_state=False) + vins_level = "ACC" + # TODO: add checks and setup ViNS presence flags accordingly + else: + # this is "invalid arguments combination" sink + # if we end up here, it means that module was invoked with vins_id=0 and rg_id=0 + self.result['failed'] = True + if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == "": + self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0." + if arg_amodule.params['rg_name'] == "": + # rg_name without account specified + self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0." + self.fail_json(**self.result) - ret_dict = dict(id=0, - name="none", - state="CHECK_MODE", - ) + return + self.rg_id = validated_rg_id + self.acc_id = validated_acc_id + return + def create(self): + self.vins_id = self.vins_provision(self.amodule.params['vins_name'], + self.acc_id, self.rg_id, + self.amodule.params['ipcidr'], + self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'], + self.amodule.params['annotation']) + + if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']: + _, self.vins_facts = self.vins_find(self.vins_id) + if self.amodule.params['connect_to']: + self.vins_update_ifaces(self.vins_facts,self.amodule.params['connect_to'],) + if self.amodule.params['mgmtaddr']: + self.vins_update_mgmt(self.vins_facts,self.amodule.params['mgmtaddr']) + + return + def action(self,d_state='',restore=False): + if restore == True: + self.vins_restore(arg_vins_id=self.vins_id) + self.vins_state(self.vins_facts, 'enabled') + self.vins_facts['status'] = "ENABLED" + self.vins_facts['VNFDev']['techStatus'] = "STARTED" + + self.vins_update_extnet(self.vins_facts, + self.amodule.params['ext_net_id'], + self.amodule.params['ext_ip_addr'], + ) + + if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED": + self.vins_state(self.vins_facts, d_state) + self.vins_facts['status'] = "ENABLED" + self.vins_facts['VNFDev']['techStatus'] = "STARTED" + d_state = '' - if arg_check_mode: - # in check mode return immediately with the default values - return ret_dict + if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED": + self.vins_update_ifaces(self.vins_facts, + self.amodule.params['connect_to'], + ) + if self.result['changed']: + _, self.vins_facts = self.vins_find(self.vins_id) + self.vins_update_mgmt(self.vins_facts, + self.amodule.params['mgmtaddr'], + ) + + if d_state != '': + self.vins_state(self.vins_facts, d_state) + return + def delete(self): + self.vins_delete(self.vins_id, permanently=True) + self.vins_facts['status'] = 'DESTROYED' + return + def nop(self): + """No operation (NOP) handler for ViNS management by decort_vins 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 ViNS state. + """ + self.result['failed'] = False + self.result['changed'] = False + if self.vins_id: + self.result['msg'] = ("No state change required for ViNS ID {} because of its " + "current status '{}'.").format(self.vins_id, self.vins_facts['status']) + else: + self.result['msg'] = ("No state change to '{}' can be done for " + "non-existent ViNS 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 ViNS ID {} in the " + "current status '{}'").format(self.vins_id, + self.amodule.params['state'], + self.vins_facts['status']) + else: + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = ("Invalid target state '{}' requested for non-existent " + "ViNS name '{}'").format(self.amodule.params['state'], + self.amodule.params['vins_name']) + return + def package_facts(self, arg_check_mode=False): + """Package a dictionary of ViNS facts according to the decort_vins module specification. + This dictionary will be returned to the upstream Ansible engine at the completion of + the module run. - if arg_vins_facts is None: - # if void facts provided - change state value to ABSENT and return - ret_dict['state'] = "ABSENT" - return ret_dict + @param arg_check_mode: boolean that tells if this Ansible module is run in check mode + """ - ret_dict['id'] = arg_vins_facts['id'] - ret_dict['name'] = arg_vins_facts['name'] - ret_dict['state'] = arg_vins_facts['status'] - ret_dict['account_id'] = arg_vins_facts['accountId'] - ret_dict['rg_id'] = arg_vins_facts['rgId'] - ret_dict['int_net_addr'] = arg_vins_facts['network'] - ret_dict['gid'] = arg_vins_facts['gid'] + ret_dict = dict(id=0, + name="none", + state="CHECK_MODE", + ) - if arg_vins_facts['vnfs'].get('GW'): - gw_config = arg_vins_facts['vnfs']['GW']['config'] - ret_dict['ext_ip_addr'] = gw_config['ext_net_ip'] - ret_dict['ext_net_id'] = gw_config['ext_net_id'] - else: - ret_dict['ext_ip_addr'] = "" - ret_dict['ext_net_id'] = -1 + if arg_check_mode: + # in check mode return immediately with the default values + return ret_dict - # arg_vins_facts['vnfs']['GW']['config'] - # ext_ip_addr -> ext_net_ip - # ??? -> ext_net_id - # tech_status -> techStatus + if self.vins_facts is None: + # if void facts provided - change state value to ABSENT and return + ret_dict['state'] = "ABSENT" + return ret_dict - return ret_dict + ret_dict['id'] = self.vins_facts['id'] + ret_dict['name'] = self.vins_facts['name'] + ret_dict['state'] = self.vins_facts['status'] + ret_dict['account_id'] = self.vins_facts['accountId'] + ret_dict['rg_id'] = self.vins_facts['rgId'] + ret_dict['int_net_addr'] = self.vins_facts['network'] + ret_dict['gid'] = self.vins_facts['gid'] + custom_interfaces = list(filter(lambda i: i['type']=="CUSTOM",self.vins_facts['VNFDev']['interfaces'])) + if custom_interfaces: + ret_dict['custom_net_addr'] = [] + for runner in custom_interfaces: + ret_dict['custom_net_addr'].append(runner['ipAddress']) + mgmt_interfaces = list(filter(lambda i: i['listenSsh'] and i['name']!="ens9",self.vins_facts['VNFDev']['interfaces'])) + if mgmt_interfaces: + ret_dict['ssh_ipaddr'] = [] + for runner in mgmt_interfaces: + ret_dict['ssh_ipaddr'].append(runner['ipAddress']) + ret_dict['ssh_password'] = self.vins_facts['VNFDev']['config']['mgmt']['password'] + ret_dict['ssh_port'] = 9022 + if self.vins_facts['vnfs'].get('GW'): + gw_config = self.vins_facts['vnfs']['GW']['config'] + ret_dict['ext_ip_addr'] = gw_config['ext_net_ip'] + ret_dict['ext_net_id'] = gw_config['ext_net_id'] + else: + ret_dict['ext_ip_addr'] = "" + ret_dict['ext_net_id'] = -1 + # arg_vins_facts['vnfs']['GW']['config'] + # ext_ip_addr -> ext_net_ip + # ??? -> ext_net_id + # tech_status -> techStatus -def decort_vins_parameters(): - """Build and return a dictionary of parameters expected by decort_vins module in a form accepted - by AnsibleModule utility class.""" + 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=''), - app_id=dict(type='str', + return dict( + account_id=dict(type='int', required=False), + account_name=dict(type='str', required=False, default=''), + annotation=dict(type='str', required=False, default=''), + 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=''), + ipcidr=dict(type='str', required=False, default=''), + mgmtaddr=dict(type='list',required=False, default=[]), + custom_config=dict(type='bool',required=False, default=False), + config_save=dict(type='bool',required=False, default=False), + connect_to=dict(type='list', default=[], required=False), + jwt=dict(type='str', required=False, - fallback=(env_fallback, ['DECORT_APP_ID'])), - app_secret=dict(type='str', + 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_APP_SECRET']), + fallback=(env_fallback, ['DECORT_PASSWORD']), 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=''), - ipcidr=dict(type='str', required=False, default=''), - mgmtaddr=dict(type='str',required=False, default=''), - custom_config=dict(type='bool',required=False, default=False), - config_save=dict(type='bool',required=False, default=False), - connect_to=dict(type='list', default=[], required=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), - state=dict(type='str', - default='present', - choices=['absent', 'disabled', 'enabled', 'present']), - 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=''), - verify_ssl=dict(type='bool', required=False, default=True), - vins_id=dict(type='int', required=False, default=0), - vins_name=dict(type='str', required=True), - workflow_callback=dict(type='str', required=False), - workflow_context=dict(type='str', required=False), - ) + state=dict(type='str', + default='present', + choices=['absent', 'disabled', 'enabled', 'present']), + 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=''), + verify_ssl=dict(type='bool', required=False, default=True), + vins_id=dict(type='int', required=False, default=0), + vins_name=dict(type='str', required=True), + workflow_callback=dict(type='str', required=False), + workflow_context=dict(type='str', required=False), + ) # Workflow digest: @@ -356,7 +535,7 @@ def decort_vins_parameters(): # 5) report result to Ansible def main(): - module_parameters = decort_vins_parameters() + module_parameters = decort_vins.build_parameters() amodule = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True, @@ -371,84 +550,7 @@ def main(): ], ) - decon = DecortController(amodule) - - vins_id = 0 - 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 - validated_acc_id = 0 - acc_facts = None # will hold Account facts - - if amodule.params['vins_id']: - # expect existing ViNS with the specified ID - # This call to vins_find will abort the module if no ViNS with such ID is present - vins_id, vins_facts = decon.vins_find(amodule.params['vins_id']) - if not vins_id: - decon.result['failed'] = True - decon.result['msg'] = "Specified ViNS ID {} not found.".format(amodule.params['vins_id']) - decon.fail_json(**decon.result) - vins_level = "ID" - validated_acc_id = vins_facts['accountId'] - validated_rg_id = vins_facts['rgId'] - - elif amodule.params['rg_id']: - # expect ViNS @ RG level in the RG with specified ID - 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 = decon.rg_find(0, # account ID set to 0 as we search for RG by RG ID - amodule.params['rg_id'], arg_rg_name="") - # This call to vins_find may return vins_id=0 if no ViNS found - vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'], - account_id=0, - rg_id=amodule.params['rg_id'], - check_state=False) - # TODO: add checks and setup ViNS presence flags accordingly - pass - elif amodule.params['account_id'] or amodule.params['account_name'] != "": - # 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.") - decon.fail_json(**decon.result) - if amodule.params['rg_name'] != "": # at this point we know that rg_id=0 - # expect ViNS @ RG level in the RG with specified name under specified account - # RG with the specified name must be present under the account, otherwise abort the module - validated_rg_id, rg_facts = decon.rg_find(validated_acc_id, 0, amodule.params['rg_name']) - if (not validated_rg_id or - rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): - decon.result['failed'] = True - decon.result['msg'] = "RG name '{}' not found or has invalid state.".format(amodule.params['rg_name']) - decon.fail_json(**decon.result) - # This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG - vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'], - account_id=0, # set to 0, as we are looking for ViNS under RG - rg_id=validated_rg_id, - check_state=False) - 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 - # This call to vins_find may return vins_id=0 if no ViNS found - vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'], - account_id=validated_acc_id, - rg_id=0, - check_state=False) - vins_level = "ACC" - # TODO: add checks and setup ViNS presence flags accordingly - else: - # this is "invalid arguments combination" sink - # if we end up here, it means that module was invoked with vins_id=0 and rg_id=0 - decon.result['failed'] = True - if amodule.params['account_id'] == 0 and amodule.params['account_name'] == "": - decon.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0." - if amodule.params['rg_name'] == "": - # rg_name without account specified - decon.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0." - decon.fail_json(**decon.result) + decon = decort_vins(amodule) # # Initial validation of module arguments is complete # @@ -465,127 +567,70 @@ def main(): # if cconfig_save is true, only config save without other updates vins_should_exist = False - if vins_id: + if decon.vins_id: vins_should_exist = True - if vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]: + if decon.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]: # error: nothing can be done to existing ViNS 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 ViNS ID {} because of its current " - "status '{}'").format(vins_id, vins_facts['status']) - elif vins_facts['status'] == "DISABLED": + "status '{}'").format(decon.vins_id, decon.vins_facts['status']) + elif decon.vins_facts['status'] == "DISABLED": if amodule.params['state'] == 'absent': - decon.vins_delete(vins_id, permanently=True) - vins_facts['status'] = 'DESTROYED' + decon.delete() vins_should_exist = False elif amodule.params['state'] in ('present', 'disabled'): # update ViNS, leave in disabled state - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr']) + decon.action() elif amodule.params['state'] == 'enabled': # update ViNS and enable - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr']) - decon.vins_state(vins_facts, 'enabled') - elif vins_facts['status'] in ["CREATED", "ENABLED"]: + decon.action('enabled') + elif decon.vins_facts['status'] in ["CREATED", "ENABLED"]: if amodule.params['state'] == 'absent': - decon.vins_delete(vins_id, permanently=True) - vins_facts['status'] = 'DESTROYED' + decon.delete() vins_should_exist = False elif amodule.params['state'] in ('present', 'enabled'): # update ViNS - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr'], - - ) - decon.vins_update_mgmt( - vins_facts, - amodule.params['mgmtaddr'], - ) - decon.vins_update_ifaces( - vins_facts, - amodule.params['connect_to'], - ) + decon.action() elif amodule.params['state'] == 'disabled': # disable and update ViNS - decon.vins_state(vins_facts, 'disabled') - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr']) - elif vins_facts['status'] == "DELETED": + decon.action('disabled') + elif decon.vins_facts['status'] == "DELETED": if amodule.params['state'] in ['present', 'enabled']: # restore and enable - decon.vins_restore(arg_vins_id=vins_id) - decon.vins_state(vins_facts, 'enabled') + decon.action(restore=True) vins_should_exist = True elif amodule.params['state'] == 'absent': # destroy permanently - decon.vins_delete(vins_id, permanently=True) - vins_facts['status'] = 'DESTROYED' + decon.delete() vins_should_exist = False elif amodule.params['state'] == 'disabled': - # error - decon.result['failed'] = True - decon.result['changed'] = False - decon.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the " - "current status '{}'").format(vins_id, - amodule.params['state'], - vins_facts['status']) + decon.error() vins_should_exist = False - elif vins_facts['status'] == "DESTROYED": + elif decon.vins_facts['status'] == "DESTROYED": if amodule.params['state'] in ('present', 'enabled'): - # need to re-provision ViNS; some attributes may be changed, some stay the same. - # account and RG - stays the same - # vins_name - stays the same - # IPcidr - take from module arguments - # ext IP address - take from module arguments - # annotation - take from module arguments - vins_id = decon.vins_provision(vins_facts['name'], - validated_acc_id, validated_rg_id, - amodule.params['ipcidr'], - amodule.params['ext_net_id'], amodule.params['ext_ip_addr'], - amodule.params['annotation']) + # need to re-provision ViNS; + decon.create() vins_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 ViNS ID {} because of its " - "current status '{}'").format(vins_id, - vins_facts['status']) + decon.nop() vins_should_exist = False elif amodule.params['state'] == 'disabled': - # error - decon.result['failed'] = True - decon.result['changed'] = False - decon.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the " - "current status '{}'").format(vins_id, - amodule.params['state'], - vins_facts['status']) + decon.error() else: # Preexisting ViNS was not found. vins_should_exist = False # we will change it back to True if ViNS is created or restored # 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 ViNS name '{}'").format(amodule.params['vins_name']) + decon.nop() elif amodule.params['state'] in ('present', 'enabled'): decon.check_amodule_argument('vins_name') # as we already have account ID and RG ID we can create ViNS and get vins_id on success - vins_id = decon.vins_provision(amodule.params['vins_name'], - validated_acc_id, validated_rg_id, - amodule.params['ipcidr'], - amodule.params['ext_net_id'], amodule.params['ext_ip_addr'], - amodule.params['annotation']) + decon.create() vins_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 " - "ViNS name '{}'").format(amodule.params['state'], - amodule.params['vins_name']) + decon.error() # # conditional switch end - complete module run # @@ -593,18 +638,9 @@ def main(): amodule.fail_json(**decon.result) else: # prepare ViNS facts to be returned as part of decon.result and then call exit_json(...) - if vins_should_exist: - if decon.result['changed']: - # If we arrive here, there is a good chance that the ViNS is present - get fresh ViNS - # facts from # the cloud by ViNS ID. - # Otherwise, ViNS facts from previous call (when the ViNS was still in existence) will - # be returned. - _, vins_facts = decon.vins_find(vins_id) - decon.result['facts'] = decort_vins_package_facts(vins_facts, amodule.check_mode) - # add password to facts if mgmtaddr is present - # need reworking - if amodule.params['mgmtaddr'] != "": - decon.result['facts'].update({'password': vins_facts['VNFDev']['config']['mgmt']['password']}) + if decon.result['changed']: + _, decon.vins_facts = decon.vins_find(decon.vins_id) + decon.result['facts'] = decon.package_facts(amodule.check_mode) amodule.exit_json(**decon.result) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index bb378c4..918cbee 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -2051,8 +2051,27 @@ class DecortController(object): "response {}.").format(vins_id, api_resp.status_code, api_resp.reason) return ret_vins_id, ret_vins_dict + def _rg_listvins(self,rg_id): + """List all ViNS in the resource group + @param (int) rg_id: id onr resource group + """ + if not rg_id: + self.result['failed'] = True + self.result['msg'] = "_rg_listvins(): 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/listVins", api_params) + if api_resp.status_code == 200: + ret_rg_vins_list = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("rg_listvins(): failed to get RG by ID {}. HTTP code {}, " + "response {}.").format(rg_id, api_resp.status_code, api_resp.reason) + return [] - def vins_find(self, vins_id, vins_name="", account_id=0, rg_id=0, check_state=True): + return ret_rg_vins_list + + def vins_find(self, vins_id, vins_name="", account_id=0, rg_id=0, rg_facts="", check_state=True): """Find specified ViNS. @param (int) vins_id: ID of the ViNS. If non-zero vins_id is specified, all other arguments @@ -2093,29 +2112,29 @@ class DecortController(object): elif vins_name != "": if rg_id > 0: # search for ViNS at RG level - validated_id, validated_facts = self._rg_get_by_id(rg_id) - if not validated_id: - self.result['failed'] = True - self.result['msg'] = "vins_find(): cannot find RG ID {}.".format(rg_id) - self.amodule.fail_json(**self.result) - # NOTE: RG's 'vins' attribute does not list destroyed ViNSes! - for runner in validated_facts['vins']: - # api_params['vinsId'] = runner - ret_vins_id, ret_vins_facts = self._vins_get_by_id(runner) - if ret_vins_id and ret_vins_facts['name'] == vins_name: + # validated_id, validated_facts = self._rg_get_by_id(rg_id) + # if not validated_id: + # self.result['failed'] = True + # self.result['msg'] = "vins_find(): cannot find RG ID {}.".format(rg_id) + # self.amodule.fail_json(**self.result) + # # NOTE: RG's 'vins' attribute does not list destroyed ViNSes! + list_vins = self._rg_listvins(rg_id) + for vins in list_vins: + if vins['name'] == vins_name: + ret_vins_id, ret_vins_facts = self._vins_get_by_id(vins['id']) if not check_state or ret_vins_facts['status'] not in VINS_INVALID_STATES: return ret_vins_id, ret_vins_facts else: return 0, None elif account_id > 0: # search for ViNS at account level - validated_id, validated_facts = self.account_find("", account_id) - if not validated_id: - self.result['failed'] = True - self.result['msg'] = "vins_find(): cannot find Account ID {}.".format(account_id) - self.amodule.fail_json(**self.result) + # validated_id, validated_facts = self.account_find("", account_id) + # if not validated_id: + # self.result['failed'] = True + # self.result['msg'] = "vins_find(): cannot find Account ID {}.".format(account_id) + # self.amodule.fail_json(**self.result) # NOTE: account's 'vins' attribute does not list destroyed ViNSes! - for runner in validated_facts['vins']: + for runner in rg_facts['vins']: # api_params['vinsId'] = runner ret_vins_id, ret_vins_facts = self._vins_get_by_id(runner) if ret_vins_id and ret_vins_facts['name'] == vins_name: @@ -2296,7 +2315,7 @@ class DecortController(object): desired_state) return - def vins_update(self, vins_dict, ext_net_id, ext_ip_addr=""): + def vins_update_extnet(self, vins_dict, ext_net_id, ext_ip_addr=""): """Update ViNS. Currently only updates to the external network connection settings and external IP address assignment are implemented. Note that as ViNS created at account level cannot have external connections, attempt @@ -2318,7 +2337,7 @@ class DecortController(object): if self.amodule.check_mode: self.result['failed'] = False - self.result['msg'] = ("vins_update() in check mode: updating ViNS ID {}, name '{}' " + self.result['msg'] = ("vins_update_extnet() in check mode: updating ViNS ID {}, name '{}' " "was requested.").format(vins_dict['id'], vins_dict['name']) return @@ -2373,10 +2392,16 @@ class DecortController(object): gw_config[ 'ext_net_id']) return - def vins_update_mgmt(self, vins_dict, mgmtaddr=""): + def vins_update_mgmt(self, vins_dict, mgmtaddr=[]): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_update_mgmt") + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = ("vins_update_mgmt() in check mode: updating ViNS ID {}, name '{}' " + "was requested.").format(vins_dict['id'], vins_dict['name']) + return + if self.amodule.params['config_save'] and vins_dict['VNFDev']['customPrecfg']: # only save config,no other modifictaion self.result['changed'] = True @@ -2386,13 +2411,12 @@ class DecortController(object): return for iface in vins_dict['VNFDev']['interfaces']: - if iface['ipAddress'] == mgmtaddr: - if not iface['listenSsh']: - self._vins_vnf_addmgmtaddr(vins_dict['VNFDev']['id'],mgmtaddr) - self.result['changed'] = True - self.result['failed'] = False - elif mgmtaddr =="": - if iface['listenSsh'] and iface['name'] != "ens9": + if iface['ipAddress'] in mgmtaddr and not iface['listenSsh']: + self._vins_vnf_addmgmtaddr(vins_dict['VNFDev']['id'],iface['ipAddress']) + self.result['changed'] = True + self.result['failed'] = False + elif iface['ipAddress'] not in mgmtaddr and iface['listenSsh']: + if iface['name'] != "ens9": self._vins_vnf_delmgmtaddr(vins_dict['VNFDev']['id'],iface['ipAddress']) self.result['changed'] = True self.result['failed'] = False @@ -2411,13 +2435,15 @@ class DecortController(object): def vins_update_ifaces(self,vins_dict,vinses=""): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_update_ifaces") - existed_conn_ip = [] - #vnf_dict = self._get_vnf_by_id(vins_dict['VNFDev']['id']) - list_account_vins = self._get_all_account_vinses(vins_dict['VNFDev']['accountId']) - list_account_vinsid = [rec['id'] for rec in list_account_vins] - list_ifaces_ip = [rec['ipaddr'] for rec in vinses] - vins_inner = [rec['id'] for rec in vinses] - vins_outer = [rec['id'] for rec in list_account_vins] + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = ("vins_update_iface() in check mode: updating ViNS ID {}, name '{}' " + "was requested.").format(vins_dict['id'], vins_dict['name']) + return + + list_ifaces_ip = [rec['ipaddr'] for rec in vinses] + vinsid_not_existed = [] for iface in vins_dict['VNFDev']['interfaces']: if iface['connType'] == "VXLAN" and iface['type'] == "CUSTOM": if iface['ipAddress'] not in list_ifaces_ip: @@ -2425,19 +2451,30 @@ class DecortController(object): self.result['changed'] = True self.result['failed'] = False else: - existed_conn_ip.append(iface['ipAddress']) + #existed_conn_ip.append(iface['ipAddress']) + vinses = list(filter(lambda i: i['ipaddr']!=iface['ipAddress'],vinses)) + if not vinses: + return + list_account_vins = self._get_all_account_vinses(vins_dict['VNFDev']['accountId']) + list_account_vinsid = [rec['id'] for rec in list_account_vins] for vins in vinses: if vins['id'] in list_account_vinsid: _,v_dict = self._vins_get_by_id(vins['id']) - if vins['ipaddr'] not in existed_conn_ip: - self._vnf_iface_add(vins_dict['VNFDev']['id'],v_dict['vxlanId'],vins['ipaddr'],vins['netmask']) - self.result['changed'] = True - self.result['failed'] = False + #TODO: vins reservation + self._vnf_iface_add(vins_dict['VNFDev']['id'],v_dict['vxlanId'],vins['ipaddr'],vins['netmask']) + self.result['changed'] = True + self.result['failed'] = False + else: + vinsid_not_existed.append(vins['id']) + if vinsid_not_existed: + self.result['warning'] = ("List ViNS id: {} that not created on account id: {}").format( + vinsid_not_existed, + vins_dict['VNFDev']['accountId'] + ) return def _vnf_iface_add(self,arg_devid,arg_vxlanid,arg_ipaddr,arg_netmask="24",arg_defgw=""): - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "_vnf_iface_add") api_params = dict( devId=arg_devid, ifType="CUSTOM", @@ -2473,7 +2510,6 @@ class DecortController(object): return ret_vnf_dict def _get_all_account_vinses(self,acc_id): - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "get_all_account_vinses") api_params = dict(accountId=acc_id) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listVins", api_params) if api_resp.status_code == 200: @@ -2484,8 +2520,6 @@ class DecortController(object): return ret_listvins_dict def _vins_vnf_addmgmtaddr(self,dev_id,mgmtip): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_addmgmtaddr") api_params = dict(devId=dev_id,ip=mgmtip) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/addMgmtAddr", api_params) if api_resp.status_code == 200: @@ -2497,8 +2531,6 @@ class DecortController(object): return def _vins_vnf_delmgmtaddr(self,dev_id,mgmtip): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_delmgmtaddr") api_params = dict(devId=dev_id,ip=mgmtip) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/delMgmtAddr", api_params) if api_resp.status_code == 200: @@ -2510,7 +2542,6 @@ class DecortController(object): return def _vins_vnf_customconfig_set(self,dev_id,arg_mode=True): - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_customconfig_set") api_params = dict(devId=dev_id,mode=arg_mode) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/customSet", api_params) if api_resp.status_code == 200: @@ -2522,7 +2553,6 @@ class DecortController(object): return def _vins_vnf_config_save(self,dev_id): - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_config_save") api_params = dict(devId=dev_id) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/configSave", api_params) if api_resp.status_code == 200: @@ -2532,12 +2562,6 @@ class DecortController(object): self.result['warning'] = ("_vins_vnf_config_set(): failed to Save configuration on the VNF device. {}. HTTP code {}, " "response {}.").format(dev_id,api_resp.status_code, api_resp.reason) return - - def vins_vnf_ifaceadd(self): - return - - def vins_vnf_ifaceremove(self): - return ############################## # @@ -3146,7 +3170,7 @@ class DecortController(object): return api_params = dict(k8sId=k8s_id, - permanently=False, + permanently=permanently, ) self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/delete", api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. @@ -3238,9 +3262,11 @@ class DecortController(object): ret_info = json.loads(api_get_resp.content.decode('utf8')) if api_get_resp.status_code == 200: if ret_info['status'] in ["PROCESSING", "SCHEDULED"]: + self.result['msg'] = ("k8s_provision(): Can't create cluster") self.result['failed'] = False time.sleep(30) elif ret_info['status'] == "ERROR": + self.result['msg'] = ("k8s_provision(): Can't create cluster") self.result['failed'] = True return elif ret_info['status'] == "OK": @@ -3250,10 +3276,13 @@ class DecortController(object): else: k8s_id = ret_info['status'] else: + self.result['msg'] = ("k8s_provision(): Can't create cluster") self.result['failed'] = True # Timeout + self.result['msg'] = ("k8s_provision(): Can't create cluster") self.result['failed'] = True else: + self.result['msg'] = ("k8s_provision(): Can't create cluster") self.result['failed'] = True return @@ -3283,15 +3312,16 @@ class DecortController(object): for rec_inn in arg_k8swg['k8sGroups']['workers']: for rec_out in arg_modwg: - if rec_inn['num'] != rec_out['num'] and rec_out['num'] != 0: - count = rec_inn['num']-rec_out['num'] - cmp_list = [] - if count > 0: - for cmp in rec_inn['detailedInfo'][:count]: - cmp_list.append(cmp['id']) - wg_moddel_list.append({rec_inn['id']:cmp_list}) - if count < 0: - wg_modadd_list.append({rec_inn['id']:abs(count)}) + if rec_inn['name'] == rec_out['name']: + if rec_inn['num'] != rec_out['num'] and rec_out['num'] != 0: + count = rec_inn['num']-rec_out['num'] + cmp_list = [] + if count > 0: + for cmp in rec_inn['detailedInfo'][-count:]: + cmp_list.append(cmp['id']) + wg_moddel_list.append({rec_inn['id']:cmp_list}) + if count < 0: + wg_modadd_list.append({rec_inn['id']:abs(count)}) if wg_del_list: for wgid in wg_del_list: @@ -3335,15 +3365,17 @@ class DecortController(object): api_params = dict(includeDisabled=False) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8ci/list", api_params) - + k8ci_id_present = False if api_resp.status_code == 200: ret_k8ci_list = json.loads(api_resp.content.decode('utf8')) for k8ci_item in ret_k8ci_list: if k8ci_item['id'] == arg_k8ci_id: + k8ci_id_present = True break - else: + + if k8ci_id_present == False: self.result['failed'] = True - self.result['msg'] = "k8s_k8ci_find(): cannot find ID." + self.result['msg'] = ("Cannot find k8ci id: {}.").format(arg_k8ci_id) self.amodule.fail_json(**self.result) else: self.result['failed'] = True From 889618f84382e03f1c10cf1b52a04d37674224ca Mon Sep 17 00:00:00 2001 From: Alex_geth Date: Tue, 20 Sep 2022 21:44:32 +0300 Subject: [PATCH 2/2] added LB module & some other fix --- library/decort_lb.py | 328 ++++++++++++++++ library/decort_vins.py | 14 +- module_utils/decort_utils.py | 718 ++++++++++++++++++++++++++++++++++- 3 files changed, 1041 insertions(+), 19 deletions(-) create mode 100644 library/decort_lb.py 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..db5421b 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 @@ -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: @@ -2060,7 +2059,7 @@ class DecortController(object): self.result['msg'] = "_rg_listvins(): zero RG ID specified." self.amodule.fail_json(**self.result) - api_params = dict(rgId=rg_id, ) + api_params = dict(rgId=rg_id) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/listVins", api_params) if api_resp.status_code == 200: ret_rg_vins_list = json.loads(api_resp.content.decode('utf8')) @@ -3270,7 +3269,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: @@ -3365,18 +3364,17 @@ class DecortController(object): api_params = dict(includeDisabled=False) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8ci/list", api_params) - k8ci_id_present = False + if api_resp.status_code == 200: ret_k8ci_list = json.loads(api_resp.content.decode('utf8')) for k8ci_item in ret_k8ci_list: if k8ci_item['id'] == arg_k8ci_id: - 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 +3734,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