From c7a3b5d6b2fab2642093b248786427fc10d7a87c Mon Sep 17 00:00:00 2001 From: Sergey Shubin svs1370 Date: Thu, 9 Jul 2020 01:11:20 +0300 Subject: [PATCH] Optimizing PFW management workflows --- library/decort_jwt.py | 2 +- library/decort_pfw.py | 18 +++++++++----- module_utils/decort_utils.py | 48 ++++++++++++++++++++++++++++++------ 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/library/decort_jwt.py b/library/decort_jwt.py index 5f9141f..9a740da 100644 --- a/library/decort_jwt.py +++ b/library/decort_jwt.py @@ -32,7 +32,7 @@ requirements: - PyJWT module - requests module - decort_utils utility library (module) - - DECORT cloud platform version 3.4.0 or higher + - DECORT cloud platform version 3.4.2 or higher options: app_id: description: diff --git a/library/decort_pfw.py b/library/decort_pfw.py index 03add21..0f4642b 100644 --- a/library/decort_pfw.py +++ b/library/decort_pfw.py @@ -182,7 +182,7 @@ from ansible.module_utils.basic import env_fallback from ansible.module_utils.decort_utils import * -def decort_pfw_package_facts(pfw_facts, check_mode=False): +def decort_pfw_package_facts(comp_id, vins_id, pfw_facts, check_mode=False): """Package a dictionary of PFW rules facts according to the decort_pfw module specification. This dictionary will be returned to the upstream Ansible engine at the completion of the module run. @@ -191,11 +191,10 @@ def decort_pfw_package_facts(pfw_facts, check_mode=False): @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", + ret_dict = dict(state="CHECK_MODE", compute_id=0, vins_id=0, + rules=[], ) if check_mode: @@ -207,7 +206,14 @@ def decort_pfw_package_facts(pfw_facts, check_mode=False): ret_dict['state'] = "ABSENT" return ret_dict - ret_dict['compute_id'] = pfw_facts['compute_id'] + ret_dict['compute_id'] = comp_id + ret_dict['vins_id'] = vins_id + + if len(pfw_facts) != 0: + ret_dict['state'] = 'PRESENT' + ret_dict['rules'] = pfw_facts + else: + ret_dict['state'] = 'ABSENT' return ret_dict @@ -316,7 +322,7 @@ def main(): amodule.fail_json(**decon.result) else: # prepare PFW facts to be returned as part of decon.result and then call exit_json(...) - decon.result['facts'] = decort_pfw_package_facts(pfw_facts, amodule.check_mode) + decon.result['facts'] = decort_pfw_package_facts(comp_facts['id'], vins_facts['id'], pfw_facts, amodule.check_mode) amodule.exit_json(**decon.result) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 48bacc7..54c0e50 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -2419,6 +2419,24 @@ class DecortController(object): # ############################## + def _pfw_get(self, comp_id, vins_id): + """Convenience method to get current PFW rules for the specified compute ID. + """ + api_params = dict(vinsId=vins_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/vins/natRuleList", api_params) + all_rules = json.loads(api_resp.content.decode('utf8')) + + if comp_id == 0: + # no filtering by compute ID, return rules as is + return all_rules + + filtered_rules = [] + for runner in all_rules: + if runner['vmId'] == comp_id: + filtered_rules.append(runner) + + return filtered_rules + def pfw_configure(self, comp_facts, vins_facts, new_rules=None): """Manage port forwarding rules for Compute in a smart way. The method will try to match existing rules against the new rules set and calculate the delta settings to apply to the corresponding @@ -2430,6 +2448,9 @@ class DecortController(object): to which PFW rules set will be applied. @param (list of dicts) new_rules: new PFW rules set. If None is passed, remove all existing PFW rules for the Compute. + + @returns: list of dictionaries with PFW rules as returned by .../vins/natRuleList on success, + None on error. """ # At the entry to this method we assume that initial validations are already passed, namely: @@ -2450,11 +2471,14 @@ class DecortController(object): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "pfw_configure") + ret_rules = [] + if self.amodule.check_mode: self.result['failed'] = False self.result['msg'] = ("pfw_configure() in check mode: port forwards configuration requested " "for Compute ID {} / ViNS ID {}").format(comp_facts['id'], vins_facts['id']) - return None + ret_rules = self._pfw_get(comp_facts['id'], vins_facts['id']) + return ret_rules iface_ipaddr = "" # keep IP address associated with Compute's connection to this ViNS - need this for natRuleDel API for iface in comp_facts['interfaces']: @@ -2464,7 +2488,7 @@ class DecortController(object): else: decon.result['failed'] = True decon.result['msg'] = "Compute ID {} is not connected to ViNS ID {}.".format(comp_facts['id'], vins_facts['id']) - return None + return ret_rules existing_rules = [] for runner in vins_facts['vnfs']['NAT']['config']['rules']: @@ -2475,7 +2499,7 @@ class DecortController(object): self.result['failed'] = False self.result['warning'] = ("pfw_configure(): both existing and new port forwarding rule lists " "for Compute ID {} are empty - nothing to do.").format(comp_facts['id']) - return None + return ret_rules if new_rules == None or len(new_rules) == 0: # delete all existing rules for this Compute @@ -2483,7 +2507,7 @@ class DecortController(object): ruleId=-1) self.decort_api_call(requests.post, "/restmachine/cloudapi/vins/natRuleDel", api_params) self.result['changed'] = True - return None + return ret_rules # # delta_list will be a list of dictionaries that describe _changes_ to the port forwarding rules @@ -2501,6 +2525,14 @@ class DecortController(object): for rule in new_rules: rule['action'] = 'add' rule_port_end = rule.get('public_port_end', rule['public_port_start']) + if rule_port_end > rule['public_port_start']: + # This is a ranged rule, i.e. when range of public ports maps to an equally + # sized range of local ports. + # For such case we have to make sure that the local port equals public + # port (this check & adjustment will be made by vnf_nat.add method anyway, but + # if we adjust here, we can avoid unnecessary rule del / add iteration in the + # module run, thus saving execution time) + rule['local_port'] = rule['public_port_start'] for runner in existing_rules: if (runner['publicPortStart'] == rule['public_port_start'] and runner['publicPortEnd'] == rule_port_end and @@ -2536,7 +2568,8 @@ class DecortController(object): self.result['failed'] = False self.result['warning'] = ("pfw_configure() no difference between current and new PFW rules " "found. No change applied to Compute ID {}.").format(comp_facts['id']) - return + ret_rules = self._pfw_get(comp_facts['id'], vins_facts['id']) + return ret_rules # now delta_list contains a list of enriched rule dictionaries with extra key 'action', which # tells what kind of action is expected on this rule - 'add' or 'del' @@ -2566,5 +2599,6 @@ class DecortController(object): self.result['changed'] = True self.result['failed'] = False - - return \ No newline at end of file + + ret_rules = self._pfw_get(comp_facts['id'], vins_facts['id']) + return ret_rules \ No newline at end of file