get_interfaces.py 17.7 KB
Newer Older
Dmitry Volodin's avatar
Dmitry Volodin committed
1
2
3
4
5
6
7
8
9
10
11
# -*- coding: utf-8 -*-
##----------------------------------------------------------------------
## Cisco.IOS.get_interfaces
##----------------------------------------------------------------------
## Copyright (C) 2007-2010 The NOC Project
## See LICENSE for details
##----------------------------------------------------------------------
"""
"""
# Python modules
import re
12
from collections import defaultdict
Dmitry Volodin's avatar
Dmitry Volodin committed
13
# NOC modules
Dmitry Volodin's avatar
Dmitry Volodin committed
14
from noc.core.script.base import BaseScript
Andrey Vertiprahov's avatar
Andrey Vertiprahov committed
15
16
from noc.sa.interfaces.base import InterfaceTypeError
from noc.sa.interfaces.igetinterfaces import IGetInterfaces
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
17
from noc.sa.profiles.Cisco.IOS import uBR
18
19


Dmitry Volodin's avatar
Dmitry Volodin committed
20
class Script(BaseScript):
21
22
23
24
25
26
27
28
29
    """
    Cisco.IOS.get_interfaces
    @todo: VRF support
    @todo: IPv6
    @todo: ISIS
    @todo: isis, bgp, rip
    @todo: subinterfaces
    @todo: Q-in-Q
    """
Dmitry Volodin's avatar
Dmitry Volodin committed
30
    name = "Cisco.IOS.get_interfaces"
Dmitry Volodin's avatar
Dmitry Volodin committed
31
    interface = IGetInterfaces
Dmitry Volodin's avatar
Dmitry Volodin committed
32

33
34
35
36
37
38
39
    rx_sh_int = re.compile(
        r"^(?P<interface>.+?)\s+is(?:\s+administratively)?\s+(?P<admin_status>up|down),\s+line\s+"
        r"protocol\s+is\s+(?P<oper_status>up|down)\s"
        r"(?:\((?:connected|notconnect|disabled|monitoring|err-disabled)\)\s*)?\n"
        r"\s+Hardware is (?P<hardw>[^\n]+)\n(?:\s+Description:\s(?P<desc>[^\n]+)\n)?"
        r"(?:\s+Internet address ((is\s(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}))|([^\d]+))\n)?"
        r"[^\n]+\n[^\n]+\n\s+Encapsulation\s+(?P<encaps>[^\n]+)",
Dmitry Lukhtionov's avatar
Dmitry Lukhtionov committed
40
       re.MULTILINE | re.IGNORECASE)
41
42
43
    rx_sh_ip_int = re.compile(
        r"^(?P<interface>.+?)\s+is(?:\s+administratively)?\s+(?P<admin_status>up|down),\s+"
        r"line\s+protocol\s+is\s+", re.IGNORECASE)
Dmitry Lukhtionov's avatar
Dmitry Lukhtionov committed
44
45
    rx_mac = re.compile(r"address\sis\s(?P<mac>\w{4}\.\w{4}\.\w{4})",
        re.MULTILINE | re.IGNORECASE)
46
47
48
49
50
51
    rx_ip = re.compile(
        r"Internet address is (?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})",
        re.MULTILINE | re.IGNORECASE)
    rx_sec_ip = re.compile(
        r"Secondary address (?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})",
        re.MULTILINE | re.IGNORECASE)
52
53
54
    rx_ipv6 = re.compile(
        r"(?P<address>\S+), subnet is (?P<net>\S+)/(?P<mask>\d+)",
        re.MULTILINE | re.IGNORECASE)
55
56
57
    rx_vlan_line = re.compile(
        r"^(?P<vlan_id>\d{1,4})\s+(?P<name>\S+)\s+(?P<status>active|suspend|act\/unsup)\s+"
        r"(?P<ports>[\w\/\s\,\.]+)$", re.MULTILINE)
58
59
    rx_vlan_line_cont = re.compile(r"^\s{10,}(?P<ports>[\w\/\s\,\.]+)$",
        re.MULTILINE)
Dmitry Volodin's avatar
Dmitry Volodin committed
60
    rx_ospf = re.compile(r"^(?P<name>\S+)\s+\d", re.MULTILINE)
61
62
    rx_pim = re.compile(r"^\S+\s+(?P<name>\S+)\s+v\d+/\S+\s+\d+")
    rx_igmp = re.compile(r"^(?P<name>\S+) is ")
