1#!/usr/bin/python
2#
3# This file is part of Ansible
4#
5# Ansible is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansible is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
17#
18
19ANSIBLE_METADATA = {'metadata_version': '1.1',
20                    'status': ['preview'],
21                    'supported_by': 'community'}
22
23DOCUMENTATION = '''
24---
25module: ce_evpn_bgp_rr
26version_added: "2.4"
27short_description: Manages RR for the VXLAN Network on HUAWEI CloudEngine switches.
28description:
29    - Configure an RR in BGP-EVPN address family view on HUAWEI CloudEngine switches.
30author: Zhijin Zhou (@QijunPan)
31notes:
32    - Ensure that BGP view is existed.
33    - The peer, peer_type, and reflect_client arguments must all exist or not exist.
34    - Recommended connection is C(network_cli).
35    - This module also works with C(local) connections for legacy playbooks.
36options:
37    as_number:
38        description:
39            - Specifies the number of the AS, in integer format.
40              The value is an integer that ranges from 1 to 4294967295.
41        required: true
42    bgp_instance:
43        description:
44            - Specifies the name of a BGP instance.
45              The value of instance-name can be an integer 1 or a string of 1 to 31.
46    bgp_evpn_enable:
47        description:
48            - Enable or disable the BGP-EVPN address family.
49        choices: ['enable','disable']
50        default: 'enable'
51    peer_type:
52        description:
53            - Specify the peer type.
54        choices: ['group_name','ipv4_address']
55    peer:
56        description:
57            - Specifies the IPv4 address or the group name of a peer.
58    reflect_client:
59        description:
60            - Configure the local device as the route reflector and the peer or peer group as the client of the route reflector.
61        choices: ['enable','disable']
62    policy_vpn_target:
63        description:
64            - Enable or disable the VPN-Target filtering.
65        choices: ['enable','disable']
66'''
67
68EXAMPLES = '''
69- name: BGP RR test
70  hosts: cloudengine
71  connection: local
72  gather_facts: no
73  vars:
74    cli:
75      host: "{{ inventory_hostname }}"
76      port: "{{ ansible_ssh_port }}"
77      username: "{{ username }}"
78      password: "{{ password }}"
79      transport: cli
80
81  tasks:
82
83  - name: "Configure BGP-EVPN address family view and ensure that BGP view has existed."
84    ce_evpn_bgp_rr:
85      as_number: 20
86      bgp_evpn_enable: enable
87      provider: "{{ cli }}"
88
89  - name: "Configure reflect client and ensure peer has existed."
90    ce_evpn_bgp_rr:
91      as_number: 20
92      peer_type: ipv4_address
93      peer: 192.8.3.3
94      reflect_client: enable
95      provider: "{{ cli }}"
96
97  - name: "Configure the VPN-Target filtering."
98    ce_evpn_bgp_rr:
99      as_number: 20
100      policy_vpn_target: enable
101      provider: "{{ cli }}"
102
103  - name: "Configure an RR in BGP-EVPN address family view."
104    ce_evpn_bgp_rr:
105      as_number: 20
106      bgp_evpn_enable: enable
107      peer_type: ipv4_address
108      peer: 192.8.3.3
109      reflect_client: enable
110      policy_vpn_target: disable
111      provider: "{{ cli }}"
112'''
113
114RETURN = '''
115proposed:
116    description: k/v pairs of parameters passed into module
117    returned: always
118    type: dict
119    sample: {
120                "as_number": "20",
121                "bgp_evpn_enable": "enable",
122                "bgp_instance": null,
123                "peer": "192.8.3.3",
124                "peer_type": "ipv4_address",
125                "policy_vpn_target": "disable",
126                "reflect_client": "enable"
127            }
128existing:
129    description: k/v pairs of existing attributes on the device
130    returned: always
131    type: dict
132    sample: {
133                "as_number": "20",
134                "bgp_evpn_enable": "disable",
135                "bgp_instance": null,
136                "peer": null,
137                "peer_type": null,
138                "policy_vpn_target": "disable",
139                "reflect_client": "disable"
140            }
141end_state:
142    description: k/v pairs of end attributes on the device
143    returned: always
144    type: dict
145    sample: {
146                "as_number": "20",
147                "bgp_evpn_enable": "enable",
148                "bgp_instance": null,
149                "peer": "192.8.3.3",
150                "peer_type": "ipv4_address",
151                "policy_vpn_target": "disable",
152                "reflect_client": "enable"
153            }
154updates:
155    description: command list sent to the device
156    returned: always
157    type: list
158    sample: [
159                "bgp 20",
160                "  l2vpn-family evpn",
161                "    peer 192.8.3.3 enable",
162                "    peer 192.8.3.3 reflect-client",
163                "    undo policy vpn-target"
164            ]
165changed:
166    description: check to see if a change was made on the device
167    returned: always
168    type: bool
169    sample: true
170'''
171
172import re
173from ansible.module_utils.basic import AnsibleModule
174from ansible.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec
175
176
177def is_config_exist(cmp_cfg, test_cfg):
178    """is configuration exist"""
179
180    if not cmp_cfg or not test_cfg:
181        return False
182
183    return bool(test_cfg in cmp_cfg)
184
185
186class EvpnBgpRr(object):
187    """Manage RR in BGP-EVPN address family view"""
188
189    def __init__(self, argument_spec):
190        self.spec = argument_spec
191        self.module = None
192        self.__init_module__()
193
194        # RR configuration parameters
195        self.as_number = self.module.params['as_number']
196        self.bgp_instance = self.module.params['bgp_instance']
197        self.peer_type = self.module.params['peer_type']
198        self.peer = self.module.params['peer']
199        self.bgp_evpn_enable = self.module.params['bgp_evpn_enable']
200        self.reflect_client = self.module.params['reflect_client']
201        self.policy_vpn_target = self.module.params['policy_vpn_target']
202
203        self.commands = list()
204        self.config = None
205        self.bgp_evpn_config = ""
206        self.cur_config = dict()
207        self.conf_exist = False
208
209        # state
210        self.changed = False
211        self.updates_cmd = list()
212        self.results = dict()
213        self.proposed = dict()
214        self.existing = dict()
215        self.end_state = dict()
216
217    def __init_module__(self):
218        """Init module"""
219
220        self.module = AnsibleModule(
221            argument_spec=self.spec, supports_check_mode=True)
222
223    def cli_load_config(self, commands):
224        """Load config by cli"""
225
226        if not self.module.check_mode:
227            load_config(self.module, commands)
228
229    def is_bgp_view_exist(self):
230        """Judge whether BGP view has existed"""
231
232        if self.bgp_instance:
233            view_cmd = "bgp %s instance %s" % (
234                self.as_number, self.bgp_instance)
235        else:
236            view_cmd = "bgp %s" % self.as_number
237
238        return is_config_exist(self.config, view_cmd)
239
240    def is_l2vpn_family_evpn_exist(self):
241        """Judge whether BGP-EVPN address family view has existed"""
242
243        view_cmd = "l2vpn-family evpn"
244        return is_config_exist(self.config, view_cmd)
245
246    def is_reflect_client_exist(self):
247        """Judge whether reflect client is configured"""
248
249        view_cmd = "peer %s reflect-client" % self.peer
250        return is_config_exist(self.bgp_evpn_config, view_cmd)
251
252    def is_policy_vpn_target_exist(self):
253        """Judge whether the VPN-Target filtering is enabled"""
254
255        view_cmd = "undo policy vpn-target"
256        if is_config_exist(self.bgp_evpn_config, view_cmd):
257            return False
258        else:
259            return True
260
261    def get_config_in_bgp_view(self):
262        """Get configuration in BGP view"""
263
264        cmd = "display current-configuration | section include"
265        if self.as_number:
266            if self.bgp_instance:
267                cmd += " bgp %s instance %s" % (self.as_number,
268                                                self.bgp_instance)
269            else:
270                cmd += " bgp %s" % self.as_number
271        rc, out, err = exec_command(self.module, cmd)
272        if rc != 0:
273            self.module.fail_json(msg=err)
274        config = out.strip() if out else ""
275        if cmd == config:
276            return ''
277
278        return config
279
280    def get_config_in_bgp_evpn_view(self):
281        """Get configuration in BGP_EVPN view"""
282
283        self.bgp_evpn_config = ""
284        if not self.config:
285            return ""
286
287        index = self.config.find("l2vpn-family evpn")
288        if index == -1:
289            return ""
290
291        return self.config[index:]
292
293    def get_current_config(self):
294        """Get current configuration"""
295
296        if not self.as_number:
297            self.module.fail_json(msg='Error: The value of as-number cannot be empty.')
298
299        self.cur_config['bgp_exist'] = False
300        self.cur_config['bgp_evpn_enable'] = 'disable'
301        self.cur_config['reflect_client'] = 'disable'
302        self.cur_config['policy_vpn_target'] = 'disable'
303        self.cur_config['peer_type'] = None
304        self.cur_config['peer'] = None
305
306        self.config = self.get_config_in_bgp_view()
307
308        if not self.is_bgp_view_exist():
309            return
310        self.cur_config['bgp_exist'] = True
311
312        if not self.is_l2vpn_family_evpn_exist():
313            return
314        self.cur_config['bgp_evpn_enable'] = 'enable'
315
316        self.bgp_evpn_config = self.get_config_in_bgp_evpn_view()
317        if self.is_reflect_client_exist():
318            self.cur_config['reflect_client'] = 'enable'
319            self.cur_config['peer_type'] = self.peer_type
320            self.cur_config['peer'] = self.peer
321
322        if self.is_policy_vpn_target_exist():
323            self.cur_config['policy_vpn_target'] = 'enable'
324
325    def get_existing(self):
326        """Get existing config"""
327
328        self.existing = dict(as_number=self.as_number,
329                             bgp_instance=self.bgp_instance,
330                             peer_type=self.cur_config['peer_type'],
331                             peer=self.cur_config['peer'],
332                             bgp_evpn_enable=self.cur_config[
333                                 'bgp_evpn_enable'],
334                             reflect_client=self.cur_config['reflect_client'],
335                             policy_vpn_target=self.cur_config[
336                                 'policy_vpn_target'])
337
338    def get_proposed(self):
339        """Get proposed config"""
340
341        self.proposed = dict(as_number=self.as_number,
342                             bgp_instance=self.bgp_instance,
343                             peer_type=self.peer_type,
344                             peer=self.peer,
345                             bgp_evpn_enable=self.bgp_evpn_enable,
346                             reflect_client=self.reflect_client,
347                             policy_vpn_target=self.policy_vpn_target)
348
349    def get_end_state(self):
350        """Get end config"""
351
352        self.get_current_config()
353        self.end_state = dict(as_number=self.as_number,
354                              bgp_instance=self.bgp_instance,
355                              peer_type=self.cur_config['peer_type'],
356                              peer=self.cur_config['peer'],
357                              bgp_evpn_enable=self.cur_config[
358                                  'bgp_evpn_enable'],
359                              reflect_client=self.cur_config['reflect_client'],
360                              policy_vpn_target=self.cur_config['policy_vpn_target'])
361        if self.end_state == self.existing:
362            self.changed = False
363
364    def show_result(self):
365        """Show result"""
366
367        self.results['changed'] = self.changed
368        self.results['proposed'] = self.proposed
369        self.results['existing'] = self.existing
370        self.results['end_state'] = self.end_state
371        if self.changed:
372            self.results['updates'] = self.updates_cmd
373        else:
374            self.results['updates'] = list()
375
376        self.module.exit_json(**self.results)
377
378    def judge_if_config_exist(self):
379        """Judge whether configuration has existed"""
380
381        if self.bgp_evpn_enable and self.bgp_evpn_enable != self.cur_config['bgp_evpn_enable']:
382            return False
383
384        if self.bgp_evpn_enable == 'disable' and self.cur_config['bgp_evpn_enable'] == 'disable':
385            return True
386
387        if self.reflect_client and self.reflect_client == 'enable':
388            if self.peer_type and self.peer_type != self.cur_config['peer_type']:
389                return False
390            if self.peer and self.peer != self.cur_config['peer']:
391                return False
392        if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']:
393            return False
394
395        if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']:
396            return False
397
398        return True
399
400    def cli_add_command(self, command, undo=False):
401        """Add command to self.update_cmd and self.commands"""
402
403        if undo and command.lower() not in ["quit", "return"]:
404            cmd = "undo " + command
405        else:
406            cmd = command
407
408        self.commands.append(cmd)          # set to device
409        if command.lower() not in ["quit", "return"]:
410            self.updates_cmd.append(cmd)   # show updates result
411
412    def config_rr(self):
413        """Configure RR"""
414
415        if self.conf_exist:
416            return
417
418        if self.bgp_instance:
419            view_cmd = "bgp %s instance %s" % (
420                self.as_number, self.bgp_instance)
421        else:
422            view_cmd = "bgp %s" % self.as_number
423        self.cli_add_command(view_cmd)
424
425        if self.bgp_evpn_enable == 'disable':
426            self.cli_add_command("undo l2vpn-family evpn")
427        else:
428            self.cli_add_command("l2vpn-family evpn")
429            if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']:
430                if self.reflect_client == 'enable':
431                    self.cli_add_command("peer %s enable" % self.peer)
432                    self.cli_add_command(
433                        "peer %s reflect-client" % self.peer)
434                else:
435                    self.cli_add_command(
436                        "undo peer %s reflect-client" % self.peer)
437                    self.cli_add_command("undo peer %s enable" % self.peer)
438            if self.cur_config['bgp_evpn_enable'] == 'enable':
439                if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']:
440                    if self.policy_vpn_target == 'enable':
441                        self.cli_add_command("policy vpn-target")
442                    else:
443                        self.cli_add_command("undo policy vpn-target")
444            else:
445                if self.policy_vpn_target and self.policy_vpn_target == 'disable':
446                    self.cli_add_command("undo policy vpn-target")
447
448        if self.commands:
449            self.cli_load_config(self.commands)
450            self.changed = True
451
452    def check_is_ipv4_addr(self):
453        """Check ipaddress validate"""
454
455        rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.'
456        rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])'
457        ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$')
458
459        return bool(re.match(ipv4_regex, self.peer))
460
461    def check_params(self):
462        """Check all input params"""
463
464        if self.cur_config['bgp_exist'] == 'false':
465            self.module.fail_json(msg="Error: BGP view does not exist.")
466
467        if self.bgp_instance:
468            if len(self.bgp_instance) < 1 or len(self.bgp_instance) > 31:
469                self.module.fail_json(
470                    msg="Error: The length of BGP instance-name must be between 1 or a string of 1 to and 31.")
471
472        if self.as_number:
473            if len(self.as_number) > 11 or len(self.as_number) == 0:
474                self.module.fail_json(
475                    msg='Error: The len of as_number %s is out of [1 - 11].' % self.as_number)
476
477        tmp_dict1 = dict(peer_type=self.peer_type,
478                         peer=self.peer,
479                         reflect_client=self.reflect_client)
480        tmp_dict2 = dict((k, v)
481                         for k, v in tmp_dict1.items() if v is not None)
482        if len(tmp_dict2) != 0 and len(tmp_dict2) != 3:
483            self.module.fail_json(
484                msg='Error: The peer, peer_type, and reflect_client arguments must all exist or not exist.')
485
486        if self.peer_type:
487            if self.peer_type == 'ipv4_address' and not self.check_is_ipv4_addr():
488                self.module.fail_json(msg='Error: Illegal IPv4 address.')
489            elif self.peer_type == 'group_name' and self.check_is_ipv4_addr():
490                self.module.fail_json(
491                    msg='Error: Ip address cannot be configured as group-name.')
492
493    def work(self):
494        """Execute task"""
495
496        self.get_current_config()
497        self.check_params()
498        self.get_existing()
499        self.get_proposed()
500        self.conf_exist = self.judge_if_config_exist()
501
502        self.config_rr()
503
504        self.get_end_state()
505        self.show_result()
506
507
508def main():
509    """Main function entry"""
510
511    argument_spec = dict(
512        as_number=dict(required=True, type='str'),
513        bgp_instance=dict(required=False, type='str'),
514        bgp_evpn_enable=dict(required=False, type='str',
515                             default='enable', choices=['enable', 'disable']),
516        peer_type=dict(required=False, type='str', choices=[
517            'group_name', 'ipv4_address']),
518        peer=dict(required=False, type='str'),
519        reflect_client=dict(required=False, type='str',
520                            choices=['enable', 'disable']),
521        policy_vpn_target=dict(required=False, choices=['enable', 'disable']),
522    )
523    argument_spec.update(ce_argument_spec)
524    evpn_bgp_rr = EvpnBgpRr(argument_spec)
525    evpn_bgp_rr.work()
526
527
528if __name__ == '__main__':
529    main()
530