1#!/usr/local/bin/python3.8 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 19from __future__ import (absolute_import, division, print_function) 20__metaclass__ = type 21 22DOCUMENTATION = ''' 23--- 24module: ce_bfd_global 25short_description: Manages BFD global configuration on HUAWEI CloudEngine devices. 26description: 27 - Manages BFD global configuration on HUAWEI CloudEngine devices. 28author: QijunPan (@QijunPan) 29notes: 30 - This module requires the netconf system service be enabled on the remote device being managed. 31 - Recommended connection is C(netconf). 32 - This module also works with C(local) connections for legacy playbooks. 33options: 34 bfd_enable: 35 description: 36 - Enables the global Bidirectional Forwarding Detection (BFD) function. 37 choices: ['enable', 'disable'] 38 default_ip: 39 description: 40 - Specifies the default multicast IP address. 41 The value ranges from 224.0.0.107 to 224.0.0.250. 42 tos_exp_dynamic: 43 description: 44 - Indicates the priority of BFD control packets for dynamic BFD sessions. 45 The value is an integer ranging from 0 to 7. 46 The default priority is 7, which is the highest priority of BFD control packets. 47 tos_exp_static: 48 description: 49 - Indicates the priority of BFD control packets for static BFD sessions. 50 The value is an integer ranging from 0 to 7. 51 The default priority is 7, which is the highest priority of BFD control packets. 52 damp_init_wait_time: 53 description: 54 - Specifies an initial flapping suppression time for a BFD session. 55 The value is an integer ranging from 1 to 3600000, in milliseconds. 56 The default value is 2000. 57 damp_max_wait_time: 58 description: 59 - Specifies a maximum flapping suppression time for a BFD session. 60 The value is an integer ranging from 1 to 3600000, in milliseconds. 61 The default value is 15000. 62 damp_second_wait_time: 63 description: 64 - Specifies a secondary flapping suppression time for a BFD session. 65 The value is an integer ranging from 1 to 3600000, in milliseconds. 66 The default value is 5000. 67 delay_up_time: 68 description: 69 - Specifies the delay before a BFD session becomes Up. 70 The value is an integer ranging from 1 to 600, in seconds. 71 The default value is 0, indicating that a BFD session immediately becomes Up. 72 state: 73 description: 74 - Determines whether the config should be present or not on the device. 75 default: present 76 choices: ['present', 'absent'] 77''' 78 79EXAMPLES = ''' 80- name: Bfd global module test 81 hosts: cloudengine 82 connection: local 83 gather_facts: no 84 vars: 85 cli: 86 host: "{{ inventory_hostname }}" 87 port: "{{ ansible_ssh_port }}" 88 username: "{{ username }}" 89 password: "{{ password }}" 90 transport: cli 91 92 tasks: 93 - name: Enable the global BFD function 94 community.network.ce_bfd_global: 95 bfd_enable: enable 96 provider: '{{ cli }}' 97 98 - name: Set the default multicast IP address to 224.0.0.150 99 community.network.ce_bfd_global: 100 bfd_enable: enable 101 default_ip: 224.0.0.150 102 state: present 103 provider: '{{ cli }}' 104 105 - name: Set the priority of BFD control packets for dynamic and static BFD sessions 106 community.network.ce_bfd_global: 107 bfd_enable: enable 108 tos_exp_dynamic: 5 109 tos_exp_static: 6 110 state: present 111 provider: '{{ cli }}' 112 113 - name: Disable the global BFD function 114 community.network.ce_bfd_global: 115 bfd_enable: disable 116 provider: '{{ cli }}' 117''' 118 119RETURN = ''' 120proposed: 121 description: k/v pairs of parameters passed into module 122 returned: verbose mode 123 type: dict 124 sample: { 125 "bfd_enalbe": "enable", 126 "damp_init_wait_time": null, 127 "damp_max_wait_time": null, 128 "damp_second_wait_time": null, 129 "default_ip": null, 130 "delayUpTimer": null, 131 "state": "present", 132 "tos_exp_dynamic": null, 133 "tos_exp_static": null 134 } 135existing: 136 description: k/v pairs of existing configuration 137 returned: verbose mode 138 type: dict 139 sample: { 140 "global": { 141 "bfdEnable": "false", 142 "dampInitWaitTime": "2000", 143 "dampMaxWaitTime": "12000", 144 "dampSecondWaitTime": "5000", 145 "defaultIp": "224.0.0.184", 146 "delayUpTimer": null, 147 "tosExp": "7", 148 "tosExpStatic": "7" 149 } 150 } 151end_state: 152 description: k/v pairs of configuration after module execution 153 returned: verbose mode 154 type: dict 155 sample: { 156 "global": { 157 "bfdEnable": "true", 158 "dampInitWaitTime": "2000", 159 "dampMaxWaitTime": "12000", 160 "dampSecondWaitTime": "5000", 161 "defaultIp": "224.0.0.184", 162 "delayUpTimer": null, 163 "tosExp": "7", 164 "tosExpStatic": "7" 165 } 166 } 167updates: 168 description: commands sent to the device 169 returned: always 170 type: list 171 sample: [ "bfd" ] 172changed: 173 description: check to see if a change was made on the device 174 returned: always 175 type: bool 176 sample: true 177''' 178 179import sys 180import socket 181from xml.etree import ElementTree 182from ansible.module_utils.basic import AnsibleModule 183from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr 184 185CE_NC_GET_BFD = """ 186 <filter type="subtree"> 187 <bfd xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 188 %s 189 </bfd> 190 </filter> 191""" 192 193CE_NC_GET_BFD_GLB = """ 194 <bfdSchGlobal> 195 <bfdEnable></bfdEnable> 196 <defaultIp></defaultIp> 197 <tosExp></tosExp> 198 <tosExpStatic></tosExpStatic> 199 <dampInitWaitTime></dampInitWaitTime> 200 <dampMaxWaitTime></dampMaxWaitTime> 201 <dampSecondWaitTime></dampSecondWaitTime> 202 <delayUpTimer></delayUpTimer> 203 </bfdSchGlobal> 204""" 205 206 207def check_default_ip(ipaddr): 208 """check the default multicast IP address""" 209 210 # The value ranges from 224.0.0.107 to 224.0.0.250 211 if not check_ip_addr(ipaddr): 212 return False 213 214 if ipaddr.count(".") != 3: 215 return False 216 217 ips = ipaddr.split(".") 218 if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": 219 return False 220 221 if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: 222 return False 223 224 return True 225 226 227class BfdGlobal(object): 228 """Manages BFD Global""" 229 230 def __init__(self, argument_spec): 231 self.spec = argument_spec 232 self.module = None 233 self.__init_module__() 234 235 # module input info 236 self.bfd_enable = self.module.params['bfd_enable'] 237 self.default_ip = self.module.params['default_ip'] 238 self.tos_exp_dynamic = self.module.params['tos_exp_dynamic'] 239 self.tos_exp_static = self.module.params['tos_exp_static'] 240 self.damp_init_wait_time = self.module.params['damp_init_wait_time'] 241 self.damp_max_wait_time = self.module.params['damp_max_wait_time'] 242 self.damp_second_wait_time = self.module.params['damp_second_wait_time'] 243 self.delay_up_time = self.module.params['delay_up_time'] 244 self.state = self.module.params['state'] 245 246 # host info 247 self.host = self.module.params['host'] 248 self.username = self.module.params['username'] 249 self.port = self.module.params['port'] 250 251 # state 252 self.changed = False 253 self.bfd_dict = dict() 254 self.updates_cmd = list() 255 self.commands = list() 256 self.results = dict() 257 self.proposed = dict() 258 self.existing = dict() 259 self.end_state = dict() 260 261 def __init_module__(self): 262 """init module""" 263 264 required_together = [('damp_init_wait_time', 'damp_max_wait_time', 'damp_second_wait_time')] 265 self.module = AnsibleModule(argument_spec=self.spec, 266 required_together=required_together, 267 supports_check_mode=True) 268 269 def get_bfd_dict(self): 270 """bfd config dict""" 271 272 bfd_dict = dict() 273 bfd_dict["global"] = dict() 274 conf_str = CE_NC_GET_BFD % CE_NC_GET_BFD_GLB 275 276 xml_str = get_nc_config(self.module, conf_str) 277 if "<data/>" in xml_str: 278 return bfd_dict 279 280 xml_str = xml_str.replace('\r', '').replace('\n', '').\ 281 replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ 282 replace('xmlns="http://www.huawei.com/netconf/vrp"', "") 283 root = ElementTree.fromstring(xml_str) 284 285 # get bfd global info 286 glb = root.find("bfd/bfdSchGlobal") 287 if glb: 288 for attr in glb: 289 if attr.text is not None: 290 bfd_dict["global"][attr.tag] = attr.text 291 292 return bfd_dict 293 294 def config_global(self): 295 """configures bfd global params""" 296 297 xml_str = "" 298 damp_chg = False 299 300 # bfd_enable 301 if self.bfd_enable: 302 if bool(self.bfd_dict["global"].get("bfdEnable", "false") == "true") != bool(self.bfd_enable == "enable"): 303 if self.bfd_enable == "enable": 304 xml_str = "<bfdEnable>true</bfdEnable>" 305 self.updates_cmd.append("bfd") 306 else: 307 xml_str = "<bfdEnable>false</bfdEnable>" 308 self.updates_cmd.append("undo bfd") 309 310 # get bfd end state 311 bfd_state = "disable" 312 if self.bfd_enable: 313 bfd_state = self.bfd_enable 314 elif self.bfd_dict["global"].get("bfdEnable", "false") == "true": 315 bfd_state = "enable" 316 317 # default_ip 318 if self.default_ip: 319 if bfd_state == "enable": 320 if self.state == "present" and self.default_ip != self.bfd_dict["global"].get("defaultIp"): 321 xml_str += "<defaultIp>%s</defaultIp>" % self.default_ip 322 if "bfd" not in self.updates_cmd: 323 self.updates_cmd.append("bfd") 324 self.updates_cmd.append("default-ip-address %s" % self.default_ip) 325 elif self.state == "absent" and self.default_ip == self.bfd_dict["global"].get("defaultIp"): 326 xml_str += "<defaultIp/>" 327 if "bfd" not in self.updates_cmd: 328 self.updates_cmd.append("bfd") 329 self.updates_cmd.append("undo default-ip-address") 330 331 # tos_exp_dynamic 332 if self.tos_exp_dynamic is not None: 333 if bfd_state == "enable": 334 if self.state == "present" and self.tos_exp_dynamic != int(self.bfd_dict["global"].get("tosExp", "7")): 335 xml_str += "<tosExp>%s</tosExp>" % self.tos_exp_dynamic 336 if "bfd" not in self.updates_cmd: 337 self.updates_cmd.append("bfd") 338 self.updates_cmd.append("tos-exp %s dynamic" % self.tos_exp_dynamic) 339 elif self.state == "absent" and self.tos_exp_dynamic == int(self.bfd_dict["global"].get("tosExp", "7")): 340 xml_str += "<tosExp/>" 341 if "bfd" not in self.updates_cmd: 342 self.updates_cmd.append("bfd") 343 self.updates_cmd.append("undo tos-exp dynamic") 344 345 # tos_exp_static 346 if self.tos_exp_static is not None: 347 if bfd_state == "enable": 348 if self.state == "present" \ 349 and self.tos_exp_static != int(self.bfd_dict["global"].get("tosExpStatic", "7")): 350 xml_str += "<tosExpStatic>%s</tosExpStatic>" % self.tos_exp_static 351 if "bfd" not in self.updates_cmd: 352 self.updates_cmd.append("bfd") 353 self.updates_cmd.append("tos-exp %s static" % self.tos_exp_static) 354 elif self.state == "absent" \ 355 and self.tos_exp_static == int(self.bfd_dict["global"].get("tosExpStatic", "7")): 356 xml_str += "<tosExpStatic/>" 357 if "bfd" not in self.updates_cmd: 358 self.updates_cmd.append("bfd") 359 self.updates_cmd.append("undo tos-exp static") 360 361 # delay_up_time 362 if self.delay_up_time is not None: 363 if bfd_state == "enable": 364 delay_time = self.bfd_dict["global"].get("delayUpTimer", "0") 365 if not delay_time or not delay_time.isdigit(): 366 delay_time = "0" 367 if self.state == "present" \ 368 and self.delay_up_time != int(delay_time): 369 xml_str += "<delayUpTimer>%s</delayUpTimer>" % self.delay_up_time 370 if "bfd" not in self.updates_cmd: 371 self.updates_cmd.append("bfd") 372 self.updates_cmd.append("delay-up %s" % self.delay_up_time) 373 elif self.state == "absent" \ 374 and self.delay_up_time == int(delay_time): 375 xml_str += "<delayUpTimer/>" 376 if "bfd" not in self.updates_cmd: 377 self.updates_cmd.append("bfd") 378 self.updates_cmd.append("undo delay-up") 379 380 # damp_init_wait_time damp_max_wait_time damp_second_wait_time 381 if self.damp_init_wait_time is not None and self.damp_second_wait_time is not None \ 382 and self.damp_second_wait_time is not None: 383 if bfd_state == "enable": 384 if self.state == "present": 385 if self.damp_max_wait_time != int(self.bfd_dict["global"].get("dampMaxWaitTime", "2000")): 386 xml_str += "<dampMaxWaitTime>%s</dampMaxWaitTime>" % self.damp_max_wait_time 387 damp_chg = True 388 if self.damp_init_wait_time != int(self.bfd_dict["global"].get("dampInitWaitTime", "12000")): 389 xml_str += "<dampInitWaitTime>%s</dampInitWaitTime>" % self.damp_init_wait_time 390 damp_chg = True 391 if self.damp_second_wait_time != int(self.bfd_dict["global"].get("dampSecondWaitTime", "5000")): 392 xml_str += "<dampSecondWaitTime>%s</dampSecondWaitTime>" % self.damp_second_wait_time 393 damp_chg = True 394 if damp_chg: 395 if "bfd" not in self.updates_cmd: 396 self.updates_cmd.append("bfd") 397 self.updates_cmd.append("dampening timer-interval maximum %s initial %s secondary %s" % ( 398 self.damp_max_wait_time, self.damp_init_wait_time, self.damp_second_wait_time)) 399 else: 400 damp_chg = True 401 if self.damp_max_wait_time != int(self.bfd_dict["global"].get("dampMaxWaitTime", "2000")): 402 damp_chg = False 403 if self.damp_init_wait_time != int(self.bfd_dict["global"].get("dampInitWaitTime", "12000")): 404 damp_chg = False 405 if self.damp_second_wait_time != int(self.bfd_dict["global"].get("dampSecondWaitTime", "5000")): 406 damp_chg = False 407 408 if damp_chg: 409 xml_str += "<dampMaxWaitTime/><dampInitWaitTime/><dampSecondWaitTime/>" 410 if "bfd" not in self.updates_cmd: 411 self.updates_cmd.append("bfd") 412 self.updates_cmd.append("undo dampening timer-interval maximum %s initial %s secondary %s" % ( 413 self.damp_max_wait_time, self.damp_init_wait_time, self.damp_second_wait_time)) 414 if xml_str: 415 return '<bfdSchGlobal operation="merge">' + xml_str + '</bfdSchGlobal>' 416 else: 417 return "" 418 419 def netconf_load_config(self, xml_str): 420 """load bfd config by netconf""" 421 422 if not xml_str: 423 return 424 425 xml_cfg = """ 426 <config> 427 <bfd xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 428 %s 429 </bfd> 430 </config>""" % xml_str 431 set_nc_config(self.module, xml_cfg) 432 self.changed = True 433 434 def check_params(self): 435 """Check all input params""" 436 437 # check default_ip 438 if self.default_ip: 439 if not check_default_ip(self.default_ip): 440 self.module.fail_json(msg="Error: Default ip is invalid.") 441 442 # check tos_exp_dynamic 443 if self.tos_exp_dynamic is not None: 444 if self.tos_exp_dynamic < 0 or self.tos_exp_dynamic > 7: 445 self.module.fail_json(msg="Error: Session tos_exp_dynamic is not ranges from 0 to 7.") 446 447 # check tos_exp_static 448 if self.tos_exp_static is not None: 449 if self.tos_exp_static < 0 or self.tos_exp_static > 7: 450 self.module.fail_json(msg="Error: Session tos_exp_static is not ranges from 0 to 7.") 451 452 # check damp_init_wait_time 453 if self.damp_init_wait_time is not None: 454 if self.damp_init_wait_time < 1 or self.damp_init_wait_time > 3600000: 455 self.module.fail_json(msg="Error: Session damp_init_wait_time is not ranges from 1 to 3600000.") 456 457 # check damp_max_wait_time 458 if self.damp_max_wait_time is not None: 459 if self.damp_max_wait_time < 1 or self.damp_max_wait_time > 3600000: 460 self.module.fail_json(msg="Error: Session damp_max_wait_time is not ranges from 1 to 3600000.") 461 462 # check damp_second_wait_time 463 if self.damp_second_wait_time is not None: 464 if self.damp_second_wait_time < 1 or self.damp_second_wait_time > 3600000: 465 self.module.fail_json(msg="Error: Session damp_second_wait_time is not ranges from 1 to 3600000.") 466 467 # check delay_up_time 468 if self.delay_up_time is not None: 469 if self.delay_up_time < 1 or self.delay_up_time > 600: 470 self.module.fail_json(msg="Error: Session delay_up_time is not ranges from 1 to 600.") 471 472 def get_proposed(self): 473 """get proposed info""" 474 475 self.proposed["bfd_enalbe"] = self.bfd_enable 476 self.proposed["default_ip"] = self.default_ip 477 self.proposed["tos_exp_dynamic"] = self.tos_exp_dynamic 478 self.proposed["tos_exp_static"] = self.tos_exp_static 479 self.proposed["damp_init_wait_time"] = self.damp_init_wait_time 480 self.proposed["damp_max_wait_time"] = self.damp_max_wait_time 481 self.proposed["damp_second_wait_time"] = self.damp_second_wait_time 482 self.proposed["delay_up_time"] = self.delay_up_time 483 self.proposed["state"] = self.state 484 485 def get_existing(self): 486 """get existing info""" 487 488 if not self.bfd_dict: 489 return 490 491 self.existing["global"] = self.bfd_dict.get("global") 492 493 def get_end_state(self): 494 """get end state info""" 495 496 bfd_dict = self.get_bfd_dict() 497 if not bfd_dict: 498 return 499 500 self.end_state["global"] = bfd_dict.get("global") 501 if self.existing == self.end_state: 502 self.changed = False 503 504 def work(self): 505 """worker""" 506 507 self.check_params() 508 self.bfd_dict = self.get_bfd_dict() 509 self.get_existing() 510 self.get_proposed() 511 512 # deal present or absent 513 xml_str = self.config_global() 514 515 # update to device 516 if xml_str: 517 self.netconf_load_config(xml_str) 518 self.changed = True 519 520 self.get_end_state() 521 self.results['changed'] = self.changed 522 self.results['proposed'] = self.proposed 523 self.results['existing'] = self.existing 524 self.results['end_state'] = self.end_state 525 if self.changed: 526 self.results['updates'] = self.updates_cmd 527 else: 528 self.results['updates'] = list() 529 530 self.module.exit_json(**self.results) 531 532 533def main(): 534 """Module main""" 535 536 argument_spec = dict( 537 bfd_enable=dict(required=False, type='str', choices=['enable', 'disable']), 538 default_ip=dict(required=False, type='str'), 539 tos_exp_dynamic=dict(required=False, type='int'), 540 tos_exp_static=dict(required=False, type='int'), 541 damp_init_wait_time=dict(required=False, type='int'), 542 damp_max_wait_time=dict(required=False, type='int'), 543 damp_second_wait_time=dict(required=False, type='int'), 544 delay_up_time=dict(required=False, type='int'), 545 state=dict(required=False, default='present', choices=['present', 'absent']) 546 ) 547 548 argument_spec.update(ce_argument_spec) 549 module = BfdGlobal(argument_spec) 550 module.work() 551 552 553if __name__ == '__main__': 554 main() 555