63
    rx_cisco_interface_name = re.compile(
Dmitry Lukhtionov's avatar
Fix    
Dmitry Lukhtionov committed
64
        r"^(?P<type>[a-z]{2})[a-z\-]*\s*(?P<number>\d+(/\d+(/\d+)?)?([.:]\d+(\.\d+)?)?(A|B)?)$",
65
        re.IGNORECASE)
Dmitry Lukhtionov's avatar
Fix    
Dmitry Lukhtionov committed
66
    rx_cisco_interface_sonet = re.compile(r"^(?P<type>Se)\s+(?P<number>\d+\S+)$")
67
    rx_ctp = re.compile(r"Keepalive set \(\d+ sec\)")
68
    rx_cdp = re.compile(r"^(?P<iface>\S+) is ")
69
70
    rx_lldp = re.compile("^(?P<iface>(?:Fa|Gi|Te)[^:]+?):.+Rx: (?P<rx_state>\S+)",
        re.MULTILINE | re.DOTALL)
71
72
73
    rx_gvtp = re.compile("VTP Operating Mode\s+: Off", re.MULTILINE)
    rx_vtp = re.compile("^\s*(?P<iface>(?:Fa|Gi|Te)[^:]+?)\s+enabled",
        re.MULTILINE)
74
75
76
77
78
    rx_vtp1 = re.compile(
        "^\s*Local updater ID is \S+ on interface (?P<iface>(?:Fa|Gi|Te)[^:]+?)\s+",
        re.MULTILINE)
    rx_oam = re.compile(
        r"^\s*(?P<iface>(?:Fa|Gi|Te)\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s*$")
79
80
81
82
83
84
85
86
87

    def get_lldp_interfaces(self):
        """
        Returns a set of normalized LLDP interface names
        :return:
        """
        try:
            v = self.cli("show lldp interface")
        except self.CLISyntaxError:
88
89
            return []
        r = []
90
91
92
93
        for s in v.strip().split("\n\n"):
            match = self.rx_lldp.search(s)
            if match:
                if match.group("rx_state").lower() == "enabled":
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
                    r += [self.profile.convert_interface_name(match.group("iface"))]
        return r

    def get_oam_interfaces(self):
        """
        Returns a set of normalized OAM interface names
        :return:
        """
        try:
            v = self.cli("show ethernet oam summary")
        except self.CLISyntaxError:
            return []
        r = []
        for s in v.strip().split("\n"):
            match = self.rx_oam.search(s)
            if match:
                r += [self.profile.convert_interface_name(match.group("iface"))]
        return r

    def get_cdp_interfaces(self):
        """
        Returns a set of normalized CDP interface names
        :return:
        """
        try:
            v = self.cli("show cdp interface")
        except self.CLISyntaxError:
            return []
        r = []
        for s in v.split("\n"):
            match = self.rx_cdp.search(s)
            if match:
                r += [self.profile.convert_interface_name(match.group("iface"))]
        return r
128

129
130
131
132
133
134
135
136
    def get_vtp_interfaces(self):
        """
        Returns a set of normalized VTP interface names
        :return:
        """
        try:
            v = self.cli("show vtp status")
        except self.CLISyntaxError:
137
            return []
138
        if self.rx_gvtp.search(v):
139
140
141
142
143
144
145
            return []
        r = []
        try:
            v1 = self.cli("show vtp interface")
        except self.CLISyntaxError:
            v1 = v
        for s in v1.strip().split("\n"):
146
147
            match = self.rx_vtp.search(s)
            if match:
148
149
150
151
152
                r += [self.profile.convert_interface_name(match.group("iface"))]
            match = self.rx_vtp1.search(s)
            if match:
                r += [self.profile.convert_interface_name(match.group("iface"))]
        return r
153

Dmitry Volodin's avatar
Dmitry Volodin committed
154
    def get_ospfint(self):
155
156
157
158
        try:
            v = self.cli("show ip ospf interface brief")
        except self.CLISyntaxError:
            return []
159
        r = []
Dmitry Volodin's avatar
Dmitry Volodin committed
160
161
162
        for s in v.split("\n"):
            match = self.rx_ospf.search(s)
            if match:
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
                r += [match.group("name")]
        return r

    def get_pimint(self):
        try:
            v = self.cli("show ip pim interface")
        except self.CLISyntaxError:
            return []
        r = []
        for s in v.split("\n"):
            match = self.rx_pim.search(s)
            if match:
                r += [self.profile.convert_interface_name(match.group("name"))]
        return r

    def get_igmpint(self):
        try:
            v = self.cli("show ip igmp interface")
        except self.CLISyntaxError:
            return []
        r = []
        for s in v.split("\n"):
            match = self.rx_igmp.search(s)
            if match:
                r += [self.profile.convert_interface_name(match.group("name"))]
        return r
