-
Notifications
You must be signed in to change notification settings - Fork 97
Expand file tree
/
Copy pathshowvid.py
More file actions
375 lines (324 loc) · 13.7 KB
/
showvid.py
File metadata and controls
375 lines (324 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# Python Scripts provided by Extreme Networks.
# This script is provided free of charge by Extreme. We hope such scripts are
# helpful when used in conjunction with Extreme products and technology;
# however, scripts are provided simply as an accommodation and are not
# supported nor maintained by Extreme. ANY SCRIPTS PROVIDED BY EXTREME ARE
# HEREBY PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL EXTREME OR ITS
# THIRD PARTY LICENSORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
# IN CONNECTION WITH THE USE OR DISTRIBUTION OF SUCH SCRIPTS.
import exsh
import sys
import collections
import json
def expand_number_string(number_string, min_limit, max_limit):
'''create a set() of unique numbers from comma and hyphen separated values
e.g, 1,3,5,7-10 = [1,3,5,7,8,9,10]
Verify that each expanded value is in the range: min_limit <= value <= max_limit
'''
expanded_list = list()
tokens = number_string.split(',')
for token in tokens:
# simple number
if token.isdigit():
expanded_list.append(int(token))
continue
# validate n-m format
minVal, sep, maxVal = token.partition('-')
if len(sep) == 0:
raise ValueError('range has a bad value: ' + token + '\n')
if not minVal.isdigit():
raise ValueError(' '.join(['range error:', token + ':', minVal, 'is not a number\n']))
if not maxVal.isdigit():
raise ValueError(' '.join(['range error:', token + ':', maxVal, 'is not a number\n']))
if int(minVal) >= int(maxVal):
raise ValueError(' '.join(['range error:', token + ':', minVal, 'is not less than', maxVal, '\n']))
try:
# expand m-n range into individual values
expanded_list += range(int(minVal), int(maxVal) + 1)
except:
raise ValueError('range has a bad format: ' + token + '\n')
# test the individual values to verify they are in the range provided
if min_limit is not None or max_limit is not None:
for num in expanded_list:
if min_limit is not None and num < min_limit:
raise OverflowError(' '.join(['range error:', 'value =',str(num), 'is less than', str(min_limit), '\n']))
if max_limit is not None and num > max_limit:
raise OverflowError(' '.join(['range error:', 'value =',str(num), 'is greater than', str(max_limit), '\n']))
return set(expanded_list)
class ExosDb(object):
def __init__(self):
self.vlan_map = None
self.vnames = []
def get_exos_json_data(self, cmd):
# run the command that returns JSON results
result = exsh.clicmd(cmd, True)
# convert the results to JSON
try:
json_result = json.loads(result)
except:
return []
# return the list of dictionaries
return json_result.get('data', [])
def get_vlan_map(self):
self.vlan_map = collections.OrderedDict()
vnames = []
# extract the entire vlan map
cmd = 'debug cfgmgr show next vlan.vlanMap vlanList=1-4094'
json_data = self.get_exos_json_data(cmd)
if len(json_data) == 0:
# the length tells us that the vlanMap is not available
# use the older slower way of building a vlan maps
cmd = 'debug cfgmgr show next vlan.vlan'
json_data = self.get_exos_json_data(cmd)
vmap = {}
for vlan in json_data:
vid = int(vlan.get('tag'))
vname = vlan.get('name')
vtype = int(vlan.get('type'))
vmap[vid] = (vname, vtype)
vnames.append(vname)
for vid in sorted(vmap.keys()):
self.vlan_map[vid] = vmap[vid]
else:
# walk the list and extract the fields to form the map
for vmap in json_data:
status = vmap.get('status')
if status == 'ERROR':
break
try:
vid = int(vmap.get('vlanId'))
except:
pass
if vid is None:
continue
vname = vmap.get('vlanName')
vtype = int(vmap.get('vlanType'))
self.vlan_map[vid] = (vname, vtype)
vnames.append(vname)
vnames.append('Mgmt')
self.vlan_map[4095] = ('Mgmt', 2)
# need a sorted vnames for flag lookups
self.vnames = sorted(vnames)
return self.vlan_map
def get_vlan_proc_name(self, vname):
if self.vlan_map is None:
self.get_vlan_map()
cmd = 'debug cfgmgr show one vlan.vlanProc action=SHOW_VLAN_NAME name1={vname}'.format(vname=vname)
json_data = self.get_exos_json_data(cmd)
if len(json_data):
return json_data[0]
return None
def get_vlan_proc_flags(self, vname):
if self.vlan_map is None:
self.get_vlan_map()
cmd = 'debug cfgmgr show one vlan.vlanProc action=SHOW_VLAN name1={vname}'.format(vname=vname)
json_data = self.get_exos_json_data(cmd)
if len(json_data):
return json_data[0]
return None
class ShowVid(object):
class Constants(object):
PROCESS_NAME = 'showvid'
# CLI constants
VLAN_LIST = '<vlan_list>'
VLAN_DETAIL = 'detail'
# CM object constants
ACTION = 'SHOW_VLAN_NAME'
FLAGS_ACTION = 'SHOW_VLAN'
VLAN_TYPE = 3
VMAN_TYPE = 5
# vlan constants
MGMT = 'Mgmt'
class PrintLine(object):
FMT = '{vid:>{vid_len}}{sep:1.1}{name:<{name_len}}{sep:1.1}{addr:<{addr_len}}{sep:1.1}{sep:1.1}{flags:<{flags_len}}{sep:1.1}{sep:1.1}{proto:<{proto_len}}{sep:1.1}{ports:{proto_len}}{sep:1.1}{vr:<{vr_len}}'
VID_LEN = 4
NAME_LEN = 15
ADDR_LEN = 18
FLAGS_LEN = 28
PROTO_LEN = 6
PORTS_LEN = 6
VR_LEN = 10
PL = PrintLine()
def __init__(self):
# init the class
self.C = ShowVid.Constants()
self.db = ExosDb()
self.vid_list = None
# display formatting
self.fmt = self.C.PL.FMT
self.footing = [
"Flags : (B) BFD Enabled, (c) 802.1ad customer VLAN, (C) EAPS Control VLAN,\n"
" (d) Dynamically created VLAN, (D) VLAN Admin Disabled,\n"
" (e) CES Configured, (E) ESRP Enabled, (f) IP Forwarding Enabled,\n"
" (F) Learning Disabled, (h) TRILL Enabled, (i) ISIS Enabled,\n"
" (I) Inter-Switch Connection VLAN for MLAG, (k) PTP Configured,\n"
" (l) MPLS Enabled, (L) Loopback Enabled, (m) IPmc Forwarding Enabled,\n"
" (M) Translation Member VLAN or Subscriber VLAN, (n) IP Multinetting Enabled,\n"
" (N) Network Login VLAN, (o) OSPF Enabled, (O) Flooding Disabled,\n"
" (p) PIM Enabled, (P) EAPS protected VLAN, (r) RIP Enabled,\n"
" (R) Sub-VLAN IP Range Configured, (s) Sub-VLAN, (S) Super-VLAN,\n"
" (t) Translation VLAN or Network VLAN, (T) Member of STP Domain,\n"
" (v) VRRP Enabled, (V) VPLS Enabled, (W) VPWS Enabled, (Z) OpenFlow Enabled\n"
]
def __call__(self):
# get command line args
try:
args = self.get_params()
except:
return
if args.vlan is not None:
try:
self.vid_list = expand_number_string(args.vlan, 1, 4095)
except OverflowError:
print 'Error: (-v) VID {0} is outside the range of {1}'.format(args.vlan, '1-4095')
return
except Exception as msg:
print 'Error: (-v) VIDs can only be defined using single numbers with commas or <start>-<end> range ("1", "1,2,3", "1-5", "1,2,3-5,6-8", etc)'
print msg
return
print 'Collecting information. This may take a moment'
# load vlan id to vlan name map
self.db.get_vlan_map()
if len(self.db.vlan_map) == 0:
exoslib.show('Error: There are no VIDs to display\n',True)
return
# display the heading
self.print_heading_fmt1()
# display the VLAN lines ordered by VID
for vid, (vname, vtype) in self.db.vlan_map.items():
if self.vid_list is not None and int(vid) not in self.vid_list:
continue
# skip VMANs
if vtype == 5:
continue
# print the detail line for the VLAN
try:
self.print_summary_line(self.db.get_vlan_proc_name(vname))
except:
break
# either the user escaped out or the list ended. Display the footer
self.print_dash_line()
self.show_list(self.footing)
def get_params(self):
import argparse
parser = argparse.ArgumentParser(prog=self.C.PROCESS_NAME)
parser.add_argument('-v', '--vlan',
help='VLAN ID range 1,2 or 1-5',
default=None)
args = parser.parse_args()
return args
def get_flags(self, v):
# The form of vlanProc object that returns the flags is SHOW_VLAN action
# The behavior of this action is to return the next VLAN
# so to spoof vlanProc, we trim the trailing character off of the
# name so it gets the next one higher
vname = v.get('name1')
vname_prev = vname[0:-1] + chr(ord(vname[-1]) - 1)
while True:
reply = self.db.get_vlan_proc_flags(vname_prev)
if reply.get('status') in ['ERROR'] or reply.get('name1') is None:
# ran off the end of all VLANs
return ''
if reply.get('name1').lower > vname.lower():
# returned name is past the one we are looking for
return ''
if reply.get('name1') == vname:
# matched the name we are looking for
return '' if reply.get('flags') is None else reply.get('flags')
# find the next VLAN name
vname_prev = reply.get('name1')
def print_summary_line(self, v):
# send a single vlan summary line to the display
if v['tag'] is None:
return
if v['ipStatus'] == '1':
addr_string = '{0}/{1}'.format(v['ipAddress'],v['maskForDisplay'])
else:
addr_string = '-' * self.C.PL.ADDR_LEN
vdesc_string = v.get('description')
if vdesc_string is None or len(vdesc_string) == 0:
vdesc_string = v['name1']
if len(vdesc_string) > self.C.PL.NAME_LEN:
# split line into 2 parts
self.output_fmt1(vid=v['tag'], name=vdesc_string)
self.output_fmt1(addr=addr_string,
flags=self.get_flags(v),
proto=v['filter'],
ports='{0}/{1}'.format(str(v['activePorts']), str(v['count1'])),
vr=v['name2'])
else:
self.output_fmt1(vid=v['tag'],
name=vdesc_string,
addr=addr_string,
flags=self.get_flags(v),
proto=v['filter'],
ports='{0}/{1}'.format(str(v['activePorts']), str(v['count1'])),
vr=v['name2'])
return
def show_list(self, display_list):
# output a constant list to the display
for dl in display_list:
try:
self.show(dl + '\n', True)
except KeyboardInterrupt:
break
def print_heading_fmt1(self):
self.print_dash_line()
self.output_fmt1(
vid='VID',
#name='Name',
name='Description',
addr='Protocol Addr',
flags='Flags',
proto='Proto',
ports='Ports',
vr='Virtual')
self.output_fmt1(
name='/VLAN Name',
ports='Active',
vr='Router'),
self.output_fmt1(
ports='/Total')
self.print_dash_line(sep='')
def print_dash_line(self, sep='-'):
self.output_fmt1(
vid='-' * self.C.PL.VID_LEN,
name='-' * self.C.PL.NAME_LEN,
addr='-' * self.C.PL.ADDR_LEN,
flags='-' * self.C.PL.FLAGS_LEN,
proto='-' * self.C.PL.PROTO_LEN,
ports='-' * self.C.PL.PORTS_LEN,
vr='-' * self.C.PL.VR_LEN,
sep=sep,
force=True)
def output_fmt1(self, vid='', name='', addr='', flags='', proto='', ports='', vr='', sep='', force=False):
line = self.fmt.format(
vid=vid,
vid_len = self.C.PL.VID_LEN,
name=name,
name_len = self.C.PL.NAME_LEN,
addr=addr,
addr_len = self.C.PL.ADDR_LEN,
flags=flags,
flags_len = self.C.PL.FLAGS_LEN,
proto=proto,
proto_len = self.C.PL.PROTO_LEN,
ports=ports,
ports_len = self.C.PL.PORTS_LEN,
vr=vr,
vr_len = self.C.PL.VR_LEN,
sep=sep)
self.show(line.rstrip() + '\n', force=force)
def show(self, display_string, force=False):
# send single line to display. return exception if user enters 'Q'
print display_string,
'''
if exoslib.show(display_string, 0 if force is False else 1) == -1:
if force is False:
raise KeyboardInterrupt
'''
c = ShowVid()
c()