blob: 5c1367cee6226983e45caacbeaba59733ef500a7 [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 = [
Steve McIntyrefdcb3782015-07-06 16:34:56 +010045 ]
46
47 # Regexps of expected hardware information - fail if we don't see
48 # this
49 _expected_manuf = re.compile('^Netgear')
50 _expected_model = re.compile('^XSM')
51
Steve McIntyrefdcb3782015-07-06 16:34:56 +010052 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
Steve McIntyrebb58a272015-07-14 15:39:54 +010053 SwitchDriver.__init__(self, switch_hostname, debug)
Steve McIntyrefdcb3782015-07-06 16:34:56 +010054 self._systemdata = []
Steve McIntyrefdcb3782015-07-06 16:34:56 +010055 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
56 self.errors = SwitchErrors()
57
58 ################################
59 ### Switch-level API functions
60 ################################
61
Steve McIntyrefdcb3782015-07-06 16:34:56 +010062 # Save the current running config into flash - we want config to
63 # remain across reboots
64 def switch_save_running_config(self):
65 try:
66 self._cli("save")
67 self.connection.expect("Are you sure")
68 self._cli("y")
69 self.connection.expect("Configuration Saved!")
70 except (PExpectError, pexpect.EOF):
71 # recurse on error
72 self._switch_connect()
73 self.switch_save_running_config()
74
75 # Restart the switch - we need to reload config to do a
76 # roll-back. Do NOT save running-config first if the switch asks -
77 # we're trying to dump recent changes, not save them.
78 #
Steve McIntyre1c8a3212015-07-14 17:07:31 +010079 # This will also implicitly cause a connection to be closed
Steve McIntyrefdcb3782015-07-06 16:34:56 +010080 def switch_restart(self):
81 self._cli("reload")
Steve McIntyre1c8a3212015-07-14 17:07:31 +010082 self.connection.expect('Are you sure')
Steve McIntyrefdcb3782015-07-06 16:34:56 +010083 self._cli("y") # Yes, continue to reset
84 self.connection.close(True)
85
86 # List the capabilities of the switch (and driver) - some things
87 # make no sense to abstract. Returns a dict of strings, each one
88 # describing an extra feature that that higher levels may care
89 # about
90 def switch_get_capabilities(self):
91 return self._capabilities
92
93 ################################
94 ### VLAN API functions
95 ################################
96
97 # Create a VLAN with the specified tag
98 def vlan_create(self, tag):
99 logging.debug("Creating VLAN %d", tag)
100 try:
101 self._cli("vlan database")
102 self._cli("vlan %d" % tag)
103 self._end_configure()
104
105 # Validate it happened
106 vlans = self.vlan_get_list()
107 for vlan in vlans:
108 if vlan == tag:
109 return
110 raise IOError("Failed to create VLAN %d" % tag)
111
112 except PExpectError:
113 # recurse on error
114 self._switch_connect()
115 self.vlan_create(tag)
116
117 # Destroy a VLAN with the specified tag
118 def vlan_destroy(self, tag):
119 logging.debug("Destroying VLAN %d", tag)
120
121 try:
122 self._cli("vlan database")
123 self._cli("no vlan %d" % tag)
124 self._end_configure()
125
126 # Validate it happened
127 vlans = self.vlan_get_list()
128 for vlan in vlans:
129 if vlan == tag:
130 raise IOError("Failed to destroy VLAN %d" % tag)
131
132 except PExpectError:
133 # recurse on error
134 self._switch_connect()
135 self.vlan_destroy(tag)
136
137 # Set the name of a VLAN
138 def vlan_set_name(self, tag, name):
139 logging.debug("Setting name of VLAN %d to %s", tag, name)
140
141 try:
142 self._cli("vlan database")
143 self._cli("vlan name %d %s" % (tag, name))
144 self._end_configure()
145
146 # Validate it happened
147 read_name = self.vlan_get_name(tag)
148 if read_name != name:
149 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
150 % (tag, read_name, name))
151 except PExpectError:
152 # recurse on error
153 self._switch_connect()
154 self.vlan_set_name(tag, name)
155
156 # Get a list of the VLAN tags currently registered on the switch
157 def vlan_get_list(self):
158 logging.debug("Grabbing list of VLANs")
159
160 try:
161 vlans = []
162
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100163 regex = re.compile(r'^ *(\d+).*(Static)')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100164
165 self._cli("show vlan brief")
166 for line in self._read_long_output("show vlan brief"):
167 match = regex.match(line)
168 if match:
169 vlans.append(int(match.group(1)))
170 return vlans
171
172 except PExpectError:
173 # recurse on error
174 self._switch_connect()
175 return self.vlan_get_list()
176
177 # For a given VLAN tag, ask the switch what the associated name is
178 def vlan_get_name(self, tag):
179
180 try:
181 logging.debug("Grabbing the name of VLAN %d", tag)
182 name = None
183 regex = re.compile('VLAN Name: (.*)')
184 self._cli("show vlan %d" % tag)
185 for line in self._read_long_output("show vlan"):
186 match = regex.match(line)
187 if match:
188 name = match.group(1)
189 name.strip()
190 return name
191
192 except PExpectError:
193 # recurse on error
194 self._switch_connect()
195 return self.vlan_get_name(tag)
196
197 ################################
198 ### Port API functions
199 ################################
200
201 # Set the mode of a port: access or trunk
202 def port_set_mode(self, port, mode):
Steve McIntyree4c97ec2015-07-16 18:27:59 +0100203 logging.debug("Setting port %s to %s mode", port, mode)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100204 if not self._is_port_mode_valid(mode):
205 raise InputError("Port mode %s is not allowed" % mode)
206 if not self._is_port_name_valid(port):
207 raise InputError("Port name %s not recognised" % port)
208
209 # This switch does not support specific modes, so we can't
210 # actually change the mode directly. However, we can and
211 # should deal with the PVID and memberships of existing VLANs
212 # etc.
213
214 try:
215 if mode == "trunk":
216 # We define a trunk port thus:
Steve McIntyred97ab292015-07-07 14:23:32 +0100217 # accept all frames on ingress
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100218 # exist on all current VLANs
219 # tag outgoing frames
220 # PVID should match the default VLAN (1).
221 self._configure()
222 self._cli("interface %s" % port)
Steve McIntyred97ab292015-07-07 14:23:32 +0100223 self._cli("vlan acceptframe all")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100224 self._cli("no vlan ingressfilter")
Steve McIntyre86cc7cc2015-07-08 15:43:48 +0100225 self._cli("vlan tagging 2-1023")
Steve McIntyre53d47192015-07-07 16:24:04 +0100226 self._cli("vlan tagging 1024-2047")
Steve McIntyre533519d2015-07-07 16:17:34 +0100227 self._cli("vlan tagging 2048-3071")
228 self._cli("vlan tagging 3072-4093")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100229 self._cli("vlan pvid 1")
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100230 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100231 self._end_configure()
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100232
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100233 # We define an access port thus:
234 # accept only untagged frames on ingress
235 # exists on one VLAN only (1 by default)
236 # do not tag outgoing frames
237 # PVID should match the VLAN it's on (1 by default)
238 if mode == "access":
239 self._configure()
240 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100241 self._cli("vlan acceptframe admituntaggedonly")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100242 self._cli("vlan ingressfilter")
Steve McIntyre53d47192015-07-07 16:24:04 +0100243 self._cli("no vlan tagging 1-1023")
244 self._cli("no vlan tagging 1024-2047")
Steve McIntyre533519d2015-07-07 16:17:34 +0100245 self._cli("no vlan tagging 2048-3071")
246 self._cli("no vlan tagging 3072-4093")
Steve McIntyreef73fca2015-07-07 14:57:59 +0100247 self._cli("vlan pvid 1") # Need to find the right VLAN first?
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100248 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100249 self._end_configure()
250
251 # Validate it happened
252 read_mode = self.port_get_mode(port)
253
254 if read_mode != mode:
255 raise IOError("Failed to set mode for port %s" % port)
256
257 except PExpectError:
258 # recurse on error
259 self._switch_connect()
260 self.port_set_mode(port, mode)
261
262 # Get the mode of a port: access or trunk
263 def port_get_mode(self, port):
264 logging.debug("Getting mode of port %s", port)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100265
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100266 if not self._is_port_name_valid(port):
267 raise InputError("Port name %s not recognised" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100268 acceptframe_re = re.compile('vlan acceptframe (.*)')
269 ingress_re = re.compile('vlan ingressfilter')
Steve McIntyreef73fca2015-07-07 14:57:59 +0100270
271 acceptframe = None
272 ingressfilter = True
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100273
274 try:
Steve McIntyreef73fca2015-07-07 14:57:59 +0100275 self._cli("show running-config interface %s" % port)
276 for line in self._read_long_output("show running-config interface"):
277
278 match = acceptframe_re.match(line)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100279 if match:
Steve McIntyreef73fca2015-07-07 14:57:59 +0100280 acceptframe = match.group(1)
281
282 match = ingress_re.match(line)
283 if match:
284 ingressfilter = True
285
Steve McIntyrebcbb4222015-07-09 18:28:02 +0100286 # Simple classifier for now; may need to revisit later...
287 if (acceptframe == "admituntaggedonly" and ingressfilter):
288 return "access"
289 else:
290 return "trunk"
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100291
292 except PExpectError:
293 # recurse on error
294 self._switch_connect()
295 return self.port_get_mode(port)
296
Steve McIntyreef73fca2015-07-07 14:57:59 +0100297
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100298 # Set an access port to be in a specified VLAN (tag)
299 def port_set_access_vlan(self, port, tag):
300 logging.debug("Setting access port %s to VLAN %d", port, tag)
301 if not self._is_port_name_valid(port):
302 raise InputError("Port name %s not recognised" % port)
303 if not (self.port_get_mode(port) == "access"):
304 raise InputError("Port %s not in access mode" % port)
305
306 try:
Steve McIntyree6172b02015-07-07 17:59:46 +0100307 current_vlans = self._get_port_vlans(port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100308 self._configure()
309 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100310 self._cli("vlan pvid %s" % tag)
311 # Find the list of VLANs we're currently on, and drop them
312 # all. "auto" mode is fine here, we won't be included
313 # unless we have GVRP configured, and we don't do
314 # that.
Steve McIntyree6172b02015-07-07 17:59:46 +0100315 for current_vlan in current_vlans:
Steve McIntyreef73fca2015-07-07 14:57:59 +0100316 self._cli("vlan participation auto %s" % current_vlan)
317 # Now specifically include the VLAN we want
318 self._cli("vlan participation include %s" % tag)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100319 self._cli("no shutdown")
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100320 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100321 self._end_configure()
322
323 # Finally, validate things worked
324 read_vlan = int(self.port_get_access_vlan(port))
325 if read_vlan != tag:
326 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
327 % (port, tag, read_vlan))
328
329 except PExpectError:
330 # recurse on error
331 self._switch_connect()
332 self.port_set_access_vlan(port, tag)
333
Steve McIntyreef73fca2015-07-07 14:57:59 +0100334 # Add a trunk port to a specified VLAN (tag). Shouldn't be needed
335 # on this switch as we don't do VLAN ingress filtering on "trunk"
336 # ports, but...
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100337 def port_add_trunk_to_vlan(self, port, tag):
338 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
339 if not self._is_port_name_valid(port):
340 raise InputError("Port name %s not recognised" % port)
341 if not (self.port_get_mode(port) == "trunk"):
342 raise InputError("Port %s not in trunk mode" % port)
343
344 try:
345 self._configure()
346 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100347 self._cli("vlan participation include %d" % tag)
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100348 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100349 self._end_configure()
350
351 # Validate it happened
352 read_vlans = self.port_get_trunk_vlan_list(port)
353 for vlan in read_vlans:
354 if vlan == tag or vlan == "ALL":
355 return
356 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
357
358 except PExpectError:
359 # recurse on error
360 self._switch_connect()
361 self.port_add_trunk_to_vlan(port, tag)
362
Steve McIntyreef73fca2015-07-07 14:57:59 +0100363 # Remove a trunk port from a specified VLAN (tag). Shouldn't be
364 # needed on this switch as we don't do VLAN ingress filtering on
365 # "trunk" ports, but...
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100366 def port_remove_trunk_from_vlan(self, port, tag):
367 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
368 if not self._is_port_name_valid(port):
369 raise InputError("Port name %s not recognised" % port)
370 if not (self.port_get_mode(port) == "trunk"):
371 raise InputError("Port %s not in trunk mode" % port)
372
373 try:
374 self._configure()
375 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100376 self._cli("vlan participation auto %d" % tag)
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100377 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100378 self._end_configure()
379
380 # Validate it happened
381 read_vlans = self.port_get_trunk_vlan_list(port)
382 for vlan in read_vlans:
383 if vlan == tag:
384 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
385
386 except PExpectError:
387 # recurse on error
388 self._switch_connect()
389 self.port_remove_trunk_from_vlan(port, tag)
390
391 # Get the configured VLAN tag for an access port (tag)
392 def port_get_access_vlan(self, port):
393 logging.debug("Getting VLAN for access port %s", port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100394 if not self._is_port_name_valid(port):
395 raise InputError("Port name %s not recognised" % port)
396 if not (self.port_get_mode(port) == "access"):
397 raise InputError("Port %s not in access mode" % port)
Steve McIntyrec4834c72015-07-07 18:00:17 +0100398 vlans = self._get_port_vlans(port)
399 if (len(vlans) > 1):
400 raise IOError("More than one VLAN on access port %s" % port)
401 return vlans[0]
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100402
403 # Get the list of configured VLAN tags for a trunk port
404 def port_get_trunk_vlan_list(self, port):
Steve McIntyreef73fca2015-07-07 14:57:59 +0100405 logging.debug("Getting VLAN(s) for trunk port %s", port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100406 if not self._is_port_name_valid(port):
407 raise InputError("Port name %s not recognised" % port)
408 if not (self.port_get_mode(port) == "trunk"):
409 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100410 return self._get_port_vlans(port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100411
412 ################################
413 ### Internal functions
414 ################################
415
416 # Connect to the switch and log in
417 def _switch_connect(self):
418
419 if not self.connection is None:
420 self.connection.close(True)
421 self.connection = None
422
423 logging.debug("Connecting to Switch with: %s", self.exec_string)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100424 self.connection = pexpect.spawn(self.exec_string, logfile = self.logger)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100425
426 self._login()
427
428 # Avoid paged output
429 self._cli("terminal length 0")
430
431 # And grab details about the switch. in case we need it
432 self._get_systemdata()
433
434 # And also validate them - make sure we're driving a switch of
435 # the correct model! Also store the serial number
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100436 manuf_regex = re.compile(r'^Manufacturer([\.\s])+(\S+)')
437 model_regex = re.compile(r'^Machine Model([\.\s])+(\S+)')
438 sn_regex = re.compile(r'^Serial Number([\.\s])+(\S+)')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100439
440 for line in self._systemdata:
441 match1 = manuf_regex.match(line)
442 if match1:
443 manuf = match1.group(2)
444
445 match2 = model_regex.match(line)
446 if match2:
447 model = match2.group(2)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100448
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100449 match3 = sn_regex.match(line)
450 if match3:
451 self.serial_number = match3.group(2)
452
453 logging.debug("manufacturer is %s", manuf)
454 logging.debug("model is %s", model)
455 logging.debug("serial number is %s", self.serial_number)
456
457 if not (self._expected_manuf.match(manuf) and self._expected_model.match(model)):
458 raise IOError("Switch %s %s not recognised by this driver: abort" % (manuf, model))
459
460 # Now build a list of our ports, for later sanity checking
461 self._ports = self._get_port_names()
462 if len(self._ports) < 4:
463 raise IOError("Not enough ports detected - problem!")
464
465 def _login(self):
466 logging.debug("attempting login with username %s, password %s", self._username, self._password)
467 if self._username is not None:
468 self.connection.expect("User:")
469 self._cli("%s" % self._username)
470 if self._password is not None:
471 self.connection.expect("Password:")
472 self._cli("%s" % self._password, False)
473 while True:
474 index = self.connection.expect(['User:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
475 if index != 4: # Any other means: failed to log in!
476 logging.error("Login failure: index %d\n", index)
477 logging.error("Login failure: %s\n", self.connection.match.before)
478 raise IOError
479
480 # else
481 self._prompt_name = re.escape(self.connection.match.group(1).strip())
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100482 if self.connection.match.group(2) == ">":
483 # Need to enter "enable" mode too
484 self._cli("enable")
485 if self._enable_password is not None:
486 self.connection.expect("Password:")
487 self._cli("%s" % self._enable_password, False)
488 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*) *(#|>)'])
489 if index != 3: # Any other means: failed to log in!
490 logging.error("Enable password failure: %s\n", self.connection.match)
491 raise IOError
492 return 0
493
494 def _logout(self):
495 logging.debug("Logging out")
496 self._cli("quit", False)
Steve McIntyredfce73f2015-07-09 17:41:55 +0100497 try:
498 self.connection.expect("Would you like to save them now")
499 self._cli("n")
500 except (pexpect.EOF):
501 pass
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100502
503 def _configure(self):
504 self._cli("configure")
505
506 def _end_configure(self):
507 self._cli("exit")
508
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100509 def _end_interface(self):
510 self._end_configure()
511
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100512 def _read_long_output(self, text):
513 longbuf = []
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100514 prompt = self._prompt_name + r'\s*#'
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100515 while True:
516 try:
517 index = self.connection.expect(['--More--', prompt])
518 if index == 0: # "--More-- or (q)uit"
519 for line in self.connection.before.split('\r\n'):
520 line1 = re.sub('(\x08|\x0D)*', '', line.strip())
521 longbuf.append(line1)
522 self._cli(' ', False)
523 elif index == 1: # Back to a prompt, says output is finished
524 break
525 except (pexpect.EOF, pexpect.TIMEOUT):
526 # Something went wrong; logout, log in and try again!
527 logging.error("PEXPECT FAILURE, RECONNECT")
528 self.errors.log_error_in(text)
529 raise PExpectError("_read_long_output failed")
530 except:
531 logging.error("prompt is \"%s\"", prompt)
532 raise
533
534 for line in self.connection.before.split('\r\n'):
535 longbuf.append(line.strip())
536 return longbuf
537
538 def _get_port_names(self):
539 logging.debug("Grabbing list of ports")
540 interfaces = []
541
542 # Use "Up" or "Down" to only identify lines in the output that
543 # match interfaces that exist
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100544 regex = re.compile(r'^(1\S+)')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100545
546 try:
547 self._cli("show port all")
548 for line in self._read_long_output("show port all"):
549 match = regex.match(line)
550 if match:
551 interface = match.group(1)
552 interfaces.append(interface)
Steve McIntyred601ab82015-07-09 17:42:36 +0100553 logging.debug(" found %d ports on the switch", len(interfaces))
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100554 return interfaces
555
556 except PExpectError:
557 # recurse on error
558 self._switch_connect()
559 return self._get_port_names()
560
561 def _show_config(self):
562 logging.debug("Grabbing config")
563 try:
564 self._cli("show running-config")
565 return self._read_long_output("show running-config")
566 except PExpectError:
567 # recurse on error
568 self._switch_connect()
569 return self._show_config()
570
571 def _show_clock(self):
572 logging.debug("Grabbing time")
573 try:
574 self._cli("show clock")
575 return self._read_long_output("show clock")
576 except PExpectError:
577 # recurse on error
578 self._switch_connect()
579 return self._show_clock()
580
581 def _get_systemdata(self):
582 logging.debug("Grabbing system sw and hw versions")
583
584 try:
585 self._systemdata = []
586 self._cli("show version")
587 for line in self._read_long_output("show version"):
588 self._systemdata.append(line)
589
590 except PExpectError:
591 # recurse on error
592 self._switch_connect()
593 return self._get_systemdata()
594
595 def _parse_vlan_list(self, inputdata):
596 vlans = []
597
598 if inputdata == "ALL":
599 return ["ALL"]
600 elif inputdata == "NONE":
601 return []
602 else:
603 # Parse the complex list
604 groups = inputdata.split(',')
605 for group in groups:
606 subgroups = group.split('-')
607 if len(subgroups) == 1:
608 vlans.append(int(subgroups[0]))
609 elif len(subgroups) == 2:
610 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
611 vlans.append(i)
612 else:
613 logging.debug("Can't parse group \"" + group + "\"")
614
615 return vlans
616
Steve McIntyreef73fca2015-07-07 14:57:59 +0100617 def _get_port_vlans(self, port):
618 vlan_text = None
619
Steve McIntyrec08cca22015-07-07 18:00:33 +0100620 vlan_part_re = re.compile('vlan participation include (.*)')
Steve McIntyreef73fca2015-07-07 14:57:59 +0100621
622 try:
623 self._cli("show running-config interface %s" % port)
624 for line in self._read_long_output("show running-config interface"):
625 match = vlan_part_re.match(line)
626 if match:
627 if vlan_text != None:
628 vlan_text += ","
629 vlan_text += (match.group(1))
630 else:
631 vlan_text = match.group(1)
Steve McIntyre8009ffe2015-07-07 18:02:21 +0100632
633 if vlan_text is None:
634 return [1]
635 else:
636 vlans = self._parse_vlan_list(vlan_text)
637 return vlans
Steve McIntyreef73fca2015-07-07 14:57:59 +0100638
639 except PExpectError:
640 # recurse on error
641 self._switch_connect()
642 return self._get_port_vlans(port)
643
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100644 # Wrapper around connection.send - by default, expect() the same
645 # text we've sent, to remove it from the output from the
646 # switch. For the few cases where we don't need that, override
647 # this using echo=False.
648 # Horrible, but seems to work.
649 def _cli(self, text, echo=True):
650 self.connection.send(text + '\r')
651 if echo:
652 try:
653 self.connection.expect(text)
654 except (pexpect.EOF, pexpect.TIMEOUT):
655 # Something went wrong; logout, log in and try again!
656 logging.error("PEXPECT FAILURE, RECONNECT")
657 self.errors.log_error_out(text)
658 raise PExpectError("_cli failed on %s" % text)
659 except:
660 logging.error("Unexpected error: %s", sys.exc_info()[0])
661 raise
662
663if __name__ == "__main__":
664
665 import optparse
666
667 switch = 'vlandswitch05'
668 parser = optparse.OptionParser()
669 parser.add_option("--switch",
670 dest = "switch",
671 action = "store",
672 nargs = 1,
673 type = "string",
674 help = "specify switch to connect to for testing",
675 metavar = "<switch>")
676 (opts, args) = parser.parse_args()
677 if opts.switch:
678 switch = opts.switch
679
Steve McIntyre144d51c2015-07-07 17:56:30 +0100680 logging.basicConfig(level = logging.DEBUG,
681 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100682 p = NetgearXSM(switch, 23, debug=True)
683 p.switch_connect('admin', '', None)
684
685 print "VLANs are:"
686 buf = p.vlan_get_list()
687 p.dump_list(buf)
688
689 buf = p.vlan_get_name(2)
690 print "VLAN 2 is named \"%s\"" % buf
691
692 print "Create VLAN 3"
693 p.vlan_create(3)
694
695 buf = p.vlan_get_name(3)
696 print "VLAN 3 is named \"%s\"" % buf
697
698 print "Set name of VLAN 3 to test333"
699 p.vlan_set_name(3, "test333")
700
701 buf = p.vlan_get_name(3)
702 print "VLAN 3 is named \"%s\"" % buf
703
704 print "VLANs are:"
705 buf = p.vlan_get_list()
706 p.dump_list(buf)
707
708 print "Destroy VLAN 3"
709 p.vlan_destroy(3)
710
711 print "VLANs are:"
712 buf = p.vlan_get_list()
713 p.dump_list(buf)
714
715 buf = p.port_get_mode("1/0/10")
716 print "Port 1/0/10 is in %s mode" % buf
717
718 buf = p.port_get_mode("1/0/11")
719 print "Port 1/0/11 is in %s mode" % buf
720
721 # Test access stuff
722 print "Set 1/0/9 to access mode"
723 p.port_set_mode("1/0/9", "access")
724
725 print "Move 1/0/9 to VLAN 4"
726 p.port_set_access_vlan("1/0/9", 4)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100727
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100728 buf = p.port_get_access_vlan("1/0/9")
729 print "Read from switch: 1/0/9 is on VLAN %s" % buf
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100730
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100731 print "Move 1/0/9 back to VLAN 1"
732 p.port_set_access_vlan("1/0/9", 1)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100733
Steve McIntyre40484f12015-07-07 18:02:50 +0100734 print "Create VLAN 2"
Steve McIntyre3a1b4832015-07-08 15:42:09 +0100735 p.vlan_create(2)
Steve McIntyre40484f12015-07-07 18:02:50 +0100736
737 print "Create VLAN 3"
738 p.vlan_create(3)
739
740 print "Create VLAN 4"
741 p.vlan_create(4)
742
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100743 # Test access stuff
744 print "Set 1/0/9 to trunk mode"
745 p.port_set_mode("1/0/9", "trunk")
746 print "Read from switch: which VLANs is 1/0/9 on?"
747 buf = p.port_get_trunk_vlan_list("1/0/9")
748 p.dump_list(buf)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100749
Steve McIntyre12b9ef92015-07-08 15:48:44 +0100750 # The adds below are NOOPs in effect on this switch - no filtering
751 # for "trunk" ports
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100752 print "Add 1/0/9 to VLAN 2"
753 p.port_add_trunk_to_vlan("1/0/9", 2)
754 print "Add 1/0/9 to VLAN 3"
755 p.port_add_trunk_to_vlan("1/0/9", 3)
756 print "Add 1/0/9 to VLAN 4"
757 p.port_add_trunk_to_vlan("1/0/9", 4)
758 print "Read from switch: which VLANs is 1/0/9 on?"
759 buf = p.port_get_trunk_vlan_list("1/0/9")
760 p.dump_list(buf)
761
Steve McIntyre12b9ef92015-07-08 15:48:44 +0100762 # And the same for removals here
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100763 p.port_remove_trunk_from_vlan("1/0/9", 3)
Steve McIntyre60671382015-07-08 15:49:03 +0100764 p.port_remove_trunk_from_vlan("1/0/9", 2)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100765 p.port_remove_trunk_from_vlan("1/0/9", 4)
766 print "Read from switch: which VLANs is 1/0/9 on?"
767 buf = p.port_get_trunk_vlan_list("1/0/9")
768 p.dump_list(buf)
769
770# print 'Restarting switch, to explicitly reset config'
771# p.switch_restart()
772
773# p.switch_save_running_config()
774
775# p.switch_disconnect()
776# p._show_config()