1from __future__ import absolute_import 2import sys 3import re 4import os 5 6from ciscoconfparse.errors import DynamicAddressException 7 8from ciscoconfparse.ccp_util import ( 9 _IPV6_REGEX_STR_COMPRESSED1, 10 _IPV6_REGEX_STR_COMPRESSED2, 11) 12from ciscoconfparse.ccp_util import _IPV6_REGEX_STR_COMPRESSED3 13from ciscoconfparse.ccp_util import CiscoRange, IPv4Obj, IPv6Obj 14from ciscoconfparse.ccp_abc import BaseCfgLine 15 16### HUGE UGLY WARNING: 17### Anything in models_nxos.py could change at any time, until I remove this 18### warning. 19### 20### THIS FILE IS NOT FULLY FUNCTIONAL. IT IS INCOMPLETE 21### 22### You have been warned :-) 23""" models_nxos.py - Parse, Query, Build, and Modify IOS-style configurations 24 25 Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems 26 Copyright (C) 2019 David Michael Pennington at ThousandEyes 27 Copyright (C) 2016-2019 David Michael Pennington at Samsung Data Services 28 29 30 This program is free software: you can redistribute it and/or modify 31 it under the terms of the GNU General Public License as published by 32 the Free Software Foundation, either version 3 of the License, or 33 (at your option) any later version. 34 35 This program is distributed in the hope that it will be useful, 36 but WITHOUT ANY WARRANTY; without even the implied warranty of 37 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 GNU General Public License for more details. 39 40 You should have received a copy of the GNU General Public License 41 along with this program. If not, see <http://www.gnu.org/licenses/>. 42 43 If you need to contact the author, you can do so by emailing: 44 mike [~at~] pennington [/dot\] net 45""" 46 47## 48##------------- IOS Configuration line object 49## 50 51MAX_VLAN = 4094 52 53 54class NXOSCfgLine(BaseCfgLine): 55 """An object for a parsed IOS-style configuration line. 56 :class:`~models_nxos.NXOSCfgLine` objects contain references to other 57 parent and child :class:`~models_nxos.NXOSCfgLine` objects. 58 59 Args: 60 - text (str): A string containing a text copy of the NXOS configuration line. :class:`~ciscoconfparse.CiscoConfParse` will automatically identify the parent and children (if any) when it parses the configuration. 61 - comment_delimiter (str): A string which is considered a comment for the configuration format. Since this is for Cisco IOS-style configurations, it defaults to ``!``. 62 63 Attributes: 64 - text (str): A string containing the parsed IOS configuration statement 65 - linenum (int): The line number of this configuration statement in the original config; default is -1 when first initialized. 66 - parent (:class:`~models_nxos.NXOSCfgLine()`): The parent of this object; defaults to ``self``. 67 - children (list): A list of ``NXOSCfgLine()`` objects which are children of this object. 68 - child_indent (int): An integer with the indentation of this object's children 69 - indent (int): An integer with the indentation of this object's ``text`` oldest_ancestor (bool): A boolean indicating whether this is the oldest ancestor in a family 70 - is_comment (bool): A boolean indicating whether this is a comment 71 72 Returns: 73 - An instance of :class:`~models_nxos.NXOSCfgLine`. 74 75 """ 76 77 def __init__(self, *args, **kwargs): 78 """Accept an IOS line number and initialize family relationship 79 attributes""" 80 super(NXOSCfgLine, self).__init__(*args, **kwargs) 81 82 @classmethod 83 def is_object_for(cls, line="", re=re): 84 ## Default object, for now 85 return True 86 87 @property 88 def is_intf(self): 89 # Includes subinterfaces 90 """Returns a boolean (True or False) to answer whether this 91 :class:`~models_nxos.NXOSCfgLine` is an interface; subinterfaces 92 also return True. 93 94 Returns: 95 - bool. 96 97 This example illustrates use of the method. 98 99 .. code-block:: python 100 :emphasize-lines: 17 101 102 >>> config = [ 103 ... '!', 104 ... 'interface Ethernet1/1', 105 ... ' ip address 1.1.1.1/30', 106 ... '!', 107 ... 'interface Ethernet1/2', 108 ... ' no ip address', 109 ... '!', 110 ... 'interface Ethernet1/3', 111 ... ' ip address 1.1.1.5/30', 112 ... ' ip dhcp relay address 172.16.1.12' 113 ... ' ip dhcp relay address 172.19.200.84', 114 ... '!', 115 ... ] 116 >>> parse = CiscoConfParse(config) 117 >>> obj = parse.find_objects('^interface\sEthernet')[0] 118 >>> obj.is_intf 119 True 120 >>> 121 """ 122 # intf_regex = r'^interface\s+(\S+.+)' 123 # if self.re_match(intf_regex): 124 if self.text[0:10] == "interface " and self.text[10] != " ": 125 return True 126 return False 127 128 @property 129 def is_subintf(self): 130 """Returns a boolean (True or False) to answer whether this 131 :class:`~models_nxos.NXOSCfgLine` is a subinterface. 132 133 Returns: 134 - bool. 135 136 This example illustrates use of the method. 137 138 .. code-block:: python 139 :emphasize-lines: 17 140 141 >>> config = [ 142 ... '!', 143 ... 'interface Ethernet1/1', 144 ... ' ip address 1.1.1.1/30', 145 ... '!', 146 ... 'interface Ethernet1/2', 147 ... ' no ip address', 148 ... '!', 149 ... 'interface Ethernet1/3.100', 150 ... ' ip address 1.1.1.5/30', 151 ... ' ip dhcp relay address 172.16.1.12' 152 ... ' ip dhcp relay address 172.19.200.84', 153 ... '!', 154 ... ] 155 >>> parse = CiscoConfParse(config) 156 >>> obj = parse.find_objects('^interface\sEthernet')[-1] 157 >>> obj.is_subintf 158 True 159 >>> 160 """ 161 intf_regex = r"^interface\s+(\S+?\.\d+)" 162 if self.re_match(intf_regex): 163 return True 164 return False 165 166 _VIRTUAL_INTF_REGEX_STR = r"""^interface\s+(Loopback|Vlan|Tunnel|Port-Channel)""" 167 _VIRTUAL_INTF_REGEX = re.compile(_VIRTUAL_INTF_REGEX_STR, re.I) 168 169 @property 170 def is_virtual_intf(self): 171 if self.re_match(self._VIRTUAL_INTF_REGEX): 172 return True 173 return False 174 175 @property 176 def is_loopback_intf(self): 177 """Returns a boolean (True or False) to answer whether this 178 :class:`~models_nxos.NXOSCfgLine` is a loopback interface. 179 180 Returns: 181 - bool. 182 183 This example illustrates use of the method. 184 185 .. code-block:: python 186 :emphasize-lines: 11,14 187 188 >>> config = [ 189 ... '!', 190 ... 'interface Ethernet1/1', 191 ... ' ip address 1.1.1.1 255.255.255.252', 192 ... '!', 193 ... 'interface Loopback0', 194 ... ' ip address 1.1.1.5 255.255.255.255', 195 ... '!', 196 ... ] 197 >>> parse = CiscoConfParse(config) 198 >>> obj = parse.find_objects('^interface\sEthernet')[0] 199 >>> obj.is_loopback_intf 200 False 201 >>> obj = parse.find_objects('^interface\sLoop')[0] 202 >>> obj.is_loopback_intf 203 True 204 >>> 205 """ 206 intf_regex = r"^interface\s+(\Soopback)" 207 if self.re_match(intf_regex): 208 return True 209 return False 210 211 @property 212 def is_ethernet_intf(self): 213 """Returns a boolean (True or False) to answer whether this 214 :class:`~models_nxos.NXOSCfgLine` is an ethernet interface. 215 Any ethernet interface (10M through 10G) is considered an ethernet 216 interface. 217 218 Returns: 219 - bool. 220 221 This example illustrates use of the method. 222 223 .. code-block:: python 224 :emphasize-lines: 17,20 225 226 >>> config = [ 227 ... '!', 228 ... 'interface FastEthernet1/0', 229 ... ' ip address 1.1.1.1 /30', 230 ... '!', 231 ... 'interface Loopback0', 232 ... ' ip address', 233 ... '!', 234 ... 'interface ATM2/0.100 point-to-point', 235 ... ' ip address 1.1.1.5 255.255.255.252', 236 ... ' pvc 0/100', 237 ... ' vbr-nrt 704 704', 238 ... '!', 239 ... ] 240 >>> parse = CiscoConfParse(config, syntax='nxos', factory=True) 241 >>> obj = parse.find_objects('^interface\sFast')[0] 242 >>> obj.is_ethernet_intf 243 True 244 >>> obj = parse.find_objects('^interface\sLoop')[0] 245 >>> obj.is_ethernet_intf 246 False 247 >>> 248 """ 249 intf_regex = r"^interface\s+(.*?\Sthernet|mgmt)" 250 if self.re_match(intf_regex): 251 return True 252 return False 253 254 @property 255 def intf_in_portchannel(self): 256 """Return a boolean indicating whether this port is configured in a port-channel 257 258 """ 259 retval = self.re_match_iter_typed( 260 r"^\s*channel-group\s+(\d+)", result_type=bool, default=False 261 ) 262 return retval 263 264 @property 265 def portchannel_number(self): 266 """Return an integer for the port-channel which it's configured in. Ret 267urn -1 if it's not configured in a port-channel 268 269 """ 270 retval = self.re_match_iter_typed( 271 r"^\s*channel-group\s+(\d+)", result_type=int, default=-1 272 ) 273 return retval 274 275 @property 276 def is_portchannel_intf(self): 277 """Return a boolean indicating whether this port is a port-channel intf 278 279 """ 280 return "channel" in self.name.lower() 281 282 @property 283 def is_vpc_peerlink(self): 284 """Return a boolean indicating whether this port is configured as a vpc peer-link port""" 285 retval = self.re_match_iter_typed( 286 r"^\s*vpc\s+peer-link", result_type=str, default="" 287 ) 288 return retval 289 290 291## 292##------------- NXOS Interface ABC 293## 294 295# Valid method name substitutions: 296# switchport -> switch 297# spanning-tree -> stp 298# interfce -> intf 299# address -> addr 300# default -> def 301 302 303class BaseNXOSIntfLine(NXOSCfgLine): 304 def __init__(self, *args, **kwargs): 305 super(BaseNXOSIntfLine, self).__init__(*args, **kwargs) 306 self.ifindex = None # Optional, for user use 307 self.default_ipv4_addr_object = IPv4Obj("127.0.0.1/32", strict=False) 308 309 def __repr__(self): 310 if not self.is_switchport: 311 try: 312 ipv4_addr_object = self.ipv4_addr_object 313 except DynamicAddressException: 314 # Interface uses dhcp 315 ipv4_addr_object = None 316 317 if ipv4_addr_object is None: 318 addr = "IPv4 dhcp" 319 elif ipv4_addr_object == self.default_ipv4_addr_object: 320 addr = "No IPv4" 321 else: 322 ip = str(self.ipv4_addr_object.ip) 323 prefixlen = str(self.ipv4_addr_object.prefixlen) 324 addr = "{0}/{1}".format(ip, prefixlen) 325 return "<%s # %s '%s' info: '%s'>" % ( 326 self.classname, 327 self.linenum, 328 self.name, 329 addr, 330 ) 331 else: 332 return "<%s # %s '%s' info: 'switchport'>" % ( 333 self.classname, 334 self.linenum, 335 self.name, 336 ) 337 338 def _build_abbvs(self): 339 """Build a set of valid abbreviations (lowercased) for the interface""" 340 retval = set([]) 341 port_type_chars = self.port_type.lower() 342 subinterface_number = self.subinterface_number 343 for sep in ["", " "]: 344 for ii in range(1, len(port_type_chars) + 1): 345 retval.add( 346 "{0}{1}{2}".format(port_type_chars[0:ii], sep, subinterface_number) 347 ) 348 return retval 349 350 def reset(self, atomic=True): 351 # Insert build_reset_string() before this line... 352 self.insert_before(self.build_reset_string(), atomic=atomic) 353 354 def build_reset_string(self): 355 # IOS interfaces are defaulted like this... 356 return "default " + self.text 357 358 @property 359 def verbose(self): 360 if not self.is_switchport: 361 return ( 362 "<%s # %s '%s' info: '%s' (child_indent: %s / len(children): %s / family_endpoint: %s)>" 363 % ( 364 self.classname, 365 self.linenum, 366 self.text, 367 self.ipv4_addr_object or "No IPv4", 368 self.child_indent, 369 len(self.children), 370 self.family_endpoint, 371 ) 372 ) 373 else: 374 return ( 375 "<%s # %s '%s' info: 'switchport' (child_indent: %s / len(children): %s / family_endpoint: %s)>" 376 % ( 377 self.classname, 378 self.linenum, 379 self.text, 380 self.child_indent, 381 len(self.children), 382 self.family_endpoint, 383 ) 384 ) 385 386 @classmethod 387 def is_object_for(cls, line="", re=re): 388 return False 389 390 ##------------- Basic interface properties 391 392 @property 393 def abbvs(self): 394 """A python set of valid abbreviations (lowercased) for the interface""" 395 return self._build_abbvs() 396 397 _INTF_NAME_RE_STR = r"^interface\s+(\S+[0-9\/\.\s]+)\s*" 398 _INTF_NAME_REGEX = re.compile(_INTF_NAME_RE_STR) 399 400 @property 401 def name(self): 402 """Return the interface name as a string, such as 'GigabitEthernet0/1' 403 404 Returns: 405 - str. The interface name as a string, or '' if the object is not an interface. 406 407 This example illustrates use of the method. 408 409 .. code-block:: python 410 :emphasize-lines: 17,20,23 411 412 >>> config = [ 413 ... '!', 414 ... 'interface FastEthernet1/0', 415 ... ' ip address 1.1.1.1 255.255.255.252', 416 ... '!', 417 ... 'interface ATM2/0', 418 ... ' no ip address', 419 ... '!', 420 ... 'interface ATM2/0.100 point-to-point', 421 ... ' ip address 1.1.1.5 255.255.255.252', 422 ... ' pvc 0/100', 423 ... ' vbr-nrt 704 704', 424 ... '!', 425 ... ] 426 >>> parse = CiscoConfParse(config, factory=True) 427 >>> obj = parse.find_objects('^interface\sFast')[0] 428 >>> obj.name 429 'FastEthernet1/0' 430 >>> obj = parse.find_objects('^interface\sATM')[0] 431 >>> obj.name 432 'ATM2/0' 433 >>> obj = parse.find_objects('^interface\sATM')[1] 434 >>> obj.name 435 'ATM2/0.100' 436 >>> 437 """ 438 if not self.is_intf: 439 return "" 440 name = self.re_match(self._INTF_NAME_REGEX).strip() 441 return name 442 443 @property 444 def port(self): 445 """Return the interface's port number 446 447 Returns: 448 - int. The interface number. 449 450 This example illustrates use of the method. 451 452 .. code-block:: python 453 :emphasize-lines: 17,20 454 455 >>> config = [ 456 ... '!', 457 ... 'interface FastEthernet1/0', 458 ... ' ip address 1.1.1.1 255.255.255.252', 459 ... '!', 460 ... 'interface ATM2/0', 461 ... ' no ip address', 462 ... '!', 463 ... 'interface ATM2/0.100 point-to-point', 464 ... ' ip address 1.1.1.5 255.255.255.252', 465 ... ' pvc 0/100', 466 ... ' vbr-nrt 704 704', 467 ... '!', 468 ... ] 469 >>> parse = CiscoConfParse(config, factory=True) 470 >>> obj = parse.find_objects('^interface\sFast')[0] 471 >>> obj.port 472 0 473 >>> obj = parse.find_objects('^interface\sATM')[0] 474 >>> obj.port 475 0 476 >>> 477 """ 478 return self.ordinal_list[-1] 479 480 @property 481 def port_type(self): 482 """Return Loopback, ATM, GigabitEthernet, Virtual-Template, etc... 483 484 Returns: 485 - str. The port type. 486 487 This example illustrates use of the method. 488 489 .. code-block:: python 490 :emphasize-lines: 17,20 491 492 >>> config = [ 493 ... '!', 494 ... 'interface FastEthernet1/0', 495 ... ' ip address 1.1.1.1 255.255.255.252', 496 ... '!', 497 ... 'interface ATM2/0', 498 ... ' no ip address', 499 ... '!', 500 ... 'interface ATM2/0.100 point-to-point', 501 ... ' ip address 1.1.1.5 255.255.255.252', 502 ... ' pvc 0/100', 503 ... ' vbr-nrt 704 704', 504 ... '!', 505 ... ] 506 >>> parse = CiscoConfParse(config, factory=True) 507 >>> obj = parse.find_objects('^interface\sFast')[0] 508 >>> obj.port_type 509 'FastEthernet' 510 >>> obj = parse.find_objects('^interface\sATM')[0] 511 >>> obj.port_type 512 'ATM' 513 >>> 514 """ 515 port_type_regex = r"^interface\s+([A-Za-z\-]+)" 516 return self.re_match(port_type_regex, group=1, default="") 517 518 @property 519 def ordinal_list(self): 520 """Return a tuple of numbers representing card, slot, port for this interface. This method strips all subinterface information in the returned value. If you call ordinal_list on GigabitEthernet2/25.100, you'll get this python tuple of integers: (2, 25). If you call ordinal_list on GigabitEthernet2/0/25.100 you'll get this python list of integers: (2, 0, 25). 521 522 Returns: 523 - tuple. A tuple of port numbers as integers. 524 525 .. warning:: 526 527 ordinal_list should silently fail (returning an empty python list) if the interface doesn't parse correctly 528 529 This example illustrates use of the method. 530 531 .. code-block:: python 532 :emphasize-lines: 17,20 533 534 >>> config = [ 535 ... '!', 536 ... 'interface FastEthernet1/0', 537 ... ' ip address 1.1.1.1 255.255.255.252', 538 ... '!', 539 ... 'interface ATM2/0', 540 ... ' no ip address', 541 ... '!', 542 ... 'interface ATM2/0.100 point-to-point', 543 ... ' ip address 1.1.1.5 255.255.255.252', 544 ... ' pvc 0/100', 545 ... ' vbr-nrt 704 704', 546 ... '!', 547 ... ] 548 >>> parse = CiscoConfParse(config, factory=True) 549 >>> obj = parse.find_objects('^interface\sFast')[0] 550 >>> obj.ordinal_list 551 (1, 0) 552 >>> obj = parse.find_objects('^interface\sATM')[0] 553 >>> obj.ordinal_list 554 (2, 0) 555 >>> 556 """ 557 if not self.is_intf: 558 return () 559 else: 560 intf_number = self.interface_number 561 if intf_number: 562 return tuple([int(ii) for ii in intf_number.split("/")]) 563 else: 564 return () 565 566 @property 567 def interface_number(self): 568 """Return a string representing the card, slot, port for this interface. If you call interface_number on GigabitEthernet2/25.100, you'll get this python string: '2/25'. If you call interface_number on GigabitEthernet2/0/25.100 you'll get this python string '2/0/25'. This method strips all subinterface information in the returned value. 569 570 Returns: 571 - string. 572 573 .. warning:: 574 575 interface_number should silently fail (returning an empty python string) if the interface doesn't parse correctly 576 577 This example illustrates use of the method. 578 579 .. code-block:: python 580 :emphasize-lines: 17,20 581 582 >>> config = [ 583 ... '!', 584 ... 'interface FastEthernet1/0', 585 ... ' ip address 1.1.1.1 255.255.255.252', 586 ... '!', 587 ... 'interface ATM2/0', 588 ... ' no ip address', 589 ... '!', 590 ... 'interface ATM2/0.100 point-to-point', 591 ... ' ip address 1.1.1.5 255.255.255.252', 592 ... ' pvc 0/100', 593 ... ' vbr-nrt 704 704', 594 ... '!', 595 ... ] 596 >>> parse = CiscoConfParse(config, factory=True) 597 >>> obj = parse.find_objects('^interface\sFast')[0] 598 >>> obj.interface_number 599 '1/0' 600 >>> obj = parse.find_objects('^interface\sATM')[-1] 601 >>> obj.interface_number 602 '2/0' 603 >>> 604 """ 605 if not self.is_intf: 606 return "" 607 else: 608 intf_regex = r"^interface\s+[A-Za-z\-]+\s*(\d+.*?)(\.\d+)*(\s\S+)*\s*$" 609 intf_number = self.re_match(intf_regex, group=1, default="") 610 return intf_number 611 612 @property 613 def subinterface_number(self): 614 """Return a string representing the card, slot, port for this interface or subinterface. If you call subinterface_number on GigabitEthernet2/25.100, you'll get this python string: '2/25.100'. If you call interface_number on GigabitEthernet2/0/25 you'll get this python string '2/0/25'. This method strips all subinterface information in the returned value. 615 616 Returns: 617 - string. 618 619 .. warning:: 620 621 subinterface_number should silently fail (returning an empty python string) if the interface doesn't parse correctly 622 623 This example illustrates use of the method. 624 625 .. code-block:: python 626 :emphasize-lines: 17,20 627 628 >>> config = [ 629 ... '!', 630 ... 'interface FastEthernet1/0', 631 ... ' ip address 1.1.1.1 255.255.255.252', 632 ... '!', 633 ... 'interface ATM2/0', 634 ... ' no ip address', 635 ... '!', 636 ... 'interface ATM2/0.100 point-to-point', 637 ... ' ip address 1.1.1.5 255.255.255.252', 638 ... ' pvc 0/100', 639 ... ' vbr-nrt 704 704', 640 ... '!', 641 ... ] 642 >>> parse = CiscoConfParse(config, factory=True) 643 >>> obj = parse.find_objects('^interface\sFast')[0] 644 >>> obj.subinterface_number 645 '1/0' 646 >>> obj = parse.find_objects('^interface\sATM')[-1] 647 >>> obj.subinterface_number 648 '2/0.100' 649 >>> 650 """ 651 if not self.is_intf: 652 return "" 653 else: 654 subintf_regex = r"^interface\s+[A-Za-z\-]+\s*(\d+.*?\.?\d?)(\s\S+)*\s*$" 655 subintf_number = self.re_match(subintf_regex, group=1, default="") 656 return subintf_number 657 658 @property 659 def description(self): 660 """Return the current interface description string. 661 662 """ 663 retval = self.re_match_iter_typed( 664 r"^\s*description\s+(\S.+)$", result_type=str, default="" 665 ) 666 return retval 667 668 @property 669 def manual_speed(self): 670 retval = self.re_match_iter_typed( 671 r"^\s*speed\s+(\d+)$", result_type=int, default=0 672 ) 673 return retval 674 675 @property 676 def manual_duplex(self): 677 retval = self.re_match_iter_typed( 678 r"^\s*duplex\s+(\S.+)$", result_type=str, default="" 679 ) 680 return retval 681 682 @property 683 def manual_beacon(self): 684 retval = self.re_match_iter_typed( 685 r"^\s*(beacon)\s*$", result_type=bool, default=False 686 ) 687 return retval 688 689 @property 690 def manual_bandwidth(self): 691 retval = self.re_match_iter_typed( 692 r"^\s*bandwidth\s+(\d+)$", result_type=int, default=0 693 ) 694 return retval 695 696 @property 697 def manual_delay(self): 698 retval = self.re_match_iter_typed( 699 r"^\s*delay\s+(\d+)$", result_type=int, default=0 700 ) 701 return retval 702 703 @property 704 def manual_holdqueue_out(self): 705 """Return the current hold-queue out depth, if default return 0""" 706 raise NotImplementedError 707 708 @property 709 def manual_holdqueue_in(self): 710 """Return the current hold-queue in depth, if default return 0""" 711 raise NotImplementedError 712 713 @property 714 def manual_encapsulation(self): 715 retval = self.re_match_iter_typed( 716 r"^\s*encapsulation\s+(\S+)", result_type=str, default="" 717 ) 718 return retval 719 720 @property 721 def has_mpls(self): 722 retval = self.re_match_iter_typed( 723 r"^\s*(mpls\s+ip\s+forwarding)$", result_type=bool, default=False 724 ) 725 return retval 726 727 @property 728 def ipv4_addr_object(self): 729 """Return a ccp_util.IPv4Obj object representing the address on this interface; if there is no address, return IPv4Obj('127.0.0.1/32')""" 730 try: 731 return IPv4Obj("%s/%s" % (self.ipv4_addr, self.ipv4_masklength)) 732 except DynamicAddressException as e: 733 raise DynamicAddressException(e) 734 except: 735 return self.default_ipv4_addr_object 736 737 @property 738 def ipv4_network_object(self): 739 """Return an ccp_util.IPv4Obj object representing the subnet on this interface; if there is no address, return ccp_util.IPv4Obj('127.0.0.1/32')""" 740 return self.ip_network_object 741 742 @property 743 def ip_network_object(self): 744 # Simplified on 2014-12-02 745 try: 746 return IPv4Obj( 747 "{0}/{1}".format(self.ipv4_addr, self.ipv4_netmask), strict=False 748 ) 749 except DynamicAddressException as e: 750 raise DynamicAddressException(e) 751 except (Exception) as e: 752 return self.default_ipv4_addr_object 753 754 @property 755 def has_autonegotiation(self): 756 if not self.is_ethernet_intf: 757 return False 758 elif self.is_ethernet_intf and ( 759 self.has_manual_speed or self.has_manual_duplex 760 ): 761 return False 762 elif self.is_ethernet_intf: 763 return True 764 else: 765 raise ValueError 766 767 @property 768 def has_manual_speed(self): 769 retval = self.re_match_iter_typed( 770 r"^\s*speed\s+(\d+)$", result_type=bool, default=False 771 ) 772 return retval 773 774 @property 775 def has_manual_duplex(self): 776 retval = self.re_match_iter_typed( 777 r"^\s*duplex\s+(\S.+)$", result_type=bool, default=False 778 ) 779 return retval 780 781 @property 782 def has_manual_carrierdelay(self): 783 """Return a python boolean for whether carrier delay is manually configured on the interface""" 784 return bool(self.manual_carrierdelay) 785 786 @property 787 def manual_carrierdelay(self): 788 """Return the manual carrier delay (in seconds) of the interface as a python float. If there is no explicit carrier delay, return 0.0""" 789 cd_seconds = self.re_match_iter_typed( 790 r"^\s*carrier-delay\s+(\d+)$", result_type=float, default=0.0 791 ) 792 cd_msec = self.re_match_iter_typed( 793 r"^\s*carrier-delay\s+msec\s+(\d+)$", result_type=float, default=0.0 794 ) 795 if cd_seconds > 0.0: 796 return cd_seconds 797 elif cd_msec > 0.0: 798 return cd_msec / 1000.0 799 else: 800 return 0.0 801 802 @property 803 def has_manual_clock_rate(self): 804 return bool(self.manual_clock_rate) 805 806 @property 807 def manual_clock_rate(self): 808 """Return the clock rate of the interface as a python integer. If there is no explicit clock rate, return 0""" 809 retval = self.re_match_iter_typed( 810 r"^\s*clock\s+rate\s+(\d+)$", result_type=int, default=0 811 ) 812 return retval 813 814 @property 815 def manual_mtu(self): 816 ## Due to the diverse platform defaults, this should be the 817 ## only mtu information I plan to support 818 """Returns a integer value for the manual MTU configured on an 819 :class:`~models_nxos.NXOSIntfLine` object. Interfaces without a 820 manual MTU configuration return 0. 821 822 Returns: 823 - integer. 824 825 This example illustrates use of the method. 826 827 .. code-block:: python 828 :emphasize-lines: 18,21 829 830 >>> config = [ 831 ... '!', 832 ... 'interface FastEthernet1/0', 833 ... ' ip address 1.1.1.1 255.255.255.252', 834 ... '!', 835 ... 'interface ATM2/0', 836 ... ' mtu 4470', 837 ... ' no ip address', 838 ... '!', 839 ... 'interface ATM2/0.100 point-to-point', 840 ... ' ip address 1.1.1.5 255.255.255.252', 841 ... ' pvc 0/100', 842 ... ' vbr-nrt 704 704', 843 ... '!', 844 ... ] 845 >>> parse = CiscoConfParse(config, factory=True) 846 >>> obj = parse.find_objects('^interface\sFast')[0] 847 >>> obj.manual_mtu 848 0 849 >>> obj = parse.find_objects('^interface\sATM')[0] 850 >>> obj.manual_mtu 851 4470 852 >>> 853 """ 854 retval = self.re_match_iter_typed( 855 r"^\s*mtu\s+(\d+)$", result_type=int, default=0 856 ) 857 return retval 858 859 @property 860 def manual_mpls_mtu(self): 861 ## Due to the diverse platform defaults, this should be the 862 ## only mtu information I plan to support 863 retval = self.re_match_iter_typed( 864 r"^\s*mpls\s+mtu\s+(\d+)$", result_type=int, default=0 865 ) 866 return retval 867 868 @property 869 def manual_ip_mtu(self): 870 ## Due to the diverse platform defaults, this should be the 871 ## only mtu information I plan to support 872 retval = self.re_match_iter_typed( 873 r"^\s*ip\s+mtu\s+(\d+)$", result_type=int, default=0 874 ) 875 return retval 876 877 @property 878 def has_manual_mtu(self): 879 return bool(self.manual_mtu) 880 881 @property 882 def has_manual_mpls_mtu(self): 883 return bool(self.manual_mpls_mtu) 884 885 @property 886 def has_manual_ip_mtu(self): 887 return bool(self.manual_ip_mtu) 888 889 @property 890 def is_shutdown(self): 891 retval = self.re_match_iter_typed( 892 r"^\s*(shut\S*)\s*$", result_type=bool, default=False 893 ) 894 return retval 895 896 @property 897 def has_vrf(self): 898 return bool(self.vrf) 899 900 @property 901 def vrf(self): 902 retval = self.re_match_iter_typed( 903 r"^\s*vrf\s+member\s(\S+)\s*$", result_type=str, default="" 904 ) 905 return retval 906 907 @property 908 def ip_addr(self): 909 return self.ipv4_addr 910 911 @property 912 def ipv4_addr(self): 913 """Return a string with the interface's IPv4 address, or '' if there is none""" 914 retval = self.re_match_iter_typed( 915 r"^\s+ip\s+address\s+(\d+\.\d+\.\d+\.\d+)\s*\/\d+\s*$", 916 result_type=str, 917 default="", 918 ) 919 condition1 = self.re_match_iter_typed( 920 r"^\s+ip\s+address\s+(dhcp)\s*$", result_type=str, default="" 921 ) 922 if condition1.lower() == "dhcp": 923 error = "Cannot parse address from a dhcp interface: {0}".format(self.name) 924 raise DynamicAddressException(error) 925 else: 926 return retval 927 928 @property 929 def ipv4_masklength(self): 930 """Return a string with the interface's IPv4 masklength, or 0 if there is none""" 931 retval = self.re_match_iter_typed( 932 r"^\s+ip\s+address\s+\d+\.\d+\.\d+\.\d+\s*\/(\d+)\s*$", 933 result_type=int, 934 default=0, 935 ) 936 return retval 937 938 @property 939 def ipv4_netmask(self): 940 """Return an integer with the interface's IPv4 mask length, or '' if there is no IP address on the interace""" 941 ipv4_addr_object = self.ipv4_addr_object 942 if ipv4_addr_object != self.default_ipv4_addr_object: 943 return str(ipv4_addr_object.netmask) 944 return "" 945 946 def is_abbreviated_as(self, val): 947 """Test whether `val` is a good abbreviation for the interface""" 948 if val.lower() in self.abbvs: 949 return True 950 return False 951 952 def in_ipv4_subnet(self, ipv4network=IPv4Obj("0.0.0.0/32", strict=False)): 953 """Accept an argument for the :class:`~ccp_util.IPv4Obj` to be 954 considered, and return a boolean for whether this interface is within 955 the requested :class:`~ccp_util.IPv4Obj`. 956 957 Kwargs: 958 - ipv4network (:class:`~ccp_util.IPv4Obj`): An object to compare against IP addresses configured on this :class:`~models_nxos.NXOSIntfLine` object. 959 960 Returns: 961 - bool if there is an ip address, or None if there is no ip address. 962 963 This example illustrates use of the method. 964 965 .. code-block:: python 966 :emphasize-lines: 21,23 967 968 >>> from ciscoconfparse.ccp_util import IPv4Obj 969 >>> from ciscoconfparse import CiscoConfParse 970 >>> config = [ 971 ... '!', 972 ... 'interface Serial1/0', 973 ... ' ip address 1.1.1.1 255.255.255.252', 974 ... '!', 975 ... 'interface ATM2/0', 976 ... ' no ip address', 977 ... '!', 978 ... 'interface ATM2/0.100 point-to-point', 979 ... ' ip address 1.1.1.5 255.255.255.252', 980 ... ' pvc 0/100', 981 ... ' vbr-nrt 704 704', 982 ... '!', 983 ... ] 984 >>> parse = CiscoConfParse(config, factory=True) 985 >>> obj = parse.find_objects('^interface\sSerial')[0] 986 >>> obj 987 <NXOSIntfLine # 1 'Serial1/0' info: '1.1.1.1/30'> 988 >>> obj.in_ipv4_subnet(IPv4Obj('1.1.1.0/24', strict=False)) 989 True 990 >>> obj.in_ipv4_subnet(IPv4Obj('2.1.1.0/24', strict=False)) 991 False 992 >>> 993 """ 994 if not (str(self.ipv4_addr_object.ip) == "127.0.0.1"): 995 try: 996 # Return a boolean for whether the interface is in that 997 # network and mask 998 return self.ipv4_network_object in ipv4network 999 except (Exception) as e: 1000 raise ValueError( 1001 "FATAL: %s.in_ipv4_subnet(ipv4network={0}) is an invalid arg: {1}".format( 1002 ipv4network, e 1003 ) 1004 ) 1005 else: 1006 return None 1007 1008 def in_ipv4_subnets(self, subnets=None): 1009 """Accept a set or list of ccp_util.IPv4Obj objects, and return a boolean for whether this interface is within the requested subnets.""" 1010 if subnets is None: 1011 raise ValueError( 1012 "A python list or set of ccp_util.IPv4Obj objects must be supplied" 1013 ) 1014 for subnet in subnets: 1015 tmp = self.in_ipv4_subnet(ipv4network=subnet) 1016 if self.ipv4_addr_object in subnet: 1017 return tmp 1018 return tmp 1019 1020 @property 1021 def has_no_icmp_unreachables(self): 1022 ## NOTE: I have no intention of checking self.is_shutdown here 1023 ## People should be able to check the sanity of interfaces 1024 ## before they put them into production 1025 1026 ## Interface must have an IP addr to respond 1027 if self.ipv4_addr == "": 1028 return False 1029 1030 retval = self.re_match_iter_typed( 1031 r"^\s*no\sip\s(unreachables)\s*$", result_type=bool, default=False 1032 ) 1033 return retval 1034 1035 @property 1036 def has_no_icmp_redirects(self): 1037 ## NOTE: I have no intention of checking self.is_shutdown here 1038 ## People should be able to check the sanity of interfaces 1039 ## before they put them into production 1040 1041 ## Interface must have an IP addr to respond 1042 if self.ipv4_addr == "": 1043 return False 1044 1045 retval = self.re_match_iter_typed( 1046 r"^\s*no\sip\s(redirects)\s*$", result_type=bool, default=False 1047 ) 1048 return retval 1049 1050 @property 1051 def has_no_ip_proxyarp(self): 1052 ## NOTE: I have no intention of checking self.is_shutdown here 1053 ## People should be able to check the sanity of interfaces 1054 ## before they put them into production 1055 """Return a boolean for whether no ip proxy-arp is configured on the 1056 interface. 1057 1058 Returns: 1059 - bool. 1060 1061 This example illustrates use of the method. 1062 1063 .. code-block:: python 1064 :emphasize-lines: 12 1065 1066 >>> from ciscoconfparse.ccp_util import IPv4Obj 1067 >>> from ciscoconfparse import CiscoConfParse 1068 >>> config = [ 1069 ... '!', 1070 ... 'interface FastEthernet1/0', 1071 ... ' ip address 1.1.1.1 255.255.255.252', 1072 ... ' no ip proxy-arp', 1073 ... '!', 1074 ... ] 1075 >>> parse = CiscoConfParse(config, factory=True) 1076 >>> obj = parse.find_objects('^interface\sFast')[0] 1077 >>> obj.has_no_ip_proxyarp 1078 True 1079 >>> 1080 """ 1081 1082 ## Interface must have an IP addr to respond 1083 if self.ipv4_addr == "": 1084 return False 1085 1086 ## By default, Cisco IOS answers proxy-arp 1087 ## By default, Nexus disables proxy-arp 1088 ## By default, IOS-XR disables proxy-arp 1089 retval = self.re_match_iter_typed( 1090 r"^\s*no\sip\s(proxy-arp)\s*$", result_type=bool, default=False 1091 ) 1092 return retval 1093 1094 @property 1095 def has_ip_pim_dense_mode(self): 1096 ## NOTE: I have no intention of checking self.is_shutdown here 1097 ## People should be able to check the sanity of interfaces 1098 ## before they put them into production 1099 1100 ## Interface must have an IP addr to run PIM 1101 if self.ipv4_addr == "": 1102 return False 1103 1104 retval = self.re_match_iter_typed( 1105 r"^\s*ip\spim\sdense-mode\s*$)\s*$", result_type=bool, default=False 1106 ) 1107 return retval 1108 1109 @property 1110 def has_ip_pim_sparse_mode(self): 1111 ## NOTE: I have no intention of checking self.is_shutdown here 1112 ## People should be able to check the sanity of interfaces 1113 ## before they put them into production 1114 1115 ## Interface must have an IP addr to run PIM 1116 if self.ipv4_addr == "": 1117 return False 1118 1119 retval = self.re_match_iter_typed( 1120 r"^\s*ip\spim\ssparse-mode\s*$)\s*$", result_type=bool, default=False 1121 ) 1122 return retval 1123 1124 @property 1125 def has_ip_pim_sparsedense_mode(self): 1126 ## NOTE: I have no intention of checking self.is_shutdown here 1127 ## People should be able to check the sanity of interfaces 1128 ## before they put them into production 1129 1130 ## Interface must have an IP addr to run PIM 1131 if self.ipv4_addr == "": 1132 return False 1133 1134 retval = self.re_match_iter_typed( 1135 r"^\s*ip\spim\ssparse-dense-mode\s*$)\s*$", result_type=bool, default=False 1136 ) 1137 return retval 1138 1139 @property 1140 def manual_arp_timeout(self): 1141 """Return an integer with the current interface ARP timeout, if there isn't one set, return 0. If there is no IP address, return -1""" 1142 ## NOTE: I have no intention of checking self.is_shutdown here 1143 ## People should be able to check the sanity of interfaces 1144 ## before they put them into production 1145 1146 ## Interface must have an IP addr to respond 1147 if self.ipv4_addr == "": 1148 return -1 1149 1150 ## By default, Cisco IOS defaults to 4 hour arp timers 1151 ## By default, Nexus defaults to 15 minute arp timers 1152 retval = self.re_match_iter_typed( 1153 r"^\s*arp\s+timeout\s+(\d+)\s*$", result_type=int, default=0 1154 ) 1155 return retval 1156 1157 @property 1158 def has_ip_helper_addresses(self): 1159 """Return a True if the intf has helper-addresses; False if not""" 1160 if len(self.ip_helper_addresses) > 0: 1161 return True 1162 return False 1163 1164 @property 1165 def ip_helper_addresses(self): 1166 """Return a list of dicts with IP helper-addresses. Each helper-address is in a dictionary. The dictionary is in this format: 1167 1168 .. code-block:: python 1169 :emphasize-lines: 11 1170 1171 >>> config = [ 1172 ... '!', 1173 ... 'interface Ethernet1/1', 1174 ... ' ip address 1.1.1.1/24', 1175 ... ' ip dhcp relay address 172.16.20.12', 1176 ... ' ip dhcp relay address 172.19.185.91', 1177 ... '!', 1178 ... ] 1179 >>> parse = CiscoConfParse(config) 1180 >>> obj = parse.find_objects('^interface\sEthernet1/1$')[0] 1181 >>> obj.ip_helper_addresses 1182 [{'addr': '172.16.20.12', 'vrf': '', 'global': False}, {'addr': '172.19.185.91', 'vrf': '', 'global': False}] 1183 >>>""" 1184 retval = list() 1185 1186 for child in self.children: 1187 if "vrf member" in child.text: 1188 vrf = child.re_match_typed(r"vrf\s+member\s+(\S+)", default="") 1189 break 1190 1191 for child in self.children: 1192 if "dhcp relay address" in child.text: 1193 addr = child.re_match_typed( 1194 r"ip\s+dhcp\s+relay\s+address\s+(\d+\.\d+\.\d+\.\d+)" 1195 ) 1196 global_addr = "" 1197 retval.append({"addr": addr, "vrf": vrf, "global": bool(global_addr)}) 1198 return retval 1199 1200 @property 1201 def is_switchport(self): 1202 retval = self.re_match_iter_typed( 1203 r"^\s*(switchport)\s*", result_type=bool, default=False 1204 ) 1205 return retval 1206 1207 @property 1208 def has_manual_switch_access(self): 1209 retval = self.re_match_iter_typed( 1210 r"^\s*(switchport\smode\s+access)\s*$", result_type=bool, default=False 1211 ) 1212 return retval 1213 1214 @property 1215 def has_manual_switch_trunk_encap(self): 1216 return bool(self.manual_switch_trunk_encap) 1217 1218 @property 1219 def manual_switch_trunk_encap(self): 1220 """Return a string with the switchport encapsulation type; if there is no manual trunk encapsulation, return ''.""" 1221 retval = self.re_match_iter_typed( 1222 r"^\s*(switchport\s+trunk\s+encapsulation\s+(\S+))\s*$", 1223 result_type=str, 1224 default="", 1225 ) 1226 return retval 1227 1228 @property 1229 def has_manual_switch_fex_fabric(self): 1230 """Return a boolean indicating whether this port is configured in fex-fabric mode""" 1231 retval = self.re_match_iter_typed( 1232 r"^\s*(switchport\smode\s+fex-fabric)\s*$", result_type=bool, default=False 1233 ) 1234 return retval 1235 1236 @property 1237 def has_manual_switch_trunk(self): 1238 retval = self.re_match_iter_typed( 1239 r"^\s*(switchport\s+mode\s+trunk)\s*$", result_type=bool, default=False 1240 ) 1241 return retval 1242 1243 @property 1244 def has_switch_portsecurity(self): 1245 if not self.is_switchport: 1246 return False 1247 ## IMPORTANT: Cisco IOS will not enable port-security on the port 1248 ## unless 'switch port-security' (with no other options) 1249 ## is in the configuration 1250 retval = self.re_match_iter_typed( 1251 r"^\s*(switchport\sport-security)\s*$", result_type=bool, default=False 1252 ) 1253 return retval 1254 1255 @property 1256 def has_switch_stormcontrol(self): 1257 if not self.is_switchport: 1258 return False 1259 retval = self.re_match_iter_typed( 1260 r"^\s*(storm-control)\s*$", result_type=bool, default=False 1261 ) 1262 return retval 1263 1264 @property 1265 def has_dtp(self): 1266 if not self.is_switchport: 1267 return False 1268 1269 ## Not using self.re_match_iter_typed, because I want to 1270 ## be sure I build the correct API for regex_match is False, and 1271 ## default value is True 1272 for obj in self.children: 1273 switch = obj.re_match(r"^\s*(switchport\snoneg\S*)\s*$") 1274 if not (switch is None): 1275 return False 1276 return True 1277 1278 @property 1279 def access_vlan(self): 1280 """Return an integer with the access vlan number. Return 1, if the switchport has no explicit vlan configured; return 0 if the port isn't a switchport""" 1281 if self.is_switchport: 1282 default_val = 1 1283 else: 1284 default_val = 0 1285 retval = self.re_match_iter_typed( 1286 r"^\s*switchport\s+access\s+vlan\s+(\d+)$", 1287 result_type=int, 1288 default=default_val, 1289 ) 1290 return retval 1291 1292 @property 1293 def manual_stp_link_type(self): 1294 """Return a string with the spanning-tree link type configured on this switchport; if there is no STP link type configured, return ''.""" 1295 retval = self.re_match_iter_typed( 1296 r"^\s*spanning-tree\s+link-type\s+(\S.+?)$", result_type=str, default="" 1297 ) 1298 return retval 1299 1300 @property 1301 def manual_stp_port_type(self): 1302 """Return a string with the spanning-tree port type configured on this switchport; if there is no STP port type configured, return '' (by default NXOS assigns this as 'normal', but this property is for a *manual* assignment).""" 1303 retval = self.re_match_iter_typed( 1304 r"^\s*spanning-tree\s+port\s+type\s+(\S.+?)$", result_type=str, default="" 1305 ) 1306 return retval 1307 1308 @property 1309 def vpc(self): 1310 """Return an integer with the vpc id; Return 0 if there is no vpc id on this port""" 1311 retval = self.re_match_iter_typed( 1312 r"^\s*vpc\s+(\d+)$", result_type=int, default=0 1313 ) 1314 return retval 1315 1316 @property 1317 def fex_associate_chassis_id(self): 1318 """Return an integer with the fex chassis-id, return 0 if there is no 'fex associate' command on this switchport""" 1319 retval = self.re_match_iter_typed( 1320 r"^\s*fex\s+associate\s+(\d+)$", result_type=int, default=0 1321 ) 1322 return retval 1323 1324 @property 1325 def trunk_vlans_allowed(self): 1326 """Return a CiscoRange() with the list of allowed vlan numbers (as int). Return 0 if the port isn't a switchport in trunk mode""" 1327 1328 # The default values... 1329 if self.is_switchport and not self.has_manual_switch_access: 1330 retval = CiscoRange("1-{0}".format(MAX_VLAN), result_type=int) 1331 else: 1332 return 0 1333 1334 ## Iterate over switchport trunk statements 1335 for obj in self.children: 1336 1337 ## For every child object, check whether the vlan list is modified 1338 abs_str = obj.re_match_typed( 1339 "^\s+switchport\s+trunk\s+allowed\s+vlan\s(all|none|\d.*?)$", 1340 default="_nomatch_", 1341 result_type=str, 1342 ).lower() 1343 add_str = obj.re_match_typed( 1344 "^\s+switchport\s+trunk\s+allowed\s+vlan\s+add\s+(\d.*?)$", 1345 default="_nomatch_", 1346 result_type=str, 1347 ).lower() 1348 exc_str = obj.re_match_typed( 1349 "^\s+switchport\s+trunk\s+allowed\s+vlan\s+except\s+(\d.*?)$", 1350 default="_nomatch_", 1351 result_type=str, 1352 ).lower() 1353 rem_str = obj.re_match_typed( 1354 "^\s+switchport\s+trunk\s+allowed\s+vlan\s+remove\s+(\d.*?)$", 1355 default="_nomatch_", 1356 result_type=str, 1357 ).lower() 1358 1359 ## Build a vdict for each vlan modification statement 1360 vdict = { 1361 "absolute_str": abs_str, 1362 "add_str": add_str, 1363 "except_str": exc_str, 1364 "remove_str": rem_str, 1365 } 1366 1367 ## Analyze each vdict in sequence and apply to retval sequentially 1368 for key, val in vdict.items(): 1369 if val != "_nomatch_": 1370 ## absolute in the key overrides previous values 1371 if "absolute" in key: 1372 if val.lower() == "all": 1373 retval = CiscoRange( 1374 "1-{0}".format(MAX_VLAN), result_type=int 1375 ) 1376 elif val.lower() == "none": 1377 retval = CiscoRange(result_type=int) 1378 else: 1379 retval = CiscoRange(val, result_type=int) 1380 elif "add" in key: 1381 retval.append(val) 1382 elif "except" in key: 1383 retval = CiscoRange("1-{0}".format(MAX_VLAN), result_type=int) 1384 retval.remove(val) 1385 elif "remove" in key: 1386 retval.remove(val) 1387 1388 return retval 1389 1390 @property 1391 def native_vlan(self): 1392 """Return an integer with the native vlan number. Return 1, if the switchport has no explicit native vlan configured; return 0 if the port isn't a switchport""" 1393 if self.is_switchport: 1394 default_val = 1 1395 else: 1396 default_val = 0 1397 retval = self.re_match_iter_typed( 1398 r"^\s*switchport\s+trunk\s+native\s+vlan\s+(\d+)$", 1399 result_type=int, 1400 default=default_val, 1401 ) 1402 return retval 1403 1404 ##------------- CDP 1405 1406 @property 1407 def has_manual_disable_cdp(self): 1408 retval = self.re_match_iter_typed( 1409 r"^\s*(no\s+cdp\s+enable\s*)", result_type=bool, default=False 1410 ) 1411 return retval 1412 1413 ##------------- EoMPLS 1414 1415 @property 1416 def has_xconnect(self): 1417 return bool(self.xconnect_vc) 1418 1419 @property 1420 def xconnect_vc(self): 1421 retval = self.re_match_iter_typed( 1422 r"^\s*xconnect\s+\S+\s+(\d+)\s+\S+", result_type=int, default=0 1423 ) 1424 return retval 1425 1426 ##------------- HSRP 1427 1428 @property 1429 def has_ip_hsrp(self): 1430 return bool(self.hsrp_ip_addr) 1431 1432 @property 1433 def hsrp_ip_addr(self): 1434 ## NOTE: I have no intention of checking self.is_shutdown here 1435 ## People should be able to check the sanity of interfaces 1436 ## before they put them into production 1437 retval = "" 1438 1439 ## For API simplicity, I always assume there is only one hsrp 1440 ## group on the interface 1441 if self.ipv4_addr == "": 1442 return "" 1443 1444 for hsrpobj in self.children: 1445 if hsrpobj.re_match_typed(r"^\s+hsrp\s+(\d+)"): 1446 for child in hsrpobj.children: 1447 retval = child.re_match_typed(r"^\s+ip\s+(\S+)") 1448 if retval: 1449 return retval 1450 return retval 1451 1452 @property 1453 def hsrp_ip_mask(self): 1454 ## NOTE: I have no intention of checking self.is_shutdown here 1455 ## People should be able to check the sanity of interfaces 1456 ## before they put them into production 1457 retval = "" 1458 1459 ## For API simplicity, I always assume there is only one hsrp 1460 ## group on the interface 1461 if self.ipv4_addr == "": 1462 return "" 1463 retval = self.re_match_iter_typed( 1464 r"^\s*standby\s+(\d+\s+)*ip\s+\S+\s+(\S+)\s*$", 1465 group=2, 1466 result_type=str, 1467 default="", 1468 ) 1469 return retval 1470 1471 @property 1472 def hsrp_group(self): 1473 ## For API simplicity, I always assume there is only one hsrp 1474 ## group on the interface 1475 retval = "" 1476 for hsrpobj in self.children: 1477 retval = hsrpobj.re_match_typed(r"^\s+hsrp\s+(\d+)") 1478 if retval: 1479 return retval 1480 return retval 1481 1482 @property 1483 def hsrp_priority(self): 1484 ## For API simplicity, I always assume there is only one hsrp 1485 ## group on the interface 1486 DEFAULT_PRI = 100 1487 if not self.has_ip_hsrp: 1488 return 0 # Return this if there is no hsrp on the interface 1489 for hsrpobj in self.children: 1490 if hsrpobj.re_match_typed(r"^\s+hsrp\s+(\d+)"): 1491 retval = hsrpobj.re_match_iter_typed( 1492 r"^\s+priority\s+(\d+)", result_type=int, default=DEFAULT_PRI 1493 ) 1494 if retval != DEFAULT_PRI: 1495 return retval 1496 1497 @property 1498 def hsrp_hello_timer(self): 1499 ## For API simplicity, I always assume there is only one hsrp 1500 ## group on the interface 1501 1502 # timers msec 250 msec 750 1503 for hsrpobj in self.children: 1504 if hsrpobj.re_match_typed(r"^\s+hsrp\s+(\d+)"): 1505 timer_sec = hsrpobj.re_match_iter_typed( 1506 r"^\s+timers\s+(\d+)\s+\d+", result_type=float, default=0.0 1507 ) 1508 timer_msec = hsrpobj.re_match_iter_typed( 1509 r"^\s+timers\s+msec\s+(\d+)\s+msec\s+\d+", 1510 result_type=float, 1511 default=0.0, 1512 ) 1513 if timer_sec > 0.0: 1514 return timer_sec 1515 elif timer_msec > 0.0: 1516 return timer_msec / 1000.0 1517 return 0.0 1518 1519 return retval 1520 1521 @property 1522 def hsrp_hold_timer(self): 1523 ## For API simplicity, I always assume there is only one hsrp 1524 ## group on the interface 1525 1526 # timers msec 250 msec 750 1527 for hsrpobj in self.children: 1528 if hsrpobj.re_match_typed(r"^\s+hsrp\s+(\d+)"): 1529 timer_sec = hsrpobj.re_match_iter_typed( 1530 r"^\s+timers\s+\d+\s+(\d+)", result_type=float, default=0.0 1531 ) 1532 timer_msec = hsrpobj.re_match_iter_typed( 1533 r"^\s+timers\s+msec\s+\d+\s+msec\s+(\d+)", 1534 result_type=float, 1535 default=0.0, 1536 ) 1537 if timer_sec > 0.0: 1538 return timer_sec 1539 elif timer_msec > 0.0: 1540 return timer_msec / 1000.0 1541 return 0.0 1542 1543 return retval 1544 1545 @property 1546 def has_hsrp_track(self): 1547 return bool(self.hsrp_track) 1548 1549 @property 1550 def hsrp_track(self): 1551 ## For API simplicity, I always assume there is only one hsrp 1552 ## group on the interface 1553 retval = "" 1554 for hsrpobj in self.children: 1555 if hsrpobj.re_match_typed(r"^\s+hsrp\s+(\d+)"): 1556 retval = hsrpobj.re_match_iter_typed( 1557 r"^\s+track\s+(\d+)", result_type=str, default="" 1558 ) 1559 return retval 1560 1561 @property 1562 def has_hsrp_usebia(self): 1563 ## For API simplicity, I always assume there is only one hsrp 1564 ## group on the interface 1565 retval = self.re_match_iter_typed( 1566 r"^\s*hsrp\s+(use-bia)", group=1, result_type=bool, default=False 1567 ) 1568 return retval 1569 1570 @property 1571 def has_hsrp_preempt(self): 1572 ## For API simplicity, I always assume there is only one hsrp 1573 ## group on the interface 1574 retval = False 1575 for hsrpobj in self.children: 1576 if hsrpobj.re_match_typed(r"^\s+hsrp\s+(\d+)"): 1577 retval = hsrpobj.re_match_iter_typed( 1578 r"^\s+(preempt)", group=1, result_type=bool, default=False 1579 ) 1580 return retval 1581 1582 @property 1583 def hsrp_authentication_md5_keychain(self): 1584 ## FIXME nxos 1585 ## For API simplicity, I always assume there is only one hsrp 1586 ## group on the interface 1587 retval = self.re_match_iter_typed( 1588 r"^\s*standby\s+(\d+\s+)*authentication\s+md5\s+key-chain\s+(\S+)", 1589 group=2, 1590 result_type=str, 1591 default="", 1592 ) 1593 return retval 1594 1595 @property 1596 def has_hsrp_authentication_md5(self): 1597 ## FIXME nxos 1598 keychain = self.hsrp_authentication_md5_keychain 1599 return bool(keychain) 1600 1601 @property 1602 def hsrp_authentication_cleartext(self): 1603 pass 1604 1605 ##------------- MAC ACLs 1606 1607 @property 1608 def has_mac_accessgroup_in(self): 1609 if not self.is_switchport: 1610 return False 1611 return bool(self.mac_accessgroup_in) 1612 1613 @property 1614 def has_mac_accessgroup_out(self): 1615 if not self.is_switchport: 1616 return False 1617 return bool(self.mac_accessgroup_out) 1618 1619 @property 1620 def mac_accessgroup_in(self): 1621 retval = self.re_match_iter_typed( 1622 r"^\s*mac\saccess-group\s+(\S+)\s+in\s*$", result_type=str, default="" 1623 ) 1624 return retval 1625 1626 @property 1627 def mac_accessgroup_out(self): 1628 retval = self.re_match_iter_typed( 1629 r"^\s*mac\saccess-group\s+(\S+)\s+out\s*$", result_type=str, default="" 1630 ) 1631 return retval 1632 1633 ##------------- IPv4 ACLs 1634 1635 @property 1636 def has_ip_accessgroup_in(self): 1637 return bool(self.ipv4_accessgroup_in) 1638 1639 @property 1640 def has_ip_accessgroup_out(self): 1641 return bool(self.ipv4_accessgroup_out) 1642 1643 @property 1644 def has_ipv4_accessgroup_in(self): 1645 return bool(self.ipv4_accessgroup_in) 1646 1647 @property 1648 def has_ipv4_accessgroup_out(self): 1649 return bool(self.ipv4_accessgroup_out) 1650 1651 @property 1652 def ip_accessgroup_in(self): 1653 return self.ipv4_accessgroup_in 1654 1655 @property 1656 def ip_accessgroup_out(self): 1657 return self.ipv4_accessgroup_out 1658 1659 @property 1660 def ipv4_accessgroup_in(self): 1661 retval = self.re_match_iter_typed( 1662 r"^\s*ip\saccess-group\s+(\S+)\s+in\s*$", result_type=str, default="" 1663 ) 1664 return retval 1665 1666 @property 1667 def ipv4_accessgroup_out(self): 1668 retval = self.re_match_iter_typed( 1669 r"^\s*ip\saccess-group\s+(\S+)\s+out\s*$", result_type=str, default="" 1670 ) 1671 return retval 1672 1673 1674## 1675##------------- IOS Interface Object 1676## 1677 1678 1679class NXOSIntfLine(BaseNXOSIntfLine): 1680 def __init__(self, *args, **kwargs): 1681 """Accept an IOS line number and initialize family relationship 1682 attributes 1683 1684 .. warning:: 1685 1686 All :class:`~models_nxos.NXOSIntfLine` methods are still considered beta-quality, until this notice is removed. The behavior of APIs on this object could change at any time. 1687 """ 1688 super(NXOSIntfLine, self).__init__(*args, **kwargs) 1689 self.feature = "interface" 1690 1691 @classmethod 1692 def is_object_for(cls, line="", re=re): 1693 if re.search(r"^interface\s+(\S.+)", line): 1694 return True 1695 return False 1696 1697 1698## 1699##------------- NXOS Interface Globals 1700## 1701 1702 1703class NXOSIntfGlobal(BaseCfgLine): 1704 def __init__(self, *args, **kwargs): 1705 super(NXOSIntfGlobal, self).__init__(*args, **kwargs) 1706 self.feature = "interface global" 1707 1708 def __repr__(self): 1709 return "<%s # %s '%s'>" % (self.classname, self.linenum, self.text) 1710 1711 @classmethod 1712 def is_object_for(cls, line="", re=re): 1713 if re.search( 1714 "^(no\s+cdp\s+run)|(logging\s+event\s+link-status\s+global)|(spanning-tree\sportfast\sdefault)|(spanning-tree\sportfast\sbpduguard\sdefault)", 1715 line, 1716 ): 1717 return True 1718 return False 1719 1720 @property 1721 def has_cdp_disabled(self): 1722 if self.re_search("^no\s+cdp\s+run\s*"): 1723 return True 1724 return False 1725 1726 @property 1727 def has_intf_logging_def(self): 1728 if self.re_search("^logging\s+event\s+link-status\s+global"): 1729 return True 1730 return False 1731 1732 @property 1733 def has_stp_portfast_bpduguard_def(self): 1734 if self.re_search("^spanning-tree\sportfast\sbpduguard\sdefault"): 1735 return True 1736 return False 1737 1738 @property 1739 def has_stp_mode_rapidpvst(self): 1740 if self.re_search("^spanning-tree\smode\srapid-pvst"): 1741 return True 1742 return False 1743 1744 1745## 1746##------------- NXOS vPC line 1747## 1748class NXOSvPCLine(BaseCfgLine): 1749 def __init__(self, *args, **kwargs): 1750 super(NXOSvPCLine, self).__init__(*args, **kwargs) 1751 self.feature = "vpc" 1752 1753 def __repr__(self): 1754 return "<%s # %s '%s'>" % (self.classname, self.linenum, self.vpc_domain_id) 1755 1756 @classmethod 1757 def is_object_for(cls, line="", re=re): 1758 if re.search(r"^vpc\s+domain", line): 1759 return True 1760 return False 1761 1762 @property 1763 def vpc_domain_id(self): 1764 retval = self.re_match_typed( 1765 r"^vpc\s+domain\s+(\d+)$", result_type=str, default=-1 1766 ) 1767 return retval 1768 1769 @property 1770 def vpc_role_priority(self): 1771 retval = self.re_match_iter_typed( 1772 r"^\s+role\s+priority\s+(\d+)", result_type=int, default=-1 1773 ) 1774 return retval 1775 1776 @property 1777 def vpc_system_priority(self): 1778 retval = self.re_match_iter_typed( 1779 r"^\s+system-priority\s+(\d+)", result_type=int, default=-1 1780 ) 1781 return retval 1782 1783 @property 1784 def vpc_system_mac(self): 1785 retval = self.re_match_iter_typed( 1786 r"^\s+system-mac\s+(\S+)", result_type=str, default="" 1787 ) 1788 return retval 1789 1790 @property 1791 def has_peer_config_check_bypass(self): 1792 retval = self.re_match_iter_typed( 1793 r"^\s+(peer-config-check-bypass)", result_type=bool, default=False 1794 ) 1795 return retval 1796 1797 @property 1798 def has_peer_switch(self): 1799 retval = self.re_match_iter_typed( 1800 r"^\s+(peer-switch)", result_type=bool, default=False 1801 ) 1802 return retval 1803 1804 @property 1805 def has_layer3_peer_router(self): 1806 retval = self.re_match_iter_typed( 1807 r"^\s+(layer3\s+peer-router)", result_type=bool, default=False 1808 ) 1809 return retval 1810 1811 @property 1812 def has_peer_gateway(self): 1813 retval = self.re_match_iter_typed( 1814 r"^\s+(peer-gateway)", result_type=bool, default=False 1815 ) 1816 return retval 1817 1818 @property 1819 def has_auto_recovery(self): 1820 retval = self.re_match_iter_typed( 1821 r"^\s+(auto-recovery)", result_type=bool, default=False 1822 ) 1823 return retval 1824 1825 @property 1826 def vpc_auto_recovery_reload_delay(self): 1827 reload_delay_regex = r"^\s+auto-recovery\s+reload-delay\s+(\d+)" 1828 retval = self.re_match_iter_typed( 1829 reload_delay_regex, result_type=int, default=-1 1830 ) 1831 return retval 1832 1833 @property 1834 def has_ip_arp_synchronize(self): 1835 retval = self.re_match_iter_typed( 1836 r"(ip\s+arp\s+synchronize)", result_type=bool, default=False 1837 ) 1838 return retval 1839 1840 @property 1841 def vpc_peer_keepalive(self): 1842 """Return a dictionary with the configured vPC peer-keepalive parameters""" 1843 dest = self.re_match_iter_typed( 1844 r"peer-keepalive\s+.*?destination\s+(\d+\.\d+\.\d+\.\d+)", 1845 result_type=str, 1846 default="", 1847 ) 1848 hold_timeout = self.re_match_iter_typed( 1849 r"peer-keepalive\s+.*?hold-timeout\s+(\d+)", result_type=int, default=-1 1850 ) 1851 interval = self.re_match_iter_typed( 1852 r"peer-keepalive\s+.*?interval\s+(\d+)", result_type=int, default=-1 1853 ) 1854 timeout = self.re_match_iter_typed( 1855 r"peer-keepalive\s+.*?timeout\s+(\d+)", result_type=int, default=-1 1856 ) 1857 prec = self.re_match_iter_typed( 1858 r"peer-keepalive\s+.*?precedence\s+(\S+)", result_type=str, default="" 1859 ) 1860 source = self.re_match_iter_typed( 1861 r"peer-keepalive\s+.*?source\s+(\d+\.\d+\.\d+\.\d+)", 1862 result_type=str, 1863 default="", 1864 ) 1865 tos = self.re_match_iter_typed( 1866 r"peer-keepalive\s+.*?tos\s+(\S+)", result_type=str, default="" 1867 ) 1868 tos_byte = self.re_match_iter_typed( 1869 r"peer-keepalive\s+.*?tos-byte\s+(\S+)", result_type=int, default=-1 1870 ) 1871 udp_port = self.re_match_iter_typed( 1872 r"peer-keepalive\s+.*?udp-port\s+(\d+)", result_type=int, default=-1 1873 ) 1874 vrf = self.re_match_iter_typed( 1875 r"peer-keepalive\s+.*?vrf\s+(\S+)", result_type=str, default="" 1876 ) 1877 retval = { 1878 "destination": dest, 1879 "hold-timeout": hold_timeout, 1880 "interval": interval, 1881 "timeout": timeout, 1882 "precedence": prec, 1883 "source": source, 1884 "tos": tos, 1885 "tos-byte": tos_byte, 1886 "udp-port": udp_port, 1887 "vrf": vrf, 1888 } 1889 return retval 1890 1891 1892## 1893##------------- NXOS Hostname Line 1894## 1895 1896 1897class NXOSHostnameLine(BaseCfgLine): 1898 def __init__(self, *args, **kwargs): 1899 super(NXOSHostnameLine, self).__init__(*args, **kwargs) 1900 self.feature = "hostname" 1901 1902 def __repr__(self): 1903 return "<%s # %s '%s'>" % (self.classname, self.linenum, self.hostname) 1904 1905 @classmethod 1906 def is_object_for(cls, line="", re=re): 1907 if re.search("^hostname", line): 1908 return True 1909 return False 1910 1911 @property 1912 def hostname(self): 1913 retval = self.re_match_typed(r"^hostname\s+(\S+)", result_type=str, default="") 1914 return retval 1915 1916 1917## 1918##------------- NXOS Access Line 1919## 1920 1921 1922class NXOSAccessLine(BaseCfgLine): 1923 def __init__(self, *args, **kwargs): 1924 super(NXOSAccessLine, self).__init__(*args, **kwargs) 1925 self.feature = "access line" 1926 1927 def __repr__(self): 1928 return "<%s # %s '%s' info: '%s'>" % ( 1929 self.classname, 1930 self.linenum, 1931 self.name, 1932 self.range_str, 1933 ) 1934 1935 @classmethod 1936 def is_object_for(cls, line="", re=re): 1937 if re.search("^line", line): 1938 return True 1939 return False 1940 1941 @property 1942 def is_accessline(self): 1943 retval = self.re_match_typed(r"^(line\s+\S+)", result_type=str, default="") 1944 return bool(retval) 1945 1946 @property 1947 def name(self): 1948 retval = self.re_match_typed(r"^line\s+(\S+)", result_type=str, default="") 1949 # special case for IOS async lines: i.e. "line 33 48" 1950 if re.search("\d+", retval): 1951 return "" 1952 return retval 1953 1954 def reset(self, atomic=True): 1955 # Insert build_reset_string() before this line... 1956 self.insert_before(self.build_reset_string(), atomic=atomic) 1957 1958 def build_reset_string(self): 1959 # IOS interfaces are defaulted like this... 1960 return "default " + self.text 1961 1962 @property 1963 def range_str(self): 1964 return " ".join(map(str, self.line_range)) 1965 1966 @property 1967 def line_range(self): 1968 ## Return the access-line's numerical range as a list 1969 ## line con 0 => [0] 1970 ## line 33 48 => [33, 48] 1971 retval = self.re_match_typed( 1972 r"([a-zA-Z]+\s+)*(\d+\s*\d*)$", group=2, result_type=str, default="" 1973 ) 1974 tmp = map(int, retval.strip().split()) 1975 return tmp 1976 1977 def manual_exectimeout_min(self): 1978 tmp = self.parse_exectimeout 1979 return tmp[0] 1980 1981 def manual_exectimeout_sec(self): 1982 tmp = self.parse_exectimeout 1983 if len(tmp > 0): 1984 return 0 1985 return tmp[1] 1986 1987 @property 1988 def parse_exectimeout(self): 1989 retval = self.re_match_iter_typed( 1990 r"^\s*exec-timeout\s+(\d+\s*\d*)\s*$", group=1, result_type=str, default="" 1991 ) 1992 tmp = map(int, retval.strip().split()) 1993 return tmp 1994 1995 1996## 1997##------------- Base NXOS Route line object 1998## 1999 2000 2001class BaseNXOSRouteLine(BaseCfgLine): 2002 def __init__(self, *args, **kwargs): 2003 super(BaseNXOSRouteLine, self).__init__(*args, **kwargs) 2004 2005 def __repr__(self): 2006 return "<%s # %s '%s' info: '%s'>" % ( 2007 self.classname, 2008 self.linenum, 2009 self.network_object, 2010 self.routeinfo, 2011 ) 2012 2013 @property 2014 def routeinfo(self): 2015 ### Route information for the repr string 2016 if self.tracking_object_name: 2017 return ( 2018 self.nexthop_str 2019 + " AD: " 2020 + str(self.admin_distance) 2021 + " Track: " 2022 + self.tracking_object_name 2023 ) 2024 else: 2025 return self.nexthop_str + " AD: " + str(self.admin_distance) 2026 2027 @classmethod 2028 def is_object_for(cls, line="", re=re): 2029 return False 2030 2031 @property 2032 def vrf(self): 2033 raise NotImplementedError 2034 2035 @property 2036 def address_family(self): 2037 ## ipv4, ipv6, etc 2038 raise NotImplementedError 2039 2040 @property 2041 def network(self): 2042 raise NotImplementedError 2043 2044 @property 2045 def netmask(self): 2046 raise NotImplementedError 2047 2048 @property 2049 def admin_distance(self): 2050 raise NotImplementedError 2051 2052 @property 2053 def nexthop_str(self): 2054 raise NotImplementedError 2055 2056 @property 2057 def tracking_object_name(self): 2058 raise NotImplementedError 2059 2060 2061## 2062##------------- NXOS Route line object 2063## 2064 2065_RE_IP_ROUTE = re.compile( 2066 r"""^ip\s+route 2067\s+ 2068(?P<prefix>\d+\.\d+\.\d+\.\d+) # Prefix detection 2069\/ 2070(?P<masklen>\d+) # Netmask detection 2071(?:\s+(?P<nh_intf>[^\d]\S+))? # NH intf 2072(?:\s+(?P<nh_addr>\d+\.\d+\.\d+\.\d+))? # NH addr 2073(?:\s+track\s+(?P<track_group>\d+))? # Tracking object 2074(?:\s+name\s+(?P<name>\S+))? # Route name 2075(?:\s+tag\s+(?P<tag>\d+))? # Route tag 2076(?:\s+(?P<ad>\d+))? # Admin distance 2077""", 2078 re.VERBOSE, 2079) 2080 2081## FIXME: nxos ipv6 route needs work 2082_RE_IPV6_ROUTE = re.compile( 2083 r"""^ipv6\s+route 2084(?:\s+vrf\s+(?P<vrf>\S+))? 2085(?:\s+(?P<prefix>{0})\/(?P<masklength>\d+)) # Prefix detection 2086(?: 2087 (?:\s+(?P<nh_addr1>{1})) 2088 |(?:\s+(?P<nh_intf>\S+(?:\s+\d\S*?\/\S+)?)(?:\s+(?P<nh_addr2>{2}))?) 2089) 2090(?:\s+nexthop-vrf\s+(?P<nexthop_vrf>\S+))? 2091(?:\s+(?P<ad>\d+))? # Administrative distance 2092(?:\s+(?:(?P<ucast>unicast)|(?P<mcast>multicast)))? 2093(?:\s+tag\s+(?P<tag>\d+))? # Route tag 2094""".format( 2095 _IPV6_REGEX_STR_COMPRESSED1, 2096 _IPV6_REGEX_STR_COMPRESSED2, 2097 _IPV6_REGEX_STR_COMPRESSED3, 2098 ), 2099 re.VERBOSE, 2100) 2101 2102 2103class NXOSRouteLine(BaseNXOSRouteLine): 2104 def __init__(self, *args, **kwargs): 2105 super(NXOSRouteLine, self).__init__(*args, **kwargs) 2106 if "ipv6" in self.text[0:4]: 2107 self.feature = "ipv6 route" 2108 self._address_family = "ipv6" 2109 mm = _RE_IPV6_ROUTE.search(self.text) 2110 if not (mm is None): 2111 self.route_info = mm.groupdict() 2112 else: 2113 raise ValueError("Could not parse '{0}'".format(self.text)) 2114 else: 2115 self.feature = "ip route" 2116 self._address_family = "ip" 2117 mm = _RE_IP_ROUTE.search(self.text) 2118 if not (mm is None): 2119 self.route_info = mm.groupdict() 2120 else: 2121 raise ValueError("Could not parse '{0}'".format(self.text)) 2122 2123 @classmethod 2124 def is_object_for(cls, line="", re=re): 2125 if (line[0:8] == "ip route") or (line[0:11] == "ipv6 route "): 2126 return True 2127 return False 2128 2129 @property 2130 def address_family(self): 2131 ## ipv4, ipv6, etc 2132 return self._address_family 2133 2134 @property 2135 def admin_distance(self): 2136 ad = self.route_info["ad"] 2137 if ad is None: 2138 return "1" 2139 else: 2140 return self.route_info["ad"] 2141 2142 @property 2143 def network(self): 2144 if self._address_family == "ip": 2145 return self.route_info["prefix"] 2146 elif self._address_family == "ipv6": 2147 retval = self.re_match_typed( 2148 r"^ipv6\s+route\s+(vrf\s+)*(\S+?)\/\d+", 2149 group=2, 2150 result_type=str, 2151 default="", 2152 ) 2153 return retval 2154 2155 @property 2156 def netmask(self): 2157 if self._address_family == "ip": 2158 return str(self.network_object.netmask) 2159 elif self._address_family == "ipv6": 2160 return str(self.network_object.netmask) 2161 return retval 2162 2163 @property 2164 def masklen(self): 2165 if self._address_family == "ip": 2166 return self.route_info["masklen"] 2167 elif self._address_family == "ipv6": 2168 masklen_str = self.route_info["masklength"] or "128" 2169 return int(masklen_str) 2170 2171 @property 2172 def network_object(self): 2173 try: 2174 if self._address_family == "ip": 2175 return IPv4Obj("%s/%s" % (self.network, self.masklen), strict=False) 2176 elif self._address_family == "ipv6": 2177 return IPv6Obj("%s/%s" % (self.network, self.masklen)) 2178 except: 2179 return None 2180 2181 @property 2182 def nexthop_str(self): 2183 if self._address_family == "ip": 2184 if self.next_hop_interface: 2185 return self.next_hop_interface + " " + self.next_hop_addr 2186 else: 2187 return self.next_hop_addr 2188 elif self._address_family == "ipv6": 2189 retval = self.re_match_typed( 2190 r"^ipv6\s+route\s+(vrf\s+)*\S+\s+(\S+)", 2191 group=2, 2192 result_type=str, 2193 default="", 2194 ) 2195 return retval 2196 2197 @property 2198 def next_hop_interface(self): 2199 if self._address_family == "ip": 2200 if self.route_info["nh_intf"]: 2201 return self.route_info["nh_intf"] 2202 else: 2203 return "" 2204 elif self._address_family == "ipv6": 2205 if self.route_info["nh_intf"]: 2206 return self.route_info["nh_intf"] 2207 else: 2208 return "" 2209 2210 @property 2211 def next_hop_addr(self): 2212 if self._address_family == "ip": 2213 return self.route_info["nh_addr"] or "" 2214 elif self._address_family == "ipv6": 2215 return self.route_info["nh_addr1"] or self.route_info["nh_addr2"] or "" 2216 2217 @property 2218 def unicast(self): 2219 ## FIXME It's unclear how to implement this... 2220 raise NotImplementedError 2221 2222 @property 2223 def route_name(self): 2224 if self._address_family == "ip": 2225 if self.route_info["name"]: 2226 return self.route_info["name"] 2227 else: 2228 return "" 2229 elif self._address_family == "ipv6": 2230 raise NotImplementedError 2231 2232 @property 2233 def tag(self): 2234 return self.route_info["tag"] or "" 2235 2236 @property 2237 def tracking_object_name(self): 2238 return self.route_info["track_group"] 2239 2240 2241################################ 2242################################ Groups ############################### 2243################################ 2244 2245 2246## 2247##------------- NXOS TACACS+ Group 2248## 2249class NXOSAaaGroupServerLine(BaseCfgLine): 2250 def __init__(self, *args, **kwargs): 2251 super(NXOSAaaGroupServerLine, self).__init__(*args, **kwargs) 2252 self.feature = "aaa group server" 2253 2254 REGEX = r"^aaa\sgroup\sserver\s(?P<protocol>\S+)\s(?P<group>\S+)\s*$" 2255 mm = re.search(REGEX, self.text) 2256 if not (mm is None): 2257 groups = mm.groupdict() 2258 self.protocol = groups.get("protocol", "") 2259 self.group = groups.get("group", "") 2260 else: 2261 raise ValueError 2262 2263 @classmethod 2264 def is_object_for(cls, line="", re=re): 2265 if re.search(r"^aaa\sgroup\sserver", line): 2266 return True 2267 return False 2268 2269 @property 2270 def vrf(self): 2271 return self.re_match_iter_typed( 2272 r"^\s+use-vrf\s+(\S+)", group=1, result_type=str, default="" 2273 ) 2274 2275 @property 2276 def source_interface(self): 2277 return self.re_match_iter_typed( 2278 r"^\s+source-interface\s+(\S.+?\S)\s*$", 2279 group=1, 2280 result_type=str, 2281 default="", 2282 ) 2283 2284 @property 2285 def server_private(self, re=re): 2286 retval = set([]) 2287 rgx_priv = re.compile("^\s+server-private\s+(\S+)\s") 2288 for cobj in self.children: 2289 mm = rgx_priv.search(cobj.text) 2290 if not (mm is None): 2291 retval.add(mm.group(1)) # This is the server's ip 2292 return retval 2293 2294 2295## 2296##------------- NXOS AAA Lines 2297## 2298 2299 2300class NXOSAaaLoginAuthenticationLine(BaseCfgLine): 2301 def __init__(self, *args, **kwargs): 2302 super(NXOSAaaLoginAuthenticationLine, self).__init__(*args, **kwargs) 2303 self.feature = "aaa authentication login" 2304 2305 regex = r"^aaa\sauthentication\slogin\s(\S+)\sgroup\s(\S+)(.+?)$" 2306 self.list_name = self.re_match_typed( 2307 regex, group=1, result_type=str, default="" 2308 ) 2309 self.group = self.re_match_typed(regex, group=2, result_type=str, default="") 2310 methods_str = self.re_match_typed(regex, group=3, result_type=str, default="") 2311 self.methods = methods_str.strip().split("\s") 2312 2313 @classmethod 2314 def is_object_for(cls, line="", re=re): 2315 if re.search(r"^aaa\sauthentication\slogin", line): 2316 return True 2317 return False 2318 2319 2320class NXOSAaaEnableAuthenticationLine(BaseCfgLine): 2321 def __init__(self, *args, **kwargs): 2322 super(NXOSAaaEnableAuthenticationLine, self).__init__(*args, **kwargs) 2323 self.feature = "aaa authentication enable" 2324 2325 regex = r"^aaa\sauthentication\senable\s(\S+)\sgroup\s(\S+)(.+?)$" 2326 self.list_name = self.re_match_typed( 2327 regex, group=1, result_type=str, default="" 2328 ) 2329 self.group = self.re_match_typed(regex, group=2, result_type=str, default="") 2330 methods_str = self.re_match_typed(regex, group=3, result_type=str, default="") 2331 self.methods = methods_str.strip().split("\s") 2332 2333 @classmethod 2334 def is_object_for(cls, line="", re=re): 2335 if re.search(r"^aaa\sauthentication\senable", line): 2336 return True 2337 return False 2338 2339 2340class NXOSAaaCommandsAuthorizationLine(BaseCfgLine): 2341 def __init__(self, *args, **kwargs): 2342 super(NXOSAaaCommandsAuthorizationLine, self).__init__(*args, **kwargs) 2343 self.feature = "aaa authorization commands" 2344 2345 regex = r"^aaa\sauthorization\scommands\s(\d+)\s(\S+)\sgroup\s(\S+)(.+?)$" 2346 self.level = self.re_match_typed(regex, group=1, result_type=int, default=0) 2347 self.list_name = self.re_match_typed( 2348 regex, group=2, result_type=str, default="" 2349 ) 2350 self.group = self.re_match_typed(regex, group=3, result_type=str, default="") 2351 methods_str = self.re_match_typed(regex, group=4, result_type=str, default="") 2352 self.methods = methods_str.strip().split("\s") 2353 2354 @classmethod 2355 def is_object_for(cls, line="", re=re): 2356 if re.search(r"^aaa\sauthorization\scommands", line): 2357 return True 2358 return False 2359 2360 2361class NXOSAaaCommandsAccountingLine(BaseCfgLine): 2362 def __init__(self, *args, **kwargs): 2363 super(NXOSAaaCommandsAccountingLine, self).__init__(*args, **kwargs) 2364 self.feature = "aaa accounting commands" 2365 2366 regex = r"^aaa\saccounting\scommands\s(\d+)\s(\S+)\s(none|stop\-only|start\-stop)\sgroup\s(\S+)$" 2367 self.level = self.re_match_typed(regex, group=1, result_type=int, default=0) 2368 self.list_name = self.re_match_typed( 2369 regex, group=2, result_type=str, default="" 2370 ) 2371 self.record_type = self.re_match_typed( 2372 regex, group=3, result_type=str, default="" 2373 ) 2374 self.group = self.re_match_typed(regex, group=4, result_type=str, default="") 2375 2376 @classmethod 2377 def is_object_for(cls, line="", re=re): 2378 if re.search(r"^aaa\saccounting\scommands", line): 2379 return True 2380 return False 2381 2382 2383class NXOSAaaExecAccountingLine(BaseCfgLine): 2384 def __init__(self, *args, **kwargs): 2385 super(NXOSAaaExecAccountingLine, self).__init__(*args, **kwargs) 2386 self.feature = "aaa accounting exec" 2387 2388 regex = r"^aaa\saccounting\sexec\s(\S+)\s(none|stop\-only|start\-stop)\sgroup\s(\S+)$" 2389 self.list_name = self.re_match_typed( 2390 regex, group=1, result_type=str, default="" 2391 ) 2392 self.record_type = self.re_match_typed( 2393 regex, group=2, result_type=str, default="" 2394 ) 2395 self.group = self.re_match_typed(regex, group=3, result_type=str, default="") 2396 2397 @classmethod 2398 def is_object_for(cls, line="", re=re): 2399 if re.search(r"^aaa\saccounting\sexec", line): 2400 return True 2401 return False 2402