Dmitry Volodin's avatar
Dmitry Volodin committed
189

190
191
192
193
194
195
196
    rx_ifindex = re.compile(
        r"^(?P<interface>\S+): Ifindex = (?P<ifindex>\d+)")

    def get_ifindex(self):
        try:
            c = self.cli("show snmp mib ifmib ifindex")
        except self.CLISyntaxError:
197
198
            return {}
        r = {}
199
200
        for l in c.split("\n"):
            match = self.rx_ifindex.match(l.strip())
201
202
            if match:
                r[match.group("interface")] = int(match.group("ifindex"))
203
204
        return r

Dmitry Volodin's avatar
Dmitry Volodin committed
205
    ## Cisco uBR7100, uBR7200, uBR7200VXR, uBR10000 Series
Dmitry Lukhtionov's avatar
Dmitry Lukhtionov committed
206
207
    rx_vlan_ubr = re.compile(
        r"^\w{4}\.\w{4}\.\w{4}\s(?P<port>\S+)\s+(?P<vlan_id>\d{1,4})")
Dmitry Volodin's avatar
Dmitry Volodin committed
208

Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
209
    def get_ubr_pvm(self):
Dmitry Volodin's avatar
Dmitry Volodin committed
210
211
        vlans = self.cli("show cable l2-vpn dot1q-vc-map")
        pvm = {}
212
        for l in vlans.split("\n"):
Dmitry Volodin's avatar
Dmitry Volodin committed
213
            match = self.rx_vlan_ubr.search(l)
Dmitry Volodin's avatar
Dmitry Volodin committed
214
            if match:
Dmitry Lukhtionov's avatar
Dmitry Lukhtionov committed
215
216
                port = match.group("port")
                vlan_id = int(match.group("vlan_id"))
217
218
                if port not in pvm:
                    pvm[port] = ["%s" % vlan_id]
Dmitry Lukhtionov's avatar
Dmitry Lukhtionov committed
219
                else:
220
                    pvm[port] += ["%s" % vlan_id]
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
221
        return pvm
Dmitry Volodin's avatar
Dmitry Volodin committed
222

Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
223
224
225
    def execute(self):
        # Get port-to-vlan mappings
        pvm = {}
226
        switchports = {}  # interface -> (untagged, tagged)
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
227
228
229
230
231
232
233
234
235
236
237
        if self.match_version(uBR):
            # uBR series
            pvm = self.get_ubr_pvm()
        else:
            vlans = None
            for cmd in ("show vlan brief", "show vlan-switch brief"):
                try:
                    vlans = self.cli(cmd)
                except self.CLISyntaxError:
                    continue
            if vlans:
238
239
240
241
242
                for sp in self.scripts.get_switchport():
                    switchports[sp["interface"]] = (
                        sp["untagged"] if "untagged" in sp else None,
                        sp["tagged"]
                    )
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
243
        # Get portchannels
Dmitry Volodin's avatar
Dmitry Volodin committed
244
245
246
247
248
249
        portchannel_members = {}
        for pc in self.scripts.get_portchannel():
            i = pc["interface"]
            t = pc["type"] == "L"
            for m in pc["members"]:
                portchannel_members[m] = (i, t)
250
251
        # Get LLDP interfaces
        lldp = self.get_lldp_interfaces()
252
253
        # Get VTP interfaces
        vtp = self.get_vtp_interfaces()
254
255
256
257
        # Get OAM interfaces
        oam = self.get_oam_interfaces()
        # Get CDP interfaces
        cdp = self.get_cdp_interfaces()
