diff options
author | Rohit Thakur <rohitthakur2590@outlook.com> | 2020-04-22 16:22:46 +0530 |
---|---|---|
committer | Rohit Thakur <rohitthakur2590@outlook.com> | 2020-05-11 19:29:07 +0530 |
commit | 7e32c63e6d065062b4540b9bf467989ee86e1f2a (patch) | |
tree | 8dfa6115d91015c8100ed9994321e4073d17448e /plugins/module_utils | |
parent | 37289b45840129f2296fbc9cff9a3eab97bdb2a5 (diff) | |
download | vyos.vyos-7e32c63e6d065062b4540b9bf467989ee86e1f2a.tar.gz vyos.vyos-7e32c63e6d065062b4540b9bf467989ee86e1f2a.zip |
test cases added
Signed-off-by: Rohit Thakur <rohitthakur2590@outlook.com>
Diffstat (limited to 'plugins/module_utils')
4 files changed, 513 insertions, 280 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py index 1b11d3c..a2a6e04 100644 --- a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py @@ -34,7 +34,6 @@ class Ospfv2Args(object): # pylint: disable=R0903 argument_spec = { 'config': { - 'elements': 'dict', 'options': { 'auto_cost': { 'options': { @@ -140,10 +139,10 @@ class Ospfv2Args(object): # pylint: disable=R0903 }, 'type': 'list' }, - 'ospf_area': { + 'areas': { 'elements': 'dict', 'options': { - 'area': { + 'area_id': { 'type': 'str' }, 'area_type': { @@ -159,6 +158,9 @@ class Ospfv2Args(object): # pylint: disable=R0903 'no_summary': { 'type': 'bool' }, + 'set': { + 'type': 'bool' + }, 'translate': { 'choices': ['always', 'candidate', 'never'], @@ -175,6 +177,9 @@ class Ospfv2Args(object): # pylint: disable=R0903 }, 'no_summary': { 'type': 'bool' + }, + 'set': { + 'type': 'bool' } }, 'type': 'dict' @@ -227,6 +232,7 @@ class Ospfv2Args(object): # pylint: disable=R0903 'authentication': { 'options': { 'md5': { + 'elements': 'dict', 'options': { 'key_id': { 'type': 'int' @@ -235,7 +241,7 @@ class Ospfv2Args(object): # pylint: disable=R0903 'type': 'str' } }, - 'type': 'dict' + 'type': 'list' }, 'plaintext_password': { 'type': 'str' @@ -343,7 +349,7 @@ class Ospfv2Args(object): # pylint: disable=R0903 'type': 'dict' } }, - 'type': 'list' + 'type': 'dict' }, "running_config": {"type": "str"}, 'state': { diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py index 0109ca1..13645cd 100644 --- a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -29,19 +29,16 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils list_diff_want_only, _in_target, _is_w_same, _bool_to_str ) + class Ospfv2(ConfigBase): + """ The vyos_ospfv2 class """ - gather_subset = [ - '!all', - '!min', - ] + gather_subset = ['!all', '!min'] - gather_network_resources = [ - 'ospfv2', - ] + gather_network_resources = ['ospfv2'] def __init__(self, module): super(Ospfv2, self).__init__(module) @@ -52,7 +49,10 @@ class Ospfv2(ConfigBase): :rtype: A dictionary :returns: The current configuration as a dictionary """ - facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources, data=data) + + (facts, _warnings) = \ + Facts(self._module).get_facts(self.gather_subset, + self.gather_network_resources, data=data) ospfv2_facts = facts['ansible_network_resources'].get('ospfv2') if not ospfv2_facts: return [] @@ -64,6 +64,7 @@ class Ospfv2(ConfigBase): :rtype: A dictionary :returns: The result from module execution """ + result = {'changed': False} warnings = list() commands = list() @@ -73,41 +74,38 @@ class Ospfv2(ConfigBase): else: existing_ospfv2_facts = [] - if self.state in self.ACTION_STATES or self.state == "rendered": + if self.state in self.ACTION_STATES or self.state == 'rendered': commands.extend(self.set_config(existing_ospfv2_facts)) if commands and self.state in self.ACTION_STATES: if not self._module.check_mode: self._connection.edit_config(commands) - result["changed"] = True + result['changed'] = True if self.state in self.ACTION_STATES: - result["commands"] = commands + result['commands'] = commands - if self.state in self.ACTION_STATES or self.state == "gathered": + if self.state in self.ACTION_STATES or self.state == 'gathered': changed_ospfv2_facts = self.get_ospfv2_facts() - elif self.state == "rendered": - result["rendered"] = commands - elif self.state == "parsed": - running_config = self._module.params["running_config"] + elif self.state == 'rendered': + result['rendered'] = commands + elif self.state == 'parsed': + running_config = self._module.params['running_config'] if not running_config: - self._module.fail_json( - msg="value of running_config parameter must not be empty for state parsed" - ) - result["parsed"] = self.get_ospfv2_facts( - data=running_config - ) + self._module.fail_json(msg='value of running_config parameter must not be empty for state parsed') + result['parsed'] = \ + self.get_ospfv2_facts(data=running_config) else: changed_ospfv2_facts = [] if self.state in self.ACTION_STATES: - result["before"] = existing_ospfv2_facts - if result["changed"]: - result["after"] = changed_ospfv2_facts - elif self.state == "gathered": - result["gathered"] = changed_ospfv2_facts + result['before'] = existing_ospfv2_facts + if result['changed']: + result['after'] = changed_ospfv2_facts + elif self.state == 'gathered': + result['gathered'] = changed_ospfv2_facts - result["warnings"] = warnings + result['warnings'] = warnings return result def set_config(self, existing_ospfv2_facts): @@ -118,6 +116,7 @@ class Ospfv2(ConfigBase): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + want = self._module.params['config'] have = existing_ospfv2_facts resp = self.set_state(want, have) @@ -132,24 +131,18 @@ class Ospfv2(ConfigBase): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + commands = [] - if self.state in ("merged", "replaced", "overridden", "rendered") and not w: - self._module.fail_json( - msg="value of config parameter must not be empty for state {0}".format( - self.state - ) - ) - if self.state == "overridden": - commands.extend(self._state_overridden(w, h)) - elif self.state == "deleted": + if self.state in ('merged', 'replaced', 'overridden', 'rendered' + ) and not w: + self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(self.state)) + + if self.state == 'deleted': commands.extend(self._state_deleted(w, h)) - elif w: - if self.state == "merged" or self.state == "rendered": - for w_item in w: - commands.extend(self._state_merged(w_item, h)) - elif self.state == "replaced": - for w_item in w: - commands.extend(self._state_replaced(w_item, h)) + elif self.state in ('merged', 'rendered'): + commands.extend(self._state_merged(w, h)) + elif self.state == 'replaced': + commands.extend(self._state_replaced(w, h)) return commands def search_obj_in_have(self, have, w_name, key): @@ -160,6 +153,7 @@ class Ospfv2(ConfigBase): :param type: rule_sets/rule/r_list. :return: rule-set/rule. """ + if have: for item in have: if item[key] == w_name[key]: @@ -173,12 +167,12 @@ class Ospfv2(ConfigBase): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + commands = [] - h_item = {} if have: - h_item = have[0] - commands.extend(self._render_ospf_param(h_item, want, opr=False)) - commands.extend(self._render_ospf_param(want, h_item)) + commands.extend(self._render_ospf_param(have, want, + opr=False)) + commands.extend(self._render_ospf_param(want, have)) return commands def _state_merged(self, want, have): @@ -188,11 +182,9 @@ class Ospfv2(ConfigBase): :returns: the commands necessary to merge the provided into the current configuration """ + commands = [] - h_item = {} - if have: - h_item = have[0] - commands.extend(self._render_ospf_param(want, h_item)) + commands.extend(self._render_ospf_param(want, have)) return commands def _state_deleted(self, want, have): @@ -202,18 +194,27 @@ class Ospfv2(ConfigBase): :returns: the commands necessary to remove the current configuration of the provided objects """ + commands = [] - if want: - for w in want: - if have: - h = have[0] - if h: - for key, val in iteritems(w): - if key in h: - if key == 'ospf_area': - key = 'area' - commands.append(self._compute_command(attr=key, opr=False)) - elif have and have[0]: + if want and have: + for (key, val) in iteritems(want): + if key in have: + if key == 'areas': + h_areas = have.get(key) or [] + key = 'area' + for area in h_areas: + h_vlist = area.get('virtual_link') or [] + if h_vlist: + for vlink in h_vlist: + cmd = self._compute_command( + key=key + ' ' + area['area_id'], attr='virtual_link', + val=vlink['address'], opr=False) + commands.append(cmd) + commands.append(self._compute_command(key=key, + attr=area['area_id'], opr=False)) + commands.append(self._compute_command(attr=key, + opr=False)) + elif have: commands.append('delete protocols ospf') return commands @@ -227,16 +228,15 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated commands list. """ + commands = [] w = deepcopy(remove_empties(want)) - leaf = ( - "default_metric", - "log_adjacency_changes" - ) + leaf = ('default_metric', 'log_adjacency_changes') if w: - for key, val in iteritems(w): + for (key, val) in iteritems(w): if opr and key in leaf and not _is_w_same(w, have, key): - commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) + commands.append(self._form_attr_cmd(attr=key, + val=_bool_to_str(val), opr=opr)) elif not opr and key in leaf and not _in_target(have, key): commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) else: @@ -253,21 +253,25 @@ class Ospfv2(ConfigBase): :param opr: operation. :return: list of commands. """ + commands = [] - if key in ("neighbor", "redistribute"): - commands.extend(self._render_list_dict_param(key, w, h, opr=opr)) - elif key in ("default_information", 'max_metric'): - commands.extend(self._render_nested_dict_param(key, w, h, opr=opr)) - elif key in ("mpls_te", "auto_cost", "parameters", "auto_cost"): + if key in ('neighbor', 'redistribute'): + commands.extend(self._render_list_dict_param(key, w, h, + opr=opr)) + elif key in ('default_information', 'max_metric'): + commands.extend(self._render_nested_dict_param(key, w, h, + opr=opr)) + elif key in ('mpls_te', 'auto_cost', 'parameters', 'auto_cost'): commands.extend(self._render_dict_param(key, w, h, opr=opr)) - elif key in ("route_map", "passive_interface", "passive_interface_exclude"): + elif key in ('route_map', 'passive_interface', + 'passive_interface_exclude'): commands.extend(self._render_list_param(key, w, h, opr=opr)) - elif key == "ospf_area": - commands.extend(self._render_ospf_area(key, w, h, opr=opr)) - elif key == "timers": - commands.extend(self._render_timers(key, w, h, opr=opr, remove=remove)) - elif key == "distance": - commands.extend(self._render_distance(key, w, h, opr=opr, remove=remove)) + elif key == 'areas': + commands.extend(self._render_areas(key, w, h, opr=opr)) + elif key == 'timers': + commands.extend(self._render_timers(key, w, h, opr=opr)) + elif key == 'distance': + commands.extend(self._render_distance(key, w, h, opr=opr)) return commands def _render_dict_param(self, attr, want, have, opr=True): @@ -279,6 +283,7 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated list of commands. """ + commands = [] h = {} if have: @@ -288,18 +293,19 @@ class Ospfv2(ConfigBase): elif want[attr]: leaf_dict = {'auto_cost': 'reference_bandwidth', 'mpls_te': ('enabled', 'router_address'), - 'parameters': ("router_id", "abr_type", "opaque_lsa", "rfc1583_compatibility")} + 'parameters': ('router_id', 'abr_type', + 'opaque_lsa', 'rfc1583_compatibility')} leaf = leaf_dict[attr] - for item, value in iteritems(want[attr]): + for (item, value) in iteritems(want[attr]): if opr and item in leaf and not _is_w_same(want[attr], h, item): - if item == "enabled": + if item == 'enabled': item = 'enable' - if item in ("opaque_lsa", "enable", "rfc1583_compatibility"): + if item in ('opaque_lsa', 'enable', 'rfc1583_compatibility'): commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr)) else: commands.append(self._form_attr_cmd(key=attr, attr=item, val=value, opr=opr)) elif not opr and item in leaf and not _in_target(h, item): - if item == "enabled": + if item == 'enabled': commands.append(self._form_attr_cmd(key=attr, attr='enable', opr=opr)) else: commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr)) @@ -315,6 +321,7 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated list of commands. """ + commands = [] h = [] if want: @@ -327,7 +334,7 @@ class Ospfv2(ConfigBase): if opr: members = list_diff_want_only(w, h) for member in members: - command = cmd + attr.replace("_","-") + " " + command = cmd + attr.replace('_', '-') + ' ' if attr == 'network': command += member['address'] else: @@ -338,11 +345,96 @@ class Ospfv2(ConfigBase): for member in w: if attr == 'network': if not self.search_obj_in_have(h, member, 'address'): - commands.append(cmd + attr.replace("_","-") + ' ' + member['address']) + commands.append(cmd + attr.replace('_','-') + + ' ' + member['address']) elif member not in h: - commands.append(cmd + attr.replace("_","-") + ' ' + member) + commands.append(cmd + attr.replace('_', '-') + + ' ' + member) else: - commands.append(cmd + " " + attr.replace("_","-")) + commands.append(cmd + ' ' + attr.replace('_', '-')) + return commands + + def _render_vlink(self, attr, want, have, cmd=None, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for attributes with in desired list of dictionary. + :param attr: attribute name. + :param w: the desired config. + :param h: the target config. + :param cmd: commands to be prepend. + :param opr: True/False. + :return: generated commands list. + """ + + commands = [] + h = [] + name = {'virtual_link': 'address'} + leaf_dict = {'virtual_link': ('address', 'dead_interval', + 'transmit_delay', 'hello_interval', + 'retransmit_interval')} + leaf = leaf_dict[attr] + w = want.get(attr) or [] + if have: + h = have.get(attr) or [] + if not opr and not h: + commands.append(cmd + attr.replace('_', '-')) + elif w: + for w_item in w: + for (key, val) in iteritems(w_item): + if not cmd: + cmd = self._compute_command(opr=opr) + h_item = self.search_obj_in_have(h, w_item, name[attr]) + if opr and key in leaf and not _is_w_same(w_item, h_item, key): + if key in 'address': + commands.append(cmd + + attr.replace('_', '-') + + ' ' + str(val)) + else: + commands.append(cmd + attr.replace('_', '-') + + ' ' + w_item[name[attr]] + + ' ' + key.replace('_', '-') + + ' ' + str(val)) + elif not opr and key in leaf and not _in_target(h_item, key): + if key in 'address': + commands.append(cmd + attr.replace('_', '-') + + ' ' + str(val)) + else: + commands.append(cmd + attr.replace('_', '-') + + ' ' + w_item[name[attr]] + + ' ' + key) + elif key == 'authentication': + commands.extend(self._render_vlink_auth( + attr, + key, + w_item, + h_item, + w_item['address'], + cmd, + opr, + )) + return commands + + def _render_vlink_auth(self, attr, key, want, have, address, cmd=None, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for attributes with in desired list of dictionary. + :param attr: attribute name. + :param w: the desired config. + :param h: the target config. + :param cmd: commands to be prepend. + :param opr: True/False. + :return: generated commands list. + """ + + commands = [] + h = [] + + w = want.get(key) or {} + if have: + h = have.get(key) or {} + cmd += attr.replace('_', '-') + ' ' + address + ' ' + key + ' ' + commands.extend(self._render_list_dict_param('md5', w, h, cmd, + opr)) return commands def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True): @@ -356,17 +448,24 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated commands list. """ + commands = [] h = [] - name = {'redistribute': 'route_type', - 'neighbor': 'neighbor_id', - 'range': 'address', - 'vlink': 'address'} - leaf_dict = {'redistribute': ("metric", "route_map", "route_type", "metric_type"), - 'neighbor': ("priority", "poll_interval", "neighbor_id"), - 'range': ("cost", "address", "substitute", "not_advertise"), - 'vlink': ("address", "dead_interval", "transmit_delay", "hello_interval", "retransmit_interval") - } + name = { + 'redistribute': 'route_type', + 'neighbor': 'neighbor_id', + 'range': 'address', + 'md5': 'key_id', + 'vlink': 'address', + } + leaf_dict = { + 'md5': 'md5_key', + 'redistribute': ('metric', 'route_map', 'route_type', 'metric_type'), + 'neighbor': ('priority', 'poll_interval', 'neighbor_id'), + 'range': ('cost', 'address', 'substitute', 'not_advertise'), + 'vlink': ('address', 'dead_interval', 'transmit_delay', + 'hello_interval', 'retransmit_interval'), + } leaf = leaf_dict[attr] w = want.get(attr) or [] if have: @@ -375,26 +474,43 @@ class Ospfv2(ConfigBase): commands.append(self._compute_command(attr=attr, opr=opr)) elif w: for w_item in w: - for key, val in iteritems(w_item): + for (key, val) in iteritems(w_item): if not cmd: cmd = self._compute_command(opr=opr) h_item = self.search_obj_in_have(h, w_item, name[attr]) if opr and key in leaf and not _is_w_same(w_item, h_item, key): - if key == 'cost': - commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key + ' ' + str(val)) - elif key == 'not_advertise': - commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_","-")) - elif key in ('route_type', 'neighbor_id', 'address'): + if key in ('route_type', 'neighbor_id', + 'address', 'key_id'): commands.append(cmd + attr + ' ' + str(val)) - elif key == 'authentication': - commands.append(self._render_vlink(key, w_item, h_item, cmd, opr)) + elif key == 'cost': + commands.append(cmd + attr + + ' ' + w_item[name[attr]] + + ' ' + key + + ' ' + str(val)) + elif key == 'not_advertise': + commands.append(cmd + attr + + ' ' + w_item[name[attr]] + + ' ' + key.replace('_', '-')) + elif key == 'md5_key': + commands.append(cmd + attr + + ' ' + 'key-id' + + ' ' + str(w_item[name[attr]]) + + ' ' + key.replace('_', '-') + + ' ' + w_item[key]) else: - commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_", "-") + ' ' + str(val)) - elif not opr and key in leaf and not _in_target(h_item, key): - if key in ('route_type', 'neighbor_id', 'address'): + commands.append(cmd + attr + + ' ' + w_item[name[attr]] + + ' ' + key.replace('_', '-') + + ' ' + str(val)) + elif not opr and key in leaf \ + and not _in_target(h_item, key): + if key in ('route_type', 'neighbor_id', + 'address', 'key_id'): commands.append(cmd + attr + ' ' + str(val)) else: - commands.append(cmd + (attr + ' ' + w_item[name[attr]] + ' ' + key)) + commands.append(cmd + attr + + ' ' + w_item[name[attr]] + + ' ' + key) return commands def _render_nested_dict_param(self, attr, want, have, opr=True): @@ -408,13 +524,12 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated commands list. """ + commands = [] attr_dict = {'default_information': 'originate', - 'max_metric': 'router_lsa', - } - leaf_dict = {'default_information': ("always", "metric", "metric_type", "route_map"), - 'max_metric': ("always", "metric", "metric_type", "route_map"), - } + 'max_metric': 'router_lsa'} + leaf_dict = {'default_information': ('always', 'metric', 'metric_type', 'route_map'), + 'max_metric': ('administrative', 'on_startup', 'on_shutdown')} h = {} w = want.get(attr) or {} if have: @@ -430,18 +545,24 @@ class Ospfv2(ConfigBase): leaf = leaf_dict[attr] if h and key in h.keys(): h_attrib = h.get(key) or {} - for item, val in iteritems(w[key]): + for (item, val) in iteritems(w[key]): if opr and item in leaf and not _is_w_same(w[key], h_attrib, item): - if item in ('administrative', 'always'): - commands.append(cmd + (attr.replace("_","-") + " " + key.replace("_","-") + " " + item.replace("_","-"))) - else: - commands.append(cmd + (attr.replace("_","-") + " " + key.replace("_","-") + " " + item.replace("_","-") + " " + str(val))) - - elif not opr and item in leaf and not _in_target(h_attrib, item): - commands.append(cmd + (attr + " " + item)) + if item in ('administrative', 'always') and val: + commands.append(cmd + attr.replace('_', '-') + + ' ' + key.replace('_', '-') + + ' ' + item.replace('_', '-')) + elif item not in ('administrative', 'always'): + commands.append(cmd + attr.replace('_', '-') + + ' ' + key.replace('_', '-') + + ' ' + item.replace('_', '-') + + ' ' + str(val)) + elif not opr and item in leaf \ + and not _in_target(h_attrib, item): + + commands.append(cmd + attr + ' ' + item) return commands - def _render_ospf_area(self, attr, want, have, opr=True): + def _render_areas(self, attr, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for ospf area attributes. @@ -451,32 +572,38 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated commands list. """ + commands = [] h_lst = {} w_lst = want.get(attr) or [] - l_set = ("area", "shortcut", "authentication") + l_set = ('area_id', 'shortcut', 'authentication') if have: h_lst = have.get(attr) or [] if not opr and not h_lst: commands.append(self._form_attr_cmd(attr='area', opr=opr)) elif w_lst: for w_area in w_lst: - cmd = self._compute_command(key='area', attr=_bool_to_str(w_area['area']), opr=opr) + ' ' - h_area = self.search_obj_in_have(h_lst, w_area, 'area') + cmd = self._compute_command( + key='area', attr=_bool_to_str(w_area['area_id']), opr=opr + ) + ' ' + h_area = self.search_obj_in_have(h_lst, w_area, 'area_id') if not opr and not h_area: - commands.append(self._form_attr_cmd(key='area', attr=w_area['area'], opr=opr)) + commands.append(self._form_attr_cmd(key='area', + attr=w_area['area_id'], opr=opr)) else: - for key, val in iteritems(w_area): + for (key, val) in iteritems(w_area): if opr and key in l_set and not _is_w_same(w_area, h_area, key): - if key == 'area': - commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) + if key == 'area_id': + commands.append(self._form_attr_cmd(attr='area', + val=_bool_to_str(val), opr=opr)) else: - commands.append(cmd + key + ' ' + _bool_to_str(val).replace("_","-")) + commands.append(cmd + key + ' ' + + _bool_to_str(val).replace('_', '-')) elif not opr and key in l_set: - if key == 'area' and not _in_target(h_area, key): + if key == 'area_id' and not _in_target(h_area, key): commands.append(cmd) continue - elif key != 'area' and not _in_target(h_area, key): + elif key != 'area_id' and not _in_target(h_area, key): commands.append(cmd + val + ' ' + key) elif key == 'area_type': commands.extend(self._render_area_type(w_area, h_area, key, cmd, opr)) @@ -499,33 +626,61 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated commands list. """ + commands = [] h_type = {} w_type = want.get(attr) or [] if have: h_type = have.get(attr) or {} if not opr and not h_type: - commands.append(cmd + attr.replace("_","-")) + commands.append(cmd + attr.replace('_', '-')) elif w_type: - key = "normal" + key = 'normal' if opr and key in w_type.keys() and not _is_w_same(w_type, h_type, key): - commands.append(cmd + attr.replace("_","-") + ' ' + key) - elif not opr and key in w_type.keys() and not (h_type and key in h_type.keys()): - commands.append(cmd + want['area'] + ' ' + attr.replace("_","-")) - - a_type = {'nssa': ("default_cost", "no_summary", "translate"), - 'stub': ("default_cost", "no_summary")} + if not w_type[key] and h_type and h_type[key]: + commands.append(cmd.replace('set', 'delete') + + attr.replace('_', '-') + + ' ' + key) + elif w_type[key]: + commands.append(cmd + attr.replace('_', '-') + + ' ' + key) + elif not opr and key in w_type.keys() and not (h_type + and key in h_type.keys()): + commands.append(cmd + want['area'] + + ' ' + attr.replace('_', '-')) + + a_type = {'nssa': ('set', 'default_cost', 'no_summary', 'translate'), + 'stub': ('set', 'default_cost', 'no_summary')} for key in a_type: w_area = want[attr].get(key) or {} h_area = {} if w_area: if h_type and key in h_type.keys(): h_area = h_type.get(key) or {} - for item, val in iteritems(w_type[key]): - if opr and item in a_type[key] and not _is_w_same(w_type[key], h_area, item): - commands.append(cmd + (attr.replace("_","-") + " " + key + " " + item.replace("_","-") + " " + str(val))) - elif not opr and item in a_type[key] and not (h_type and key in h_type): - commands.append(cmd + (want['area'] + ' ' + attr.replace("_","-") + " " + key + " " + item.replace("_","-"))) + for (item, val) in iteritems(w_type[key]): + if opr and item in a_type[key] \ + and not _is_w_same(w_type[key], h_area, item): + if item == 'set' and val: + commands.append(cmd + attr.replace('_', '-') + + ' ' + key) + elif not val and h_area and h_area[item]: + commands.append(cmd.replace('set', 'delete') + + attr.replace('_', '-') + + ' ' + key) + elif item != 'set': + commands.append(cmd + attr.replace('_', '-') + + ' ' + key + + ' ' + item.replace('_', '-') + + ' ' + str(val)) + elif not opr and item in a_type[key] \ + and not (h_type and key in h_type): + if item == 'set': + commands.append(cmd + attr.replace('_', '-') + + ' ' + key) + else: + commands.append(cmd + want['area'] + + ' ' + attr.replace('_', '-') + + ' ' + key + ' ' + item.replace('_', '-')) return commands def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): @@ -537,6 +692,7 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated command. """ + return self._compute_command(key, attr=self._map_attrib(attr), val=val, opr=opr) def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True): @@ -548,15 +704,16 @@ class Ospfv2(ConfigBase): :param opr: True/False. :return: generated command. """ + if remove or not opr: - cmd = "delete protocols ospf " + cmd = 'delete protocols ospf ' else: - cmd = "set protocols ospf " + cmd = 'set protocols ospf ' if key: - cmd += key.replace("_", "-") + " " + cmd += key.replace('_', '-') + ' ' if attr: - cmd += attr.replace("_", "-") - if val and opr: + cmd += attr.replace('_', '-') + if val: cmd += " '" + str(val) + "'" return cmd @@ -567,4 +724,6 @@ class Ospfv2(ConfigBase): :param attrib: attribute :return: regex string """ - return 'disable' if attrib =='disabled' else attrib.replace("_","-") + + return ('disable' if attrib == 'disabled' + else attrib.replace('_', '-')) diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py index 3457fac..0467b72 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -9,19 +9,29 @@ It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + from re import findall, search, M from copy import deepcopy -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( - utils, -) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import Ospfv2Args class Ospfv2Facts(object): + """ The vyos ospfv2 fact class """ - def __init__(self, module, subspec='config', options='options'): + def __init__( + self, + module, + subspec='config', + options='options', + ): + self._module = module self.argument_spec = Ospfv2Args.argument_spec spec = deepcopy(self.argument_spec) @@ -46,25 +56,22 @@ class Ospfv2Facts(object): :rtype: dictionary :returns: facts """ + if not data: data = self.get_device_data(connection) + # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead - objs = [] + + objs = {} ospfv2 = findall(r"^set protocols ospf (.+)", data, M) if ospfv2: - config = self.render_config(ospfv2) - if config: - objs.append(config) - ansible_facts["ansible_network_resources"].pop("ospfv2", None) + objs = self.render_config(ospfv2) facts = {} - if objs: - facts["ospfv2"] = [] - params = utils.validate_config(self.argument_spec, {"config": objs}) - for cfg in params["config"]: - facts["ospfv2"].append(utils.remove_empties(cfg)) - ansible_facts["ansible_network_resources"].update(facts) + params = utils.validate_config(self.argument_spec,{'config': objs}) + facts['ospfv2'] = utils.remove_empties(params['config']) + ansible_facts['ansible_network_resources'].update(facts) return ansible_facts def render_config(self, conf): @@ -74,37 +81,38 @@ class Ospfv2Facts(object): :param conf: The configuration :returns: The generated config """ - conf = "\n".join(filter(lambda x: x, conf)) - a_lst = ["default_metric", "log_adjacency_changes"] + + conf = '\n'.join(filter(lambda x: x, conf)) + a_lst = ['default_metric', 'log_adjacency_changes'] config = self.parse_attr(conf, a_lst) if not config: config = {} - config["timers"] = self.parse_timers(conf) - config["auto_cost"] = self.parse_auto_cost(conf) - config["distance"] = self.parse_distance(conf) - config["max_metric"] = self.parse_max_metric(conf) - config["mpls_te"] = self.parse_attrib(conf, "mpls_te", "mpls-te") - config["default_information"] = self.parse_def_info(conf) - config["parameters"] = self.parse_attrib(conf, "parameters", "parameters") - config["route_map"] = self.parse_leaf_list(conf, "route-map") - config["ospf_area"] = self.parse_attrib_list(conf, "area", "area") - config["neighbor"] = self.parse_attrib_list(conf, "neighbor", "neighbor_id") - config["passive_interface"] = self.parse_leaf_list(conf, "passive-interface") - config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type") - config["passive_interface_exclude"] = self.parse_leaf_list(conf, "passive-interface-exclude") + config['timers'] = self.parse_timers(conf) + config['auto_cost'] = self.parse_auto_cost(conf) + config['distance'] = self.parse_distance(conf) + config['max_metric'] = self.parse_max_metric(conf) + config['default_information'] = self.parse_def_info(conf) + config['route_map'] = self.parse_leaf_list(conf, 'route-map') + config['mpls_te'] = self.parse_attrib(conf, 'mpls_te', 'mpls-te') + config['areas'] = self.parse_attrib_list(conf, 'area', 'area_id') + config['parameters'] = self.parse_attrib(conf, 'parameters', 'parameters') + config['neighbor'] = self.parse_attrib_list(conf, 'neighbor', 'neighbor_id') + config['passive_interface'] = self.parse_leaf_list(conf, 'passive-interface') + config['redistribute'] = self.parse_attrib_list(conf, 'redistribute', 'route_type') + config['passive_interface_exclude'] = self.parse_leaf_list(conf, 'passive-interface-exclude') return config - def parse_timers(self, conf, attrib=None): + def parse_timers(self, conf): """ This function triggers the parsing of 'timers' attributes :param conf: configuration - :param attrib: attribute name :return: generated config dictionary """ + cfg_dict = {} - cfg_dict["refresh"] = self.parse_refresh(conf, "refresh") - cfg_dict["throttle"] = self.parse_throttle(conf, "spf") + cfg_dict['refresh'] = self.parse_refresh(conf, 'refresh') + cfg_dict['throttle'] = self.parse_throttle(conf, 'spf') return cfg_dict def parse_throttle(self, conf, attrib=None): @@ -114,6 +122,7 @@ class Ospfv2Facts(object): :param attrib: 'spf' :return: generated config dictionary """ + cfg_dict = {} cfg_dict[attrib] = self.parse_attrib(conf, attrib, match=attrib) return cfg_dict @@ -125,39 +134,9 @@ class Ospfv2Facts(object): :param attrib: 'refresh' :return: generated config dictionary """ - cfg_dict = self.parse_attr(conf, ["timers"], match=attrib) - return cfg_dict - def parse_attrib_list(self, conf, attrib, param): - """ - This function forms the regex to fetch the listed attributes - from config - :param conf: configuration data - :param attrib: attribute name - :param param: parameter data - :return: generated rule list configuration - """ - r_lst = [] - if attrib == "area": - items = findall(r"^" + attrib + " (?:\'*)(\S+)(?:\'*)", conf, M) - else: - items = findall(r"" + attrib + " (?:\'*)(\S+)(?:\'*)", conf, M) - if items: - a_lst = [] - for item in set(items): - i_regex = r" %s .+$" % item - cfg = "\n".join(findall(i_regex, conf, M)) - if attrib == 'area': - obj = self.parse_area(cfg, item) - elif attrib == 'virtual-link': - obj = self.parse_vlink(cfg) - else: - obj = self.parse_attrib(cfg, attrib) - obj[param] = item.strip("'") - if obj: - a_lst.append(obj) - r_lst = sorted(a_lst, key=lambda i: i[param]) - return r_lst + cfg_dict = self.parse_attr(conf, ['timers'], match=attrib) + return cfg_dict def parse_leaf_list(self, conf, attrib): """ @@ -167,8 +146,9 @@ class Ospfv2Facts(object): :param attrib: attribute name :return: generated rule list configuration """ + lst = [] - items = findall(r"^" + attrib + " (?:\'*)(\S+)(?:\'*)", conf, M) + items = findall(r"^" + attrib + " (?:\'*)(\\S+)(?:\'*)", conf, M) if items: for i in set(items): lst.append(i.strip("'")) @@ -181,8 +161,9 @@ class Ospfv2Facts(object): :param attrib: attribute name :return: generated config dictionary """ - cfg_dict = self.parse_attr(conf, ["global"], match=attrib) - cfg_dict["ospf"] = self.parse_ospf(conf, "ospf") + + cfg_dict = self.parse_attr(conf, ['global'], match=attrib) + cfg_dict['ospf'] = self.parse_ospf(conf, 'ospf') return cfg_dict def parse_ospf(self, conf, attrib=None): @@ -192,18 +173,19 @@ class Ospfv2Facts(object): :param attrib: 'ospf' :return: generated config dictionary """ + cfg_dict = self.parse_attrib(conf, 'ospf', match=attrib) return cfg_dict - def parse_max_metric(self, conf, attrib=None): + def parse_max_metric(self, conf): """ This function triggers the parsing of 'max_metric' attributes :param conf: configuration - :param attrib: attribute name :return: generated config dictionary """ + cfg_dict = {} - cfg_dict["router_lsa"] = self.parse_attrib(conf, "router_lsa", match="router-lsa") + cfg_dict['router_lsa'] = self.parse_attrib(conf, 'router_lsa', match='router-lsa') return cfg_dict def parse_auto_cost(self, conf, attrib=None): @@ -213,18 +195,20 @@ class Ospfv2Facts(object): :param attrib: attribute name :return: generated config dictionary """ - cfg_dict = self.parse_attr(conf, ["reference_bandwidth"], match=attrib) + + cfg_dict = self.parse_attr(conf, ['reference_bandwidth'], + match=attrib) return cfg_dict - def parse_def_info(self, conf, attrib=None): + def parse_def_info(self, conf): """ This function triggers the parsing of 'default_information' attributes :param conf: configuration - :param attrib: attribute name :return: generated config dictionary """ + cfg_dict = {} - cfg_dict["originate"] = self.parse_attrib(conf, "originate", "originate") + cfg_dict['originate'] = self.parse_attrib(conf, 'originate', 'originate') return cfg_dict def parse_area(self, conf, area_id): @@ -234,16 +218,28 @@ class Ospfv2Facts(object): :param area_id: area identity :return: generated rule configuration dictionary. """ - rule = self.parse_attrib(conf, "area", match=area_id) + + rule = self.parse_attrib(conf, 'area_id', match=area_id) r_sub = { - "area_type": self.parse_area_type(conf, "area-type"), - "network": self.parse_network(conf), - "range": self.parse_attrib_list(conf, "range", "address"), - "virtual_link": self.parse_attrib_list(conf, "virtual-link", "address") - } + 'area_type': self.parse_area_type(conf, 'area-type'), + 'network': self.parse_network(conf), + 'range': self.parse_attrib_list(conf, 'range', 'address'), + 'virtual_link': self.parse_attrib_list(conf, 'virtual-link', 'address'), + } rule.update(r_sub) return rule + def parse_key(self, conf, key_id): + """ + This function triggers the parsing of 'area' attributes. + :param conf: configuration data + :param area_id: area identity + :return: generated rule configuration dictionary. + """ + + rule = self.parse_attrib(conf, 'key_id', match=key_id) + return rule + def parse_area_type(self, conf, attrib=None): """ This function triggers the parsing of 'area_type' attributes @@ -251,9 +247,10 @@ class Ospfv2Facts(object): :param attrib: 'area-type' :return: generated config dictionary """ - cfg_dict = self.parse_attr(conf, ["normal"], match=attrib) - cfg_dict["nssa"] = self.parse_attrib(conf, "nssa") - cfg_dict["stub"] = self.parse_attrib(conf, "stub") + + cfg_dict = self.parse_attr(conf, ['normal'], match=attrib) + cfg_dict['nssa'] = self.parse_attrib(conf, 'nssa', match='nssa') + cfg_dict['stub'] = self.parse_attrib(conf, 'stub', match='stub') return cfg_dict def parse_network(self, conf): @@ -262,24 +259,27 @@ class Ospfv2Facts(object): :param conf: configuration data :return: generated rule list configuration """ + a_lst = [] applications = findall(r"network (.+)", conf, M) if applications: app_lst = [] for r in set(applications): - obj = {"address": r.strip("'")} + obj = {'address': r.strip("'")} app_lst.append(obj) - a_lst = sorted(app_lst, key=lambda i: i["address"]) + a_lst = sorted(app_lst, key=lambda i: i['address']) return a_lst def parse_vlink(self, conf): """ - This function triggers the parsing of 'vitual_link' attributes + This function triggers the parsing of 'virtual_link' attributes :param conf: configuration data :return: generated rule configuration dictionary """ + rule = self.parse_attrib(conf, 'vlink') - r_sub = {"authentication": self.parse_authentication(conf, "authentication")} + r_sub = {'authentication': self.parse_authentication(conf, + 'authentication')} rule.update(r_sub) return rule @@ -290,19 +290,49 @@ class Ospfv2Facts(object): :param attrib: 'authentication' :return: generated config dictionary """ - cfg_dict = self.parse_attr(conf, ["plaintext_password"], match=attrib) - cfg_dict["md5"] = self.parse_md5(conf, "md5") + + cfg_dict = self.parse_attr(conf, ['plaintext_password'], + match=attrib) + cfg_dict['md5'] = self.parse_attrib_list(conf, 'key-id', 'key_id') return cfg_dict - def parse_md5(self, conf, attrib=None): + def parse_attrib_list(self, conf, attrib, param): """ - This function triggers the parsing of 'md5' attributes - :param conf: configuration - :param attrib: 'md5' - :return: generated config dictionary + This function forms the regex to fetch the listed attributes + from config + :param conf: configuration data + :param attrib: attribute name + :param param: parameter data + :return: generated rule list configuration """ - cfg_dict = self.parse_attr(conf, ["key_id"], match=attrib) - return cfg_dict + + r_lst = [] + if attrib == 'area': + items = findall(r"^" + attrib.replace('_', '-') + + " (?:\'*)(\\S+)(?:\'*)", conf, M) + elif attrib == 'key-id': + items = findall(r"^.*" + attrib.replace('_', '-') + + " (?:\'*)(\\S+)(?:\'*)", conf, M) + else: + items = findall(r"" + attrib + " (?:\'*)(\\S+)(?:\'*)", conf, M) + if items: + a_lst = [] + for item in set(items): + i_regex = r" %s .+$" % item + cfg = '\n'.join(findall(i_regex, conf, M)) + if attrib == 'area': + obj = self.parse_area(cfg, item) + elif attrib == 'virtual-link': + obj = self.parse_vlink(cfg) + elif attrib == 'key-id': + obj = self.parse_key(cfg, item) + else: + obj = self.parse_attrib(cfg, attrib) + obj[param] = item.strip("'") + if obj: + a_lst.append(obj) + r_lst = sorted(a_lst, key=lambda i: i[param]) + return r_lst def parse_attrib(self, conf, param, match=None): """ @@ -310,21 +340,28 @@ class Ospfv2Facts(object): :param conf: configuration data :return: generated configuration dictionary """ + param_lst = { - 'stub': ["default_cost", "no_summary"], - 'area': ["shortcut", "authentication"], - 'mpls_te': ["enabled", "router_address"], - 'neighbor': ["priority", "poll_interval"], - 'ospf': ["external", "inter_area", "intra_area"], - 'nssa': ["translate", "default_cost", "no_summary"], - 'redistribute': ["metric", "metric_type", "route_map"], - 'spf': ["delay", "max_holdtime", "initial_holdtime"], - 'range': ["cost", "substitute", "not_advertise"], - 'originate': ["always", "metric", "metric_type", "route_map"], - 'router_lsa': ["administrative", "on_shutdown", "on_startup"], - 'config_routes': ["default_metric", "log_adjacency_changes"], - 'parameters': ["abr_type", "opaque_lsa", "router_id", "rfc1583_compatibility"], - 'vlink': ["dead_interval", "hello_interval", "transmit_delay", "retransmit_interval"] + 'key_id': ['md5_key'], + 'mpls_te': ['enabled', 'router_address'], + 'area_id': ['shortcut', 'authentication'], + 'neighbor': ['priority', 'poll_interval'], + 'stub': ['set', 'default_cost', 'no_summary'], + 'range': ['cost', 'substitute', 'not_advertise'], + 'ospf': ['external', 'inter_area', 'intra_area'], + 'spf': ['delay', 'max_holdtime', 'initial_holdtime'], + 'redistribute': ['metric', 'metric_type', 'route_map'], + 'nssa': ['set', 'translate', 'default_cost', 'no_summary'], + 'config_routes': ['default_metric', 'log_adjacency_changes' + ], + 'originate': ['always', 'metric', 'metric_type', 'route_map' + ], + 'router_lsa': ['administrative', 'on_shutdown', 'on_startup' + ], + 'parameters': ['abr_type', 'opaque_lsa', 'router_id', + 'rfc1583_compatibility'], + 'vlink': ['dead_interval', 'hello_interval', + 'transmit_delay', 'retransmit_interval'], } cfg_dict = self.parse_attr(conf, param_lst[param], match) return cfg_dict @@ -339,16 +376,21 @@ class Ospfv2Facts(object): :param match: parent node/attribute name. :return: generated config dictionary. """ + config = {} for attrib in attr_list: regex = self.map_regex(attrib) + if match: - regex = match.replace("_", "-") + " " + regex + regex = match.replace('_', '-') + ' ' + regex if conf: if self.is_bool(attrib): - out = conf.find(attrib.replace("_", "-")) - dis = conf.find(attrib.replace("_", "-") + " 'disable'") + out = conf.find(attrib.replace('_', '-')) + dis = conf.find(attrib.replace('_', '-') + + " 'disable'") if match: + if attrib == 'set' and conf.find(match) >= 1: + config[attrib] = True en = conf.find(match + " 'enable'") if out >= 1: if dis >= 1: @@ -358,7 +400,7 @@ class Ospfv2Facts(object): elif match and en >= 1: config[attrib] = True else: - out = search(r"^.*" + regex + " (.+)", conf, M) + out = search(r"^.*" + regex + ' (.+)', conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): @@ -373,7 +415,10 @@ class Ospfv2Facts(object): :param attrib: attribute :return: regex string """ - return 'disable' if attrib == "disabled" else 'enable' if attrib == "enabled" else attrib.replace("_","-") + + return ('disable' if attrib == 'disabled' else ('enable' + if attrib == 'enabled' else ('area' if attrib + == 'area_id' else attrib.replace('_', '-')))) def is_bool(self, attrib): """ @@ -381,7 +426,17 @@ class Ospfv2Facts(object): :param attrib: attribute. :return: True/False """ - bool_set = ("always", "normal", "enabled", "opaque_lsa", "not_advertise", "administrative", "rfc1583_compatibility") + + bool_set = ( + 'set', + 'always', + 'normal', + 'enabled', + 'opaque_lsa', + 'not_advertise', + 'administrative', + 'rfc1583_compatibility', + ) return True if attrib in bool_set else False def is_num(self, attrib): @@ -390,6 +445,20 @@ class Ospfv2Facts(object): :param attrib: attribute. :return: True/false. """ - num_set = ("ospf", "delay", "metric", "inter_area", "intra_area", "on_startup", "metric_type", "on_shutdown", - "max_holdtime", "default_metric", "initial_holdtime") + + num_set = ( + 'ospf', + 'delay', + 'metric', + 'inter_area', + 'intra_area', + 'on_startup', + 'metric_type', + 'on_shutdown', + 'max_holdtime', + 'poll_interval', + 'default_metric', + 'initial_holdtime', + 'key_id', + ) return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index c7dc575..c539beb 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -7,7 +7,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import q from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ( ipaddress, |