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_ospf 25short_description: Manages configuration of an OSPF instance on HUAWEI CloudEngine switches. 26description: 27 - Manages configuration of an OSPF instance on HUAWEI CloudEngine switches. 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 process_id: 35 description: 36 - Specifies a process ID. 37 The value is an integer ranging from 1 to 4294967295. 38 required: true 39 area: 40 description: 41 - Specifies the area ID. The area with the area-id being 0 is a backbone area. 42 Valid values are a string, formatted as an IP address 43 (i.e. "0.0.0.0") or as an integer between 1 and 4294967295. 44 addr: 45 description: 46 - Specifies the address of the network segment where the interface resides. 47 The value is in dotted decimal notation. 48 mask: 49 description: 50 - IP network wildcard bits in decimal format between 0 and 32. 51 auth_mode: 52 description: 53 - Specifies the authentication type. 54 choices: ['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'] 55 auth_text_simple: 56 description: 57 - Specifies a password for simple authentication. 58 The value is a string of 1 to 8 characters. 59 auth_key_id: 60 description: 61 - Authentication key id when C(auth_mode) is 'hmac-sha256', 'md5' or 'hmac-md5. 62 Valid value is an integer is in the range from 1 to 255. 63 auth_text_md5: 64 description: 65 - Specifies a password for MD5, HMAC-MD5, or HMAC-SHA256 authentication. 66 The value is a string of 1 to 255 case-sensitive characters, spaces not supported. 67 nexthop_addr: 68 description: 69 - IPv4 address for configure next-hop address's weight. 70 Valid values are a string, formatted as an IP address. 71 nexthop_weight: 72 description: 73 - Indicates the weight of the next hop. 74 The smaller the value is, the higher the preference of the route is. 75 It is an integer that ranges from 1 to 254. 76 max_load_balance: 77 description: 78 - The maximum number of paths for forward packets over multiple paths. 79 Valid value is an integer in the range from 1 to 64. 80 state: 81 description: 82 - Determines whether the config should be present or not 83 on the device. 84 default: present 85 choices: ['present','absent'] 86''' 87 88EXAMPLES = ''' 89- name: Ospf module test 90 hosts: cloudengine 91 connection: local 92 gather_facts: no 93 vars: 94 cli: 95 host: "{{ inventory_hostname }}" 96 port: "{{ ansible_ssh_port }}" 97 username: "{{ username }}" 98 password: "{{ password }}" 99 transport: cli 100 101 tasks: 102 103 - name: Configure ospf 104 community.network.ce_ospf: 105 process_id: 1 106 area: 100 107 state: present 108 provider: "{{ cli }}" 109''' 110 111RETURN = ''' 112proposed: 113 description: k/v pairs of parameters passed into module 114 returned: verbose mode 115 type: dict 116 sample: {"process_id": "1", "area": "100"} 117existing: 118 description: k/v pairs of existing configuration 119 returned: verbose mode 120 type: dict 121 sample: {"process_id": "1", "areas": [], "nexthops":[], "max_load_balance": "32"} 122end_state: 123 description: k/v pairs of configuration after module execution 124 returned: verbose mode 125 type: dict 126 sample: {"process_id": "1", 127 "areas": [{"areaId": "0.0.0.100", "areaType": "Normal"}], 128 "nexthops":[], "max_load_balance": "32"} 129updates: 130 description: commands sent to the device 131 returned: always 132 type: list 133 sample: ["ospf 1", "area 0.0.0.100"] 134changed: 135 description: check to see if a change was made on the device 136 returned: always 137 type: bool 138 sample: true 139''' 140 141from xml.etree import ElementTree 142from ansible.module_utils.basic import AnsibleModule 143from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec 144 145CE_NC_GET_OSPF = """ 146 <filter type="subtree"> 147 <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 148 <ospfv2comm> 149 <ospfSites> 150 <ospfSite> 151 <processId>%s</processId> 152 <routerId></routerId> 153 <vrfName></vrfName> 154 <ProcessTopologys> 155 <ProcessTopology> 156 <nexthopMTs></nexthopMTs> 157 <maxLoadBalancing></maxLoadBalancing> 158 </ProcessTopology> 159 </ProcessTopologys> 160 <areas> 161 <area> 162 <areaId></areaId> 163 <areaType></areaType> 164 <authenticationMode></authenticationMode> 165 <authTextSimple></authTextSimple> 166 <keyId></keyId> 167 <authTextMd5></authTextMd5> 168 <networks> 169 <network> 170 <ipAddress></ipAddress> 171 <wildcardMask></wildcardMask> 172 </network> 173 </networks> 174 </area> 175 </areas> 176 </ospfSite> 177 </ospfSites> 178 </ospfv2comm> 179 </ospfv2> 180 </filter> 181""" 182 183CE_NC_CREATE_PROCESS = """ 184 <config> 185 <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 186 <ospfv2comm> 187 <ospfSites> 188 <ospfSite operation="merge"> 189 <processId>%s</processId> 190 </ospfSite> 191 </ospfSites> 192 </ospfv2comm> 193 </ospfv2> 194 </config> 195""" 196 197CE_NC_DELETE_PROCESS = """ 198 <config> 199 <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 200 <ospfv2comm> 201 <ospfSites> 202 <ospfSite operation="delete"> 203 <processId>%s</processId> 204 </ospfSite> 205 </ospfSites> 206 </ospfv2comm> 207 </ospfv2> 208 </config> 209""" 210 211CE_NC_XML_BUILD_MERGE_PROCESS = """ 212 <config> 213 <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 214 <ospfv2comm> 215 <ospfSites> 216 <ospfSite operation="merge"> 217 <processId>%s</processId> 218 %s 219 </ospfSite> 220 </ospfSites> 221 </ospfv2comm> 222 </ospfv2> 223 </config> 224""" 225 226CE_NC_XML_BUILD_PROCESS = """ 227 <config> 228 <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 229 <ospfv2comm> 230 <ospfSites> 231 <ospfSite> 232 <processId>%s</processId> 233 %s 234 </ospfSite> 235 </ospfSites> 236 </ospfv2comm> 237 </ospfv2> 238 </config> 239""" 240 241CE_NC_XML_BUILD_MERGE_AREA = """ 242 <areas> 243 <area operation="merge"> 244 <areaId>%s</areaId> 245 %s 246 </area> 247 </areas> 248""" 249 250CE_NC_XML_BUILD_DELETE_AREA = """ 251 <areas> 252 <area operation="delete"> 253 <areaId>%s</areaId> 254 %s 255 </area> 256 </areas> 257""" 258 259CE_NC_XML_BUILD_AREA = """ 260 <areas> 261 <area> 262 <areaId>%s</areaId> 263 %s 264 </area> 265 </areas> 266""" 267 268CE_NC_XML_SET_AUTH_MODE = """ 269 <authenticationMode>%s</authenticationMode> 270""" 271CE_NC_XML_SET_AUTH_TEXT_SIMPLE = """ 272 <authTextSimple>%s</authTextSimple> 273""" 274 275CE_NC_XML_SET_AUTH_MD5 = """ 276 <keyId>%s</keyId> 277 <authTextMd5>%s</authTextMd5> 278""" 279 280 281CE_NC_XML_MERGE_NETWORKS = """ 282 <networks> 283 <network operation="merge"> 284 <ipAddress>%s</ipAddress> 285 <wildcardMask>%s</wildcardMask> 286 </network> 287 </networks> 288""" 289 290CE_NC_XML_DELETE_NETWORKS = """ 291 <networks> 292 <network operation="delete"> 293 <ipAddress>%s</ipAddress> 294 <wildcardMask>%s</wildcardMask> 295 </network> 296 </networks> 297""" 298 299CE_NC_XML_SET_LB = """ 300 <maxLoadBalancing>%s</maxLoadBalancing> 301""" 302 303 304CE_NC_XML_BUILD_MERGE_TOPO = """ 305 <ProcessTopologys> 306 <ProcessTopology operation="merge"> 307 <topoName>base</topoName> 308 %s 309 </ProcessTopology> 310 </ProcessTopologys> 311 312""" 313 314CE_NC_XML_BUILD_TOPO = """ 315 <ProcessTopologys> 316 <ProcessTopology > 317 <topoName>base</topoName> 318 %s 319 </ProcessTopology> 320 </ProcessTopologys> 321 322""" 323 324CE_NC_XML_MERGE_NEXTHOP = """ 325 <nexthopMTs> 326 <nexthopMT operation="merge"> 327 <ipAddress>%s</ipAddress> 328 <weight>%s</weight> 329 </nexthopMT> 330 </nexthopMTs> 331""" 332 333CE_NC_XML_DELETE_NEXTHOP = """ 334 <nexthopMTs> 335 <nexthopMT operation="delete"> 336 <ipAddress>%s</ipAddress> 337 </nexthopMT> 338 </nexthopMTs> 339""" 340 341 342class OSPF(object): 343 """ 344 Manages configuration of an ospf instance. 345 """ 346 347 def __init__(self, argument_spec): 348 self.spec = argument_spec 349 self.module = None 350 self.init_module() 351 352 # module input info 353 self.process_id = self.module.params['process_id'] 354 self.area = self.module.params['area'] 355 self.addr = self.module.params['addr'] 356 self.mask = self.module.params['mask'] 357 self.auth_mode = self.module.params['auth_mode'] 358 self.auth_text_simple = self.module.params['auth_text_simple'] 359 self.auth_key_id = self.module.params['auth_key_id'] 360 self.auth_text_md5 = self.module.params['auth_text_md5'] 361 self.nexthop_addr = self.module.params['nexthop_addr'] 362 self.nexthop_weight = self.module.params['nexthop_weight'] 363 self.max_load_balance = self.module.params['max_load_balance'] 364 self.state = self.module.params['state'] 365 366 # ospf info 367 self.ospf_info = dict() 368 369 # state 370 self.changed = False 371 self.updates_cmd = list() 372 self.results = dict() 373 self.proposed = dict() 374 self.existing = dict() 375 self.end_state = dict() 376 377 def init_module(self): 378 """ init module """ 379 380 required_together = [ 381 ("addr", "mask"), 382 ("auth_key_id", "auth_text_md5"), 383 ("nexthop_addr", "nexthop_weight") 384 ] 385 self.module = AnsibleModule( 386 argument_spec=self.spec, required_together=required_together, supports_check_mode=True) 387 388 def check_response(self, xml_str, xml_name): 389 """Check if response message is already succeed.""" 390 391 if "<ok/>" not in xml_str: 392 self.module.fail_json(msg='Error: %s failed.' % xml_name) 393 394 def get_wildcard_mask(self): 395 """convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255""" 396 397 mask_int = ["255"] * 4 398 length = int(self.mask) 399 400 if length > 32: 401 self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') 402 if length < 8: 403 mask_int[0] = str(int(~(0xFF << (8 - length % 8)) & 0xFF)) 404 if length >= 8: 405 mask_int[0] = '0' 406 mask_int[1] = str(int(~(0xFF << (16 - (length % 16))) & 0xFF)) 407 if length >= 16: 408 mask_int[1] = '0' 409 mask_int[2] = str(int(~(0xFF << (24 - (length % 24))) & 0xFF)) 410 if length >= 24: 411 mask_int[2] = '0' 412 mask_int[3] = str(int(~(0xFF << (32 - (length % 32))) & 0xFF)) 413 if length == 32: 414 mask_int[3] = '0' 415 416 return '.'.join(mask_int) 417 418 def get_area_ip(self): 419 """convert integer to ip address""" 420 421 if not self.area.isdigit(): 422 return self.area 423 424 addr_int = ['0'] * 4 425 addr_int[0] = str(((int(self.area) & 0xFF000000) >> 24) & 0xFF) 426 addr_int[1] = str(((int(self.area) & 0x00FF0000) >> 16) & 0xFF) 427 addr_int[2] = str(((int(self.area) & 0x0000FF00) >> 8) & 0XFF) 428 addr_int[3] = str(int(self.area) & 0xFF) 429 430 return '.'.join(addr_int) 431 432 def get_ospf_dict(self, process_id): 433 """ get one ospf attributes dict.""" 434 435 ospf_info = dict() 436 conf_str = CE_NC_GET_OSPF % process_id 437 xml_str = get_nc_config(self.module, conf_str) 438 if "<data/>" in xml_str: 439 return ospf_info 440 441 xml_str = xml_str.replace('\r', '').replace('\n', '').\ 442 replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ 443 replace('xmlns="http://www.huawei.com/netconf/vrp"', "") 444 445 # get process base info 446 root = ElementTree.fromstring(xml_str) 447 ospfsite = root.find("ospfv2/ospfv2comm/ospfSites/ospfSite") 448 if ospfsite: 449 for site in ospfsite: 450 if site.tag in ["processId", "routerId", "vrfName"]: 451 ospf_info[site.tag] = site.text 452 453 # get Topology info 454 topo = root.find( 455 "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology") 456 if topo: 457 for eles in topo: 458 if eles.tag in ["maxLoadBalancing"]: 459 ospf_info[eles.tag] = eles.text 460 461 # get nexthop info 462 ospf_info["nexthops"] = list() 463 nexthops = root.findall( 464 "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology/nexthopMTs/nexthopMT") 465 if nexthops: 466 for nexthop in nexthops: 467 nh_dict = dict() 468 for ele in nexthop: 469 if ele.tag in ["ipAddress", "weight"]: 470 nh_dict[ele.tag] = ele.text 471 ospf_info["nexthops"].append(nh_dict) 472 473 # get areas info 474 ospf_info["areas"] = list() 475 areas = root.findall( 476 "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area") 477 if areas: 478 for area in areas: 479 area_dict = dict() 480 for ele in area: 481 if ele.tag in ["areaId", "authTextSimple", "areaType", 482 "authenticationMode", "keyId", "authTextMd5"]: 483 area_dict[ele.tag] = ele.text 484 if ele.tag == "networks": 485 # get networks info 486 area_dict["networks"] = list() 487 for net in ele: 488 net_dict = dict() 489 for net_ele in net: 490 if net_ele.tag in ["ipAddress", "wildcardMask"]: 491 net_dict[net_ele.tag] = net_ele.text 492 area_dict["networks"].append(net_dict) 493 494 ospf_info["areas"].append(area_dict) 495 return ospf_info 496 497 def is_area_exist(self): 498 """is ospf area exist""" 499 if not self.ospf_info: 500 return False 501 for area in self.ospf_info["areas"]: 502 if area["areaId"] == self.get_area_ip(): 503 return True 504 505 return False 506 507 def is_network_exist(self): 508 """is ospf area network exist""" 509 if not self.ospf_info: 510 return False 511 512 for area in self.ospf_info["areas"]: 513 if area["areaId"] == self.get_area_ip(): 514 if not area.get("networks"): 515 return False 516 for network in area.get("networks"): 517 if network["ipAddress"] == self.addr and network["wildcardMask"] == self.get_wildcard_mask(): 518 return True 519 return False 520 521 def is_nexthop_exist(self): 522 """is ospf nexthop exist""" 523 524 if not self.ospf_info: 525 return False 526 for nexthop in self.ospf_info["nexthops"]: 527 if nexthop["ipAddress"] == self.nexthop_addr: 528 return True 529 530 return False 531 532 def is_nexthop_change(self): 533 """is ospf nexthop change""" 534 if not self.ospf_info: 535 return True 536 537 for nexthop in self.ospf_info["nexthops"]: 538 if nexthop["ipAddress"] == self.nexthop_addr: 539 if nexthop["weight"] == self.nexthop_weight: 540 return False 541 else: 542 return True 543 544 return True 545 546 def create_process(self): 547 """Create ospf process""" 548 549 xml_area = "" 550 self.updates_cmd.append("ospf %s" % self.process_id) 551 xml_create = CE_NC_CREATE_PROCESS % self.process_id 552 set_nc_config(self.module, xml_create) 553 554 # nexthop weight 555 xml_nh = "" 556 if self.nexthop_addr: 557 xml_nh = CE_NC_XML_MERGE_NEXTHOP % ( 558 self.nexthop_addr, self.nexthop_weight) 559 self.updates_cmd.append("nexthop %s weight %s" % ( 560 self.nexthop_addr, self.nexthop_weight)) 561 562 # max load balance 563 xml_lb = "" 564 if self.max_load_balance: 565 xml_lb = CE_NC_XML_SET_LB % self.max_load_balance 566 self.updates_cmd.append( 567 "maximum load-balancing %s" % self.max_load_balance) 568 569 xml_topo = "" 570 if xml_lb or xml_nh: 571 xml_topo = CE_NC_XML_BUILD_TOPO % (xml_nh + xml_lb) 572 573 if self.area: 574 self.updates_cmd.append("area %s" % self.get_area_ip()) 575 xml_auth = "" 576 xml_network = "" 577 578 # networks 579 if self.addr and self.mask: 580 xml_network = CE_NC_XML_MERGE_NETWORKS % ( 581 self.addr, self.get_wildcard_mask()) 582 self.updates_cmd.append("network %s %s" % ( 583 self.addr, self.get_wildcard_mask())) 584 585 # authentication mode 586 if self.auth_mode: 587 xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode 588 if self.auth_mode == "none": 589 self.updates_cmd.append("undo authentication-mode") 590 else: 591 self.updates_cmd.append( 592 "authentication-mode %s" % self.auth_mode) 593 if self.auth_mode == "simple" and self.auth_text_simple: 594 xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple 595 self.updates_cmd.pop() 596 self.updates_cmd.append( 597 "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple)) 598 if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: 599 if self.auth_key_id and self.auth_text_md5: 600 xml_auth += CE_NC_XML_SET_AUTH_MD5 % ( 601 self.auth_key_id, self.auth_text_md5) 602 self.updates_cmd.pop() 603 self.updates_cmd.append( 604 "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) 605 if xml_network or xml_auth or not self.is_area_exist(): 606 xml_area += CE_NC_XML_BUILD_MERGE_AREA % ( 607 self.get_area_ip(), xml_network + xml_auth) 608 609 xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % ( 610 self.process_id, xml_topo + xml_area) 611 recv_xml = set_nc_config(self.module, xml_str) 612 self.check_response(recv_xml, "CREATE_PROCESS") 613 self.changed = True 614 615 def delete_process(self): 616 """Delete ospf process""" 617 618 xml_str = CE_NC_DELETE_PROCESS % self.process_id 619 recv_xml = set_nc_config(self.module, xml_str) 620 self.check_response(recv_xml, "DELETE_PROCESS") 621 self.updates_cmd.append("undo ospf %s" % self.process_id) 622 self.changed = True 623 624 def merge_process(self): 625 """merge ospf process""" 626 627 xml_area = "" 628 xml_str = "" 629 self.updates_cmd.append("ospf %s" % self.process_id) 630 631 # nexthop weight 632 xml_nh = "" 633 if self.nexthop_addr and self.is_nexthop_change(): 634 xml_nh = CE_NC_XML_MERGE_NEXTHOP % ( 635 self.nexthop_addr, self.nexthop_weight) 636 self.updates_cmd.append("nexthop %s weight %s" % ( 637 self.nexthop_addr, self.nexthop_weight)) 638 639 # max load balance 640 xml_lb = "" 641 if self.max_load_balance and self.ospf_info.get("maxLoadBalancing") != self.max_load_balance: 642 xml_lb = CE_NC_XML_SET_LB % self.max_load_balance 643 self.updates_cmd.append( 644 "maximum load-balancing %s" % self.max_load_balance) 645 646 xml_topo = "" 647 if xml_lb or xml_nh: 648 xml_topo = CE_NC_XML_BUILD_MERGE_TOPO % (xml_nh + xml_lb) 649 650 if self.area: 651 self.updates_cmd.append("area %s" % self.get_area_ip()) 652 xml_network = "" 653 xml_auth = "" 654 if self.addr and self.mask: 655 if not self.is_network_exist(): 656 xml_network += CE_NC_XML_MERGE_NETWORKS % ( 657 self.addr, self.get_wildcard_mask()) 658 self.updates_cmd.append("network %s %s" % ( 659 self.addr, self.get_wildcard_mask())) 660 661 # NOTE: for security, authentication config will always be update 662 if self.auth_mode: 663 xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode 664 if self.auth_mode == "none": 665 self.updates_cmd.append("undo authentication-mode") 666 else: 667 self.updates_cmd.append( 668 "authentication-mode %s" % self.auth_mode) 669 if self.auth_mode == "simple" and self.auth_text_simple: 670 xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple 671 self.updates_cmd.pop() 672 self.updates_cmd.append( 673 "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple)) 674 if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: 675 if self.auth_key_id and self.auth_text_md5: 676 xml_auth += CE_NC_XML_SET_AUTH_MD5 % ( 677 self.auth_key_id, self.auth_text_md5) 678 self.updates_cmd.pop() 679 self.updates_cmd.append( 680 "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) 681 if xml_network or xml_auth or not self.is_area_exist(): 682 xml_area += CE_NC_XML_BUILD_MERGE_AREA % ( 683 self.get_area_ip(), xml_network + xml_auth) 684 elif self.is_area_exist(): 685 self.updates_cmd.pop() # remove command: area 686 else: 687 pass 688 689 if xml_area or xml_topo: 690 xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % ( 691 self.process_id, xml_topo + xml_area) 692 recv_xml = set_nc_config(self.module, xml_str) 693 self.check_response(recv_xml, "MERGE_PROCESS") 694 self.changed = True 695 696 def remove_area_network(self): 697 """remvoe ospf area network""" 698 699 if not self.is_network_exist(): 700 return 701 702 xml_network = CE_NC_XML_DELETE_NETWORKS % ( 703 self.addr, self.get_wildcard_mask()) 704 xml_area = CE_NC_XML_BUILD_AREA % (self.get_area_ip(), xml_network) 705 xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area) 706 recv_xml = set_nc_config(self.module, xml_str) 707 self.check_response(recv_xml, "DELETE_AREA_NETWORK") 708 self.updates_cmd.append("ospf %s" % self.process_id) 709 self.updates_cmd.append("area %s" % self.get_area_ip()) 710 self.updates_cmd.append("undo network %s %s" % 711 (self.addr, self.get_wildcard_mask())) 712 self.changed = True 713 714 def remove_area(self): 715 """remove ospf area""" 716 717 if not self.is_area_exist(): 718 return 719 720 xml_area = CE_NC_XML_BUILD_DELETE_AREA % (self.get_area_ip(), "") 721 xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area) 722 recv_xml = set_nc_config(self.module, xml_str) 723 self.check_response(recv_xml, "DELETE_AREA") 724 self.updates_cmd.append("ospf %s" % self.process_id) 725 self.updates_cmd.append("undo area %s" % self.get_area_ip()) 726 self.changed = True 727 728 def remove_nexthop(self): 729 """remove ospf nexthop weight""" 730 731 if not self.is_nexthop_exist(): 732 return 733 734 xml_nh = CE_NC_XML_DELETE_NEXTHOP % self.nexthop_addr 735 xml_topo = CE_NC_XML_BUILD_TOPO % xml_nh 736 xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_topo) 737 recv_xml = set_nc_config(self.module, xml_str) 738 self.check_response(recv_xml, "DELETE_NEXTHOP_WEIGHT") 739 self.updates_cmd.append("ospf %s" % self.process_id) 740 self.updates_cmd.append("undo nexthop %s" % self.nexthop_addr) 741 self.changed = True 742 743 def is_valid_v4addr(self, addr): 744 """check is ipv4 addr is valid""" 745 746 if addr.find('.') != -1: 747 addr_list = addr.split('.') 748 if len(addr_list) != 4: 749 return False 750 for each_num in addr_list: 751 if not each_num.isdigit(): 752 return False 753 if int(each_num) > 255: 754 return False 755 return True 756 757 return False 758 759 def convert_ip_to_network(self): 760 """convert ip to subnet address""" 761 762 ip_list = self.addr.split('.') 763 mask_list = self.get_wildcard_mask().split('.') 764 765 for i in range(len(ip_list)): 766 ip_list[i] = str((int(ip_list[i]) & (~int(mask_list[i]))) & 0xff) 767 768 self.addr = '.'.join(ip_list) 769 770 def check_params(self): 771 """Check all input params""" 772 773 # process_id check 774 if not self.process_id.isdigit(): 775 self.module.fail_json(msg="Error: process_id is not digit.") 776 if int(self.process_id) < 1 or int(self.process_id) > 4294967295: 777 self.module.fail_json( 778 msg="Error: process_id must be an integer between 1 and 4294967295.") 779 780 if self.area: 781 # area check 782 if self.area.isdigit(): 783 if int(self.area) < 0 or int(self.area) > 4294967295: 784 self.module.fail_json( 785 msg="Error: area id (Integer) must be between 0 and 4294967295.") 786 787 else: 788 if not self.is_valid_v4addr(self.area): 789 self.module.fail_json(msg="Error: area id is invalid.") 790 791 # area network check 792 if self.addr: 793 if not self.is_valid_v4addr(self.addr): 794 self.module.fail_json( 795 msg="Error: network addr is invalid.") 796 if not self.mask.isdigit(): 797 self.module.fail_json( 798 msg="Error: network mask is not digit.") 799 if int(self.mask) < 0 or int(self.mask) > 32: 800 self.module.fail_json( 801 msg="Error: network mask is invalid.") 802 803 # area authentication check 804 if self.state == "present" and self.auth_mode: 805 if self.auth_mode == "simple": 806 if self.auth_text_simple and len(self.auth_text_simple) > 8: 807 self.module.fail_json( 808 msg="Error: auth_text_simple is not in the range from 1 to 8.") 809 if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: 810 if self.auth_key_id: 811 if not self.auth_key_id.isdigit(): 812 self.module.fail_json( 813 msg="Error: auth_key_id is not digit.") 814 if int(self.auth_key_id) < 1 or int(self.auth_key_id) > 255: 815 self.module.fail_json( 816 msg="Error: auth_key_id is not in the range from 1 to 255.") 817 if self.auth_text_md5 and len(self.auth_text_md5) > 255: 818 self.module.fail_json( 819 msg="Error: auth_text_md5 is not in the range from 1 to 255.") 820 821 # process max load balance check 822 if self.state == "present" and self.max_load_balance: 823 if not self.max_load_balance.isdigit(): 824 self.module.fail_json( 825 msg="Error: max_load_balance is not digit.") 826 if int(self.max_load_balance) < 1 or int(self.max_load_balance) > 64: 827 self.module.fail_json( 828 msg="Error: max_load_balance is not in the range from 1 to 64.") 829 830 # process nexthop weight check 831 if self.nexthop_addr: 832 if not self.is_valid_v4addr(self.nexthop_addr): 833 self.module.fail_json(msg="Error: nexthop_addr is invalid.") 834 if not self.nexthop_weight.isdigit(): 835 self.module.fail_json( 836 msg="Error: nexthop_weight is not digit.") 837 if int(self.nexthop_weight) < 1 or int(self.nexthop_weight) > 254: 838 self.module.fail_json( 839 msg="Error: nexthop_weight is not in the range from 1 to 254.") 840 841 if self.addr: 842 self.convert_ip_to_network() 843 844 def get_proposed(self): 845 """get proposed info""" 846 847 self.proposed["process_id"] = self.process_id 848 self.proposed["area"] = self.area 849 if self.area: 850 self.proposed["addr"] = self.addr 851 self.proposed["mask"] = self.mask 852 if self.auth_mode: 853 self.proposed["auth_mode"] = self.auth_mode 854 if self.auth_mode == "simple": 855 self.proposed["auth_text_simple"] = self.auth_text_simple 856 if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: 857 self.proposed["auth_key_id"] = self.auth_key_id 858 self.proposed["auth_text_md5"] = self.auth_text_md5 859 860 if self.nexthop_addr: 861 self.proposed["nexthop_addr"] = self.nexthop_addr 862 self.proposed["nexthop_weight"] = self.nexthop_weight 863 self.proposed["max_load_balance"] = self.max_load_balance 864 self.proposed["state"] = self.state 865 866 def get_existing(self): 867 """get existing info""" 868 869 if not self.ospf_info: 870 return 871 872 self.existing["process_id"] = self.process_id 873 self.existing["areas"] = self.ospf_info["areas"] 874 self.existing["nexthops"] = self.ospf_info["nexthops"] 875 self.existing["max_load_balance"] = self.ospf_info.get( 876 "maxLoadBalancing") 877 878 def get_end_state(self): 879 """get end state info""" 880 881 ospf_info = self.get_ospf_dict(self.process_id) 882 883 if not ospf_info: 884 return 885 886 self.end_state["process_id"] = self.process_id 887 self.end_state["areas"] = ospf_info["areas"] 888 self.end_state["nexthops"] = ospf_info["nexthops"] 889 self.end_state["max_load_balance"] = ospf_info.get("maxLoadBalancing") 890 891 if self.end_state == self.existing: 892 if not self.auth_text_simple and not self.auth_text_md5: 893 self.changed = False 894 895 def work(self): 896 """worker""" 897 898 self.check_params() 899 self.ospf_info = self.get_ospf_dict(self.process_id) 900 self.get_existing() 901 self.get_proposed() 902 903 # deal present or absent 904 if self.state == "present": 905 if not self.ospf_info: 906 # create ospf process 907 self.create_process() 908 else: 909 # merge ospf 910 self.merge_process() 911 else: 912 if self.ospf_info: 913 if self.area: 914 if self.addr: 915 # remove ospf area network 916 self.remove_area_network() 917 else: 918 # remove ospf area 919 self.remove_area() 920 if self.nexthop_addr: 921 # remove ospf nexthop weight 922 self.remove_nexthop() 923 924 if not self.area and not self.nexthop_addr: 925 # remove ospf process 926 self.delete_process() 927 else: 928 self.module.fail_json(msg='Error: ospf process does not exist') 929 930 self.get_end_state() 931 self.results['changed'] = self.changed 932 self.results['proposed'] = self.proposed 933 self.results['existing'] = self.existing 934 self.results['end_state'] = self.end_state 935 if self.changed: 936 self.results['updates'] = self.updates_cmd 937 else: 938 self.results['updates'] = list() 939 940 self.module.exit_json(**self.results) 941 942 943def main(): 944 """Module main""" 945 946 argument_spec = dict( 947 process_id=dict(required=True, type='str'), 948 area=dict(required=False, type='str'), 949 addr=dict(required=False, type='str'), 950 mask=dict(required=False, type='str'), 951 auth_mode=dict(required=False, 952 choices=['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'], type='str'), 953 auth_text_simple=dict(required=False, type='str', no_log=True), 954 auth_key_id=dict(required=False, type='str'), 955 auth_text_md5=dict(required=False, type='str', no_log=True), 956 nexthop_addr=dict(required=False, type='str'), 957 nexthop_weight=dict(required=False, type='str'), 958 max_load_balance=dict(required=False, type='str'), 959 state=dict(required=False, default='present', 960 choices=['present', 'absent']) 961 ) 962 argument_spec.update(ce_argument_spec) 963 module = OSPF(argument_spec) 964 module.work() 965 966 967if __name__ == '__main__': 968 main() 969