258
        # Get IPv4 interfaces
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
        ipv4_interfaces = defaultdict(list)  # interface -> [ipv4 addresses]
        c_iface = None
        for l in self.cli("show ip interface").splitlines():
            match = self.rx_sh_ip_int.search(l)
            if match:
                c_iface = self.profile.convert_interface_name(
                    match.group("interface"))
                continue
            # Primary ip
            match = self.rx_ip.search(l)
            if not match:
                # Secondary ip
                match = self.rx_sec_ip.search(l)
                if not match:
                    continue
            ip = match.group("ip")
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
            ipv4_interfaces[c_iface] += [ip]
        # Get IPv6 interfaces
        ipv6_interfaces = defaultdict(list)  # interface -> [ipv6 addresses]
        c_iface = None
        try:
            v = self.cli("show ipv6 interface")
        except self.CLISyntaxError:
            v = ""
        for l in v.splitlines():
            match = self.rx_sh_ip_int.search(l)
            if match:
                iface = match.group("interface")
                try:
                    c_iface = self.profile.convert_interface_name(iface)
                except InterfaceTypeError:
                    c_iface = None
                continue
            if not c_iface:
                continue  # Skip wierd interfaces
            # Primary ip
            match = self.rx_ipv6.search(l)
            if not match:
                # Secondary ip?
                continue
            ip = "%s/%s" % (match.group("address"), match.group("mask"))
            ipv6_interfaces[c_iface] += [ip]
301
        #
Dmitry Volodin's avatar
Dmitry Volodin committed
302
        interfaces = []
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
303
        # Get OSPF interfaces
Dmitry Volodin's avatar
Dmitry Volodin committed
304
        ospfs = self.get_ospfint()
305
306
307
308
        # Get PIM interfaces
        pims = self.get_pimint()
        # Get IGMP interfaces
        igmps = self.get_igmpint()
309
310
        # Get interfaces SNMP ifIndex
        ifindex = self.get_ifindex()
Dmitry Volodin's avatar
Dmitry Volodin committed
311
312
313

        v = self.cli("show interface")
        for match in self.rx_sh_int.finditer(v):
314
315
            full_ifname = match.group("interface")
            ifname = self.profile.convert_interface_name(full_ifname)
316
            if ifname[:2] in ["Vi", "Di", "GM", "CP", "Nv", "Do", "Nu", "Co"]:
317
                continue
318
            # NOC-378 - Dirty hack for interface like ATM0/IMA0
319
            if "/ima" in full_ifname.lower():
320
                continue
321
322
            if ":" in ifname:
                inm = ifname.split(":")[0]
323
                # Create root interface if not exists yet
324
                if inm != interfaces[-1]["name"]:
Dmitry Lukhtionov's avatar
Dmitry Lukhtionov committed
325
                    iface = {
326
327
328
                        "name": inm,
                        "admin_status": True,
                        "oper_status": True,
329
330
                        "type": "physical",
                        "enabled_protocols": []
Dmitry Lukhtionov's avatar
Dmitry Lukhtionov committed
331
                    }
332
333
                    if inm in lldp:
                        iface["enabled_protocols"] += ["LLDP"]
334
335
                    if inm in vtp:
                        iface["enabled_protocols"] += ["VTP"]
336
337
338
339
                    if inm in oam:
                        iface["enabled_protocols"] += ["OAM"]
                    if inm in cdp:
                        iface["enabled_protocols"] += ["CDP"]
340
341
342
343
                    interfaces += [iface]
            a_stat = match.group("admin_status").lower() == "up"
            o_stat = match.group("oper_status").lower() == "up"
            hw = match.group("hardw")
Dmitry Volodin's avatar
Dmitry Volodin committed
344
            sub = {
345
346
347
                "name": ifname,
                "admin_status": a_stat,
                "oper_status": o_stat,
348
349
                "enabled_afi": [],
                "enabled_protocols": []
350
351
352
353
354
            }
            if "alias" in match.groups():
                sub["description"] = match.group("alias")
            if match.group("desc"):
                sub["description"] = match.group("desc")
Dmitry Volodin's avatar
Dmitry Volodin committed
355
356
            matchmac = self.rx_mac.search(hw)
            if matchmac:
357
                sub["mac"] = matchmac.group("mac")
358
            if ifname in switchports and ifname not in portchannel_members:
359
                sub["enabled_afi"] += ["BRIDGE"]
360
361
362
363
364
                u, t = switchports[ifname]
                if u:
                    sub["untagged_vlan"] = u
                if t:
                    sub["tagged_vlans"] = t
Dmitry Volodin's avatar
Dmitry Volodin committed
365

366
367
368
369
370
371
            # Static vlans
            if match.group("encaps"):
                encaps = match.group("encaps")
                if encaps[:6] == "802.1Q":
                    sub["vlan_ids"] = [encaps.split(",")[1].split()[2][:-1]]
            # uBR ?
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
372
            if ifname in pvm:
373
374
375
376
                sub["vlan_ids"] = pvm[ifname]
            # IPv4/Ipv6
            if match.group("ip"):
                if ifname in ipv4_interfaces:
377
                    sub["enabled_afi"] += ["IPv4"]
