blob: a6f54466fad8a1bd31e8b1bc5fea430cf1cf8a8c [file] [log] [blame]
Steve McIntyrecc297112014-08-11 18:46:58 +01001#! /usr/bin/python
2
Steve McIntyre94ef65e2015-09-25 01:08:14 +01003# Copyright 2014-2015 Linaro Limited
Steve McIntyrecc297112014-08-11 18:46:58 +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 McIntyrecc297112014-08-11 18:46:58 +010021import sys
Steve McIntyrecc297112014-08-11 18:46:58 +010022import re
Steve McIntyre4267c6a2018-01-24 16:57:28 +000023import pexpect
Steve McIntyrecc297112014-08-11 18:46:58 +010024
Steve McIntyre72a8bce2015-01-23 18:02:19 +000025if __name__ == '__main__':
Steve McIntyrea67473a2015-02-12 08:26:33 +000026 import os
Steve McIntyre72a8bce2015-01-23 18:02:19 +000027 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
28 sys.path.insert(0, vlandpath)
29 sys.path.insert(0, "%s/.." % vlandpath)
30
Steve McIntyre5fa22652015-04-01 18:01:45 +010031from errors import InputError, PExpectError
Steve McIntyre17c421c2015-04-29 14:37:36 +010032from drivers.common import SwitchDriver, SwitchErrors
Steve McIntyre72a8bce2015-01-23 18:02:19 +000033
Steve McIntyrecc297112014-08-11 18:46:58 +010034class CiscoSX300(SwitchDriver):
35
36 connection = None
Steve McIntyrebbd2ac82015-02-12 03:08:48 +000037 _username = None
38 _password = None
39 _enable_password = None
Steve McIntyre366046f2014-08-18 18:57:03 +010040
41 # No extra capabilities for this switch/driver yet
42 _capabilities = [
43 ]
44
Steve McIntyrecc297112014-08-11 18:46:58 +010045 # Regexp of expected hardware information - fail if we don't see
46 # this
Steve McIntyre1c8a3212015-07-14 17:07:31 +010047 _expected_descr_re = re.compile(r'.*\d+-Port.*Managed Switch.*')
Steve McIntyrecc297112014-08-11 18:46:58 +010048
Steve McIntyre48dc6ae2014-12-23 16:08:19 +000049 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
Steve McIntyrebb58a272015-07-14 15:39:54 +010050 SwitchDriver.__init__(self, switch_hostname, debug)
51 self._systemdata = []
Steve McIntyrecc297112014-08-11 18:46:58 +010052 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
Steve McIntyre3c3f4fc2015-02-12 04:26:27 +000053 self.errors = SwitchErrors()
Steve McIntyrecc297112014-08-11 18:46:58 +010054
55 ################################
56 ### Switch-level API functions
57 ################################
58
Steve McIntyrecc297112014-08-11 18:46:58 +010059 # Save the current running config into flash - we want config to
60 # remain across reboots
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010061 def switch_save_running_config(self):
Steve McIntyrec5359e62015-02-12 04:54:07 +000062 try:
63 self._cli("copy running-config startup-config")
64 self.connection.expect("Y/N")
65 self._cli("y")
66 self.connection.expect("succeeded")
Steve McIntyre2d84e522015-02-12 06:44:42 +000067 except (PExpectError, pexpect.EOF, pexpect.TIMEOUT):
Steve McIntyrec5359e62015-02-12 04:54:07 +000068 # recurse on error
69 self._switch_connect()
70 self.switch_save_running_config()
Steve McIntyrecc297112014-08-11 18:46:58 +010071
Steve McIntyre095b4452014-12-19 17:53:43 +000072 # Restart the switch - we need to reload config to do a
73 # roll-back. Do NOT save running-config first if the switch asks -
74 # we're trying to dump recent changes, not save them.
75 #
Steve McIntyre1c8a3212015-07-14 17:07:31 +010076 # This will also implicitly cause a connection to be closed
Steve McIntyre095b4452014-12-19 17:53:43 +000077 def switch_restart(self):
78 self._cli("reload")
79 index = self.connection.expect(['Are you sure', 'will reset'])
80 if index == 0:
81 self._cli("y") # Yes, continue without saving
82 self.connection.expect("reset the whole")
83
84 # Fall through
85 self._cli("y") # Yes, continue to reset
86 self.connection.close(True)
87
Steve McIntyrecc297112014-08-11 18:46:58 +010088 ################################
89 ### VLAN API functions
90 ################################
91
92 # Create a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010093 def vlan_create(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +010094 logging.debug("Creating VLAN %d", tag)
Steve McIntyrecc297112014-08-11 18:46:58 +010095
Steve McIntyrec5359e62015-02-12 04:54:07 +000096 try:
97 self._configure()
98 self._cli("vlan database")
99 self._cli("vlan %d" % tag)
100 self._end_configure()
101
102 # Validate it happened
103 vlans = self.vlan_get_list()
104 for vlan in vlans:
105 if vlan == tag:
106 return
Steve McIntyre661af762015-02-12 06:18:07 +0000107 raise IOError("Failed to create VLAN %d" % tag)
Steve McIntyrec5359e62015-02-12 04:54:07 +0000108
109 except PExpectError:
110 # recurse on error
111 self._switch_connect()
112 self.vlan_create(tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100113
114 # Destroy a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100115 def vlan_destroy(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100116 logging.debug("Destroying VLAN %d", tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100117
Steve McIntyrec5359e62015-02-12 04:54:07 +0000118 try:
119 self._configure()
120 self._cli("no vlan %d" % tag)
121 self._end_configure()
122
123 # Validate it happened
124 vlans = self.vlan_get_list()
125 for vlan in vlans:
126 if vlan == tag:
127 raise IOError("Failed to destroy VLAN %d" % tag)
128
129 except PExpectError:
130 # recurse on error
131 self._switch_connect()
132 self.vlan_destroy(tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100133
134 # Set the name of a VLAN
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100135 def vlan_set_name(self, tag, name):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100136 logging.debug("Setting name of VLAN %d to %s", tag, name)
Steve McIntyrecc297112014-08-11 18:46:58 +0100137
Steve McIntyrec5359e62015-02-12 04:54:07 +0000138 try:
139 self._configure()
140 self._cli("vlan %d" % tag)
141 self._cli("interface vlan %d" % tag)
142 self._cli("name %s" % name)
143 self._end_configure()
144
145 # Validate it happened
146 read_name = self.vlan_get_name(tag)
147 if read_name != name:
148 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
149 % (tag, read_name, name))
150
151 except PExpectError:
152 # recurse on error
153 self._switch_connect()
154 self.vlan_set_name(tag, name)
Steve McIntyrecc297112014-08-11 18:46:58 +0100155
156 # Get a list of the VLAN tags currently registered on the switch
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100157 def vlan_get_list(self):
Steve McIntyrecc297112014-08-11 18:46:58 +0100158 logging.debug("Grabbing list of VLANs")
Steve McIntyrecc297112014-08-11 18:46:58 +0100159
Steve McIntyrec5359e62015-02-12 04:54:07 +0000160 try:
161 vlans = []
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100162 regex = re.compile(r'^ *(\d+).*(D|S|G|R)')
Steve McIntyrecc297112014-08-11 18:46:58 +0100163
Steve McIntyrec5359e62015-02-12 04:54:07 +0000164 self._cli("show vlan")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100165 for line in self._read_long_output("show vlan"):
Steve McIntyrec5359e62015-02-12 04:54:07 +0000166 match = regex.match(line)
167 if match:
168 vlans.append(int(match.group(1)))
169 return vlans
170
171 except PExpectError:
172 # recurse on error
173 self._switch_connect()
174 return self.vlan_get_list()
Steve McIntyrecc297112014-08-11 18:46:58 +0100175
176 # For a given VLAN tag, ask the switch what the associated name is
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100177 def vlan_get_name(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100178 logging.debug("Grabbing the name of VLAN %d", tag)
Steve McIntyrec5359e62015-02-12 04:54:07 +0000179
180 try:
181 name = None
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100182 regex = re.compile(r'^ *\d+\s+(\S+).*(D|S|G|R)')
Steve McIntyrec5359e62015-02-12 04:54:07 +0000183 self._cli("show vlan tag %d" % tag)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100184 for line in self._read_long_output("show vlan tag"):
Steve McIntyrec5359e62015-02-12 04:54:07 +0000185 match = regex.match(line)
186 if match:
187 name = match.group(1)
188 name.strip()
189 return name
190
191 except PExpectError:
192 # recurse on error
193 self._switch_connect()
194 return self.vlan_get_name(tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100195
196 ################################
197 ### Port API functions
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100198 ################################
Steve McIntyrecc297112014-08-11 18:46:58 +0100199
Steve McIntyre9936d002014-10-01 15:54:10 +0100200 # Set the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100201 def port_set_mode(self, port, mode):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100202 logging.debug("Setting port %s to %s", port, mode)
Steve McIntyrecc297112014-08-11 18:46:58 +0100203 if not self._is_port_mode_valid(mode):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000204 raise InputError("Port mode %s is not allowed" % mode)
Steve McIntyrecc297112014-08-11 18:46:58 +0100205 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000206 raise InputError("Port name %s not recognised" % port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100207
Steve McIntyrec5359e62015-02-12 04:54:07 +0000208 try:
209 self._configure()
210 self._cli("interface %s" % port)
211 self._cli("switchport mode %s" % mode)
212 self._end_configure()
Steve McIntyrecc297112014-08-11 18:46:58 +0100213
Steve McIntyrec5359e62015-02-12 04:54:07 +0000214 # Validate it happened
Steve McIntyreb79cea52015-07-24 17:03:31 +0100215 read_mode = self._port_get_mode(port)
Steve McIntyrec5359e62015-02-12 04:54:07 +0000216 if read_mode != mode:
217 raise IOError("Failed to set mode for port %s" % port)
218
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100219 # And cache the result
220 self._port_modes[port] = mode
221
Steve McIntyrec5359e62015-02-12 04:54:07 +0000222 except PExpectError:
223 # recurse on error
224 self._switch_connect()
225 self.port_set_mode(port, mode)
Steve McIntyrecc297112014-08-11 18:46:58 +0100226
Steve McIntyre9936d002014-10-01 15:54:10 +0100227 # Set an access port to be in a specified VLAN (tag)
228 def port_set_access_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100229 logging.debug("Setting access port %s to VLAN %d", port, tag)
Steve McIntyrecc297112014-08-11 18:46:58 +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 McIntyre9936d002014-10-01 15:54:10 +0100232 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000233 raise InputError("Port %s not in access mode" % port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100234
Steve McIntyrec5359e62015-02-12 04:54:07 +0000235 try:
236 self._configure()
237 self._cli("interface %s" % port)
238 self._cli("switchport access vlan %d" % tag)
239 self._end_configure()
Steve McIntyrecc297112014-08-11 18:46:58 +0100240
Steve McIntyrec5359e62015-02-12 04:54:07 +0000241 # Validate things worked
242 read_vlan = int(self.port_get_access_vlan(port))
243 if read_vlan != tag:
244 raise IOError("Failed to move access port %s to VLAN %d - got VLAN %d instead"
245 % (port, tag, read_vlan))
246
247 except PExpectError:
248 # recurse on error
249 self._switch_connect()
250 self.port_set_access_vlan(port, tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100251
Steve McIntyrecc297112014-08-11 18:46:58 +0100252 # Add a trunk port to a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100253 def port_add_trunk_to_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100254 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100255 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000256 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100257 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000258 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100259
Steve McIntyrec5359e62015-02-12 04:54:07 +0000260 try:
261 self._configure()
262 self._cli("interface %s" % port)
263 self._cli("switchport trunk allowed vlan add %d" % tag)
264 self._end_configure()
265
266 # Validate it happened
267 read_vlans = self.port_get_trunk_vlan_list(port)
268 for vlan in read_vlans:
269 if vlan == tag:
270 return
Steve McIntyref1771282015-04-29 18:08:34 +0100271 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
Steve McIntyrec5359e62015-02-12 04:54:07 +0000272
273 except PExpectError:
274 # recurse on error
275 self._switch_connect()
276 self.port_add_trunk_to_vlan(port, tag)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100277
Steve McIntyrecc297112014-08-11 18:46:58 +0100278 # Remove a trunk port from a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100279 def port_remove_trunk_from_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100280 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100281 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000282 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100283 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000284 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100285
Steve McIntyrec5359e62015-02-12 04:54:07 +0000286 try:
287 self._configure()
288 self._cli("interface %s" % port)
289 self._cli("switchport trunk allowed vlan remove %d" % tag)
290 self._end_configure()
291
292 # Validate it happened
293 read_vlans = self.port_get_trunk_vlan_list(port)
294 for vlan in read_vlans:
295 if vlan == tag:
296 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
297
298 except PExpectError:
299 # recurse on error
300 self._switch_connect()
301 self.port_remove_trunk_from_vlan(port, tag)
Steve McIntyrecc297112014-08-11 18:46:58 +0100302
Steve McIntyre9936d002014-10-01 15:54:10 +0100303 # Get the configured VLAN tag for an access port (tag)
304 def port_get_access_vlan(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100305 logging.debug("Getting VLAN for access port %s", port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100306 vlan = 1
307 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000308 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100309 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000310 raise InputError("Port %s not in access mode" % port)
Steve McIntyre6b4ff2e2015-09-04 14:36:00 +0100311 regex = re.compile(r'(\d+)\s+\S+\s+Untagged\s+(D|S|G|R)')
Steve McIntyrec5359e62015-02-12 04:54:07 +0000312
313 try:
314 self._cli("show interfaces switchport %s" % port)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100315 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyrec5359e62015-02-12 04:54:07 +0000316 match = regex.match(line)
317 if match:
318 vlan = match.group(1)
319 return int(vlan)
320
321 except PExpectError:
322 # recurse on error
323 self._switch_connect()
324 return self.port_get_access_vlan(port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100325
326 # Get the list of configured VLAN tags for a trunk port
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100327 def port_get_trunk_vlan_list(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100328 logging.debug("Getting VLANs for trunk port %s", port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100329 vlans = [ ]
330 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000331 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100332 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000333 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyre6b4ff2e2015-09-04 14:36:00 +0100334 regex = re.compile(r'(\d+)\s+\S+\s+(Tagged|Untagged)\s+(D|S|G|R)')
Steve McIntyrec5359e62015-02-12 04:54:07 +0000335
336 try:
337 self._cli("show interfaces switchport %s" % port)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100338 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyrec5359e62015-02-12 04:54:07 +0000339 match = regex.match(line)
340 if match:
341 vlans.append (int(match.group(1)))
342 return vlans
343
344 except PExpectError:
345 # recurse on error
346 self._switch_connect()
347 return self.port_get_trunk_vlan_list(port)
Steve McIntyrecc297112014-08-11 18:46:58 +0100348
349 ################################
350 ### Internal functions
351 ################################
352
Steve McIntyreb345fc52015-02-12 06:37:05 +0000353 # Connect to the switch and log in
354 def _switch_connect(self):
355
356 if not self.connection is None:
357 self.connection.close(True)
358 self.connection = None
359
Steve McIntyre5fa22652015-04-01 18:01:45 +0100360 logging.debug("Connecting to Switch with: %s", self.exec_string)
Steve McIntyre06c644a2015-07-17 17:07:41 +0100361 self.connection = pexpect.spawn(self.exec_string, logfile=self.logger)
Steve McIntyreb345fc52015-02-12 06:37:05 +0000362 self._login()
363
364 # Avoid paged output
365 self._cli("terminal datadump")
366
367 # And grab details about the switch. in case we need it
368 self._get_systemdata()
369
370 # And also validate them - make sure we're driving a switch of
371 # the correct model! Also store the serial number
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100372 descr_regex = re.compile(r'System Description:.\s+(.*)')
373 sn_regex = re.compile(r'SN:\s+(\S_)')
Steve McIntyreb345fc52015-02-12 06:37:05 +0000374 descr = ""
375
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100376 for line in self._systemdata:
Steve McIntyreb345fc52015-02-12 06:37:05 +0000377 match = descr_regex.match(line)
378 if match:
379 descr = match.group(1)
380 match = sn_regex.match(line)
381 if match:
382 self.serial_number = match.group(1)
383
384 if not self._expected_descr_re.match(descr):
385 raise IOError("Switch %s not recognised by this driver: abort" % descr)
386
387 # Now build a list of our ports, for later sanity checking
388 self._ports = self._get_port_names()
389 if len(self._ports) < 4:
390 raise IOError("Not enough ports detected - problem!")
391
Steve McIntyrebbd2ac82015-02-12 03:08:48 +0000392 def _login(self):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100393 logging.debug("attempting login with username %s, password %s", self._username, self._password)
Steve McIntyrecc297112014-08-11 18:46:58 +0100394 self._cli("")
395 self.connection.expect("User Name:")
Steve McIntyrebbd2ac82015-02-12 03:08:48 +0000396 self._cli("%s" % self._username)
Steve McIntyrecc297112014-08-11 18:46:58 +0100397 self.connection.expect("Password:")
Steve McIntyrebbd2ac82015-02-12 03:08:48 +0000398 self._cli("%s" % self._password, False)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100399 self.connection.expect(r"\*\*")
Steve McIntyrecc297112014-08-11 18:46:58 +0100400 while True:
Steve McIntyre2651aca2015-02-12 05:10:58 +0000401 index = self.connection.expect(['User Name:', 'authentication failed', r'([^#]+)#', 'Password:', '.+'])
Steve McIntyrecc297112014-08-11 18:46:58 +0100402 if index == 0 or index == 1: # Failed to log in!
Steve McIntyre5fa22652015-04-01 18:01:45 +0100403 logging.error("Login failure: %s\n", self.connection.match)
Steve McIntyrecc297112014-08-11 18:46:58 +0100404 raise IOError
405 elif index == 2:
Steve McIntyre65dfe7f2015-06-09 15:57:02 +0100406 self._prompt_name = re.escape(self.connection.match.group(1).strip())
Steve McIntyre5c51bc52015-02-12 05:34:09 +0000407 # Horrible output from the switch at login time may
408 # confuse our pexpect stuff here. If we've somehow got
409 # multiple lines of output, clean up and just take the
410 # *last* line here. Anything before that is going to
411 # just be noise from the "***" password input, etc.
412 prompt_lines = self._prompt_name.split('\r\n')
413 if len(prompt_lines) > 1:
414 self._prompt_name = prompt_lines[-1]
Steve McIntyre5fa22652015-04-01 18:01:45 +0100415 logging.debug("Got prompt name %s", self._prompt_name)
Steve McIntyrecc297112014-08-11 18:46:58 +0100416 return 0
Steve McIntyreca6c5a32014-12-23 15:34:29 +0000417 elif index == 3 or index == 4:
418 self._cli("", False)
Steve McIntyrecc297112014-08-11 18:46:58 +0100419
420 def _logout(self):
421 logging.debug("Logging out")
422 self._cli("exit", False)
Steve McIntyre26c89562015-10-09 15:02:37 +0100423 self.connection.close(True)
Steve McIntyrecc297112014-08-11 18:46:58 +0100424
Steve McIntyrecc297112014-08-11 18:46:58 +0100425 def _configure(self):
426 self._cli("configure terminal")
427
428 def _end_configure(self):
429 self._cli("end")
430
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100431 def _read_long_output(self, text):
Steve McIntyre366046f2014-08-18 18:57:03 +0100432 prompt = self._prompt_name + '#'
Steve McIntyre2eececb2015-02-12 03:09:17 +0000433 try:
434 self.connection.expect(prompt)
Steve McIntyre1d23b492015-02-12 03:47:24 +0000435 except (pexpect.EOF, pexpect.TIMEOUT):
Steve McIntyred0614fb2015-02-12 03:44:42 +0000436 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000437 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100438 self.errors.log_error_in(text)
Steve McIntyre1a3e2f72015-02-12 03:49:05 +0000439 raise PExpectError("_read_long_output failed")
Steve McIntyre2eececb2015-02-12 03:09:17 +0000440 except:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100441 logging.error("prompt is \"%s\"", prompt)
Steve McIntyre2eececb2015-02-12 03:09:17 +0000442 raise
Steve McIntyredf6aabf2015-02-12 06:33:19 +0000443
Steve McIntyre5fa22652015-04-01 18:01:45 +0100444 longbuf = []
Steve McIntyrea7cdefc2014-12-24 00:48:19 +0000445 for line in self.connection.before.split('\r\n'):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100446 longbuf.append(line.strip())
447 return longbuf
Steve McIntyrecc297112014-08-11 18:46:58 +0100448
449 def _get_port_names(self):
450 logging.debug("Grabbing list of ports")
451 interfaces = []
452
453 # Use "Up" or "Down" to only identify lines in the output that
454 # match interfaces that exist
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100455 regex = re.compile(r'^(\w+).*(Up|Down)')
Steve McIntyrecc297112014-08-11 18:46:58 +0100456
Steve McIntyre48ab82c2015-02-12 04:59:10 +0000457 try:
458 self._cli("show interfaces status detailed")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100459 for line in self._read_long_output("show interfaces status detailed"):
Steve McIntyre48ab82c2015-02-12 04:59:10 +0000460 match = regex.match(line)
461 if match:
Steve McIntyred5cfde12015-08-05 13:49:42 +0100462 interface = match.group(1)
463 interfaces.append(interface)
464 self._port_numbers[interface] = len(interfaces)
Steve McIntyred601ab82015-07-09 17:42:36 +0100465 logging.debug(" found %d ports on the switch", len(interfaces))
Steve McIntyre48ab82c2015-02-12 04:59:10 +0000466 return interfaces
467 except PExpectError:
468 # recurse on error
469 self._switch_connect()
470 return self._get_port_names()
Steve McIntyrecc297112014-08-11 18:46:58 +0100471
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100472 # Get the mode of a port: access or trunk
473 def _port_get_mode(self, port):
474 logging.debug("Getting mode of port %s", port)
475 mode = ''
476 if not self._is_port_name_valid(port):
477 raise InputError("Port name %s not recognised" % port)
478 regex = re.compile(r'Port Mode: (\S+)')
479
480 try:
481 self._cli("show interfaces switchport %s" % port)
482 for line in self._read_long_output("show interfaces switchport"):
483 match = regex.match(line)
484 if match:
485 mode = match.group(1)
486 return mode.lower()
487
488 except PExpectError:
489 # recurse on error
490 self._switch_connect()
491 return self.port_get_mode(port)
492
Steve McIntyrecc297112014-08-11 18:46:58 +0100493 def _show_config(self):
494 logging.debug("Grabbing config")
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000495 try:
496 self._cli("show running-config")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100497 return self._read_long_output("show running-config")
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000498 except PExpectError:
499 # recurse on error
500 self._switch_connect()
501 return self._show_config()
Steve McIntyrecc297112014-08-11 18:46:58 +0100502
503 def _show_clock(self):
504 logging.debug("Grabbing time")
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000505 try:
506 self._cli("show clock")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100507 return self._read_long_output("show clock")
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000508 except PExpectError:
509 # recurse on error
510 self._switch_connect()
511 return self._show_clock()
Steve McIntyrecc297112014-08-11 18:46:58 +0100512
Steve McIntyrecc297112014-08-11 18:46:58 +0100513 def _get_systemdata(self):
Steve McIntyredf6aabf2015-02-12 06:33:19 +0000514 logging.debug("Grabbing system data")
Steve McIntyrecc297112014-08-11 18:46:58 +0100515
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000516 try:
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100517 self._systemdata = []
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000518 self._cli("show system")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100519 for line in self._read_long_output("show system"):
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100520 self._systemdata.append(line)
Steve McIntyre28d34ca2014-08-12 15:40:24 +0100521
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000522 logging.debug("Grabbing system sw and hw versions")
523 self._cli("show version")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100524 for line in self._read_long_output("show version"):
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100525 self._systemdata.append(line)
Steve McIntyre0fbb2dc2015-02-12 05:11:27 +0000526
527 except PExpectError:
528 # recurse on error
529 self._switch_connect()
530 return self._get_systemdata()
Steve McIntyrecc297112014-08-11 18:46:58 +0100531
Steve McIntyre16f02162014-08-14 17:47:36 +0100532 ######################################
533 # Internal port access helper methods
534 ######################################
535 # N.B. No parameter checking here, for speed reasons - if you're
536 # calling this internal API then you should already have validated
537 # things yourself! Equally, no post-set checks in here - do that
538 # at the higher level.
539 ######################################
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100540
Steve McIntyrecc297112014-08-11 18:46:58 +0100541 # Wrapper around connection.send - by default, expect() the same
542 # text we've sent, to remove it from the output from the
543 # switch. For the few cases where we don't need that, override
544 # this using echo=False.
545 # Horrible, but seems to work.
546 def _cli(self, text, echo=True):
547 self.connection.send(text + '\r')
548 if echo:
Steve McIntyre9f7f03b2015-02-12 03:10:13 +0000549 try:
550 self.connection.expect(text)
Steve McIntyre1d23b492015-02-12 03:47:24 +0000551 except (pexpect.EOF, pexpect.TIMEOUT):
Steve McIntyre9f7f03b2015-02-12 03:10:13 +0000552 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000553 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyre3c3f4fc2015-02-12 04:26:27 +0000554 self.errors.log_error_out(text)
Steve McIntyre729d9502015-02-12 03:14:10 +0000555 raise PExpectError("_cli failed on %s" % text)
Steve McIntyre9f7f03b2015-02-12 03:10:13 +0000556 except:
Steve McIntyrea67473a2015-02-12 08:26:33 +0000557 logging.error("Unexpected error: %s", sys.exc_info()[0])
Steve McIntyre9f7f03b2015-02-12 03:10:13 +0000558 raise
Steve McIntyrecc297112014-08-11 18:46:58 +0100559
560if __name__ == "__main__":
Steve McIntyref1699322014-08-19 22:49:43 +0100561# p = CiscoSX300('10.172.2.52', 23)
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000562
Steve McIntyreda9e9c12018-01-24 16:58:13 +0000563 # Simple test harness - exercise the main working functions above to verify
564 # they work. This does *NOT* test really disruptive things like "save
565 # running-config" and "reload" - test those by hand.
566
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000567 import optparse
568
569 switch = 'vlandswitch02'
570 parser = optparse.OptionParser()
571 parser.add_option("--switch",
572 dest = "switch",
573 action = "store",
574 nargs = 1,
575 type = "string",
576 help = "specify switch to connect to for testing",
577 metavar = "<switch>")
578 (opts, args) = parser.parse_args()
579 if opts.switch:
580 switch = opts.switch
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100581
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100582 portname_base = "fa"
583 # Text to match if we're on a SG-series switch, ports all called gi<number>
584 sys_descr_re = re.compile('System Description.*Gigabit')
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000585
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100586 def _port_name(number):
587 return "%s%d" % (portname_base, number)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100588
Steve McIntyre144d51c2015-07-07 17:56:30 +0100589 logging.basicConfig(level = logging.DEBUG,
590 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000591 p = CiscoSX300(switch, 23, debug = True)
Steve McIntyreaea995c2014-12-22 17:17:38 +0000592 p.switch_connect('cisco', 'cisco', None)
Steve McIntyrecc297112014-08-11 18:46:58 +0100593 #buf = p._show_clock()
594 #print "%s" % buf
595 #buf = p._show_config()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100596 #p.dump_list(buf)
Steve McIntyrecc297112014-08-11 18:46:58 +0100597
Steve McIntyre366046f2014-08-18 18:57:03 +0100598 print "System data:"
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100599 p.dump_list(p._systemdata)
600 for l in p._systemdata:
601 m = sys_descr_re.match(l)
602 if m:
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100603 print 'Found an SG switch, using "gi" as port name prefix for testing'
604 portname_base = "gi"
605
606 if portname_base == "fa":
607 print 'Found an SF switch, using "fa" as port name prefix for testing'
Steve McIntyrecc297112014-08-11 18:46:58 +0100608
609 print "Creating VLANs for testing:"
610 for i in [ 2, 3, 4, 5, 20 ]:
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100611 p.vlan_create(i)
612 p.vlan_set_name(i, "test%d" % i)
Steve McIntyrecc297112014-08-11 18:46:58 +0100613 print " %d (test%d)" % (i, i)
614
615 #print "And dump config\n"
616 #buf = p._show_config()
617 #print "%s" % buf
618
619 #print "Destroying VLAN 2\n"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100620 #p.vlan_destroy(2)
Steve McIntyrecc297112014-08-11 18:46:58 +0100621
622 #print "And dump config\n"
623 #buf = p._show_config()
624 #print "%s" % buf
625
626 #print "Port names are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100627 #buf = p.switch_get_port_names()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100628 #p.dump_list(buf)
Steve McIntyrecc297112014-08-11 18:46:58 +0100629
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100630 #buf = p.vlan_get_name(25)
Steve McIntyrecc297112014-08-11 18:46:58 +0100631 #print "VLAN with tag 25 is called \"%s\"" % buf
632
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100633 #p.vlan_set_name(35, "foo")
Steve McIntyrecc297112014-08-11 18:46:58 +0100634 #print "VLAN with tag 35 is called \"foo\""
635
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100636 #buf = p.port_get_mode(_port_name(12))
637 #print "Port %s is in %s mode" % (_port_name(12), buf)
Steve McIntyrecc297112014-08-11 18:46:58 +0100638
Steve McIntyre9936d002014-10-01 15:54:10 +0100639 # Test access stuff
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100640 print "Set %s to access mode" % _port_name(6)
641 p.port_set_mode(_port_name(6), "access")
642 print "Move %s to VLAN 2" % _port_name(6)
643 p.port_set_access_vlan(_port_name(6), 2)
644 buf = p.port_get_access_vlan(_port_name(6))
645 print "Read from switch: %s is on VLAN %s" % (_port_name(6), buf)
646 print "Move %s back to default VLAN 1" % _port_name(6)
647 p.port_set_access_vlan(_port_name(6), 1)
648 #print "And move %s back to a trunk port" % _port_name(6)
649 #p.port_set_mode(_port_name(6), "trunk")
650 #buf = p.port_get_mode(_port_name(6))
651 #print "Port %s is in %s mode" % (_port_name(6), buf)
Steve McIntyrecc297112014-08-11 18:46:58 +0100652
653 # Test trunk stuff
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100654 print "Set %s to trunk mode" % _port_name(2)
655 p.port_set_mode(_port_name(2), "trunk")
656 print "Add %s to VLAN 2" % _port_name(2)
657 p.port_add_trunk_to_vlan(_port_name(2), 2)
658 print "Add %s to VLAN 3" % _port_name(2)
659 p.port_add_trunk_to_vlan(_port_name(2), 3)
660 print "Add %s to VLAN 4" % _port_name(2)
661 p.port_add_trunk_to_vlan(_port_name(2), 4)
662 print "Read from switch: which VLANs is %s on?" % _port_name(2)
663 buf = p.port_get_trunk_vlan_list(_port_name(2))
Steve McIntyre5fa22652015-04-01 18:01:45 +0100664 p.dump_list(buf)
Steve McIntyrecc297112014-08-11 18:46:58 +0100665
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100666 print "Remove %s from VLANs 3,3,4" % _port_name(2)
667 p.port_remove_trunk_from_vlan(_port_name(2), 3)
668 p.port_remove_trunk_from_vlan(_port_name(2), 3)
669 p.port_remove_trunk_from_vlan(_port_name(2), 4)
670 print "Read from switch: which VLANs is %s on?" % _port_name(2)
671 buf = p.port_get_trunk_vlan_list(_port_name(2))
Steve McIntyre5fa22652015-04-01 18:01:45 +0100672 p.dump_list(buf)
Steve McIntyrecc297112014-08-11 18:46:58 +0100673
674 # print "Adding lots of ports to VLANs"
Steve McIntyre3d92ef72015-07-08 20:02:18 +0100675 # p.port_add_trunk_to_vlan(_port_name(1), 2)
676 # p.port_add_trunk_to_vlan(_port_name(3), 2)
677 # p.port_add_trunk_to_vlan(_port_name(5), 2)
678 # p.port_add_trunk_to_vlan(_port_name(7), 2)
679 # p.port_add_trunk_to_vlan(_port_name(9), 2)
680 # p.port_add_trunk_to_vlan(_port_name(11), 2)
681 # p.port_add_trunk_to_vlan(_port_name(13), 2)
682 # p.port_add_trunk_to_vlan(_port_name(15), 2)
683 # p.port_add_trunk_to_vlan(_port_name(17), 2)
684 # p.port_add_trunk_to_vlan(_port_name(19), 2)
685 # p.port_add_trunk_to_vlan(_port_name(21), 2)
686 # p.port_add_trunk_to_vlan(_port_name(23), 2)
687 # p.port_add_trunk_to_vlan(_port_name(4, 2)
Steve McIntyrecc297112014-08-11 18:46:58 +0100688
689 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100690 buf = p.vlan_get_list()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100691 p.dump_list(buf)
Steve McIntyrecc297112014-08-11 18:46:58 +0100692
Steve McIntyre7460d972014-12-23 14:45:30 +0000693# print 'Restarting switch, to explicitly reset config'
694# p.switch_restart()
Steve McIntyrecc297112014-08-11 18:46:58 +0100695
Steve McIntyrebf5dc882014-12-19 17:54:58 +0000696# p.switch_save_running_config()
Steve McIntyrecc297112014-08-11 18:46:58 +0100697# p._show_config()