blob: 1281cbc39d383ce5e3208c52579793439e2031b0 [file] [log] [blame]
Steve McIntyrefdcb3782015-07-06 16:34:56 +01001#! /usr/bin/python
2
3# Copyright 2015 Linaro Limited
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18# MA 02110-1301, USA.
19
20import logging
21import pexpect
22import sys
23import re
24
25# Netgear XSM family driver
26# Developed and tested against the XSM7224S in the Linaro LAVA lab
27
28if __name__ == '__main__':
29 import os
30 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
31 sys.path.insert(0, vlandpath)
32 sys.path.insert(0, "%s/.." % vlandpath)
33
34from errors import InputError, PExpectError
35from drivers.common import SwitchDriver, SwitchErrors
36
37class NetgearXSM(SwitchDriver):
38
39 connection = None
40 _username = None
41 _password = None
42 _enable_password = None
43
44 _capabilities = [
45 'TrunkWildCardVlans' # Trunk ports are on all VLANs by
46 # default, so we shouldn't need to
47 # bugger with them
48 ]
49
50 # Regexps of expected hardware information - fail if we don't see
51 # this
52 _expected_manuf = re.compile('^Netgear')
53 _expected_model = re.compile('^XSM')
54
55 logfile = None
56
57 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
58 SwitchDriver.__init__(self)
59 self._systemdata = []
60 if debug:
61 self.logfile = sys.stderr
62 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
63 self.errors = SwitchErrors()
64
65 ################################
66 ### Switch-level API functions
67 ################################
68
69 # Connect to the switch and log in
70 def switch_connect(self, username, password, enablepassword):
71 self._username = username
72 self._password = password
73 self._enable_password = enablepassword
74 self._switch_connect()
75
76 # Log out of the switch and drop the connection and all state
77 def switch_disconnect(self):
78 self._logout()
79 logging.debug("Closing connection: %s", self.connection)
80 self.connection.close(True)
81 self._ports = []
82 self._prompt_name = ''
83 self._systemdata = []
84 del(self)
85
86 # Save the current running config into flash - we want config to
87 # remain across reboots
88 def switch_save_running_config(self):
89 try:
90 self._cli("save")
91 self.connection.expect("Are you sure")
92 self._cli("y")
93 self.connection.expect("Configuration Saved!")
94 except (PExpectError, pexpect.EOF):
95 # recurse on error
96 self._switch_connect()
97 self.switch_save_running_config()
98
99 # Restart the switch - we need to reload config to do a
100 # roll-back. Do NOT save running-config first if the switch asks -
101 # we're trying to dump recent changes, not save them.
102 #
103 # This will also implicitly cause a connection to be closed
104 def switch_restart(self):
105 self._cli("reload")
106 index = self.connection.expect('Are you sure')
107 self._cli("y") # Yes, continue to reset
108 self.connection.close(True)
109
110 # List the capabilities of the switch (and driver) - some things
111 # make no sense to abstract. Returns a dict of strings, each one
112 # describing an extra feature that that higher levels may care
113 # about
114 def switch_get_capabilities(self):
115 return self._capabilities
116
117 ################################
118 ### VLAN API functions
119 ################################
120
121 # Create a VLAN with the specified tag
122 def vlan_create(self, tag):
123 logging.debug("Creating VLAN %d", tag)
124 try:
125 self._cli("vlan database")
126 self._cli("vlan %d" % tag)
127 self._end_configure()
128
129 # Validate it happened
130 vlans = self.vlan_get_list()
131 for vlan in vlans:
132 if vlan == tag:
133 return
134 raise IOError("Failed to create VLAN %d" % tag)
135
136 except PExpectError:
137 # recurse on error
138 self._switch_connect()
139 self.vlan_create(tag)
140
141 # Destroy a VLAN with the specified tag
142 def vlan_destroy(self, tag):
143 logging.debug("Destroying VLAN %d", tag)
144
145 try:
146 self._cli("vlan database")
147 self._cli("no vlan %d" % tag)
148 self._end_configure()
149
150 # Validate it happened
151 vlans = self.vlan_get_list()
152 for vlan in vlans:
153 if vlan == tag:
154 raise IOError("Failed to destroy VLAN %d" % tag)
155
156 except PExpectError:
157 # recurse on error
158 self._switch_connect()
159 self.vlan_destroy(tag)
160
161 # Set the name of a VLAN
162 def vlan_set_name(self, tag, name):
163 logging.debug("Setting name of VLAN %d to %s", tag, name)
164
165 try:
166 self._cli("vlan database")
167 self._cli("vlan name %d %s" % (tag, name))
168 self._end_configure()
169
170 # Validate it happened
171 read_name = self.vlan_get_name(tag)
172 if read_name != name:
173 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
174 % (tag, read_name, name))
175 except PExpectError:
176 # recurse on error
177 self._switch_connect()
178 self.vlan_set_name(tag, name)
179
180 # Get a list of the VLAN tags currently registered on the switch
181 def vlan_get_list(self):
182 logging.debug("Grabbing list of VLANs")
183
184 try:
185 vlans = []
186
187 regex = re.compile('^ *(\d+).*(Static)')
188
189 self._cli("show vlan brief")
190 for line in self._read_long_output("show vlan brief"):
191 match = regex.match(line)
192 if match:
193 vlans.append(int(match.group(1)))
194 return vlans
195
196 except PExpectError:
197 # recurse on error
198 self._switch_connect()
199 return self.vlan_get_list()
200
201 # For a given VLAN tag, ask the switch what the associated name is
202 def vlan_get_name(self, tag):
203
204 try:
205 logging.debug("Grabbing the name of VLAN %d", tag)
206 name = None
207 regex = re.compile('VLAN Name: (.*)')
208 self._cli("show vlan %d" % tag)
209 for line in self._read_long_output("show vlan"):
210 match = regex.match(line)
211 if match:
212 name = match.group(1)
213 name.strip()
214 return name
215
216 except PExpectError:
217 # recurse on error
218 self._switch_connect()
219 return self.vlan_get_name(tag)
220
221 ################################
222 ### Port API functions
223 ################################
224
225 # Set the mode of a port: access or trunk
226 def port_set_mode(self, port, mode):
227 logging.debug("Setting port %s to %s", port, mode)
228 if not self._is_port_mode_valid(mode):
229 raise InputError("Port mode %s is not allowed" % mode)
230 if not self._is_port_name_valid(port):
231 raise InputError("Port name %s not recognised" % port)
232
233 # This switch does not support specific modes, so we can't
234 # actually change the mode directly. However, we can and
235 # should deal with the PVID and memberships of existing VLANs
236 # etc.
237
238 try:
239 if mode == "trunk":
240 # We define a trunk port thus:
Steve McIntyred97ab292015-07-07 14:23:32 +0100241 # accept all frames on ingress
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100242 # exist on all current VLANs
243 # tag outgoing frames
244 # PVID should match the default VLAN (1).
245 self._configure()
246 self._cli("interface %s" % port)
Steve McIntyred97ab292015-07-07 14:23:32 +0100247 self._cli("vlan acceptframe all")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100248 self._cli("no vlan ingressfilter")
Steve McIntyre86cc7cc2015-07-08 15:43:48 +0100249 self._cli("vlan tagging 2-1023")
Steve McIntyre53d47192015-07-07 16:24:04 +0100250 self._cli("vlan tagging 1024-2047")
Steve McIntyre533519d2015-07-07 16:17:34 +0100251 self._cli("vlan tagging 2048-3071")
252 self._cli("vlan tagging 3072-4093")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100253 self._cli("vlan pvid 1")
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100254 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100255 self._end_configure()
256
257 # We define an access port thus:
258 # accept only untagged frames on ingress
259 # exists on one VLAN only (1 by default)
260 # do not tag outgoing frames
261 # PVID should match the VLAN it's on (1 by default)
262 if mode == "access":
263 self._configure()
264 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100265 self._cli("vlan acceptframe admituntaggedonly")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100266 self._cli("vlan ingressfilter")
Steve McIntyre53d47192015-07-07 16:24:04 +0100267 self._cli("no vlan tagging 1-1023")
268 self._cli("no vlan tagging 1024-2047")
Steve McIntyre533519d2015-07-07 16:17:34 +0100269 self._cli("no vlan tagging 2048-3071")
270 self._cli("no vlan tagging 3072-4093")
Steve McIntyreef73fca2015-07-07 14:57:59 +0100271 self._cli("vlan pvid 1") # Need to find the right VLAN first?
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100272 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100273 self._end_configure()
274
275 # Validate it happened
276 read_mode = self.port_get_mode(port)
277
278 if read_mode != mode:
279 raise IOError("Failed to set mode for port %s" % port)
280
281 except PExpectError:
282 # recurse on error
283 self._switch_connect()
284 self.port_set_mode(port, mode)
285
286 # Get the mode of a port: access or trunk
287 def port_get_mode(self, port):
288 logging.debug("Getting mode of port %s", port)
289 mode = ''
290 if not self._is_port_name_valid(port):
291 raise InputError("Port name %s not recognised" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100292 acceptframe_re = re.compile('vlan acceptframe (.*)')
293 ingress_re = re.compile('vlan ingressfilter')
294 tagging_re = re.compile('vlan tagging (.*)')
295
296 acceptframe = None
297 ingressfilter = True
298 tagging = None
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100299
300 try:
Steve McIntyreef73fca2015-07-07 14:57:59 +0100301 self._cli("show running-config interface %s" % port)
302 for line in self._read_long_output("show running-config interface"):
303
304 match = acceptframe_re.match(line)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100305 if match:
Steve McIntyreef73fca2015-07-07 14:57:59 +0100306 acceptframe = match.group(1)
Steve McIntyre00dd3d22015-07-07 16:45:11 +0100307 logging.debug("acceptframe is %s", acceptframe)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100308
309 match = ingress_re.match(line)
310 if match:
311 ingressfilter = True
Steve McIntyre00dd3d22015-07-07 16:45:11 +0100312 logging.debug("ingressfilter is %s", ingressfilter)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100313
314 match = tagging_re.match(line)
315 if match:
316 tagging = match.group(1)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100317
318 except PExpectError:
319 # recurse on error
320 self._switch_connect()
321 return self.port_get_mode(port)
322
Steve McIntyreef73fca2015-07-07 14:57:59 +0100323 # Simple classifier for now; may need to revisit later...
Steve McIntyre82154452015-07-07 17:59:02 +0100324 if (acceptframe == "admituntaggedonly" and ingressfilter == True):
Steve McIntyreef73fca2015-07-07 14:57:59 +0100325 return "access"
326 else:
327 return "trunk"
328
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100329 # Set an access port to be in a specified VLAN (tag)
330 def port_set_access_vlan(self, port, tag):
331 logging.debug("Setting access port %s to VLAN %d", port, tag)
332 if not self._is_port_name_valid(port):
333 raise InputError("Port name %s not recognised" % port)
334 if not (self.port_get_mode(port) == "access"):
335 raise InputError("Port %s not in access mode" % port)
336
337 try:
Steve McIntyree6172b02015-07-07 17:59:46 +0100338 current_vlans = self._get_port_vlans(port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100339 self._configure()
340 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100341 self._cli("vlan pvid %s" % tag)
342 # Find the list of VLANs we're currently on, and drop them
343 # all. "auto" mode is fine here, we won't be included
344 # unless we have GVRP configured, and we don't do
345 # that.
Steve McIntyree6172b02015-07-07 17:59:46 +0100346 for current_vlan in current_vlans:
Steve McIntyreef73fca2015-07-07 14:57:59 +0100347 self._cli("vlan participation auto %s" % current_vlan)
348 # Now specifically include the VLAN we want
349 self._cli("vlan participation include %s" % tag)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100350 self._cli("no shutdown")
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100351 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100352 self._end_configure()
353
354 # Finally, validate things worked
355 read_vlan = int(self.port_get_access_vlan(port))
356 if read_vlan != tag:
357 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
358 % (port, tag, read_vlan))
359
360 except PExpectError:
361 # recurse on error
362 self._switch_connect()
363 self.port_set_access_vlan(port, tag)
364
Steve McIntyreef73fca2015-07-07 14:57:59 +0100365 # Add a trunk port to a specified VLAN (tag). Shouldn't be needed
366 # on this switch as we don't do VLAN ingress filtering on "trunk"
367 # ports, but...
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100368 def port_add_trunk_to_vlan(self, port, tag):
369 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
370 if not self._is_port_name_valid(port):
371 raise InputError("Port name %s not recognised" % port)
372 if not (self.port_get_mode(port) == "trunk"):
373 raise InputError("Port %s not in trunk mode" % port)
374
375 try:
376 self._configure()
377 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100378 self._cli("vlan participation include %d" % tag)
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100379 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100380 self._end_configure()
381
382 # Validate it happened
383 read_vlans = self.port_get_trunk_vlan_list(port)
384 for vlan in read_vlans:
385 if vlan == tag or vlan == "ALL":
386 return
387 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
388
389 except PExpectError:
390 # recurse on error
391 self._switch_connect()
392 self.port_add_trunk_to_vlan(port, tag)
393
Steve McIntyreef73fca2015-07-07 14:57:59 +0100394 # Remove a trunk port from a specified VLAN (tag). Shouldn't be
395 # needed on this switch as we don't do VLAN ingress filtering on
396 # "trunk" ports, but...
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100397 def port_remove_trunk_from_vlan(self, port, tag):
398 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
399 if not self._is_port_name_valid(port):
400 raise InputError("Port name %s not recognised" % port)
401 if not (self.port_get_mode(port) == "trunk"):
402 raise InputError("Port %s not in trunk mode" % port)
403
404 try:
405 self._configure()
406 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100407 self._cli("vlan participation auto %d" % tag)
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100408 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100409 self._end_configure()
410
411 # Validate it happened
412 read_vlans = self.port_get_trunk_vlan_list(port)
413 for vlan in read_vlans:
414 if vlan == tag:
415 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
416
417 except PExpectError:
418 # recurse on error
419 self._switch_connect()
420 self.port_remove_trunk_from_vlan(port, tag)
421
422 # Get the configured VLAN tag for an access port (tag)
423 def port_get_access_vlan(self, port):
424 logging.debug("Getting VLAN for access port %s", port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100425 if not self._is_port_name_valid(port):
426 raise InputError("Port name %s not recognised" % port)
427 if not (self.port_get_mode(port) == "access"):
428 raise InputError("Port %s not in access mode" % port)
Steve McIntyrec4834c72015-07-07 18:00:17 +0100429 vlans = self._get_port_vlans(port)
430 if (len(vlans) > 1):
431 raise IOError("More than one VLAN on access port %s" % port)
432 return vlans[0]
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100433
434 # Get the list of configured VLAN tags for a trunk port
435 def port_get_trunk_vlan_list(self, port):
Steve McIntyreef73fca2015-07-07 14:57:59 +0100436 logging.debug("Getting VLAN(s) for trunk port %s", port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100437 if not self._is_port_name_valid(port):
438 raise InputError("Port name %s not recognised" % port)
439 if not (self.port_get_mode(port) == "trunk"):
440 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100441 return self._get_port_vlans(port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100442
443 ################################
444 ### Internal functions
445 ################################
446
447 # Connect to the switch and log in
448 def _switch_connect(self):
449
450 if not self.connection is None:
451 self.connection.close(True)
452 self.connection = None
453
454 logging.debug("Connecting to Switch with: %s", self.exec_string)
455 self.connection = pexpect.spawn(self.exec_string, logfile = self.logfile)
456
457 self._login()
458
459 # Avoid paged output
460 self._cli("terminal length 0")
461
462 # And grab details about the switch. in case we need it
463 self._get_systemdata()
464
465 # And also validate them - make sure we're driving a switch of
466 # the correct model! Also store the serial number
467 manuf_regex = re.compile('^Manufacturer([\.\s])+(\S+)')
468 model_regex = re.compile('^Machine Model([\.\s])+(\S+)')
469 sn_regex = re.compile('^Serial Number([\.\s])+(\S+)')
470 descr = ""
471
472 for line in self._systemdata:
473 match1 = manuf_regex.match(line)
474 if match1:
475 manuf = match1.group(2)
476
477 match2 = model_regex.match(line)
478 if match2:
479 model = match2.group(2)
480
481 match3 = sn_regex.match(line)
482 if match3:
483 self.serial_number = match3.group(2)
484
485 logging.debug("manufacturer is %s", manuf)
486 logging.debug("model is %s", model)
487 logging.debug("serial number is %s", self.serial_number)
488
489 if not (self._expected_manuf.match(manuf) and self._expected_model.match(model)):
490 raise IOError("Switch %s %s not recognised by this driver: abort" % (manuf, model))
491
492 # Now build a list of our ports, for later sanity checking
493 self._ports = self._get_port_names()
494 if len(self._ports) < 4:
495 raise IOError("Not enough ports detected - problem!")
496
497 def _login(self):
498 logging.debug("attempting login with username %s, password %s", self._username, self._password)
499 if self._username is not None:
500 self.connection.expect("User:")
501 self._cli("%s" % self._username)
502 if self._password is not None:
503 self.connection.expect("Password:")
504 self._cli("%s" % self._password, False)
505 while True:
506 index = self.connection.expect(['User:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
507 if index != 4: # Any other means: failed to log in!
508 logging.error("Login failure: index %d\n", index)
509 logging.error("Login failure: %s\n", self.connection.match.before)
510 raise IOError
511
512 # else
513 self._prompt_name = re.escape(self.connection.match.group(1).strip())
514 logging.error('\n\nSet _prompt_name to "%s"\n\n\n' % self._prompt_name)
515 if self.connection.match.group(2) == ">":
516 # Need to enter "enable" mode too
517 self._cli("enable")
518 if self._enable_password is not None:
519 self.connection.expect("Password:")
520 self._cli("%s" % self._enable_password, False)
521 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*) *(#|>)'])
522 if index != 3: # Any other means: failed to log in!
523 logging.error("Enable password failure: %s\n", self.connection.match)
524 raise IOError
525 return 0
526
527 def _logout(self):
528 logging.debug("Logging out")
529 self._cli("quit", False)
Steve McIntyredfce73f2015-07-09 17:41:55 +0100530 try:
531 self.connection.expect("Would you like to save them now")
532 self._cli("n")
533 except (pexpect.EOF):
534 pass
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100535
536 def _configure(self):
537 self._cli("configure")
538
539 def _end_configure(self):
540 self._cli("exit")
541
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100542 def _end_interface(self):
543 self._end_configure()
544
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100545 def _read_long_output(self, text):
546 longbuf = []
547 prompt = self._prompt_name + '\s*#'
548 while True:
549 try:
550 index = self.connection.expect(['--More--', prompt])
551 if index == 0: # "--More-- or (q)uit"
552 for line in self.connection.before.split('\r\n'):
553 line1 = re.sub('(\x08|\x0D)*', '', line.strip())
554 longbuf.append(line1)
555 self._cli(' ', False)
556 elif index == 1: # Back to a prompt, says output is finished
557 break
558 except (pexpect.EOF, pexpect.TIMEOUT):
559 # Something went wrong; logout, log in and try again!
560 logging.error("PEXPECT FAILURE, RECONNECT")
561 self.errors.log_error_in(text)
562 raise PExpectError("_read_long_output failed")
563 except:
564 logging.error("prompt is \"%s\"", prompt)
565 raise
566
567 for line in self.connection.before.split('\r\n'):
568 longbuf.append(line.strip())
569 return longbuf
570
571 def _get_port_names(self):
572 logging.debug("Grabbing list of ports")
573 interfaces = []
574
575 # Use "Up" or "Down" to only identify lines in the output that
576 # match interfaces that exist
577 regex = re.compile('^(1\S+)')
578
579 try:
580 self._cli("show port all")
581 for line in self._read_long_output("show port all"):
582 match = regex.match(line)
583 if match:
584 interface = match.group(1)
585 interfaces.append(interface)
586 return interfaces
587
588 except PExpectError:
589 # recurse on error
590 self._switch_connect()
591 return self._get_port_names()
592
593 def _show_config(self):
594 logging.debug("Grabbing config")
595 try:
596 self._cli("show running-config")
597 return self._read_long_output("show running-config")
598 except PExpectError:
599 # recurse on error
600 self._switch_connect()
601 return self._show_config()
602
603 def _show_clock(self):
604 logging.debug("Grabbing time")
605 try:
606 self._cli("show clock")
607 return self._read_long_output("show clock")
608 except PExpectError:
609 # recurse on error
610 self._switch_connect()
611 return self._show_clock()
612
613 def _get_systemdata(self):
614 logging.debug("Grabbing system sw and hw versions")
615
616 try:
617 self._systemdata = []
618 self._cli("show version")
619 for line in self._read_long_output("show version"):
620 self._systemdata.append(line)
621
622 except PExpectError:
623 # recurse on error
624 self._switch_connect()
625 return self._get_systemdata()
626
627 def _parse_vlan_list(self, inputdata):
628 vlans = []
629
630 if inputdata == "ALL":
631 return ["ALL"]
632 elif inputdata == "NONE":
633 return []
634 else:
635 # Parse the complex list
636 groups = inputdata.split(',')
637 for group in groups:
638 subgroups = group.split('-')
639 if len(subgroups) == 1:
640 vlans.append(int(subgroups[0]))
641 elif len(subgroups) == 2:
642 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
643 vlans.append(i)
644 else:
645 logging.debug("Can't parse group \"" + group + "\"")
646
647 return vlans
648
Steve McIntyreef73fca2015-07-07 14:57:59 +0100649 def _get_port_vlans(self, port):
650 vlan_text = None
651
Steve McIntyrec08cca22015-07-07 18:00:33 +0100652 vlan_part_re = re.compile('vlan participation include (.*)')
Steve McIntyreef73fca2015-07-07 14:57:59 +0100653
654 try:
655 self._cli("show running-config interface %s" % port)
656 for line in self._read_long_output("show running-config interface"):
657 match = vlan_part_re.match(line)
658 if match:
659 if vlan_text != None:
660 vlan_text += ","
661 vlan_text += (match.group(1))
662 else:
663 vlan_text = match.group(1)
Steve McIntyre8009ffe2015-07-07 18:02:21 +0100664
665 if vlan_text is None:
666 return [1]
667 else:
668 vlans = self._parse_vlan_list(vlan_text)
669 return vlans
Steve McIntyreef73fca2015-07-07 14:57:59 +0100670
671 except PExpectError:
672 # recurse on error
673 self._switch_connect()
674 return self._get_port_vlans(port)
675
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100676 # Wrapper around connection.send - by default, expect() the same
677 # text we've sent, to remove it from the output from the
678 # switch. For the few cases where we don't need that, override
679 # this using echo=False.
680 # Horrible, but seems to work.
681 def _cli(self, text, echo=True):
682 self.connection.send(text + '\r')
683 if echo:
684 try:
685 self.connection.expect(text)
686 except (pexpect.EOF, pexpect.TIMEOUT):
687 # Something went wrong; logout, log in and try again!
688 logging.error("PEXPECT FAILURE, RECONNECT")
689 self.errors.log_error_out(text)
690 raise PExpectError("_cli failed on %s" % text)
691 except:
692 logging.error("Unexpected error: %s", sys.exc_info()[0])
693 raise
694
695if __name__ == "__main__":
696
697 import optparse
698
699 switch = 'vlandswitch05'
700 parser = optparse.OptionParser()
701 parser.add_option("--switch",
702 dest = "switch",
703 action = "store",
704 nargs = 1,
705 type = "string",
706 help = "specify switch to connect to for testing",
707 metavar = "<switch>")
708 (opts, args) = parser.parse_args()
709 if opts.switch:
710 switch = opts.switch
711
Steve McIntyre144d51c2015-07-07 17:56:30 +0100712 logging.basicConfig(level = logging.DEBUG,
713 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100714 p = NetgearXSM(switch, 23, debug=True)
715 p.switch_connect('admin', '', None)
716
717 print "VLANs are:"
718 buf = p.vlan_get_list()
719 p.dump_list(buf)
720
721 buf = p.vlan_get_name(2)
722 print "VLAN 2 is named \"%s\"" % buf
723
724 print "Create VLAN 3"
725 p.vlan_create(3)
726
727 buf = p.vlan_get_name(3)
728 print "VLAN 3 is named \"%s\"" % buf
729
730 print "Set name of VLAN 3 to test333"
731 p.vlan_set_name(3, "test333")
732
733 buf = p.vlan_get_name(3)
734 print "VLAN 3 is named \"%s\"" % buf
735
736 print "VLANs are:"
737 buf = p.vlan_get_list()
738 p.dump_list(buf)
739
740 print "Destroy VLAN 3"
741 p.vlan_destroy(3)
742
743 print "VLANs are:"
744 buf = p.vlan_get_list()
745 p.dump_list(buf)
746
747 buf = p.port_get_mode("1/0/10")
748 print "Port 1/0/10 is in %s mode" % buf
749
750 buf = p.port_get_mode("1/0/11")
751 print "Port 1/0/11 is in %s mode" % buf
752
753 # Test access stuff
754 print "Set 1/0/9 to access mode"
755 p.port_set_mode("1/0/9", "access")
756
757 print "Move 1/0/9 to VLAN 4"
758 p.port_set_access_vlan("1/0/9", 4)
759
760 buf = p.port_get_access_vlan("1/0/9")
761 print "Read from switch: 1/0/9 is on VLAN %s" % buf
762
763 print "Move 1/0/9 back to VLAN 1"
764 p.port_set_access_vlan("1/0/9", 1)
765
Steve McIntyre40484f12015-07-07 18:02:50 +0100766 print "Create VLAN 2"
Steve McIntyre3a1b4832015-07-08 15:42:09 +0100767 p.vlan_create(2)
Steve McIntyre40484f12015-07-07 18:02:50 +0100768
769 print "Create VLAN 3"
770 p.vlan_create(3)
771
772 print "Create VLAN 4"
773 p.vlan_create(4)
774
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100775 # Test access stuff
776 print "Set 1/0/9 to trunk mode"
777 p.port_set_mode("1/0/9", "trunk")
778 print "Read from switch: which VLANs is 1/0/9 on?"
779 buf = p.port_get_trunk_vlan_list("1/0/9")
780 p.dump_list(buf)
Steve McIntyrea38e81b2015-07-08 15:46:25 +0100781
Steve McIntyre12b9ef92015-07-08 15:48:44 +0100782 # The adds below are NOOPs in effect on this switch - no filtering
783 # for "trunk" ports
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100784 print "Add 1/0/9 to VLAN 2"
785 p.port_add_trunk_to_vlan("1/0/9", 2)
786 print "Add 1/0/9 to VLAN 3"
787 p.port_add_trunk_to_vlan("1/0/9", 3)
788 print "Add 1/0/9 to VLAN 4"
789 p.port_add_trunk_to_vlan("1/0/9", 4)
790 print "Read from switch: which VLANs is 1/0/9 on?"
791 buf = p.port_get_trunk_vlan_list("1/0/9")
792 p.dump_list(buf)
793
Steve McIntyre12b9ef92015-07-08 15:48:44 +0100794 # And the same for removals here
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100795 p.port_remove_trunk_from_vlan("1/0/9", 3)
Steve McIntyre60671382015-07-08 15:49:03 +0100796 p.port_remove_trunk_from_vlan("1/0/9", 2)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100797 p.port_remove_trunk_from_vlan("1/0/9", 4)
798 print "Read from switch: which VLANs is 1/0/9 on?"
799 buf = p.port_get_trunk_vlan_list("1/0/9")
800 p.dump_list(buf)
801
802# print 'Restarting switch, to explicitly reset config'
803# p.switch_restart()
804
805# p.switch_save_running_config()
806
807# p.switch_disconnect()
808# p._show_config()