378
379
                    sub["ipv4_addresses"] = ipv4_interfaces[ifname]
                if ifname in ipv6_interfaces:
380
                    sub["enabled_afi"] += ["IPv6"]
381
                    sub["ipv6_addresses"] = ipv6_interfaces[ifname]
Dmitry Volodin's avatar
Dmitry Volodin committed
382
            matchifn = self.rx_cisco_interface_name.match(ifname)
Dmitry Lukhtionov's avatar
Fix    
Dmitry Lukhtionov committed
383
384
            if not matchifn:
                matchifn = self.rx_cisco_interface_sonet.match(ifname)
385
386
            shotn = (matchifn.group("type").capitalize() +
                     matchifn.group("number"))
Dmitry Volodin's avatar
Dmitry Volodin committed
387
            if shotn in ospfs:
388
                sub["enabled_protocols"] += ["OSPF"]
389
390
391
392
            if ifname in pims:
                sub["enabled_protocols"] += ["PIM"]
            if ifname in igmps:
                sub["enabled_protocols"] += ["IGMP"]
393

394
            if full_ifname in ifindex:
395
                sub["snmp_ifindex"] = ifindex[full_ifname]
396

397
            if "." not in ifname and ":" not in ifname:
398
                iftype = self.profile.get_interface_type(ifname)
399
400
401
402
403
                if not iftype:
                    self.logger.info(
                        "Ignoring unknown interface type: '%s", iftype
                    )
                    continue
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
404
405
406
407
                iface = {
                    "name": ifname,
                    "admin_status": a_stat,
                    "oper_status": o_stat,
408
                    "type": iftype,
409
                    "enabled_protocols": [],
410
                    "subinterfaces": [sub]
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
411
                }
412
413
                if ifname in lldp:
                    iface["enabled_protocols"] += ["LLDP"]
414
415
                if ifname in vtp:
                    iface["enabled_protocols"] += ["VTP"]
416
417
418
419
                if ifname in oam:
                    iface["enabled_protocols"] += ["OAM"]
                if ifname in cdp:
                    iface["enabled_protocols"] += ["CDP"]
420
421
422
                match1 = self.rx_ctp.search(v)
                if match1:
                    iface["enabled_protocols"] += ["CTP"]
423
424
425
426
427
428
                if match.group("desc"):
                    iface["description"] = match.group("desc")
                if "mac" in sub:
                    iface["mac"] = sub["mac"]
                if "alias" in sub:
                    iface["alias"] = sub["alias"]
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
429
                # Set VLAN IDs for SVI
430
                if iface["type"] == "SVI":
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
431
                    sub["vlan_ids"] = [int(shotn[2:].strip())]
432
433
434
435
                # Portchannel member
                if ifname in portchannel_members:
                    ai, is_lacp = portchannel_members[ifname]
                    iface["aggregated_interface"] = ai
436
                    iface["enabled_protocols"] += ["LACP"]
437
438
                # Ifindex
                if full_ifname in ifindex:
439
                    iface["snmp_ifindex"] = ifindex[full_ifname]
Dmitry Volodin's avatar
Bugfix    
Dmitry Volodin committed
440
                interfaces += [iface]
Dmitry Volodin's avatar
Dmitry Volodin committed
441
            else:
442
                # Append additional subinterface
443
444
445
446
                try:
                    interfaces[-1]["subinterfaces"] += [sub]
                except KeyError:
                    interfaces[-1]["subinterfaces"] = [sub]
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
        # Process VRFs
        vrfs = {
            "default": {
                "forwarding_instance": "default",
                "type": "ip",
                "interfaces": []
            }
        }
        imap = {}  # interface -> VRF
        try:
            r = self.scripts.get_mpls_vpn()
        except self.CLISyntaxError:
            r = []
        for v in r:
            if v["type"] == "VRF":
                vrfs[v["name"]] = {
                    "forwarding_instance": v["name"],
                    "type": "VRF",
                    "interfaces": []
                }
467
468
469
                rd = v.get("rd")
                if rd:
                    vrfs[v["name"]]["rd"] = rd
470
471
472
473
474
475
476
477
478
479
                for i in v["interfaces"]:
                    imap[i] = v["name"]
        for i in interfaces:
            subs = i["subinterfaces"]
            for vrf in set(imap.get(si["name"], "default") for si in subs):
                c = i.copy()
                c["subinterfaces"] = [si for si in subs
                                      if imap.get(si["name"], "default") == vrf]
                vrfs[vrf]["interfaces"] += [c]
        return vrfs.values()