blob: 82073630a6c48d79a6f81fb13eef7ec477bb4dcd [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 McIntyre53d47192015-07-07 16:24:04 +0100249 self._cli("vlan tagging 1-1023")
250 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)
530 self.connection.expect("Would you like to save them now")
531 self._cli("n")
532
533 def _configure(self):
534 self._cli("configure")
535
536 def _end_configure(self):
537 self._cli("exit")
538
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100539 def _end_interface(self):
540 self._end_configure()
541
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100542 def _read_long_output(self, text):
543 longbuf = []
544 prompt = self._prompt_name + '\s*#'
545 while True:
546 try:
547 index = self.connection.expect(['--More--', prompt])
548 if index == 0: # "--More-- or (q)uit"
549 for line in self.connection.before.split('\r\n'):
550 line1 = re.sub('(\x08|\x0D)*', '', line.strip())
551 longbuf.append(line1)
552 self._cli(' ', False)
553 elif index == 1: # Back to a prompt, says output is finished
554 break
555 except (pexpect.EOF, pexpect.TIMEOUT):
556 # Something went wrong; logout, log in and try again!
557 logging.error("PEXPECT FAILURE, RECONNECT")
558 self.errors.log_error_in(text)
559 raise PExpectError("_read_long_output failed")
560 except:
561 logging.error("prompt is \"%s\"", prompt)
562 raise
563
564 for line in self.connection.before.split('\r\n'):
565 longbuf.append(line.strip())
566 return longbuf
567
568 def _get_port_names(self):
569 logging.debug("Grabbing list of ports")
570 interfaces = []
571
572 # Use "Up" or "Down" to only identify lines in the output that
573 # match interfaces that exist
574 regex = re.compile('^(1\S+)')
575
576 try:
577 self._cli("show port all")
578 for line in self._read_long_output("show port all"):
579 match = regex.match(line)
580 if match:
581 interface = match.group(1)
582 interfaces.append(interface)
583 return interfaces
584
585 except PExpectError:
586 # recurse on error
587 self._switch_connect()
588 return self._get_port_names()
589
590 def _show_config(self):
591 logging.debug("Grabbing config")
592 try:
593 self._cli("show running-config")
594 return self._read_long_output("show running-config")
595 except PExpectError:
596 # recurse on error
597 self._switch_connect()
598 return self._show_config()
599
600 def _show_clock(self):
601 logging.debug("Grabbing time")
602 try:
603 self._cli("show clock")
604 return self._read_long_output("show clock")
605 except PExpectError:
606 # recurse on error
607 self._switch_connect()
608 return self._show_clock()
609
610 def _get_systemdata(self):
611 logging.debug("Grabbing system sw and hw versions")
612
613 try:
614 self._systemdata = []
615 self._cli("show version")
616 for line in self._read_long_output("show version"):
617 self._systemdata.append(line)
618
619 except PExpectError:
620 # recurse on error
621 self._switch_connect()
622 return self._get_systemdata()
623
624 def _parse_vlan_list(self, inputdata):
625 vlans = []
626
627 if inputdata == "ALL":
628 return ["ALL"]
629 elif inputdata == "NONE":
630 return []
631 else:
632 # Parse the complex list
633 groups = inputdata.split(',')
634 for group in groups:
635 subgroups = group.split('-')
636 if len(subgroups) == 1:
637 vlans.append(int(subgroups[0]))
638 elif len(subgroups) == 2:
639 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
640 vlans.append(i)
641 else:
642 logging.debug("Can't parse group \"" + group + "\"")
643
644 return vlans
645
Steve McIntyreef73fca2015-07-07 14:57:59 +0100646 def _get_port_vlans(self, port):
647 vlan_text = None
648
Steve McIntyrec08cca22015-07-07 18:00:33 +0100649 vlan_part_re = re.compile('vlan participation include (.*)')
Steve McIntyreef73fca2015-07-07 14:57:59 +0100650
651 try:
652 self._cli("show running-config interface %s" % port)
653 for line in self._read_long_output("show running-config interface"):
654 match = vlan_part_re.match(line)
655 if match:
656 if vlan_text != None:
657 vlan_text += ","
658 vlan_text += (match.group(1))
659 else:
660 vlan_text = match.group(1)
Steve McIntyre8009ffe2015-07-07 18:02:21 +0100661
662 if vlan_text is None:
663 return [1]
664 else:
665 vlans = self._parse_vlan_list(vlan_text)
666 return vlans
Steve McIntyreef73fca2015-07-07 14:57:59 +0100667
668 except PExpectError:
669 # recurse on error
670 self._switch_connect()
671 return self._get_port_vlans(port)
672
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100673 # Wrapper around connection.send - by default, expect() the same
674 # text we've sent, to remove it from the output from the
675 # switch. For the few cases where we don't need that, override
676 # this using echo=False.
677 # Horrible, but seems to work.
678 def _cli(self, text, echo=True):
679 self.connection.send(text + '\r')
680 if echo:
681 try:
682 self.connection.expect(text)
683 except (pexpect.EOF, pexpect.TIMEOUT):
684 # Something went wrong; logout, log in and try again!
685 logging.error("PEXPECT FAILURE, RECONNECT")
686 self.errors.log_error_out(text)
687 raise PExpectError("_cli failed on %s" % text)
688 except:
689 logging.error("Unexpected error: %s", sys.exc_info()[0])
690 raise
691
692if __name__ == "__main__":
693
694 import optparse
695
696 switch = 'vlandswitch05'
697 parser = optparse.OptionParser()
698 parser.add_option("--switch",
699 dest = "switch",
700 action = "store",
701 nargs = 1,
702 type = "string",
703 help = "specify switch to connect to for testing",
704 metavar = "<switch>")
705 (opts, args) = parser.parse_args()
706 if opts.switch:
707 switch = opts.switch
708
Steve McIntyre144d51c2015-07-07 17:56:30 +0100709 logging.basicConfig(level = logging.DEBUG,
710 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100711 p = NetgearXSM(switch, 23, debug=True)
712 p.switch_connect('admin', '', None)
713
714 print "VLANs are:"
715 buf = p.vlan_get_list()
716 p.dump_list(buf)
717
718 buf = p.vlan_get_name(2)
719 print "VLAN 2 is named \"%s\"" % buf
720
721 print "Create VLAN 3"
722 p.vlan_create(3)
723
724 buf = p.vlan_get_name(3)
725 print "VLAN 3 is named \"%s\"" % buf
726
727 print "Set name of VLAN 3 to test333"
728 p.vlan_set_name(3, "test333")
729
730 buf = p.vlan_get_name(3)
731 print "VLAN 3 is named \"%s\"" % buf
732
733 print "VLANs are:"
734 buf = p.vlan_get_list()
735 p.dump_list(buf)
736
737 print "Destroy VLAN 3"
738 p.vlan_destroy(3)
739
740 print "VLANs are:"
741 buf = p.vlan_get_list()
742 p.dump_list(buf)
743
744 buf = p.port_get_mode("1/0/10")
745 print "Port 1/0/10 is in %s mode" % buf
746
747 buf = p.port_get_mode("1/0/11")
748 print "Port 1/0/11 is in %s mode" % buf
749
750 # Test access stuff
751 print "Set 1/0/9 to access mode"
752 p.port_set_mode("1/0/9", "access")
753
754 print "Move 1/0/9 to VLAN 4"
755 p.port_set_access_vlan("1/0/9", 4)
756
757 buf = p.port_get_access_vlan("1/0/9")
758 print "Read from switch: 1/0/9 is on VLAN %s" % buf
759
760 print "Move 1/0/9 back to VLAN 1"
761 p.port_set_access_vlan("1/0/9", 1)
762
Steve McIntyre40484f12015-07-07 18:02:50 +0100763 print "Create VLAN 2"
764 p.vlan_create(3)
765
766 print "Create VLAN 3"
767 p.vlan_create(3)
768
769 print "Create VLAN 4"
770 p.vlan_create(4)
771
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100772 # Test access stuff
773 print "Set 1/0/9 to trunk mode"
774 p.port_set_mode("1/0/9", "trunk")
775 print "Read from switch: which VLANs is 1/0/9 on?"
776 buf = p.port_get_trunk_vlan_list("1/0/9")
777 p.dump_list(buf)
778 print "Add 1/0/9 to VLAN 2"
779 p.port_add_trunk_to_vlan("1/0/9", 2)
780 print "Add 1/0/9 to VLAN 3"
781 p.port_add_trunk_to_vlan("1/0/9", 3)
782 print "Add 1/0/9 to VLAN 4"
783 p.port_add_trunk_to_vlan("1/0/9", 4)
784 print "Read from switch: which VLANs is 1/0/9 on?"
785 buf = p.port_get_trunk_vlan_list("1/0/9")
786 p.dump_list(buf)
787
788 p.port_remove_trunk_from_vlan("1/0/9", 3)
789 p.port_remove_trunk_from_vlan("1/0/9", 3)
790 p.port_remove_trunk_from_vlan("1/0/9", 4)
791 print "Read from switch: which VLANs is 1/0/9 on?"
792 buf = p.port_get_trunk_vlan_list("1/0/9")
793 p.dump_list(buf)
794
795# print 'Restarting switch, to explicitly reset config'
796# p.switch_restart()
797
798# p.switch_save_running_config()
799
800# p.switch_disconnect()
801# p._show_config()