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