diff --git a/lib/ansible/modules/network/cloudengine/ce_evpn_bgp_rr.py b/lib/ansible/modules/network/cloudengine/ce_evpn_bgp_rr.py new file mode 100644 index 00000000000..25ff926c3d8 --- /dev/null +++ b/lib/ansible/modules/network/cloudengine/ce_evpn_bgp_rr.py @@ -0,0 +1,535 @@ +#!/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 . +# + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.0'} + +DOCUMENTATION = ''' +--- +module: ce_evpn_bgp_rr +version_added: "2.4" +short_description: Manages RR for the VXLAN Network on HUAWEI CloudEngine switches. +description: + - Configure an RR in BGP-EVPN address family view on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@CloudEngine-Ansible) +notes: + - Ensure that BGP view is existed. + - The peer, peer_type, and reflect_client arguments must all exist or not exist. +options: + as_number: + description: + - Specifies the number of the AS, in integer format. + The value is an integer that ranges from 1 to 4294967295. + required: true + default: null + bgp_instance: + description: + - Specifies the name of a BGP instance. + The value of instance-name can be an integer 1 or a string of 1 to 31. + required: false + default: null + bgp_evpn_enable: + description: + - Enable or disable the BGP-EVPN address family. + required: false + choices: ['enable','disable'] + default: 'enable' + peer_type: + description: + - Specify the peer type. + required: false + choices: ['group_name','ipv4_address'] + default: null + peer: + description: + - Specifies the IPv4 address or the group name of a peer. + required: false + default: null + reflect_client: + description: + - Configure the local device as the route reflector and the peer or peer group as the client of the route reflector. + required: false + choices: ['enable','disable'] + default: null + policy_vpn_target: + description: + - Enable or disable the VPN-Target filtering. + required: false + choices: ['enable','disable'] + default: null +''' + +EXAMPLES = ''' +- name: BGP RR test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure BGP-EVPN address family view and ensure that BGP view has existed." + ce_evpn_bgp_rr: + as_number: 20 + bgp_evpn_enable: enable + provider: "{{ cli }}" + + - name: "Configure reflect client and ensure peer has existed." + ce_evpn_bgp_rr: + as_number: 20 + peer_type: ipv4_address + peer: 192.8.3.3 + reflect_client: enable + provider: "{{ cli }}" + + - name: "Configure the VPN-Target filtering." + ce_evpn_bgp_rr: + as_number: 20 + policy_vpn_target: enable + provider: "{{ cli }}" + + - name: "Configure an RR in BGP-EVPN address family view." + ce_evpn_bgp_rr: + as_number: 20 + bgp_evpn_enable: enable + peer_type: ipv4_address + peer: 192.8.3.3 + reflect_client: enable + policy_vpn_target: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "enable", + "bgp_instance": null, + "peer": "192.8.3.3", + "peer_type": "ipv4_address", + "policy_vpn_target": "disable", + "reflect_client": "enable" + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "disable", + "bgp_instance": null, + "peer": null, + "peer_type": null, + "policy_vpn_target": "disable", + "reflect_client": "disable" + } +end_state: + description: k/v pairs of end attributes on the device + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "enable", + "bgp_instance": null, + "peer": "192.8.3.3", + "peer_type": "ipv4_address", + "policy_vpn_target": "disable", + "reflect_client": "enable" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "bgp 20", + " l2vpn-family evpn", + " peer 192.8.3.3 enable", + " peer 192.8.3.3 reflect-client", + " undo policy vpn-target" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: boolean + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ce import get_config, load_config, ce_argument_spec + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +class EvpnBgpRr(object): + """Manange RR in BGP-EVPN address family view""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # RR configuration parameters + self.as_number = self.module.params['as_number'] + self.bgp_instance = self.module.params['bgp_instance'] + self.peer_type = self.module.params['peer_type'] + self.peer = self.module.params['peer'] + self.bgp_evpn_enable = self.module.params['bgp_evpn_enable'] + self.reflect_client = self.module.params['reflect_client'] + self.policy_vpn_target = self.module.params['policy_vpn_target'] + + self.commands = list() + self.config = None + self.bgp_evpn_config = "" + self.cur_config = dict() + self.conf_exist = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """Init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """Load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def is_bgp_view_exist(self): + """Judge whether BGP view has existed""" + + if self.bgp_instance: + view_cmd = "bgp %s instance %s" % ( + self.as_number, self.bgp_instance) + else: + view_cmd = "bgp %s" % self.as_number + + return is_config_exist(self.config, view_cmd) + + def is_l2vpn_family_evpn_exist(self): + """Judge whether BGP-EVPN address family view has existed""" + + view_cmd = "l2vpn-family evpn" + return is_config_exist(self.config, view_cmd) + + def is_reflect_client_exist(self): + """Judge whether reflect client is configured""" + + view_cmd = "peer %s reflect-client" % self.peer + return is_config_exist(self.bgp_evpn_config, view_cmd) + + def is_policy_vpn_target_exist(self): + """Judge whether the VPN-Target filtering is enabled""" + + view_cmd = "undo policy vpn-target" + if is_config_exist(self.bgp_evpn_config, view_cmd): + return False + else: + return True + + def get_config_in_bgp_view(self): + """Get configuration in BGP view""" + + flags = list() + exp = " | section include" + if self.as_number: + if self.bgp_instance: + exp += " bgp %s instance %s" % (self.as_number, + self.bgp_instance) + else: + exp += " bgp %s" % self.as_number + + flags.append(exp) + config = get_config(self.module, flags) + + return config + + def get_config_in_bgp_evpn_view(self): + """Get configuration in BGP_EVPN view""" + + self.bgp_evpn_config = "" + if not self.config: + return "" + + index = self.config.find("l2vpn-family evpn") + if index == -1: + return "" + + return self.config[index:] + + def get_current_config(self): + """Get current configuration""" + + if not self.as_number: + self.module.fail_json(msg='Error: The value of as-number cannot be empty.') + + self.cur_config['bgp_exist'] = False + self.cur_config['bgp_evpn_enable'] = 'disable' + self.cur_config['reflect_client'] = 'disable' + self.cur_config['policy_vpn_target'] = 'disable' + self.cur_config['peer_type'] = None + self.cur_config['peer'] = None + + self.config = self.get_config_in_bgp_view() + + if not self.is_bgp_view_exist(): + return + self.cur_config['bgp_exist'] = True + + if not self.is_l2vpn_family_evpn_exist(): + return + self.cur_config['bgp_evpn_enable'] = 'enable' + + self.bgp_evpn_config = self.get_config_in_bgp_evpn_view() + if self.is_reflect_client_exist(): + self.cur_config['reflect_client'] = 'enable' + self.cur_config['peer_type'] = self.peer_type + self.cur_config['peer'] = self.peer + + if self.is_policy_vpn_target_exist(): + self.cur_config['policy_vpn_target'] = 'enable' + + def get_existing(self): + """Get existing config""" + + self.existing = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.cur_config['peer_type'], + peer=self.cur_config['peer'], + bgp_evpn_enable=self.cur_config[ + 'bgp_evpn_enable'], + reflect_client=self.cur_config['reflect_client'], + policy_vpn_target=self.cur_config[ + 'policy_vpn_target']) + + def get_proposed(self): + """Get proposed config""" + + self.proposed = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.peer_type, + peer=self.peer, + bgp_evpn_enable=self.bgp_evpn_enable, + reflect_client=self.reflect_client, + policy_vpn_target=self.policy_vpn_target) + + def get_end_state(self): + """Get end config""" + + self.get_current_config() + self.end_state = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.cur_config['peer_type'], + peer=self.cur_config['peer'], + bgp_evpn_enable=self.cur_config[ + 'bgp_evpn_enable'], + reflect_client=self.cur_config['reflect_client'], + policy_vpn_target=self.cur_config['policy_vpn_target']) + + def show_result(self): + """Show result""" + + 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 judge_if_config_exist(self): + """Judge whether configuration has existed""" + + if self.bgp_evpn_enable and self.bgp_evpn_enable != self.cur_config['bgp_evpn_enable']: + return False + + if self.bgp_evpn_enable == 'disable' and self.cur_config['bgp_evpn_enable'] == 'disable': + return True + + if self.reflect_client and self.reflect_client == 'enable': + if self.peer_type and self.peer_type != self.cur_config['peer_type']: + return False + if self.peer and self.peer != self.cur_config['peer']: + return False + if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']: + return False + + if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']: + return False + + return True + + def cli_add_command(self, command, undo=False): + """Add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def config_rr(self): + """Configure RR""" + + if self.conf_exist: + return + + if self.bgp_instance: + view_cmd = "bgp %s instance %s" % ( + self.as_number, self.bgp_instance) + else: + view_cmd = "bgp %s" % self.as_number + self.cli_add_command(view_cmd) + + if self.bgp_evpn_enable == 'disable': + self.cli_add_command(" undo l2vpn-family evpn") + else: + self.cli_add_command(" l2vpn-family evpn") + if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']: + if self.reflect_client == 'enable': + self.cli_add_command(" peer %s enable" % self.peer) + self.cli_add_command( + " peer %s reflect-client" % self.peer) + else: + self.cli_add_command( + " undo peer %s reflect-client" % self.peer) + self.cli_add_command(" undo peer %s enable" % self.peer) + if self.cur_config['bgp_evpn_enable'] == 'enable': + if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']: + if self.policy_vpn_target == 'enable': + self.cli_add_command(" policy vpn-target") + else: + self.cli_add_command(" undo policy vpn-target") + else: + if self.policy_vpn_target and self.policy_vpn_target == 'disable': + self.cli_add_command(" undo policy vpn-target") + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def check_is_ipv4_addr(self): + """Check ipaddress validate""" + + rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.' + rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])' + ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$') + + return bool(re.match(ipv4_regex, self.peer)) + + def check_params(self): + """Check all input params""" + + if self.cur_config['bgp_exist'] == 'false': + self.module.fail_json(msg="Error: BGP view doesnot exist.") + + if self.bgp_instance: + if len(self.bgp_instance) < 1 or len(self.bgp_instance) > 31: + self.module.fail_json( + msg="Error: The length of BGP instance-name must be between 1 or a string of 1 to and 31.") + + if self.as_number: + if len(self.as_number) > 11 or len(self.as_number) == 0: + self.module.fail_json( + msg='Error: The len of as_number %s is out of [1 - 11].' % self.as_number) + + tmp_dict1 = dict(peer_type=self.peer_type, + peer=self.peer, + reflect_client=self.reflect_client) + tmp_dict2 = dict((k, v) + for k, v in tmp_dict1.items() if v is not None) + if len(tmp_dict2) != 0 and len(tmp_dict2) != 3: + self.module.fail_json( + msg='Error: The peer, peer_type, and reflect_client arguments must all exist or not exist.') + + if self.peer_type: + if self.peer_type == 'ipv4_address' and not self.check_is_ipv4_addr(): + self.module.fail_json(msg='Error: Illegal IPv4 address.') + elif self.peer_type == 'group_name' and self.check_is_ipv4_addr(): + self.module.fail_json( + msg='Error: Ip address cannot be configured as group-name.') + + def work(self): + """Excute task""" + + self.get_current_config() + self.check_params() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_rr() + + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + as_number=dict(required=True, type='str'), + bgp_instance=dict(required=False, type='str'), + bgp_evpn_enable=dict(required=False, type='str', + default='enable', choices=['enable', 'disable']), + peer_type=dict(required=False, type='str', choices=[ + 'group_name', 'ipv4_address']), + peer=dict(required=False, type='str'), + reflect_client=dict(required=False, type='str', + choices=['enable', 'disable']), + policy_vpn_target=dict(required=False, choices=['enable', 'disable']), + ) + argument_spec.update(ce_argument_spec) + evpn_bgp_rr = EvpnBgpRr(argument_spec) + evpn_bgp_rr.work() + + +if __name__ == '__main__': + main()