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_vxlan_global 26version_added: "2.4" 27short_description: Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices. 28description: 29 - Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices. 30author: QijunPan (@QijunPan) 31notes: 32 - Recommended connection is C(network_cli). 33 - This module also works with C(local) connections for legacy playbooks. 34options: 35 bridge_domain_id: 36 description: 37 - Specifies a bridge domain ID. 38 The value is an integer ranging from 1 to 16777215. 39 tunnel_mode_vxlan: 40 description: 41 - Set the tunnel mode to VXLAN when configuring the VXLAN feature. 42 choices: ['enable', 'disable'] 43 nvo3_prevent_loops: 44 description: 45 - Loop prevention of VXLAN traffic in non-enhanced mode. 46 When the device works in non-enhanced mode, 47 inter-card forwarding of VXLAN traffic may result in loops. 48 choices: ['enable', 'disable'] 49 nvo3_acl_extend: 50 description: 51 - Enabling or disabling the VXLAN ACL extension function. 52 choices: ['enable', 'disable'] 53 nvo3_gw_enhanced: 54 description: 55 - Configuring the Layer 3 VXLAN Gateway to Work in Non-loopback Mode. 56 choices: ['l2', 'l3'] 57 nvo3_service_extend: 58 description: 59 - Enabling or disabling the VXLAN service extension function. 60 choices: ['enable', 'disable'] 61 nvo3_eth_trunk_hash: 62 description: 63 - Eth-Trunk from load balancing VXLAN packets in optimized mode. 64 choices: ['enable','disable'] 65 nvo3_ecmp_hash: 66 description: 67 - Load balancing of VXLAN packets through ECMP in optimized mode. 68 choices: ['enable', 'disable'] 69 state: 70 description: 71 - Determines whether the config should be present or not 72 on the device. 73 default: present 74 choices: ['present', 'absent'] 75""" 76 77EXAMPLES = ''' 78- name: vxlan global module test 79 hosts: ce128 80 connection: local 81 gather_facts: no 82 vars: 83 cli: 84 host: "{{ inventory_hostname }}" 85 port: "{{ ansible_ssh_port }}" 86 username: "{{ username }}" 87 password: "{{ password }}" 88 transport: cli 89 90 tasks: 91 92 - name: Create bridge domain and set tunnel mode to VXLAN 93 ce_vxlan_global: 94 bridge_domain_id: 100 95 nvo3_acl_extend: enable 96 provider: "{{ cli }}" 97''' 98 99RETURN = ''' 100proposed: 101 description: k/v pairs of parameters passed into module 102 returned: verbose mode 103 type: dict 104 sample: {"bridge_domain_id": "100", "nvo3_acl_extend": "enable", state="present"} 105existing: 106 description: k/v pairs of existing configuration 107 returned: verbose mode 108 type: dict 109 sample: {"bridge_domain": {"80", "90"}, "nvo3_acl_extend": "disable"} 110end_state: 111 description: k/v pairs of configuration after module execution 112 returned: verbose mode 113 type: dict 114 sample: {"bridge_domain_id": {"80", "90", "100"}, "nvo3_acl_extend": "enable"} 115updates: 116 description: commands sent to the device 117 returned: always 118 type: list 119 sample: ["bridge-domain 100", 120 "ip tunnel mode vxlan"] 121changed: 122 description: check to see if a change was made on the device 123 returned: always 124 type: bool 125 sample: true 126''' 127 128import re 129from ansible.module_utils.basic import AnsibleModule 130from ansible.module_utils.network.cloudengine.ce import load_config 131from ansible.module_utils.network.cloudengine.ce import ce_argument_spec 132from ansible.module_utils.connection import exec_command 133 134 135def is_config_exist(cmp_cfg, test_cfg): 136 """is configuration exist?""" 137 138 if not cmp_cfg or not test_cfg: 139 return False 140 141 return bool(test_cfg in cmp_cfg) 142 143 144def get_nvo3_gw_enhanced(cmp_cfg): 145 """get the Layer 3 VXLAN Gateway to Work in Non-loopback Mode """ 146 147 get = re.findall( 148 r"assign forward nvo3-gateway enhanced (l[2|3])", cmp_cfg) 149 if not get: 150 return None 151 else: 152 return get[0] 153 154 155class VxlanGlobal(object): 156 """ 157 Manages global attributes of VXLAN and bridge domain. 158 """ 159 160 def __init__(self, argument_spec): 161 self.spec = argument_spec 162 self.module = None 163 self.init_module() 164 165 # module input info 166 self.tunnel_mode_vxlan = self.module.params['tunnel_mode_vxlan'] 167 self.nvo3_prevent_loops = self.module.params['nvo3_prevent_loops'] 168 self.nvo3_acl_extend = self.module.params['nvo3_acl_extend'] 169 self.nvo3_gw_enhanced = self.module.params['nvo3_gw_enhanced'] 170 self.nvo3_service_extend = self.module.params['nvo3_service_extend'] 171 self.nvo3_eth_trunk_hash = self.module.params['nvo3_eth_trunk_hash'] 172 self.nvo3_ecmp_hash = self.module.params['nvo3_ecmp_hash'] 173 self.bridge_domain_id = self.module.params['bridge_domain_id'] 174 self.state = self.module.params['state'] 175 176 # state 177 self.config = "" # current config 178 self.bd_info = list() 179 self.changed = False 180 self.updates_cmd = list() 181 self.commands = list() 182 self.results = dict() 183 self.proposed = dict() 184 self.existing = dict() 185 self.end_state = dict() 186 187 def init_module(self): 188 """init module""" 189 190 self.module = AnsibleModule( 191 argument_spec=self.spec, supports_check_mode=True) 192 193 def cli_load_config(self, commands): 194 """load config by cli""" 195 196 if not self.module.check_mode: 197 load_config(self.module, commands) 198 199 def get_config(self, flags=None): 200 """Retrieves the current config from the device or cache 201 """ 202 flags = [] if flags is None else flags 203 204 cmd = 'display current-configuration ' 205 cmd += ' '.join(flags) 206 cmd = cmd.strip() 207 208 rc, out, err = exec_command(self.module, cmd) 209 if rc != 0: 210 self.module.fail_json(msg=err) 211 cfg = str(out).strip() 212 213 return cfg 214 215 def get_current_config(self): 216 """get current configuration""" 217 218 flags = list() 219 exp = " include-default | include vxlan|assign | exclude undo" 220 flags.append(exp) 221 return self.get_config(flags) 222 223 def cli_add_command(self, command, undo=False): 224 """add command to self.update_cmd and self.commands""" 225 226 if undo and command.lower() not in ["quit", "return"]: 227 cmd = "undo " + command 228 else: 229 cmd = command 230 231 self.commands.append(cmd) # set to device 232 if command.lower() not in ["quit", "return"]: 233 self.updates_cmd.append(cmd) # show updates result 234 235 def get_bd_list(self): 236 """get bridge domain list""" 237 flags = list() 238 bd_info = list() 239 exp = " include-default | include bridge-domain | exclude undo" 240 flags.append(exp) 241 bd_str = self.get_config(flags) 242 if not bd_str: 243 return bd_info 244 bd_num = re.findall(r'bridge-domain\s*([0-9]+)', bd_str) 245 bd_info.extend(bd_num) 246 return bd_info 247 248 def config_bridge_domain(self): 249 """manage bridge domain""" 250 251 if not self.bridge_domain_id: 252 return 253 254 cmd = "bridge-domain %s" % self.bridge_domain_id 255 exist = self.bridge_domain_id in self.bd_info 256 if self.state == "present": 257 if not exist: 258 self.cli_add_command(cmd) 259 self.cli_add_command("quit") 260 else: 261 if exist: 262 self.cli_add_command(cmd, undo=True) 263 264 def config_tunnel_mode(self): 265 """config tunnel mode vxlan""" 266 267 # ip tunnel mode vxlan 268 if self.tunnel_mode_vxlan: 269 cmd = "ip tunnel mode vxlan" 270 exist = is_config_exist(self.config, cmd) 271 if self.tunnel_mode_vxlan == "enable": 272 if not exist: 273 self.cli_add_command(cmd) 274 else: 275 if exist: 276 self.cli_add_command(cmd, undo=True) 277 278 def config_assign_forward(self): 279 """config assign forward command""" 280 281 # [undo] assign forward nvo3-gateway enhanced {l2|l3) 282 if self.nvo3_gw_enhanced: 283 cmd = "assign forward nvo3-gateway enhanced %s" % self.nvo3_gw_enhanced 284 exist = is_config_exist(self.config, cmd) 285 if self.state == "present": 286 if not exist: 287 self.cli_add_command(cmd) 288 else: 289 if exist: 290 self.cli_add_command(cmd, undo=True) 291 292 # [undo] assign forward nvo3 f-linecard compatibility enable 293 if self.nvo3_prevent_loops: 294 cmd = "assign forward nvo3 f-linecard compatibility enable" 295 exist = is_config_exist(self.config, cmd) 296 if self.nvo3_prevent_loops == "enable": 297 if not exist: 298 self.cli_add_command(cmd) 299 else: 300 if exist: 301 self.cli_add_command(cmd, undo=True) 302 303 # [undo] assign forward nvo3 acl extend enable 304 if self.nvo3_acl_extend: 305 cmd = "assign forward nvo3 acl extend enable" 306 exist = is_config_exist(self.config, cmd) 307 if self.nvo3_acl_extend == "enable": 308 if not exist: 309 self.cli_add_command(cmd) 310 else: 311 if exist: 312 self.cli_add_command(cmd, undo=True) 313 314 # [undo] assign forward nvo3 service extend enable 315 if self.nvo3_service_extend: 316 cmd = "assign forward nvo3 service extend enable" 317 exist = is_config_exist(self.config, cmd) 318 if self.nvo3_service_extend == "enable": 319 if not exist: 320 self.cli_add_command(cmd) 321 else: 322 if exist: 323 self.cli_add_command(cmd, undo=True) 324 325 # assign forward nvo3 eth-trunk hash {enable|disable} 326 if self.nvo3_eth_trunk_hash: 327 cmd = "assign forward nvo3 eth-trunk hash enable" 328 exist = is_config_exist(self.config, cmd) 329 if self.nvo3_eth_trunk_hash == "enable": 330 if not exist: 331 self.cli_add_command(cmd) 332 else: 333 if exist: 334 self.cli_add_command(cmd, undo=True) 335 336 # [undo] assign forward nvo3 ecmp hash enable 337 if self.nvo3_ecmp_hash: 338 cmd = "assign forward nvo3 ecmp hash enable" 339 exist = is_config_exist(self.config, cmd) 340 if self.nvo3_ecmp_hash == "enable": 341 if not exist: 342 self.cli_add_command(cmd) 343 else: 344 if exist: 345 self.cli_add_command(cmd, undo=True) 346 347 def check_params(self): 348 """Check all input params""" 349 350 # bridge domain id check 351 if self.bridge_domain_id: 352 if not self.bridge_domain_id.isdigit(): 353 self.module.fail_json( 354 msg="Error: bridge domain id is not digit.") 355 if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215: 356 self.module.fail_json( 357 msg="Error: bridge domain id is not in the range from 1 to 16777215.") 358 359 def get_proposed(self): 360 """get proposed info""" 361 362 if self.tunnel_mode_vxlan: 363 self.proposed["tunnel_mode_vxlan"] = self.tunnel_mode_vxlan 364 if self.nvo3_prevent_loops: 365 self.proposed["nvo3_prevent_loops"] = self.nvo3_prevent_loops 366 if self.nvo3_acl_extend: 367 self.proposed["nvo3_acl_extend"] = self.nvo3_acl_extend 368 if self.nvo3_gw_enhanced: 369 self.proposed["nvo3_gw_enhanced"] = self.nvo3_gw_enhanced 370 if self.nvo3_service_extend: 371 self.proposed["nvo3_service_extend"] = self.nvo3_service_extend 372 if self.nvo3_eth_trunk_hash: 373 self.proposed["nvo3_eth_trunk_hash"] = self.nvo3_eth_trunk_hash 374 if self.nvo3_ecmp_hash: 375 self.proposed["nvo3_ecmp_hash"] = self.nvo3_ecmp_hash 376 if self.bridge_domain_id: 377 self.proposed["bridge_domain_id"] = self.bridge_domain_id 378 self.proposed["state"] = self.state 379 380 def get_existing(self): 381 """get existing info""" 382 383 self.existing["bridge_domain"] = self.bd_info 384 385 cmd = "ip tunnel mode vxlan" 386 exist = is_config_exist(self.config, cmd) 387 if exist: 388 self.existing["tunnel_mode_vxlan"] = "enable" 389 else: 390 self.existing["tunnel_mode_vxlan"] = "disable" 391 392 cmd = "assign forward nvo3 f-linecard compatibility enable" 393 exist = is_config_exist(self.config, cmd) 394 if exist: 395 self.existing["nvo3_prevent_loops"] = "enable" 396 else: 397 self.existing["nvo3_prevent_loops"] = "disable" 398 399 cmd = "assign forward nvo3 acl extend enable" 400 exist = is_config_exist(self.config, cmd) 401 if exist: 402 self.existing["nvo3_acl_extend"] = "enable" 403 else: 404 self.existing["nvo3_acl_extend"] = "disable" 405 406 self.existing["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced( 407 self.config) 408 409 cmd = "assign forward nvo3 service extend enable" 410 exist = is_config_exist(self.config, cmd) 411 if exist: 412 self.existing["nvo3_service_extend"] = "enable" 413 else: 414 self.existing["nvo3_service_extend"] = "disable" 415 416 cmd = "assign forward nvo3 eth-trunk hash enable" 417 exist = is_config_exist(self.config, cmd) 418 if exist: 419 self.existing["nvo3_eth_trunk_hash"] = "enable" 420 else: 421 self.existing["nvo3_eth_trunk_hash"] = "disable" 422 423 cmd = "assign forward nvo3 ecmp hash enable" 424 exist = is_config_exist(self.config, cmd) 425 if exist: 426 self.existing["nvo3_ecmp_hash"] = "enable" 427 else: 428 self.existing["nvo3_ecmp_hash"] = "disable" 429 430 def get_end_state(self): 431 """get end state info""" 432 433 config = self.get_current_config() 434 435 self.end_state["bridge_domain"] = self.get_bd_list() 436 437 cmd = "ip tunnel mode vxlan" 438 exist = is_config_exist(config, cmd) 439 if exist: 440 self.end_state["tunnel_mode_vxlan"] = "enable" 441 else: 442 self.end_state["tunnel_mode_vxlan"] = "disable" 443 444 cmd = "assign forward nvo3 f-linecard compatibility enable" 445 exist = is_config_exist(config, cmd) 446 if exist: 447 self.end_state["nvo3_prevent_loops"] = "enable" 448 else: 449 self.end_state["nvo3_prevent_loops"] = "disable" 450 451 cmd = "assign forward nvo3 acl extend enable" 452 exist = is_config_exist(config, cmd) 453 if exist: 454 self.end_state["nvo3_acl_extend"] = "enable" 455 else: 456 self.end_state["nvo3_acl_extend"] = "disable" 457 458 self.end_state["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced(config) 459 460 cmd = "assign forward nvo3 service extend enable" 461 exist = is_config_exist(config, cmd) 462 if exist: 463 self.end_state["nvo3_service_extend"] = "enable" 464 else: 465 self.end_state["nvo3_service_extend"] = "disable" 466 467 cmd = "assign forward nvo3 eth-trunk hash enable" 468 exist = is_config_exist(config, cmd) 469 if exist: 470 self.end_state["nvo3_eth_trunk_hash"] = "enable" 471 else: 472 self.end_state["nvo3_eth_trunk_hash"] = "disable" 473 474 cmd = "assign forward nvo3 ecmp hash enable" 475 exist = is_config_exist(config, cmd) 476 if exist: 477 self.end_state["nvo3_ecmp_hash"] = "enable" 478 else: 479 self.end_state["nvo3_ecmp_hash"] = "disable" 480 if self.existing == self.end_state: 481 self.changed = True 482 483 def work(self): 484 """worker""" 485 486 self.check_params() 487 self.config = self.get_current_config() 488 self.bd_info = self.get_bd_list() 489 self.get_existing() 490 self.get_proposed() 491 492 # deal present or absent 493 self.config_bridge_domain() 494 self.config_tunnel_mode() 495 self.config_assign_forward() 496 if self.commands: 497 self.cli_load_config(self.commands) 498 self.changed = True 499 500 self.get_end_state() 501 self.results['changed'] = self.changed 502 self.results['proposed'] = self.proposed 503 self.results['existing'] = self.existing 504 self.results['end_state'] = self.end_state 505 if self.changed: 506 self.results['updates'] = self.updates_cmd 507 else: 508 self.results['updates'] = list() 509 510 self.module.exit_json(**self.results) 511 512 513def main(): 514 """Module main""" 515 516 argument_spec = dict( 517 tunnel_mode_vxlan=dict(required=False, type='str', 518 choices=['enable', 'disable']), 519 nvo3_prevent_loops=dict(required=False, type='str', 520 choices=['enable', 'disable']), 521 nvo3_acl_extend=dict(required=False, type='str', 522 choices=['enable', 'disable']), 523 nvo3_gw_enhanced=dict(required=False, type='str', 524 choices=['l2', 'l3']), 525 nvo3_service_extend=dict(required=False, type='str', 526 choices=['enable', 'disable']), 527 nvo3_eth_trunk_hash=dict(required=False, type='str', 528 choices=['enable', 'disable']), 529 nvo3_ecmp_hash=dict(required=False, type='str', 530 choices=['enable', 'disable']), 531 bridge_domain_id=dict(required=False, type='str'), 532 state=dict(required=False, default='present', 533 choices=['present', 'absent']) 534 ) 535 argument_spec.update(ce_argument_spec) 536 module = VxlanGlobal(argument_spec) 537 module.work() 538 539 540if __name__ == '__main__': 541 main() 542