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