blob: f8ba8a5dec2559c3962809ed82007974024026b4 [file] [log] [blame]
Steve McIntyrefdcb3782015-07-06 16:34:56 +01001#! /usr/bin/python
2
Steve McIntyree237c9b2018-01-24 16:27:32 +00003# Copyright 2015-2018 Linaro Limited
Steve McIntyrefdcb3782015-07-06 16:34:56 +01004#
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
Steve McIntyrefdcb3782015-07-06 16:34:56 +010021import sys
22import re
Steve McIntyre4267c6a2018-01-24 16:57:28 +000023import pexpect
Steve McIntyrefdcb3782015-07-06 16:34:56 +010024
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 McIntyre4f945292015-07-16 18:31:21 +0100217 # * accept all frames on ingress
218 # * accept packets for all VLANs (no ingress filter)
219 # * tags frames on transmission (do that later when
220 # * adding VLANs to the port)
221 # * PVID should match the default VLAN (1).
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100222 self._configure()
223 self._cli("interface %s" % port)
Steve McIntyred97ab292015-07-07 14:23:32 +0100224 self._cli("vlan acceptframe all")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100225 self._cli("no vlan ingressfilter")
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100226 self._cli("vlan pvid 1")
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100227 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100228 self._end_configure()
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100229
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100230 # We define an access port thus:
Steve McIntyre4f945292015-07-16 18:31:21 +0100231 # * accept only untagged frames on ingress
232 # * accept packets for only desired VLANs (ingress filter)
233 # * exists on one VLAN only (1 by default)
234 # * do not tag frames on transmission (the devices
235 # we're talking to are expecting untagged frames)
236 # * PVID should match the VLAN it's on (1 by default,
237 # but don't do that here)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100238 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 McIntyrefc7a6272015-07-07 16:22:32 +0100247 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100248 self._end_configure()
249
250 # Validate it happened
Steve McIntyreb79cea52015-07-24 17:03:31 +0100251 read_mode = self._port_get_mode(port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100252
253 if read_mode != mode:
254 raise IOError("Failed to set mode for port %s" % port)
255
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100256 # And cache the result
257 self._port_modes[port] = mode
258
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100259 except PExpectError:
260 # recurse on error
261 self._switch_connect()
262 self.port_set_mode(port, mode)
263
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100264 # Set an access port to be in a specified VLAN (tag)
265 def port_set_access_vlan(self, port, tag):
266 logging.debug("Setting access port %s to VLAN %d", port, tag)
267 if not self._is_port_name_valid(port):
268 raise InputError("Port name %s not recognised" % port)
269 if not (self.port_get_mode(port) == "access"):
270 raise InputError("Port %s not in access mode" % port)
271
272 try:
Steve McIntyree6172b02015-07-07 17:59:46 +0100273 current_vlans = self._get_port_vlans(port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100274 self._configure()
275 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100276 self._cli("vlan pvid %s" % tag)
277 # Find the list of VLANs we're currently on, and drop them
278 # all. "auto" mode is fine here, we won't be included
279 # unless we have GVRP configured, and we don't do
280 # that.
Steve McIntyree6172b02015-07-07 17:59:46 +0100281 for current_vlan in current_vlans:
Steve McIntyreef73fca2015-07-07 14:57:59 +0100282 self._cli("vlan participation auto %s" % current_vlan)
283 # Now specifically include the VLAN we want
284 self._cli("vlan participation include %s" % tag)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100285 self._cli("no shutdown")
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100286 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100287 self._end_configure()
288
289 # Finally, validate things worked
290 read_vlan = int(self.port_get_access_vlan(port))
291 if read_vlan != tag:
292 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
293 % (port, tag, read_vlan))
294
295 except PExpectError:
296 # recurse on error
297 self._switch_connect()
298 self.port_set_access_vlan(port, tag)
299
Steve McIntyre4f945292015-07-16 18:31:21 +0100300 # Add a trunk port to a specified VLAN (tag)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100301 def port_add_trunk_to_vlan(self, port, tag):
302 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
303 if not self._is_port_name_valid(port):
304 raise InputError("Port name %s not recognised" % port)
305 if not (self.port_get_mode(port) == "trunk"):
306 raise InputError("Port %s not in trunk mode" % port)
307
308 try:
309 self._configure()
310 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100311 self._cli("vlan participation include %d" % tag)
Steve McIntyre4f945292015-07-16 18:31:21 +0100312 self._cli("vlan tagging %d" % tag)
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100313 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100314 self._end_configure()
315
316 # Validate it happened
317 read_vlans = self.port_get_trunk_vlan_list(port)
318 for vlan in read_vlans:
319 if vlan == tag or vlan == "ALL":
320 return
321 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
322
323 except PExpectError:
324 # recurse on error
325 self._switch_connect()
326 self.port_add_trunk_to_vlan(port, tag)
327
Steve McIntyre4f945292015-07-16 18:31:21 +0100328 # Remove a trunk port from a specified VLAN (tag)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100329 def port_remove_trunk_from_vlan(self, port, tag):
330 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
331 if not self._is_port_name_valid(port):
332 raise InputError("Port name %s not recognised" % port)
333 if not (self.port_get_mode(port) == "trunk"):
334 raise InputError("Port %s not in trunk mode" % port)
335
336 try:
337 self._configure()
338 self._cli("interface %s" % port)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100339 self._cli("vlan participation auto %d" % tag)
Steve McIntyre4f945292015-07-16 18:31:21 +0100340 self._cli("no vlan tagging %d" % tag)
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100341 self._end_interface()
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100342 self._end_configure()
343
344 # Validate it happened
345 read_vlans = self.port_get_trunk_vlan_list(port)
346 for vlan in read_vlans:
347 if vlan == tag:
348 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
349
350 except PExpectError:
351 # recurse on error
352 self._switch_connect()
353 self.port_remove_trunk_from_vlan(port, tag)
354
355 # Get the configured VLAN tag for an access port (tag)
356 def port_get_access_vlan(self, port):
357 logging.debug("Getting VLAN for access port %s", port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100358 if not self._is_port_name_valid(port):
359 raise InputError("Port name %s not recognised" % port)
360 if not (self.port_get_mode(port) == "access"):
361 raise InputError("Port %s not in access mode" % port)
Steve McIntyrec4834c72015-07-07 18:00:17 +0100362 vlans = self._get_port_vlans(port)
363 if (len(vlans) > 1):
364 raise IOError("More than one VLAN on access port %s" % port)
365 return vlans[0]
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100366
367 # Get the list of configured VLAN tags for a trunk port
368 def port_get_trunk_vlan_list(self, port):
Steve McIntyreef73fca2015-07-07 14:57:59 +0100369 logging.debug("Getting VLAN(s) for trunk port %s", port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100370 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)
Steve McIntyreef73fca2015-07-07 14:57:59 +0100374 return self._get_port_vlans(port)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100375
376 ################################
377 ### Internal functions
378 ################################
379
380 # Connect to the switch and log in
381 def _switch_connect(self):
382
383 if not self.connection is None:
384 self.connection.close(True)
385 self.connection = None
386
387 logging.debug("Connecting to Switch with: %s", self.exec_string)
Steve McIntyre06c644a2015-07-17 17:07:41 +0100388 self.connection = pexpect.spawn(self.exec_string, logfile=self.logger)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100389 self._login()
390
391 # Avoid paged output
392 self._cli("terminal length 0")
393
394 # And grab details about the switch. in case we need it
395 self._get_systemdata()
396
397 # And also validate them - make sure we're driving a switch of
398 # the correct model! Also store the serial number
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100399 manuf_regex = re.compile(r'^Manufacturer([\.\s])+(\S+)')
400 model_regex = re.compile(r'^Machine Model([\.\s])+(\S+)')
401 sn_regex = re.compile(r'^Serial Number([\.\s])+(\S+)')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100402
403 for line in self._systemdata:
404 match1 = manuf_regex.match(line)
405 if match1:
406 manuf = match1.group(2)
407
408 match2 = model_regex.match(line)
409 if match2:
410 model = match2.group(2)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100411
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100412 match3 = sn_regex.match(line)
413 if match3:
414 self.serial_number = match3.group(2)
415
416 logging.debug("manufacturer is %s", manuf)
417 logging.debug("model is %s", model)
418 logging.debug("serial number is %s", self.serial_number)
419
420 if not (self._expected_manuf.match(manuf) and self._expected_model.match(model)):
421 raise IOError("Switch %s %s not recognised by this driver: abort" % (manuf, model))
422
423 # Now build a list of our ports, for later sanity checking
424 self._ports = self._get_port_names()
425 if len(self._ports) < 4:
426 raise IOError("Not enough ports detected - problem!")
427
428 def _login(self):
429 logging.debug("attempting login with username %s, password %s", self._username, self._password)
430 if self._username is not None:
431 self.connection.expect("User:")
432 self._cli("%s" % self._username)
433 if self._password is not None:
434 self.connection.expect("Password:")
435 self._cli("%s" % self._password, False)
436 while True:
437 index = self.connection.expect(['User:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
438 if index != 4: # Any other means: failed to log in!
439 logging.error("Login failure: index %d\n", index)
440 logging.error("Login failure: %s\n", self.connection.match.before)
441 raise IOError
442
443 # else
444 self._prompt_name = re.escape(self.connection.match.group(1).strip())
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100445 if self.connection.match.group(2) == ">":
446 # Need to enter "enable" mode too
447 self._cli("enable")
448 if self._enable_password is not None:
449 self.connection.expect("Password:")
450 self._cli("%s" % self._enable_password, False)
451 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*) *(#|>)'])
452 if index != 3: # Any other means: failed to log in!
453 logging.error("Enable password failure: %s\n", self.connection.match)
454 raise IOError
455 return 0
456
457 def _logout(self):
458 logging.debug("Logging out")
459 self._cli("quit", False)
Steve McIntyredfce73f2015-07-09 17:41:55 +0100460 try:
461 self.connection.expect("Would you like to save them now")
462 self._cli("n")
463 except (pexpect.EOF):
464 pass
Steve McIntyre26c89562015-10-09 15:02:37 +0100465 self.connection.close(True)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100466
467 def _configure(self):
468 self._cli("configure")
469
470 def _end_configure(self):
471 self._cli("exit")
472
Steve McIntyrefc7a6272015-07-07 16:22:32 +0100473 def _end_interface(self):
474 self._end_configure()
475
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100476 def _read_long_output(self, text):
477 longbuf = []
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100478 prompt = self._prompt_name + r'\s*#'
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100479 while True:
480 try:
481 index = self.connection.expect(['--More--', prompt])
482 if index == 0: # "--More-- or (q)uit"
483 for line in self.connection.before.split('\r\n'):
484 line1 = re.sub('(\x08|\x0D)*', '', line.strip())
485 longbuf.append(line1)
486 self._cli(' ', False)
487 elif index == 1: # Back to a prompt, says output is finished
488 break
489 except (pexpect.EOF, pexpect.TIMEOUT):
490 # Something went wrong; logout, log in and try again!
491 logging.error("PEXPECT FAILURE, RECONNECT")
492 self.errors.log_error_in(text)
493 raise PExpectError("_read_long_output failed")
494 except:
495 logging.error("prompt is \"%s\"", prompt)
496 raise
497
498 for line in self.connection.before.split('\r\n'):
499 longbuf.append(line.strip())
500 return longbuf
501
502 def _get_port_names(self):
503 logging.debug("Grabbing list of ports")
504 interfaces = []
505
Steve McIntyre8400e742015-08-04 18:01:52 +0100506 # Look for "1" at the beginning of the output lines to just
507 # match lines with interfaces - they have names like
508 # "1/0/22". We do not care about Link Aggregation Groups (lag)
509 # here.
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100510 regex = re.compile(r'^(1\S+)')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100511
512 try:
513 self._cli("show port all")
514 for line in self._read_long_output("show port all"):
515 match = regex.match(line)
516 if match:
517 interface = match.group(1)
518 interfaces.append(interface)
Steve McIntyred5cfde12015-08-05 13:49:42 +0100519 self._port_numbers[interface] = len(interfaces)
Steve McIntyred601ab82015-07-09 17:42:36 +0100520 logging.debug(" found %d ports on the switch", len(interfaces))
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100521 return interfaces
522
523 except PExpectError:
524 # recurse on error
525 self._switch_connect()
526 return self._get_port_names()
527
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100528 # Get the mode of a port: access or trunk
529 def _port_get_mode(self, port):
530 logging.debug("Getting mode of port %s", port)
531
532 if not self._is_port_name_valid(port):
533 raise InputError("Port name %s not recognised" % port)
534 acceptframe_re = re.compile('vlan acceptframe (.*)')
535 ingress_re = re.compile('vlan ingressfilter')
536
537 acceptframe = None
538 ingressfilter = True
539
540 try:
541 self._cli("show running-config interface %s" % port)
542 for line in self._read_long_output("show running-config interface"):
543
544 match = acceptframe_re.match(line)
545 if match:
546 acceptframe = match.group(1)
547
548 match = ingress_re.match(line)
549 if match:
550 ingressfilter = True
551
552 # Simple classifier for now; may need to revisit later...
553 if (ingressfilter and acceptframe == "admituntaggedonly"):
554 return "access"
555 else:
556 return "trunk"
557
558 except PExpectError:
559 # recurse on error
560 self._switch_connect()
561 return self.port_get_mode(port)
562
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100563 def _show_config(self):
564 logging.debug("Grabbing config")
565 try:
566 self._cli("show running-config")
567 return self._read_long_output("show running-config")
568 except PExpectError:
569 # recurse on error
570 self._switch_connect()
571 return self._show_config()
572
573 def _show_clock(self):
574 logging.debug("Grabbing time")
575 try:
576 self._cli("show clock")
577 return self._read_long_output("show clock")
578 except PExpectError:
579 # recurse on error
580 self._switch_connect()
581 return self._show_clock()
582
583 def _get_systemdata(self):
584 logging.debug("Grabbing system sw and hw versions")
585
586 try:
587 self._systemdata = []
588 self._cli("show version")
589 for line in self._read_long_output("show version"):
590 self._systemdata.append(line)
591
592 except PExpectError:
593 # recurse on error
594 self._switch_connect()
595 return self._get_systemdata()
596
597 def _parse_vlan_list(self, inputdata):
598 vlans = []
599
600 if inputdata == "ALL":
601 return ["ALL"]
602 elif inputdata == "NONE":
603 return []
604 else:
605 # Parse the complex list
606 groups = inputdata.split(',')
607 for group in groups:
608 subgroups = group.split('-')
609 if len(subgroups) == 1:
610 vlans.append(int(subgroups[0]))
611 elif len(subgroups) == 2:
612 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
613 vlans.append(i)
614 else:
615 logging.debug("Can't parse group \"" + group + "\"")
616
617 return vlans
618
Steve McIntyreef73fca2015-07-07 14:57:59 +0100619 def _get_port_vlans(self, port):
620 vlan_text = None
621
Steve McIntyrec08cca22015-07-07 18:00:33 +0100622 vlan_part_re = re.compile('vlan participation include (.*)')
Steve McIntyreef73fca2015-07-07 14:57:59 +0100623
624 try:
625 self._cli("show running-config interface %s" % port)
626 for line in self._read_long_output("show running-config interface"):
627 match = vlan_part_re.match(line)
628 if match:
629 if vlan_text != None:
630 vlan_text += ","
631 vlan_text += (match.group(1))
632 else:
633 vlan_text = match.group(1)
Steve McIntyre8009ffe2015-07-07 18:02:21 +0100634
635 if vlan_text is None:
636 return [1]
637 else:
638 vlans = self._parse_vlan_list(vlan_text)
639 return vlans
Steve McIntyreef73fca2015-07-07 14:57:59 +0100640
641 except PExpectError:
642 # recurse on error
643 self._switch_connect()
644 return self._get_port_vlans(port)
645
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100646 # Wrapper around connection.send - by default, expect() the same
647 # text we've sent, to remove it from the output from the
648 # switch. For the few cases where we don't need that, override
649 # this using echo=False.
650 # Horrible, but seems to work.
651 def _cli(self, text, echo=True):
652 self.connection.send(text + '\r')
653 if echo:
654 try:
655 self.connection.expect(text)
656 except (pexpect.EOF, pexpect.TIMEOUT):
657 # Something went wrong; logout, log in and try again!
658 logging.error("PEXPECT FAILURE, RECONNECT")
659 self.errors.log_error_out(text)
660 raise PExpectError("_cli failed on %s" % text)
661 except:
662 logging.error("Unexpected error: %s", sys.exc_info()[0])
663 raise
664
665if __name__ == "__main__":
666
Steve McIntyreda9e9c12018-01-24 16:58:13 +0000667 # Simple test harness - exercise the main working functions above to verify
668 # they work. This does *NOT* test really disruptive things like "save
669 # running-config" and "reload" - test those by hand.
670
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100671 import optparse
672
673 switch = 'vlandswitch05'
674 parser = optparse.OptionParser()
675 parser.add_option("--switch",
676 dest = "switch",
677 action = "store",
678 nargs = 1,
679 type = "string",
680 help = "specify switch to connect to for testing",
681 metavar = "<switch>")
682 (opts, args) = parser.parse_args()
683 if opts.switch:
684 switch = opts.switch
685
Steve McIntyre144d51c2015-07-07 17:56:30 +0100686 logging.basicConfig(level = logging.DEBUG,
687 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100688 p = NetgearXSM(switch, 23, debug=True)
689 p.switch_connect('admin', '', None)
690
691 print "VLANs are:"
692 buf = p.vlan_get_list()
693 p.dump_list(buf)
694
695 buf = p.vlan_get_name(2)
696 print "VLAN 2 is named \"%s\"" % buf
697
698 print "Create VLAN 3"
699 p.vlan_create(3)
700
701 buf = p.vlan_get_name(3)
702 print "VLAN 3 is named \"%s\"" % buf
703
704 print "Set name of VLAN 3 to test333"
705 p.vlan_set_name(3, "test333")
706
707 buf = p.vlan_get_name(3)
708 print "VLAN 3 is named \"%s\"" % buf
709
710 print "VLANs are:"
711 buf = p.vlan_get_list()
712 p.dump_list(buf)
713
714 print "Destroy VLAN 3"
715 p.vlan_destroy(3)
716
717 print "VLANs are:"
718 buf = p.vlan_get_list()
719 p.dump_list(buf)
720
721 buf = p.port_get_mode("1/0/10")
722 print "Port 1/0/10 is in %s mode" % buf
723
724 buf = p.port_get_mode("1/0/11")
725 print "Port 1/0/11 is in %s mode" % buf
726
727 # Test access stuff
728 print "Set 1/0/9 to access mode"
729 p.port_set_mode("1/0/9", "access")
730
731 print "Move 1/0/9 to VLAN 4"
732 p.port_set_access_vlan("1/0/9", 4)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100733
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100734 buf = p.port_get_access_vlan("1/0/9")
735 print "Read from switch: 1/0/9 is on VLAN %s" % buf
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100736
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100737 print "Move 1/0/9 back to VLAN 1"
738 p.port_set_access_vlan("1/0/9", 1)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100739
Steve McIntyre40484f12015-07-07 18:02:50 +0100740 print "Create VLAN 2"
Steve McIntyre3a1b4832015-07-08 15:42:09 +0100741 p.vlan_create(2)
Steve McIntyre40484f12015-07-07 18:02:50 +0100742
743 print "Create VLAN 3"
744 p.vlan_create(3)
745
746 print "Create VLAN 4"
747 p.vlan_create(4)
748
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100749 # Test access stuff
750 print "Set 1/0/9 to trunk mode"
751 p.port_set_mode("1/0/9", "trunk")
752 print "Read from switch: which VLANs is 1/0/9 on?"
753 buf = p.port_get_trunk_vlan_list("1/0/9")
754 p.dump_list(buf)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100755
Steve McIntyre12b9ef92015-07-08 15:48:44 +0100756 # The adds below are NOOPs in effect on this switch - no filtering
757 # for "trunk" ports
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100758 print "Add 1/0/9 to VLAN 2"
759 p.port_add_trunk_to_vlan("1/0/9", 2)
760 print "Add 1/0/9 to VLAN 3"
761 p.port_add_trunk_to_vlan("1/0/9", 3)
762 print "Add 1/0/9 to VLAN 4"
763 p.port_add_trunk_to_vlan("1/0/9", 4)
764 print "Read from switch: which VLANs is 1/0/9 on?"
765 buf = p.port_get_trunk_vlan_list("1/0/9")
766 p.dump_list(buf)
767
Steve McIntyre12b9ef92015-07-08 15:48:44 +0100768 # And the same for removals here
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100769 p.port_remove_trunk_from_vlan("1/0/9", 3)
Steve McIntyre60671382015-07-08 15:49:03 +0100770 p.port_remove_trunk_from_vlan("1/0/9", 2)
Steve McIntyrefdcb3782015-07-06 16:34:56 +0100771 p.port_remove_trunk_from_vlan("1/0/9", 4)
772 print "Read from switch: which VLANs is 1/0/9 on?"
773 buf = p.port_get_trunk_vlan_list("1/0/9")
774 p.dump_list(buf)
775
776# print 'Restarting switch, to explicitly reset config'
777# p.switch_restart()
778
779# p.switch_save_running_config()
780
781# p.switch_disconnect()
782# p._show_config()