blob: 15b4feba95938d0ed8141d8bd73e04b729e9e03f [file] [log] [blame]
Steve McIntyred6759dd2014-08-12 18:10:00 +01001#! /usr/bin/python
2
3# Copyright 2014 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 time
24import re
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000025from common import SwitchDriver, SwitchErrors
26
Steve McIntyre72a8bce2015-01-23 18:02:19 +000027if __name__ == '__main__':
28 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
29 sys.path.insert(0, vlandpath)
30 sys.path.insert(0, "%s/.." % vlandpath)
31
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000032from errors import CriticalError, InputError, PExpectError
Steve McIntyred6759dd2014-08-12 18:10:00 +010033
34class CiscoCatalyst(SwitchDriver):
35
36 connection = None
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000037 _username = None
38 _password = None
39 _enable_password = None
Steve McIntyre3f287882014-08-18 19:02:15 +010040
41 _capabilities = [
42 'TrunkWildCardVlans' # Trunk ports are on all VLANs by
43 # default, so we shouldn't need to
44 # bugger with them
45 ]
46
Steve McIntyred6759dd2014-08-12 18:10:00 +010047 # Regexp of expected hardware information - fail if we don't see
48 # this
Steve McIntyre3f287882014-08-18 19:02:15 +010049 _expected_descr_re = re.compile('WS-C\S+-\d+P')
Steve McIntyred6759dd2014-08-12 18:10:00 +010050
Steve McIntyred6759dd2014-08-12 18:10:00 +010051 logfile = None
52
Steve McIntyre48dc6ae2014-12-23 16:08:19 +000053 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
54 if debug:
Steve McIntyre9f5a0232014-12-23 16:14:28 +000055 self.logfile = sys.stderr
Steve McIntyred6759dd2014-08-12 18:10:00 +010056 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000057 self.errors = SwitchErrors()
Steve McIntyred6759dd2014-08-12 18:10:00 +010058
59 ################################
60 ### Switch-level API functions
61 ################################
62
63 # Connect to the switch and log in
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010064 def switch_connect(self, username, password, enablepassword):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000065 self._username = username
66 self._password = password
67 self._enable_password = enablepassword
68 self._switch_connect()
Steve McIntyred6759dd2014-08-12 18:10:00 +010069
70 # Log out of the switch and drop the connection and all state
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010071 def switch_disconnect(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +010072 self._logout()
73 logging.debug("Closing connection: %s" % self.connection)
74 self.connection.close(True)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000075 self._ports = []
76 self._prompt_name = ''
77 self._systemdata = []
Steve McIntyred6759dd2014-08-12 18:10:00 +010078 del(self)
79
80 # Save the current running config into flash - we want config to
81 # remain across reboots
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010082 def switch_save_running_config(self):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000083 try:
84 self._cli("copy running-config startup-config")
85 self.connection.expect("startup-config")
86 self._cli("startup-config")
87 self.connection.expect("OK")
88 except PExpectError:
89 # recurse on error
90 self._switch_connect()
91 self.switch_save_running_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +010092
Steve McIntyre095b4452014-12-19 17:53:43 +000093 # Restart the switch - we need to reload config to do a
94 # roll-back. Do NOT save running-config first if the switch asks -
95 # we're trying to dump recent changes, not save them.
96 #
97 # This will also implicitly cause a connection to be closed
98 def switch_restart(self):
99 self._cli("reload")
100 index = self.connection.expect(['has been modified', 'Proceed'])
101 if index == 0:
102 self._cli("n") # No, don't save
103 self.connection.expect("Proceed")
104
105 # Fall through
106 self._cli("y") # Yes, continue to reset
107 self.connection.close(True)
108
Steve McIntyre3f287882014-08-18 19:02:15 +0100109 # List the capabilities of the switch (and driver) - some things
110 # make no sense to abstract. Returns a dict of strings, each one
111 # describing an extra feature that that higher levels may care
112 # about
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100113 def switch_get_capabilities(self):
Steve McIntyre3f287882014-08-18 19:02:15 +0100114 return self._capabilities
Steve McIntyred6759dd2014-08-12 18:10:00 +0100115
116 ################################
117 ### VLAN API functions
118 ################################
119
120 # Create a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100121 def vlan_create(self, tag):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100122 logging.debug("Creating VLAN %d" % tag)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000123 try:
124 self._configure()
125 self._cli("vlan %d" % tag)
126 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100127
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000128 # Validate it happened
129 vlans = self.vlan_get_list()
130 for vlan in vlans:
131 if vlan == tag:
132 return
133 raise IOError("Failed to create VLAN %d" % tag)
134
135 except PExpectError:
136 # recurse on error
137 self._switch_connect()
138 self.vlan_create(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100139
140 # Destroy a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100141 def vlan_destroy(self, tag):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100142 logging.debug("Destroying VLAN %d" % tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100143
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000144 try:
145 self._configure()
146 self._cli("no vlan %d" % tag)
147 self._end_configure()
148
149 # Validate it happened
150 vlans = self.vlan_get_list()
151 for vlan in vlans:
152 if vlan == tag:
153 raise IOError("Failed to destroy VLAN %d" % tag)
154
155 except PExpectError:
156 # recurse on error
157 self._switch_connect()
158 self.vlan_destroy(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100159
160 # Set the name of a VLAN
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100161 def vlan_set_name(self, tag, name):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100162 logging.debug("Setting name of VLAN %d to %s" % (tag, name))
Steve McIntyred6759dd2014-08-12 18:10:00 +0100163
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000164 try:
165 self._configure()
166 self._cli("vlan %d" % tag)
167 self._cli("name %s" % 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)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100179
180 # Get a list of the VLAN tags currently registered on the switch
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100181 def vlan_get_list(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100182 logging.debug("Grabbing list of VLANs")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100183
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000184 try:
185 vlans = []
Steve McIntyred6759dd2014-08-12 18:10:00 +0100186
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000187 regex = re.compile('^ *(\d+).*(active)')
188
189 self._cli("show vlan brief")
190 for line in self._read_long_output():
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()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100200
201 # For a given VLAN tag, ask the switch what the associated name is
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100202 def vlan_get_name(self, tag):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000203
204 try:
205 logging.debug("Grabbing the name of VLAN %d" % tag)
206 name = None
207 regex = re.compile('^ *\d+\s+(\S+).*(active)')
208 self._cli("show vlan id %d" % tag)
209 for line in self._read_long_output():
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)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100220
221 ################################
222 ### Port API functions
Steve McIntyree1bf11a2014-08-14 17:56:25 +0100223 ################################
Steve McIntyred6759dd2014-08-12 18:10:00 +0100224
Steve McIntyre9936d002014-10-01 15:54:10 +0100225 # Set the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100226 def port_set_mode(self, port, mode):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100227 logging.debug("Setting port %s to %s" % (port, mode))
228 if not self._is_port_mode_valid(mode):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000229 raise InputError("Port mode %s is not allowed" % mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100230 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000231 raise InputError("Port name %s not recognised" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100232
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000233 try:
234 self._configure()
235 self._cli("interface %s" % port)
236 self._cli("switchport mode %s" % mode)
237 if mode == "trunk":
238 self._cli("switchport trunk encapsulation dot1q")
239 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100240
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000241 # Validate it happened
242 read_mode = self.port_get_mode(port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100243
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000244 if read_mode != mode:
245 raise IOError("Failed to set mode for port %s" % port)
246
247 except PExpectError:
248 # recurse on error
249 self._switch_connect()
250 self.port_set_mode(port, mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100251
Steve McIntyre9936d002014-10-01 15:54:10 +0100252 # Get the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100253 def port_get_mode(self, port):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100254 logging.debug("Getting mode of port %s" % port)
255 mode = ''
256 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000257 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100258 regex = re.compile('Administrative Mode: (.*)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000259
260 try:
261 self._cli("show interfaces %s switchport" % port)
262 for line in self._read_long_output():
263 match = regex.match(line)
264 if match:
265 mode = match.group(1)
266 if mode == 'static access':
267 return 'access'
268 if mode == 'dynamic auto':
269 return 'trunk'
270 return mode
271
272 except PExpectError:
273 # recurse on error
274 self._switch_connect()
275 return self.port_get_mode(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100276
Steve McIntyre9936d002014-10-01 15:54:10 +0100277 # Set an access port to be in a specified VLAN (tag)
278 def port_set_access_vlan(self, port, tag):
279 logging.debug("Setting access port %s to VLAN %d" % (port, tag))
Steve McIntyred6759dd2014-08-12 18:10:00 +0100280 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000281 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100282 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000283 raise InputError("Port %s not in access mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100284
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000285 try:
286 self._configure()
287 self._cli("interface %s" % port)
288 self._cli("switchport access vlan %d" % tag)
289 self._cli("no shutdown")
290 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100291
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000292 # Finally, validate things worked
293 read_vlan = int(self.port_get_access_vlan(port))
294 if read_vlan != tag:
295 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
296 % (port, tag, read_vlan))
297
298 except PExpectError:
299 # recurse on error
300 self._switch_connect()
301 self.port_set_access_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100302
Steve McIntyred6759dd2014-08-12 18:10:00 +0100303 # Add a trunk port to a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100304 def port_add_trunk_to_vlan(self, port, tag):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100305 logging.debug("Adding trunk port %s to VLAN %d" % (port, tag))
306 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000307 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100308 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000309 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100310
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000311 try:
312 self._configure()
313 self._cli("interface %s" % port)
314 self._cli("switchport trunk allowed vlan add %d" % tag)
315 self._end_configure()
316
317 # Validate it happened
318 read_vlans = self.port_get_trunk_vlan_list(port)
319 for vlan in read_vlans:
320 if vlan == tag or vlan == "ALL":
321 return
322 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
323
324 except PExpectError:
325 # recurse on error
326 self._switch_connect()
327 self.port_add_trunk_to_vlan(port, tag)
328
Steve McIntyred6759dd2014-08-12 18:10:00 +0100329 # Remove a trunk port from a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100330 def port_remove_trunk_from_vlan(self, port, tag):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100331 logging.debug("Removing trunk port %s from VLAN %d" % (port, tag))
332 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000333 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100334 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000335 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100336
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000337 try:
338 self._configure()
339 self._cli("interface %s" % port)
340 self._cli("switchport trunk allowed vlan remove %d" % tag)
341 self._end_configure()
342
343 # Validate it happened
344 read_vlans = self.port_get_trunk_vlan_list(port)
345 for vlan in read_vlans:
346 if vlan == tag:
347 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
348
349 except PExpectError:
350 # recurse on error
351 self._switch_connect()
352 self.port_remove_trunk_from_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100353
Steve McIntyre9936d002014-10-01 15:54:10 +0100354 # Get the configured VLAN tag for an access port (tag)
355 def port_get_access_vlan(self, port):
356 logging.debug("Getting VLAN for access port %s" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100357 vlan = 1
358 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000359 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100360 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000361 raise InputError("Port %s not in access mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100362 regex = re.compile('Access Mode VLAN: (\d+)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000363
364 try:
365 self._cli("show interfaces %s switchport" % port)
366 for line in self._read_long_output():
367 match = regex.match(line)
368 if match:
369 vlan = match.group(1)
370 return int(vlan)
371
372 except PExpectError:
373 # recurse on error
374 self._switch_connect()
375 return self.port_get_access_vlan(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100376
377 # Get the list of configured VLAN tags for a trunk port
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100378 def port_get_trunk_vlan_list(self, port):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100379 logging.debug("Getting VLANs for trunk port %s" % port)
380 vlans = [ ]
381 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000382 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100383 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000384 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100385 regex_start = re.compile('Trunking VLANs Enabled: (.*)')
386 regex_continue = re.compile('\s*(\d.*)')
Steve McIntyre3f287882014-08-18 19:02:15 +0100387
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000388 try:
389 self._cli("show interfaces %s switchport" % port)
390
391 # Horrible parsing work - VLAN list may extend over several lines
392 in_match = False
393 vlan_text = ''
394
395 for line in self._read_long_output():
396 if in_match:
397 match = regex_continue.match(line)
398 if match:
399 vlan_text += match.group(1)
400 next
401 else:
402 in_match = False
403 next
Steve McIntyre3f287882014-08-18 19:02:15 +0100404 else:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000405 match = regex_start.match(line)
406 if match:
407 vlan_text += match.group(1)
408 in_match = True
Steve McIntyre3f287882014-08-18 19:02:15 +0100409
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000410 vlans = self._parse_vlan_list(vlan_text)
411 return vlans
Steve McIntyre3f287882014-08-18 19:02:15 +0100412
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000413 except PExpectError:
414 # recurse on error
415 self._switch_connect()
416 return self.port_get_trunk_vlan_list(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100417
418 ################################
419 ### Internal functions
420 ################################
421
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000422 # Connect to the switch and log in
423 def _switch_connect(self):
424
425 if not self.connection is None:
426 self.connection.close(True)
427 self.connection = None
428
429 logging.debug("Connecting to Switch with: %s" % self.exec_string)
430 self.connection = pexpect.spawn(self.exec_string, logfile = self.logfile)
431
432 self._login()
433
434 # Avoid paged output
435 self._cli("terminal length 0")
436
437 # And grab details about the switch. in case we need it
438 self._get_systemdata()
439
440 # And also validate them - make sure we're driving a switch of
441 # the correct model! Also store the serial number
442 descr_regex = re.compile('^cisco\s+(\S+)')
443 sn_regex = re.compile('System serial number\s+:\s+(\S+)')
444 descr = ""
445
446 for line in self._systemdata:
447 match = descr_regex.match(line)
448 if match:
449 descr = match.group(1)
450 match = sn_regex.match(line)
451 if match:
452 self.serial_number = match.group(1)
453
454 logging.debug("serial number is %s" % self.serial_number)
455 logging.debug("system description is %s" % descr)
456
457 if not self._expected_descr_re.match(descr):
458 raise IOError("Switch %s not recognised by this driver: abort" % descr)
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))
Steve McIntyred6759dd2014-08-12 18:10:00 +0100467 self.connection.expect('User Access Verification')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000468 if self._username is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100469 self.connection.expect("User Name:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000470 self._cli("%s" % self._username)
471 if self._password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100472 self.connection.expect("Password:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000473 self._cli("%s" % self._password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100474 while True:
475 index = self.connection.expect(['User Name:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
476 if index != 4: # Any other means: failed to log in!
477 logging.error("Login failure: index %d\n" % index)
478 logging.error("Login failure: %s\n" % self.connection.match.before)
479 raise IOError
480
481 # else
Steve McIntyre3f287882014-08-18 19:02:15 +0100482 self._prompt_name = self.connection.match.group(1).strip()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100483 if self.connection.match.group(2) == ">":
484 # Need to enter "enable" mode too
485 self._cli("enable")
Steve McIntyread12cd72015-02-12 06:41:29 +0000486 if self._enable_password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100487 self.connection.expect("Password:")
Steve McIntyread12cd72015-02-12 06:41:29 +0000488 self._cli("%s" % self._enable_password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100489 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
490 if index != 3: # Any other means: failed to log in!
491 logging.error("Enable password failure: %s\n" % self.connection.match)
492 raise IOError
493 return 0
494
495 def _logout(self):
496 logging.debug("Logging out")
497 self._cli("exit", False)
498
499 def _configure(self):
500 self._cli("configure terminal")
501
502 def _end_configure(self):
503 self._cli("end")
504
Steve McIntyrec68d6d72014-12-24 00:23:36 +0000505 def _read_long_output(self):
Steve McIntyre3f287882014-08-18 19:02:15 +0100506 prompt = self._prompt_name + '#'
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000507 try:
508 self.connection.expect(prompt)
509 except (pexpect.EOF, pexpect.TIMEOUT):
510 # Something went wrong; logout, log in and try again!
511 print "PEXPECT FAILURE, RECONNECT"
512 self.errors.log_error_in("")
513 raise PExpectError("_read_long_output failed")
514 except:
515 print "prompt is \"%s\"" % prompt
516 raise
517
Steve McIntyrea7cdefc2014-12-24 00:48:19 +0000518 buf = []
519 for line in self.connection.before.split('\r\n'):
520 buf.append(line.strip())
521 return buf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100522
523 def _get_port_names(self):
524 logging.debug("Grabbing list of ports")
525 interfaces = []
526
527 # Use "Up" or "Down" to only identify lines in the output that
528 # match interfaces that exist
529 regex = re.compile('^\s*([a-zA-Z0-9_/]*).*(connect)(.*)')
530 regex1 = re.compile('.*Not Present.*')
Steve McIntyre6c279b42014-12-23 22:09:04 +0000531 regex2 = re.compile('.*routed.*')
Steve McIntyred6759dd2014-08-12 18:10:00 +0100532
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000533 try:
534 self._cli("show interfaces status")
535 for line in self._read_long_output():
536 match = regex.match(line)
537 if match:
538 interface = match.group(1)
539 junk = match.group(3)
540 match1 = regex1.match(junk) # Deliberately drop things
541 # marked as "Not Present"
542 match2 = regex2.match(junk) # Deliberately drop things
543 # marked as "routed"
544 if not match1 and not match2:
545 interfaces.append(interface)
546 return interfaces
547
548 except PExpectError:
549 # recurse on error
550 self._switch_connect()
551 return self._get_port_names()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100552
Steve McIntyred6759dd2014-08-12 18:10:00 +0100553 def _show_config(self):
554 logging.debug("Grabbing config")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000555 try:
556 self._cli("show running-config")
557 return self._read_long_output()
558 except PExpectError:
559 # recurse on error
560 self._switch_connect()
561 return self._show_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100562
563 def _show_clock(self):
564 logging.debug("Grabbing time")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000565 try:
566 self._cli("show clock")
567 return self._read_long_output()
568 except PExpectError:
569 # recurse on error
570 self._switch_connect()
571 return self._show_clock()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100572
Steve McIntyred6759dd2014-08-12 18:10:00 +0100573 def _get_systemdata(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100574 logging.debug("Grabbing system sw and hw versions")
Steve McIntyreffb9b5a2014-10-10 16:31:58 +0100575
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000576 try:
577 self._systemdata = []
578 self._cli("show version")
579 for line in self._read_long_output():
580 self._systemdata.append(line)
581
582 except PExpectError:
583 # recurse on error
584 self._switch_connect()
585 return self._get_systemdata()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100586
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000587 def _parse_vlan_list(self, inputdata):
Steve McIntyre3f287882014-08-18 19:02:15 +0100588 vlans = []
589
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000590 if inputdata == "ALL":
Steve McIntyre3f287882014-08-18 19:02:15 +0100591 return ["ALL"]
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000592 elif inputdata == "NONE":
Steve McIntyre3f287882014-08-18 19:02:15 +0100593 return []
594 else:
595 # Parse the complex list
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000596 groups = inputdata.split(',')
Steve McIntyre3f287882014-08-18 19:02:15 +0100597 for group in groups:
598 subgroups = group.split('-')
599 if len(subgroups) == 1:
600 vlans.append(int(subgroups[0]))
601 elif len(subgroups) == 2:
602 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
603 vlans.append(i)
604 else:
Steve McIntyre6d5594f2014-12-23 14:28:47 +0000605 logging.debug("Can't parse group \"" + group + "\"")
Steve McIntyre3f287882014-08-18 19:02:15 +0100606
607 return vlans
Steve McIntyred6759dd2014-08-12 18:10:00 +0100608
609 # Wrapper around connection.send - by default, expect() the same
610 # text we've sent, to remove it from the output from the
611 # switch. For the few cases where we don't need that, override
612 # this using echo=False.
613 # Horrible, but seems to work.
614 def _cli(self, text, echo=True):
615 self.connection.send(text + '\r')
616 if echo:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000617 try:
618 self.connection.expect(text)
619 except (pexpect.EOF, pexpect.TIMEOUT):
620 # Something went wrong; logout, log in and try again!
621 print "PEXPECT FAILURE, RECONNECT"
622 self.errors.log_error_out(text)
623 raise PExpectError("_cli failed on %s" % text)
624 except:
625 print "Unexpected error:", sys.exc_info()[0]
626 raise
Steve McIntyred6759dd2014-08-12 18:10:00 +0100627
628if __name__ == "__main__":
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000629
630 import optparse
631
632 switch = 'vlandswitch01'
633 parser = optparse.OptionParser()
634 parser.add_option("--switch",
635 dest = "switch",
636 action = "store",
637 nargs = 1,
638 type = "string",
639 help = "specify switch to connect to for testing",
640 metavar = "<switch>")
641 (opts, args) = parser.parse_args()
642 if opts.switch:
643 switch = opts.switch
644
645 p = CiscoCatalyst(switch, 23, debug=True)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100646 p.switch_connect(None, 'lngvirtual', 'lngenable')
Steve McIntyred6759dd2014-08-12 18:10:00 +0100647
648 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100649 buf = p.vlan_get_list()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100650 p._dump_list(buf)
651
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100652 buf = p.vlan_get_name(2)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100653 print "VLAN 2 is named \"%s\"" % buf
654
655 print "Create VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100656 p.vlan_create(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100657
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100658 buf = p.vlan_get_name(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100659 print "VLAN 3 is named \"%s\"" % buf
660
661 print "Set name of VLAN 3 to test333"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100662 p.vlan_set_name(3, "test333")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100663
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100664 buf = p.vlan_get_name(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100665 print "VLAN 3 is named \"%s\"" % buf
666
667 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100668 buf = p.vlan_get_list()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100669 p._dump_list(buf)
670
671 print "Destroy VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100672 p.vlan_destroy(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100673
674 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100675 buf = p.vlan_get_list()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100676 p._dump_list(buf)
677
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100678 buf = p.port_get_mode("Gi1/0/10")
Steve McIntyreb7adc782014-08-13 00:22:21 +0100679 print "Port Gi1/0/10 is in %s mode" % buf
680
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100681 buf = p.port_get_mode("Gi1/0/11")
Steve McIntyreb7adc782014-08-13 00:22:21 +0100682 print "Port Gi1/0/11 is in %s mode" % buf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100683
Steve McIntyre9936d002014-10-01 15:54:10 +0100684 # Test access stuff
685 print "Set Gi1/0/9 to access mode"
686 p.port_set_mode("Gi1/0/9", "access")
Steve McIntyre3f287882014-08-18 19:02:15 +0100687
688 print "Move Gi1/0/9 to VLAN 4"
Steve McIntyre9936d002014-10-01 15:54:10 +0100689 p.port_set_access_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100690
Steve McIntyre9936d002014-10-01 15:54:10 +0100691 buf = p.port_get_access_vlan("Gi1/0/9")
Steve McIntyre3f287882014-08-18 19:02:15 +0100692 print "Read from switch: Gi1/0/9 is on VLAN %s" % buf
693
694 print "Move Gi1/0/9 back to VLAN 1"
Steve McIntyre9936d002014-10-01 15:54:10 +0100695 p.port_set_access_vlan("Gi1/0/9", 1)
Steve McIntyre3f287882014-08-18 19:02:15 +0100696
Steve McIntyre9936d002014-10-01 15:54:10 +0100697 # Test access stuff
Steve McIntyre3f287882014-08-18 19:02:15 +0100698 print "Set Gi1/0/9 to trunk mode"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100699 p.port_set_mode("Gi1/0/9", "trunk")
Steve McIntyre3f287882014-08-18 19:02:15 +0100700 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100701 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre3f287882014-08-18 19:02:15 +0100702 p._dump_list(buf)
703 print "Add Gi1/0/9 to VLAN 2"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100704 p.port_add_trunk_to_vlan("Gi1/0/9", 2)
Steve McIntyre3f287882014-08-18 19:02:15 +0100705 print "Add Gi1/0/9 to VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100706 p.port_add_trunk_to_vlan("Gi1/0/9", 3)
Steve McIntyre3f287882014-08-18 19:02:15 +0100707 print "Add Gi1/0/9 to VLAN 4"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100708 p.port_add_trunk_to_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100709 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100710 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre3f287882014-08-18 19:02:15 +0100711 p._dump_list(buf)
712
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100713 p.port_remove_trunk_from_vlan("Gi1/0/9", 3)
714 p.port_remove_trunk_from_vlan("Gi1/0/9", 3)
715 p.port_remove_trunk_from_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100716 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100717 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre3f287882014-08-18 19:02:15 +0100718 p._dump_list(buf)
719
Steve McIntyre7460d972014-12-23 14:45:30 +0000720# print 'Restarting switch, to explicitly reset config'
721# p.switch_restart()
Steve McIntyre3f287882014-08-18 19:02:15 +0100722
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100723# p.switch_save_running_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100724
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100725# p.switch_disconnect()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100726# p._show_config()