From 5c5a315a445fb08a4acdcc5c042d850bc1a53ac6 Mon Sep 17 00:00:00 2001 From: Xu Yuandong Date: Fri, 25 Oct 2019 18:40:30 +0800 Subject: [PATCH] a new module to manage static route and bfd. (#58004) * add a new module * update for shippable * update * update for shippable * update for shippable * update for shippable * update * add unittest. * update * update integration * update unittest * update * update unittest. * update for syntax error * update for reviewing. --- .../cloudengine/ce_static_route_bfd.py | 1595 +++++++++++++++++ .../ce_static_route_bfd/defaults/main.yaml | 3 + .../ce_static_route_bfd/tasks/main.yaml | 2 + .../ce_static_route_bfd/tasks/netconf.yaml | 17 + .../tests/netconf/ce_static_route_bfd.yaml | 150 ++ .../tests/netconf/cleanup.yaml | 31 + .../ce_static_route_bfd/result_ok.txt | 3 + .../ce_static_route_bfd/srBfdPara_1.txt | 18 + .../ce_static_route_bfd/srBfdPara_2.txt | 18 + .../ce_static_route_bfd/staticrtbase_1.txt | 18 + .../ce_static_route_bfd/staticrtbase_2.txt | 18 + .../cloudengine/test_ce_static_route_bfd.py | 102 ++ 12 files changed, 1975 insertions(+) create mode 100644 lib/ansible/modules/network/cloudengine/ce_static_route_bfd.py create mode 100644 test/integration/targets/ce_static_route_bfd/defaults/main.yaml create mode 100644 test/integration/targets/ce_static_route_bfd/tasks/main.yaml create mode 100644 test/integration/targets/ce_static_route_bfd/tasks/netconf.yaml create mode 100644 test/integration/targets/ce_static_route_bfd/tests/netconf/ce_static_route_bfd.yaml create mode 100644 test/integration/targets/ce_static_route_bfd/tests/netconf/cleanup.yaml create mode 100644 test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/result_ok.txt create mode 100644 test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_1.txt create mode 100644 test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_2.txt create mode 100644 test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_1.txt create mode 100644 test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_2.txt create mode 100644 test/units/modules/network/cloudengine/test_ce_static_route_bfd.py diff --git a/lib/ansible/modules/network/cloudengine/ce_static_route_bfd.py b/lib/ansible/modules/network/cloudengine/ce_static_route_bfd.py new file mode 100644 index 00000000000..eed59e8ca25 --- /dev/null +++ b/lib/ansible/modules/network/cloudengine/ce_static_route_bfd.py @@ -0,0 +1,1595 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: ce_static_route_bfd +version_added: "2.10" +short_description: Manages static route configuration on HUAWEI CloudEngine switches. +description: + - Manages the static routes on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - If no vrf is supplied, vrf is set to default. + If I(state=absent), the route configuration will be removed, regardless of the + non-required parameters. +options: + prefix: + description: + - Destination ip address of static route. + required: true + type: str + mask: + description: + - Destination ip mask of static route. + type: str + aftype: + description: + - Destination ip address family type of static route. + required: true + type: str + choices: ['v4','v6'] + next_hop: + description: + - Next hop address of static route. + type: str + nhp_interface: + description: + - Next hop interface full name of static route. + type: str + vrf: + description: + - VPN instance of destination ip address. + type: str + destvrf: + description: + - VPN instance of next hop ip address. + type: str + tag: + description: + - Route tag value (numeric). + type: int + description: + description: + - Name of the route. Used with the name parameter on the CLI. + type: str + pref: + description: + - Preference or administrative difference of route (range 1-255). + type: int + function_flag: + description: + - Used to distinguish between command line functions. + required: true + choices: ['globalBFD','singleBFD','dynamicBFD','staticBFD'] + type: str + min_tx_interval: + description: + - Set the minimum BFD session sending interval (range 50-1000). + type: int + min_rx_interval: + description: + - Set the minimum BFD receive interval (range 50-1000). + type: int + detect_multiplier: + description: + - Configure the BFD multiplier (range 3-50). + type: int + bfd_session_name: + description: + - bfd name (range 1-15). + type: str + commands: + description: + - Incoming command line is used to send sys,undo ip route-static default-bfd,commit. + type: list + state: + description: + - Specify desired state of the resource. + required: false + choices: ['present','absent'] + type: str + default: present +''' + +EXAMPLES = ''' + #ip route-static bfd interface-type interface-number nexthop-address [ local-address address ] + #[ min-rx-interval min-rx-interval | min-tx-interval min-tx-interval | detect-multiplier multiplier ] + - name: Config an ip route-static bfd 10GE1/0/1 3.3.3.3 min-rx-interval 50 min-tx-interval 50 detect-multiplier 5 + ce_static_route_bfd: + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.3 + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 5 + aftype: v4 + state: present + + #undo ip route-static bfd [ interface-type interface-number | vpn-instance vpn-instance-name ] nexthop-address + - name: undo ip route-static bfd 10GE1/0/1 3.3.3.4 + ce_static_route_bfd: + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.4 + aftype: v4 + state: absent + + #ip route-static default-bfd { min-rx-interval {min-rx-interval} | min-tx-interval {min-tx-interval} | detect-multiplier {multiplier}} + - name: Config an ip route-static default-bfd min-rx-interval 50 min-tx-interval 50 detect-multiplier 6 + ce_static_route_bfd: + function_flag: 'globalBFD' + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 6 + aftype: v4 + state: present + + - name: undo ip route-static default-bfd + ce_static_route_bfd: + function_flag: 'globalBFD' + aftype: v4 + state: absent + commands: 'sys,undo ip route-static default-bfd,commit' + + - name: Config an ipv4 static route 2.2.2.0/24 2.2.2.1 preference 1 tag 2 description test for staticBFD + ce_static_route_bfd: + function_flag: 'staticBFD' + prefix: 2.2.2.2 + mask: 24 + next_hop: 2.2.2.1 + tag: 2 + description: test + pref: 1 + aftype: v4 + bfd_session_name: btoa + state: present +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"function_flag": "staticBFD", "next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.642", "mask": "24", "description": "testing", + "vrf": "_public_", "bfd_session_name": "btoa"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {"function_flag": "", "next_hop": "", "pref": "101", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null", "bfd_session_name": "btoa"} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"function_flag": "staticBFD", "next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null", "bfd_session_name": "btoa"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["ip route-static 192.168.20.0 255.255.255.0 3.3.3.3 preference 100 description testing"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import string_types +from ansible.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_STATIC_ROUTE_BFD_SESSIONNAME = """ + + + + + + + + + + + + + + + + + + + + + + +""" +# bfd enable +CE_NC_GET_STATIC_ROUTE_BFD_ENABLE = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_BFD_ABSENT = """ + + + + + + %s + %s + %s + %s + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_BFD = """ + + + + + + %s + %s + %s + %s + + + + + + + + + +""" +CE_NC_GET_STATIC_ROUTE_IPV4_GLOBAL_BFD = """ + + + + + + + + + + + +""" +CE_NC_GET_STATIC_ROUTE_ABSENT = """ + + + + + + + + + + + + + + + + + + +""" + +CE_NC_DELETE_STATIC_ROUTE_SINGLEBFD = """ + + + + + %s + %s + %s + %s + + + + +""" +CE_NC_SET_STATIC_ROUTE_SINGLEBFD = """ + + + + + %s + %s + %s + %s%s%s%s%s + + + + + +""" +CE_NC_SET_STATIC_ROUTE_SINGLEBFD_LOCALADRESS = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD = """ + + + + %s%s%s + + + +""" + +CE_NC_SET_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s%s%s%s%s + + + + +""" +CE_NC_SET_DESCRIPTION = """ +%s +""" + +CE_NC_SET_PREFERENCE = """ +%s +""" + +CE_NC_SET_TAG = """ +%s +""" +CE_NC_SET_BFDSESSIONNAME = """ +%s +""" +CE_NC_SET_BFDENABLE = """ +true +""" +CE_NC_DELETE_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +def is_valid_v4addr(addr): + """check if ipv4 addr is valid""" + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + return False + + +def is_valid_v6addr(addr): + """check if ipv6 addr is valid""" + if addr.find(':') != -1: + addr_list = addr.split(':') + if len(addr_list) > 6: + return False + if addr_list[1] == "": + return False + return True + return False + + +def is_valid_tag(tag): + """check if the tag is valid""" + + if int(tag) < 1 or int(tag) > 4294967295: + return False + return True + + +def is_valid_bdf_interval(interval): + """check if the min_tx_interva,min-rx-interval is valid""" + + if interval < 50 or interval > 1000: + return False + return True + + +def is_valid_bdf_multiplier(multiplier): + """check if the detect_multiplier is valid""" + + if multiplier < 3 or multiplier > 50: + return False + return True + + +def is_valid_bdf_session_name(session_name): + """check if the bfd_session_name is valid""" + if session_name.find(' ') != -1: + return False + if len(session_name) < 1 or len(session_name) > 15: + return False + return True + + +def is_valid_preference(pref): + """check if the preference is valid""" + + if int(pref) > 0 and int(pref) < 256: + return True + return False + + +def is_valid_description(description): + """check if the description is valid""" + if description.find('?') != -1: + return False + if len(description) < 1 or len(description) > 255: + return False + return True + + +def compare_command(commands): + """check if the commands is valid""" + if len(commands) < 3: + return True + if commands[0] != 'sys' or commands[1] != 'undo ip route-static default-bfd' \ + or commands[2] != 'commit': + return True + + +def get_to_lines(stdout): + """data conversion""" + lines = list() + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + lines.append(item) + return lines + + +def get_change_state(oldvalue, newvalue, change): + """get change state""" + if newvalue is not None: + if oldvalue != str(newvalue): + change = True + else: + if oldvalue != newvalue: + change = True + return change + + +def get_xml(xml, value): + """operate xml""" + if value is None: + value = '' + else: + value = value + tempxml = xml % value + return tempxml + + +class StaticRouteBFD(object): + """static route module""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self._initmodule_() + + # static route info + self.function_flag = self.module.params['function_flag'] + self.aftype = self.module.params['aftype'] + self.state = self.module.params['state'] + if self.aftype == "v4": + self.version = "ipv4unicast" + else: + self.version = "ipv6unicast" + if self.function_flag != 'globalBFD': + self.nhp_interface = self.module.params['nhp_interface'] + if self.nhp_interface is None: + self.nhp_interface = "Invalid0" + + self.destvrf = self.module.params['destvrf'] + if self.destvrf is None: + self.destvrf = "_public_" + + self.next_hop = self.module.params['next_hop'] + self.prefix = self.module.params['prefix'] + + if self.function_flag != 'globalBFD' and self.function_flag != 'singleBFD': + self.mask = self.module.params['mask'] + self.tag = self.module.params['tag'] + self.description = self.module.params['description'] + self.pref = self.module.params['pref'] + if self.pref is None: + self.pref = 60 + # vpn instance info + self.vrf = self.module.params['vrf'] + if self.vrf is None: + self.vrf = "_public_" + # bfd session name + self.bfd_session_name = self.module.params['bfd_session_name'] + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + self.min_tx_interval = self.module.params['min_tx_interval'] + self.min_rx_interval = self.module.params['min_rx_interval'] + self.detect_multiplier = self.module.params['detect_multiplier'] + if self.function_flag == 'globalBFD' and self.state == 'absent': + self.commands = self.module.params['commands'] + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.static_routes_info = dict() + + def _initmodule_(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=False) + + def _checkresponse_(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def _convertlentomask_(self, masklen): + """convert mask length to ip address mask, i.e. 24 to 255.255.255.0""" + + mask_int = ["0"] * 4 + length = int(masklen) + + if length > 32: + self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') + if length < 8: + mask_int[0] = str(int((0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '255' + mask_int[1] = str(int((0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '255' + mask_int[2] = str(int((0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '255' + mask_int[3] = str(int((0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '255' + + return '.'.join(mask_int) + + def _convertipprefix_(self): + """convert prefix to real value i.e. 2.2.2.2/24 to 2.2.2.0/24""" + if self.function_flag == 'singleBFD': + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + else: + if self.prefix.find(':') == -1: + return False + else: + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + if self.mask == '32': + self.prefix = self.prefix + return True + if self.mask == '0': + self.prefix = '0.0.0.0' + return True + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + byte_len = 8 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + else: + if self.prefix.find(':') == -1: + return False + if self.mask == '128': + self.prefix = self.prefix + return True + if self.mask == '0': + self.prefix = '::' + return True + addr_list = self.prefix.split(':') + length = len(addr_list) + if length > 6: + return False + byte_len = 16 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + + if self.aftype == "v4": + for i in range(ip_len + 1, length): + addr_list[i] = 0 + else: + for i in range(length - ip_len, length): + addr_list[i] = 0 + for j in range(0, byte_len - ip_bit): + if self.aftype == "v4": + addr_list[ip_len] = int(addr_list[ip_len]) & (0 << j) + else: + if addr_list[length - ip_len - 1] == "": + continue + addr_list[length - ip_len - + 1] = '0x%s' % addr_list[length - ip_len - 1] + addr_list[length - ip_len - + 1] = int(addr_list[length - ip_len - 1], 16) & (0 << j) + + if self.aftype == "v4": + self.prefix = '%s.%s.%s.%s' % (addr_list[0], addr_list[1], addr_list[2], addr_list[3]) + return True + if self.aftype == "v6": + ipv6_addr_str = "" + for num in range(0, length - ip_len): + ipv6_addr_str += '%s:' % addr_list[num] + self.prefix = ipv6_addr_str + + return True + + def set_update_cmd_globalbfd(self): + """set globalBFD update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('ip route-static default-bfd') + if self.min_tx_interval: + self.updates_cmd.append(' min-rx-interval %s' % (self.min_tx_interval)) + if self.min_rx_interval: + self.updates_cmd.append(' min-tx-interval %s' % (self.min_rx_interval)) + if self.detect_multiplier: + self.updates_cmd.append(' detect-multiplier %s' % (self.detect_multiplier)) + else: + self.updates_cmd.append('undo ip route-static default-bfd') + + def set_update_cmd_singlebfd(self): + """set singleBFD update command""" + if not self.changed: + return + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.prefix == "0.0.0.0": + prefix = '' + else: + prefix = self.prefix + if self.state == "present": + if nhp_interface: + self.updates_cmd.append('ip route-static bfd %s %s' % (nhp_interface, next_hop)) + elif destvrf: + self.updates_cmd.append('ip route-static bfd vpn-instance %s %s' % (destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static bfd %s' % (next_hop)) + if prefix: + self.updates_cmd.append(' local-address %s' % (self.prefix)) + if self.min_tx_interval: + self.updates_cmd.append(' min-rx-interval %s' % (self.min_tx_interval)) + if self.min_rx_interval: + self.updates_cmd.append(' min-tx-interval %s' % (self.min_rx_interval)) + if self.detect_multiplier: + self.updates_cmd.append(' detect-multiplier %s' % (self.detect_multiplier)) + else: + if nhp_interface: + self.updates_cmd.append('undo ip route-static bfd %s %s' % (nhp_interface, next_hop)) + elif destvrf: + self.updates_cmd.append('undo ip route-static bfd vpn-instance %s %s' % (destvrf, next_hop)) + else: + self.updates_cmd.append('undo ip route-static bfd %s' % (next_hop)) + + def set_update_cmd(self): + """set update command""" + if not self.changed: + return + + if self.aftype == "v4": + maskstr = self._convertlentomask_(self.mask) + else: + maskstr = self.mask + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + if self.vrf == "_public_": + vrf = '' + else: + vrf = self.vrf + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.state == "present": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('ip route-static vpn-instance %s %s %s vpn-instance %s %s' + % (vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static vpn-instance %s %s %s %s %s' + % (vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('ip route-static %s %s vpn-instance %s %s' + % (self.prefix, maskstr, self.destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static %s %s %s %s' + % (self.prefix, maskstr, nhp_interface, next_hop)) + if self.pref != 60: + self.updates_cmd.append(' preference %s' % (self.pref)) + if self.tag: + self.updates_cmd.append(' tag %s' % (self.tag)) + if not static_bfd_flag: + self.updates_cmd.append(' track bfd-session %s' % (self.bfd_session_name)) + else: + self.updates_cmd.append(' bfd enable') + if self.description: + self.updates_cmd.append(' description %s' % (self.description)) + + if self.state == "absent": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('undo ip route-static vpn-instance %s %s %s vpn-instance %s %s' + % (vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('undo ip route-static vpn-instance %s %s %s %s %s' + % (vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('undo ip route-static %s %s vpn-instance %s %s' + % (self.prefix, maskstr, self.destvrf, self.next_hop)) + else: + self.updates_cmd.append('undo ip route-static %s %s %s %s' + % (self.prefix, maskstr, nhp_interface, next_hop)) + + def operate_static_route_globalbfd(self): + """set globalbfd update command""" + min_tx_interval = self.min_tx_interval + min_rx_interval = self.min_rx_interval + multiplier = self.detect_multiplier + min_tx_interval_xml = """\n""" + min_rx_interval_xml = """\n""" + multiplier_xml = """\n""" + if self.state == "present": + if min_tx_interval is not None: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % min_tx_interval + if min_rx_interval is not None: + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % min_rx_interval + if multiplier is not None: + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % multiplier + + configxmlstr = CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD % ( + min_tx_interval_xml, min_rx_interval_xml, multiplier_xml) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_globalBFD") + + if self.state == "absent" and self.commands: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % 1000 + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % 1000 + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % 3 + + configxmlstr = CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD % ( + min_tx_interval_xml, min_rx_interval_xml, multiplier_xml) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_globalBFD") + + def operate_static_route_singlebfd(self, version, prefix, nhp_interface, next_hop, destvrf, state): + """operate ipv4 static route singleBFD""" + min_tx_interval = self.min_tx_interval + min_rx_interval = self.min_rx_interval + multiplier = self.detect_multiplier + min_tx_interval_xml = """\n""" + min_rx_interval_xml = """\n""" + multiplier_xml = """\n""" + local_address_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + + if nhp_interface is None: + nhp_interface = "Invalid0" + + if min_tx_interval is not None: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % min_tx_interval + if min_rx_interval is not None: + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % min_rx_interval + if multiplier is not None: + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % multiplier + + if prefix is not None: + local_address_xml = CE_NC_SET_STATIC_ROUTE_SINGLEBFD_LOCALADRESS % prefix + + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE_SINGLEBFD % ( + version, nhp_interface, dest_vpn_instance, + next_hop, local_address_xml, min_tx_interval_xml, + min_rx_interval_xml, multiplier_xml) + + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE_SINGLEBFD % ( + version, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_singleBFD") + + def operate_static_route(self, version, prefix, mask, nhp_interface, next_hop, vrf, destvrf, state): + """operate ipv4 static route""" + description_xml = """\n""" + preference_xml = """\n""" + tag_xml = """\n""" + bfd_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + if nhp_interface is None: + nhp_interface = "Invalid0" + + if vrf is None: + vpn_instance = "_public_" + else: + vpn_instance = vrf + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + + description_xml = get_xml(CE_NC_SET_DESCRIPTION, self.description) + + preference_xml = get_xml(CE_NC_SET_PREFERENCE, self.pref) + + tag_xml = get_xml(CE_NC_SET_TAG, self.tag) + + if self.function_flag == 'staticBFD': + if self.bfd_session_name: + bfd_xml = CE_NC_SET_BFDSESSIONNAME % self.bfd_session_name + else: + bfd_xml = CE_NC_SET_BFDENABLE + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, + dest_vpn_instance, next_hop, description_xml, preference_xml, tag_xml, bfd_xml) + + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE") + + def get_change_state_global_bfd(self): + """get ipv4 global bfd change state""" + + self.get_global_bfd(self.state) + change = False + if self.state == "present": + if self.static_routes_info["sroute_global_bfd"]: + for static_route in self.static_routes_info["sroute_global_bfd"]: + if static_route is not None: + if self.min_tx_interval is not None: + if int(static_route["minTxInterval"]) != self.min_tx_interval: + change = True + if self.min_rx_interval is not None: + if int(static_route["minRxInterval"]) != self.min_rx_interval: + change = True + if self.detect_multiplier is not None: + if int(static_route["multiplier"]) != self.detect_multiplier: + change = True + return change + else: + continue + else: + change = True + else: + if self.commands: + if self.static_routes_info["sroute_global_bfd"]: + for static_route in self.static_routes_info["sroute_global_bfd"]: + if static_route is not None: + if int(static_route["minTxInterval"]) != 1000 or \ + int(static_route["minRxInterval"]) != 1000 or \ + int(static_route["multiplier"]) != 3: + change = True + return change + + def get_global_bfd(self, state): + """get ipv4 global bfd""" + + self.static_routes_info["sroute_global_bfd"] = list() + + getglobalbfdxmlstr = None + if self.aftype == 'v4': + getglobalbfdxmlstr = CE_NC_GET_STATIC_ROUTE_IPV4_GLOBAL_BFD + + if getglobalbfdxmlstr is not None: + xml_global_bfd_str = get_nc_config(self.module, getglobalbfdxmlstr) + + if 'data/' in xml_global_bfd_str: + return + + xml_global_bfd_str = xml_global_bfd_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_global_bfd_str) + static_routes_global_bfd = root.findall( + "staticrt/staticrtbase/srIPv4StaticSite") + + if static_routes_global_bfd: + for static_route in static_routes_global_bfd: + static_info = dict() + for static_ele in static_route: + if static_ele.tag == "minTxInterval": + if static_ele.text is not None: + static_info["minTxInterval"] = static_ele.text + if static_ele.tag == "minRxInterval": + if static_ele.text is not None: + static_info["minRxInterval"] = static_ele.text + if static_ele.tag == "multiplier": + if static_ele.text is not None: + static_info["multiplier"] = static_ele.text + + self.static_routes_info["sroute_global_bfd"].append(static_info) + + def get_change_state_single_bfd(self): + """get ipv4 single bfd change state""" + + self.get_single_bfd(self.state) + change = False + version = self.version + if self.state == 'present': + if self.static_routes_info["sroute_single_bfd"]: + for static_route in self.static_routes_info["sroute_single_bfd"]: + if static_route is not None and static_route['afType'] == version: + if self.nhp_interface: + if static_route["ifName"].lower() != self.nhp_interface.lower(): + change = True + if self.destvrf: + if static_route["destVrfName"].lower() != self.destvrf.lower(): + change = True + if self.next_hop: + if static_route["nexthop"].lower() != self.next_hop.lower(): + change = True + if self.prefix: + if static_route["localAddress"].lower() != self.prefix.lower(): + change = True + if self.min_tx_interval: + if int(static_route["minTxInterval"]) != self.min_tx_interval: + change = True + if self.min_rx_interval: + if int(static_route["minRxInterval"]) != self.min_rx_interval: + change = True + if self.detect_multiplier: + if int(static_route["multiplier"]) != self.detect_multiplier: + change = True + return change + + else: + continue + else: + change = True + else: + for static_route in self.static_routes_info["sroute_single_bfd"]: + # undo ip route-static bfd [ interface-type interface-number | + # vpn-instance vpn-instance-name ] nexthop-address + + if static_route["ifName"] and self.nhp_interface: + if static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + + if static_route["destVrfName"] and self.destvrf: + if static_route["destVrfName"].lower() == self.destvrf.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + + if static_route["nexthop"] and self.next_hop: + if static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_single_bfd(self, state): + """get ipv4 sigle bfd""" + self.static_routes_info["sroute_single_bfd"] = list() + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + if state == 'absent': + getbfdxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_ABSENT % ( + version, self.nhp_interface, self.destvrf, self.next_hop) + else: + getbfdxmlstr = CE_NC_GET_STATIC_ROUTE_BFD % ( + version, self.nhp_interface, self.destvrf, self.next_hop) + xml_bfd_str = get_nc_config(self.module, getbfdxmlstr) + + if 'data/' in xml_bfd_str: + return + xml_bfd_str = xml_bfd_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_bfd_str) + static_routes_bfd = root.findall( + "staticrt/staticrtbase/srBfdParas/srBfdPara") + if static_routes_bfd: + for static_route in static_routes_bfd: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["afType", "destVrfName", "nexthop", "ifName"]: + static_info[static_ele.tag] = static_ele.text + if static_ele.tag == "localAddress": + if static_ele.text is not None: + static_info["localAddress"] = static_ele.text + else: + static_info["localAddress"] = "None" + if static_ele.tag == "minTxInterval": + if static_ele.text is not None: + static_info["minTxInterval"] = static_ele.text + if static_ele.tag == "minRxInterval": + if static_ele.text is not None: + static_info["minRxInterval"] = static_ele.text + if static_ele.tag == "multiplier": + if static_ele.text is not None: + static_info["multiplier"] = static_ele.text + self.static_routes_info["sroute_single_bfd"].append(static_info) + + def get_static_route(self, state): + """get ipv4 static route about BFD""" + self.static_routes_info["sroute"] = list() + # Increase the parameter used to distinguish whether the incoming bfdSessionName + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + + if state == 'absent': + getxmlstr = CE_NC_GET_STATIC_ROUTE_ABSENT + else: + # self.static_bfd_flag is true + if static_bfd_flag: + getxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_ENABLE + + else: + getxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_SESSIONNAME + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + static_routes = root.findall( + "staticrt/staticrtbase/srRoutes/srRoute") + + if static_routes: + for static_route in static_routes: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["vrfName", "afType", "topologyName", + "prefix", "maskLength", "destVrfName", + "nexthop", "ifName", "preference", "description"]: + static_info[static_ele.tag] = static_ele.text + if static_ele.tag == "tag": + if static_ele.text is not None: + static_info["tag"] = static_ele.text + else: + static_info["tag"] = "None" + if static_bfd_flag: + if static_ele.tag == "bfdEnable": + if static_ele.text is not None: + static_info["bfdEnable"] = static_ele.text + else: + static_info["bfdEnable"] = "None" + else: + if static_ele.tag == "sessionName": + if static_ele.text is not None: + static_info["sessionName"] = static_ele.text + else: + static_info["sessionName"] = "None" + self.static_routes_info["sroute"].append(static_info) + + def _checkparams_(self): + """check all input params""" + if self.function_flag == 'singleBFD': + if not self.next_hop: + self.module.fail_json(msg='Error: missing required argument: next_hop.') + if self.state != 'absent': + if self.nhp_interface == "Invalid0" and (not self.prefix or self.prefix == '0.0.0.0'): + self.module.fail_json(msg='Error: If a nhp_interface is not configured, ' + 'the prefix must be configured.') + + if self.function_flag != 'globalBFD': + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if not self.mask: + self.module.fail_json(msg='Error: missing required argument: mask.') + # check prefix and mask + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: Mask is invalid.') + if self.function_flag != 'singleBFD' or (self.function_flag == 'singleBFD' and self.destvrf != "_public_"): + if not self.prefix: + self.module.fail_json(msg='Error: missing required argument: prefix.') + # convert prefix + if not self._convertipprefix_(): + self.module.fail_json(msg='Error: The %s is not a valid address' % self.prefix) + + if self.nhp_interface != "Invalid0" and self.destvrf != "_public_": + self.module.fail_json(msg='Error: Destination vrf dose not support next hop is interface.') + + if not self.next_hop and self.nhp_interface == "Invalid0": + self.module.fail_json(msg='Error: one of the following is required: next_hop,nhp_interface.') + + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + # description check + if self.description: + if not is_valid_description(self.description): + self.module.fail_json( + msg='Error: Dsecription length should be 1 - 35, and can not contain "?".') + # tag check + if self.tag is not None: + if not is_valid_tag(self.tag): + self.module.fail_json( + msg='Error: Tag should be integer 1 - 4294967295.') + # preference check + if self.pref is not None: + if not is_valid_preference(self.pref): + self.module.fail_json( + msg='Error: Preference should be integer 1 - 255.') + + if self.function_flag == 'staticBFD': + if self.bfd_session_name: + if not is_valid_bdf_session_name(self.bfd_session_name): + self.module.fail_json( + msg='Error: bfd_session_name length should be 1 - 15, and can not contain Space.') + + # ipv4 check + if self.aftype == "v4": + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if int(self.mask) > 32 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv4 mask must be an integer between 1 and 32.') + # next_hop check + if self.function_flag != 'globalBFD': + if self.next_hop: + if not is_valid_v4addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.next_hop) + # ipv6 check + if self.aftype == "v6": + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if int(self.mask) > 128 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv6 mask must be an integer between 1 and 128.') + if self.function_flag != 'globalBFD': + if self.next_hop: + if not is_valid_v6addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.next_hop) + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + # BFD prarams + if self.min_tx_interval: + if not is_valid_bdf_interval(self.min_tx_interval): + self.module.fail_json( + msg='Error: min_tx_interval should be integer 50 - 1000.') + if self.min_rx_interval: + if not is_valid_bdf_interval(self.min_rx_interval): + self.module.fail_json( + msg='Error: min_rx_interval should be integer 50 - 1000.') + if self.detect_multiplier: + if not is_valid_bdf_multiplier(self.detect_multiplier): + self.module.fail_json( + msg='Error: detect_multiplier should be integer 3 - 50.') + + if self.function_flag == 'globalBFD': + if self.state != 'absent': + if not self.min_tx_interval and not self.min_rx_interval and not self.detect_multiplier: + self.module.fail_json( + msg='Error: one of the following is required: min_tx_interval,' + 'detect_multiplier,min_rx_interval.') + else: + if not self.commands: + self.module.fail_json( + msg='Error: missing required argument: command.') + if compare_command(self.commands): + self.module.fail_json( + msg='Error: The command %s line is incorrect.' % (',').join(self.commands)) + + def set_ip_static_route_globalbfd(self): + """set ip static route globalBFD""" + if not self.changed: + return + if self.aftype == "v4": + self.operate_static_route_globalbfd() + + def set_ip_static_route_singlebfd(self): + """set ip static route singleBFD""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route_singlebfd(version, self.prefix, self.nhp_interface, + self.next_hop, self.destvrf, self.state) + + def set_ip_static_route(self): + """set ip static route""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route(version, self.prefix, self.mask, self.nhp_interface, + self.next_hop, self.vrf, self.destvrf, self.state) + + def is_prefix_exist(self, static_route, version): + """is prefix mask nex_thop exist""" + if static_route is None: + return False + if self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if self.next_hop and not self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if not self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() + + def get_ip_static_route(self): + """get ip static route""" + change = False + version = self.version + self.get_static_route(self.state) + change_list = list() + if self.state == 'present': + for static_route in self.static_routes_info["sroute"]: + if self.is_prefix_exist(static_route, self.version): + info_dict = dict() + exist_dict = dict() + if self.vrf: + info_dict["vrfName"] = self.vrf + exist_dict["vrfName"] = static_route["vrfName"] + if self.destvrf: + info_dict["destVrfName"] = self.destvrf + exist_dict["destVrfName"] = static_route["destVrfName"] + if self.description: + info_dict["description"] = self.description + exist_dict["description"] = static_route["description"] + if self.tag: + info_dict["tag"] = self.tag + exist_dict["tag"] = static_route["tag"] + if self.pref: + info_dict["preference"] = str(self.pref) + exist_dict["preference"] = static_route["preference"] + if self.nhp_interface: + if self.nhp_interface.lower() == "invalid0": + info_dict["ifName"] = "Invalid0" + else: + info_dict["ifName"] = "Invalid0" + exist_dict["ifName"] = static_route["ifName"] + if self.next_hop: + info_dict["nexthop"] = self.next_hop + exist_dict["nexthop"] = static_route["nexthop"] + + if self.bfd_session_name: + info_dict["bfdEnable"] = 'true' + + else: + info_dict["bfdEnable"] = 'false' + exist_dict["bfdEnable"] = static_route["bfdEnable"] + + if exist_dict != info_dict: + change = True + else: + change = False + change_list.append(change) + + if False in change_list: + change = False + else: + change = True + return change + + else: + for static_route in self.static_routes_info["sroute"]: + if static_route["nexthop"] and self.next_hop: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + if static_route["ifName"] and self.nhp_interface: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_proposed(self): + """get proposed information""" + self.proposed['afType'] = self.aftype + self.proposed['state'] = self.state + if self.function_flag != 'globalBFD': + self.proposed['ifName'] = self.nhp_interface + self.proposed['destVrfName'] = self.destvrf + self.proposed['next_hop'] = self.next_hop + + if self.function_flag == 'singleBFD': + if self.prefix: + self.proposed['localAddress'] = self.prefix + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + self.proposed['minTxInterval'] = self.min_tx_interval + self.proposed['minRxInterval'] = self.min_rx_interval + self.proposed['multiplier'] = self.detect_multiplier + + if self.function_flag != 'globalBFD' and self.function_flag != 'singleBFD': + self.proposed['prefix'] = self.prefix + self.proposed['mask'] = self.mask + self.proposed['vrfName'] = self.vrf + if self.tag: + self.proposed['tag'] = self.tag + if self.description: + self.proposed['description'] = self.description + if self.pref is None: + self.proposed['preference'] = 60 + else: + self.proposed['preference'] = self.pref + + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + if not static_bfd_flag: + self.proposed['sessionName'] = self.bfd_session_name + else: + self.proposed['bfdEnable'] = 'true' + + def get_existing(self): + """get existing information""" + # globalBFD + if self.function_flag == 'globalBFD': + change = self.get_change_state_global_bfd() + self.existing['sroute_global_bfd'] = self.static_routes_info["sroute_global_bfd"] + # singleBFD + elif self.function_flag == 'singleBFD': + change = self.get_change_state_single_bfd() + self.existing['sroute_single_bfd'] = self.static_routes_info["sroute_single_bfd"] + # dynamicBFD / staticBFD + else: + change = self.get_ip_static_route() + self.existing['static_sroute'] = self.static_routes_info["sroute"] + self.changed = bool(change) + + def get_end_state(self): + """get end state information""" + + # globalBFD + if self.function_flag == 'globalBFD': + self.get_global_bfd(self.state) + self.end_state['sroute_global_bfd'] = self.static_routes_info["sroute_global_bfd"] + # singleBFD + elif self.function_flag == 'singleBFD': + self.static_routes_info["sroute_single_bfd"] = list() + self.get_single_bfd(self.state) + self.end_state['sroute_single_bfd'] = self.static_routes_info["sroute_single_bfd"] + # dynamicBFD / staticBFD + else: + self.get_static_route(self.state) + self.end_state['static_sroute'] = self.static_routes_info["sroute"] + + def work(self): + """worker""" + self._checkparams_() + self.get_existing() + self.get_proposed() + + if self.function_flag == 'globalBFD': + self.set_ip_static_route_globalbfd() + self.set_update_cmd_globalbfd() + elif self.function_flag == 'singleBFD': + self.set_ip_static_route_singlebfd() + self.set_update_cmd_singlebfd() + else: + self.set_ip_static_route() + self.set_update_cmd() + + self.get_end_state() + if self.existing == self.end_state: + self.changed = False + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + prefix=dict(type='str'), + mask=dict(type='str'), + aftype=dict(choices=['v4', 'v6'], required=True), + next_hop=dict(type='str'), + nhp_interface=dict(type='str'), + vrf=dict(type='str'), + destvrf=dict(type='str'), + tag=dict(type='int'), + description=dict(type='str'), + pref=dict(type='int'), + # bfd + function_flag=dict(required=True, choices=['globalBFD', 'singleBFD', 'dynamicBFD', 'staticBFD']), + min_tx_interval=dict(type='int'), + min_rx_interval=dict(type='int'), + detect_multiplier=dict(type='int'), + # bfd session name + bfd_session_name=dict(type='str'), + commands=dict(type='list', required=False), + state=dict(choices=['absent', 'present'], default='present', required=False), + ) + interface = StaticRouteBFD(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/ce_static_route_bfd/defaults/main.yaml b/test/integration/targets/ce_static_route_bfd/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/ce_static_route_bfd/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/ce_static_route_bfd/tasks/main.yaml b/test/integration/targets/ce_static_route_bfd/tasks/main.yaml new file mode 100644 index 00000000000..cc27f174fd8 --- /dev/null +++ b/test/integration/targets/ce_static_route_bfd/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/ce_static_route_bfd/tasks/netconf.yaml b/test/integration/targets/ce_static_route_bfd/tasks/netconf.yaml new file mode 100644 index 00000000000..73b91adfaa2 --- /dev/null +++ b/test/integration/targets/ce_static_route_bfd/tasks/netconf.yaml @@ -0,0 +1,17 @@ +--- +- name: collect all netconf test cases + find: + paths: "{{ role_path }}/tests/netconf" + patterns: "{{ testcase }}.yaml" + use_regex: true + connection: local + register: test_cases + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case (connection=netconf) + include: "{{ test_case_to_run }} ansible_connection=netconf" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/ce_static_route_bfd/tests/netconf/ce_static_route_bfd.yaml b/test/integration/targets/ce_static_route_bfd/tests/netconf/ce_static_route_bfd.yaml new file mode 100644 index 00000000000..91a6b15c9ff --- /dev/null +++ b/test/integration/targets/ce_static_route_bfd/tests/netconf/ce_static_route_bfd.yaml @@ -0,0 +1,150 @@ +--- +- debug: + msg: "START ce_static_route_bfd presented integration tests on connection={{ ansible_connection }}" +- include_tasks: cleanup.yaml +- name: Config an ip route-static bfd 10GE1/0/1 3.3.3.3 min-rx-interval 50 min-tx-interval 50 detect-multiplier 5 + ce_static_route_bfd: &merge + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.3 + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 5 + aftype: v4 + state: present + register: result1 +- name: (repeat)Config an ip route-static bfd 10GE1/0/1 3.3.3.3 min-rx-interval 50 min-tx-interval 50 detect-multiplier 5 + ce_static_route_bfd: + <<: *merge + register: result2 + +- name: Assert the configuration is reflected on host + assert: + that: + - "result1['changed'] == true" + - "result2['changed'] == false" + + +# ip route-static bfd [ interface-type interface-number | vpn-instance vpn-instance-name ] nexthop-address +- name: ip route-static bfd 10GE1/0/1 3.3.3.4 + ce_static_route_bfd: &merge + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.4 + aftype: v4 + register: result1 +- name: (repeat)ip route-static bfd 10GE1/0/1 3.3.3.4 + ce_static_route_bfd: + <<: *merge + register: result2 +- name: Assert the configuration is reflected on host + assert: + that: + - "result1['changed'] == true" + - "result2['changed'] == false" +#ip route-static default-bfd { min-rx-interval {min-rx-interval} | min-tx-interval {min-tx-interval} | detect-multiplier {multiplier}} +- name: Config an ip route-static default-bfd min-rx-interval 50 min-tx-interval 50 detect-multiplier 6 + ce_static_route_bfd: &merge + function_flag: 'globalBFD' + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 6 + aftype: v4 + state: present + register: result1 +- name: (repeat)Config an ip route-static default-bfd min-rx-interval 50 min-tx-interval 50 detect-multiplier 6 + ce_static_route_bfd: + <<: *merge + register: result2 +- name: Assert the configuration is reflected on host + assert: + that: + - "result1['changed'] == true" + - "result2['changed'] == false" + +- name: undo ip route-static default-bfd + ce_static_route_bfd: &merge + function_flag: 'globalBFD' + aftype: v4 + state: absent + commands: 'sys,undo ip route-static default-bfd,commit' + register: result1 +- name: (repeat)undo ip route-static default-bfd + ce_static_route_bfd: + <<: *merge + register: result2 +- name: Assert the configuration is reflected on host + assert: + that: + - "result1['changed'] == true" + - "result2['changed'] == false" + +- name: Config an ipv4 static route 2.2.2.0/24 2.2.2.1 preference 1 tag 2 description test for staticBFD + ce_static_route_bfd: &merge + function_flag: 'staticBFD' + prefix: 2.2.2.2 + mask: 24 + next_hop: 2.2.2.1 + tag: 2 + description: test + pref: 1 + aftype: v4 + bfd_session_name: btoa + state: present + register: result1 +- name: (repeat) Config an ipv4 static route 2.2.2.0/24 2.2.2.1 preference 1 tag 2 description test for staticBFD + ce_static_route_bfd: + <<: *merge + register: result2 +- name: Assert the configuration is reflected on host + assert: + that: + - "result1['changed'] == true" + - "result2['changed'] == false" + +- name: Get lacp config by ce_netconf. + ce_netconf: + rpc: get + cfg_xml: " + + + + + + + + + + + + + + + + + + + " + register: result_present + +- name: Assert that the previous task was idempotent + assert: + that: + - "'v4' == result_present.end_state.result" + - "'10GE1/0/1' == result_present.end_state.result" + - "'Fast' == result_present.end_state.result" + - "'__publiv__' == result_present.end_state.result" + - "'Prority' == result_present.end_state.result" + - "'2.2.2.1' == result_present.end_state.result" + - "'2.2.2.2' == result_present.end_state.result" + - "'12' in result_present.end_state.result" + - "'true' in result_present.end_state.result" + - "'true' in result_present.end_state.result" + - "'true' in result_present.end_state.result" + - "'true' in result_present.end_state.result" + - "'true' in result_present.end_state.result" + - "'1111-2222-3333' in result_present.end_state.result" + - "'123' in result_present.end_state.result" +- include_tasks: cleanup.yaml +- debug: + msg: "END ce_static_route_bfd presentd integration tests on connection={{ ansible_connection }}" \ No newline at end of file diff --git a/test/integration/targets/ce_static_route_bfd/tests/netconf/cleanup.yaml b/test/integration/targets/ce_static_route_bfd/tests/netconf/cleanup.yaml new file mode 100644 index 00000000000..10ad3e5c184 --- /dev/null +++ b/test/integration/targets/ce_static_route_bfd/tests/netconf/cleanup.yaml @@ -0,0 +1,31 @@ +--- +- name: Merge the provided configuration with the exisiting running configuration + ce_static_route_bfd: + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.3 + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 5 + aftype: v4 + state: absent + register: result + +- name: Assert the configuration is reflected on host + assert: + that: + - "result['changed'] == true" +- name: ip route-static bfd 10GE1/0/1 3.3.3.4 + ce_static_route_bfd: &merge + function_flag: 'globalBFD' + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 6 + aftype: v4 + state: absent + register: result + +- name: Assert the configuration is reflected on host + assert: + that: + - "result['changed'] == true" diff --git a/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/result_ok.txt b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/result_ok.txt new file mode 100644 index 00000000000..5e245cf5b6b --- /dev/null +++ b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/result_ok.txt @@ -0,0 +1,3 @@ + + + diff --git a/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_1.txt b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_1.txt new file mode 100644 index 00000000000..6e5e930093d --- /dev/null +++ b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_1.txt @@ -0,0 +1,18 @@ + + + + + + ipv4unicast + Ethernet3/0/0 + _public_ + 192.168.2.2 + 192.168.2.1 + 50 + 50 + 3 + + + + + diff --git a/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_2.txt b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_2.txt new file mode 100644 index 00000000000..6e5e930093d --- /dev/null +++ b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/srBfdPara_2.txt @@ -0,0 +1,18 @@ + + + + + + ipv4unicast + Ethernet3/0/0 + _public_ + 192.168.2.2 + 192.168.2.1 + 50 + 50 + 3 + + + + + diff --git a/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_1.txt b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_1.txt new file mode 100644 index 00000000000..62800c80a82 --- /dev/null +++ b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_1.txt @@ -0,0 +1,18 @@ + + + + + + _public_ + ipv4unicast + base + 192.168.20.0 + 24 + + _public_ + 189.88.252.1 + + + + + diff --git a/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_2.txt b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_2.txt new file mode 100644 index 00000000000..62800c80a82 --- /dev/null +++ b/test/units/modules/network/cloudengine/fixtures/ce_static_route_bfd/staticrtbase_2.txt @@ -0,0 +1,18 @@ + + + + + + _public_ + ipv4unicast + base + 192.168.20.0 + 24 + + _public_ + 189.88.252.1 + + + + + diff --git a/test/units/modules/network/cloudengine/test_ce_static_route_bfd.py b/test/units/modules/network/cloudengine/test_ce_static_route_bfd.py new file mode 100644 index 00000000000..bba16a5d63e --- /dev/null +++ b/test/units/modules/network/cloudengine/test_ce_static_route_bfd.py @@ -0,0 +1,102 @@ +# (c) 2019 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +from units.compat.mock import patch +from ansible.modules.network.cloudengine import ce_static_route_bfd +from units.modules.network.cloudengine.ce_module import TestCloudEngineModule, load_fixture +from units.modules.utils import set_module_args + + +class TestCloudEngineLacpModule(TestCloudEngineModule): + module = ce_static_route_bfd + + def setUp(self): + super(TestCloudEngineLacpModule, self).setUp() + + self.mock_get_config = patch('ansible.modules.network.cloudengine.ce_static_route_bfd.get_nc_config') + self.get_nc_config = self.mock_get_config.start() + + self.mock_set_config = patch('ansible.modules.network.cloudengine.ce_static_route_bfd.set_nc_config') + self.set_nc_config = self.mock_set_config.start() + self.set_nc_config.return_value = load_fixture('ce_lldp', 'result_ok.txt') + + def tearDown(self): + super(TestCloudEngineLacpModule, self).tearDown() + self.mock_set_config.stop() + self.mock_get_config.stop() + + def test_ce_static_route_bfd_changed_false(self): + srBfdPara_1 = load_fixture('ce_static_route_bfd', 'srBfdPara_1.txt') + staticrtbase_1 = load_fixture('ce_static_route_bfd', 'staticrtbase_1.txt') + self.get_nc_config.side_effect = (srBfdPara_1, srBfdPara_1, staticrtbase_1, staticrtbase_1) + + config = dict( + prefix='255.255.0.0', + mask=22, + aftype='v4', + next_hop='10.10.1.1', + nhp_interface='10GE1/0/1', + vrf='mgnt', + destvrf='_public_', + tag=23, + description='for a test', + pref='22', + function_flag='dynamicBFD', + min_tx_interval='32', + min_rx_interval='23', + detect_multiplier='24', + bfd_session_name='43' + ) + set_module_args(config) + self.execute_module(changed=False) + + def test_ce_static_route_bfd_changed_true(self): + srBfdPara_1 = load_fixture('ce_static_route_bfd', 'srBfdPara_1.txt') + srBfdPara_2 = load_fixture('ce_static_route_bfd', 'srBfdPara_2.txt') + staticrtbase_1 = load_fixture('ce_static_route_bfd', 'staticrtbase_1.txt') + staticrtbase_2 = load_fixture('ce_static_route_bfd', 'staticrtbase_2.txt') + self.get_nc_config.side_effect = (srBfdPara_1, staticrtbase_1, srBfdPara_2, staticrtbase_2) + updates = ['ip route-static vpn-instance mgnt 255.255.0.0 255.255.252.0 10GE1/0/1 10.10.1.1', + ' preference 22', + ' tag 23', + ' track bfd-session 43', + ' description for a test'] + config = dict( + prefix='255.255.0.0', + mask=22, + aftype='v4', + next_hop='10.10.1.1', + nhp_interface='10GE1/0/1', + vrf='mgnt', + destvrf='_public_', + tag=23, + description='for a test', + pref='22', + function_flag='dynamicBFD', + min_tx_interval='32', + min_rx_interval='23', + detect_multiplier='24', + bfd_session_name='43' + ) + set_module_args(config) + result = self.execute_module(changed=True) + self.assertEquals(sorted(result['updates']), sorted(updates))