blob: d3cea5956345ff8cc7d0c77639250ba9d5cefc61 [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
Steve McIntyred6759dd2014-08-12 18:10:00 +010023import re
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000024
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 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):
Steve McIntyre5fa22652015-04-01 18:01:45 +010054 SwitchDriver.__init__(self)
55 self._systemdata = []
Steve McIntyre48dc6ae2014-12-23 16:08:19 +000056 if debug:
Steve McIntyre9f5a0232014-12-23 16:14:28 +000057 self.logfile = sys.stderr
Steve McIntyred6759dd2014-08-12 18:10:00 +010058 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000059 self.errors = SwitchErrors()
Steve McIntyred6759dd2014-08-12 18:10:00 +010060
61 ################################
62 ### Switch-level API functions
63 ################################
64
65 # Connect to the switch and log in
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010066 def switch_connect(self, username, password, enablepassword):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000067 self._username = username
68 self._password = password
69 self._enable_password = enablepassword
70 self._switch_connect()
Steve McIntyred6759dd2014-08-12 18:10:00 +010071
72 # Log out of the switch and drop the connection and all state
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010073 def switch_disconnect(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +010074 self._logout()
Steve McIntyre5fa22652015-04-01 18:01:45 +010075 logging.debug("Closing connection: %s", self.connection)
Steve McIntyred6759dd2014-08-12 18:10:00 +010076 self.connection.close(True)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000077 self._ports = []
78 self._prompt_name = ''
79 self._systemdata = []
Steve McIntyred6759dd2014-08-12 18:10:00 +010080 del(self)
81
82 # Save the current running config into flash - we want config to
83 # remain across reboots
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010084 def switch_save_running_config(self):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000085 try:
86 self._cli("copy running-config startup-config")
87 self.connection.expect("startup-config")
88 self._cli("startup-config")
89 self.connection.expect("OK")
Steve McIntyre2d84e522015-02-12 06:44:42 +000090 except (PExpectError, pexpect.EOF):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000091 # recurse on error
92 self._switch_connect()
93 self.switch_save_running_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +010094
Steve McIntyre095b4452014-12-19 17:53:43 +000095 # Restart the switch - we need to reload config to do a
96 # roll-back. Do NOT save running-config first if the switch asks -
97 # we're trying to dump recent changes, not save them.
98 #
99 # This will also implicitly cause a connection to be closed
100 def switch_restart(self):
101 self._cli("reload")
102 index = self.connection.expect(['has been modified', 'Proceed'])
103 if index == 0:
104 self._cli("n") # No, don't save
105 self.connection.expect("Proceed")
106
107 # Fall through
108 self._cli("y") # Yes, continue to reset
109 self.connection.close(True)
110
Steve McIntyre3f287882014-08-18 19:02:15 +0100111 # List the capabilities of the switch (and driver) - some things
112 # make no sense to abstract. Returns a dict of strings, each one
113 # describing an extra feature that that higher levels may care
114 # about
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100115 def switch_get_capabilities(self):
Steve McIntyre3f287882014-08-18 19:02:15 +0100116 return self._capabilities
Steve McIntyred6759dd2014-08-12 18:10:00 +0100117
118 ################################
119 ### VLAN API functions
120 ################################
121
122 # Create a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100123 def vlan_create(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100124 logging.debug("Creating VLAN %d", tag)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000125 try:
126 self._configure()
127 self._cli("vlan %d" % tag)
128 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100129
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000130 # Validate it happened
131 vlans = self.vlan_get_list()
132 for vlan in vlans:
133 if vlan == tag:
134 return
135 raise IOError("Failed to create VLAN %d" % tag)
136
137 except PExpectError:
138 # recurse on error
139 self._switch_connect()
140 self.vlan_create(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100141
142 # Destroy a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100143 def vlan_destroy(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100144 logging.debug("Destroying VLAN %d", tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100145
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000146 try:
147 self._configure()
148 self._cli("no vlan %d" % tag)
149 self._end_configure()
150
151 # Validate it happened
152 vlans = self.vlan_get_list()
153 for vlan in vlans:
154 if vlan == tag:
155 raise IOError("Failed to destroy VLAN %d" % tag)
156
157 except PExpectError:
158 # recurse on error
159 self._switch_connect()
160 self.vlan_destroy(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100161
162 # Set the name of a VLAN
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100163 def vlan_set_name(self, tag, name):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100164 logging.debug("Setting name of VLAN %d to %s", tag, name)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100165
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000166 try:
167 self._configure()
168 self._cli("vlan %d" % tag)
169 self._cli("name %s" % name)
170 self._end_configure()
171
172 # Validate it happened
173 read_name = self.vlan_get_name(tag)
174 if read_name != name:
175 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
176 % (tag, read_name, name))
177 except PExpectError:
178 # recurse on error
179 self._switch_connect()
180 self.vlan_set_name(tag, name)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100181
182 # Get a list of the VLAN tags currently registered on the switch
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100183 def vlan_get_list(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100184 logging.debug("Grabbing list of VLANs")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100185
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000186 try:
187 vlans = []
Steve McIntyred6759dd2014-08-12 18:10:00 +0100188
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000189 regex = re.compile('^ *(\d+).*(active)')
190
191 self._cli("show vlan brief")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100192 for line in self._read_long_output("show vlan brief"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000193 match = regex.match(line)
194 if match:
195 vlans.append(int(match.group(1)))
196 return vlans
197
198 except PExpectError:
199 # recurse on error
200 self._switch_connect()
201 return self.vlan_get_list()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100202
203 # For a given VLAN tag, ask the switch what the associated name is
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100204 def vlan_get_name(self, tag):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000205
206 try:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100207 logging.debug("Grabbing the name of VLAN %d", tag)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000208 name = None
209 regex = re.compile('^ *\d+\s+(\S+).*(active)')
210 self._cli("show vlan id %d" % tag)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100211 for line in self._read_long_output("show vlan id"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000212 match = regex.match(line)
213 if match:
214 name = match.group(1)
215 name.strip()
216 return name
217
218 except PExpectError:
219 # recurse on error
220 self._switch_connect()
221 return self.vlan_get_name(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100222
223 ################################
224 ### Port API functions
Steve McIntyree1bf11a2014-08-14 17:56:25 +0100225 ################################
Steve McIntyred6759dd2014-08-12 18:10:00 +0100226
Steve McIntyre9936d002014-10-01 15:54:10 +0100227 # Set the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100228 def port_set_mode(self, port, mode):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100229 logging.debug("Setting port %s to %s", port, mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100230 if not self._is_port_mode_valid(mode):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000231 raise InputError("Port mode %s is not allowed" % mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100232 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000233 raise InputError("Port name %s not recognised" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100234
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000235 try:
236 self._configure()
237 self._cli("interface %s" % port)
238 self._cli("switchport mode %s" % mode)
239 if mode == "trunk":
240 self._cli("switchport trunk encapsulation dot1q")
Steve McIntyre747050a2015-06-09 18:04:34 +0100241 self._cli("switchport trunk native vlan 1")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000242 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100243
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000244 # Validate it happened
245 read_mode = self.port_get_mode(port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100246
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000247 if read_mode != mode:
248 raise IOError("Failed to set mode for port %s" % port)
249
250 except PExpectError:
251 # recurse on error
252 self._switch_connect()
253 self.port_set_mode(port, mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100254
Steve McIntyre9936d002014-10-01 15:54:10 +0100255 # Get the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100256 def port_get_mode(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100257 logging.debug("Getting mode of port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100258 mode = ''
259 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000260 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100261 regex = re.compile('Administrative Mode: (.*)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000262
263 try:
264 self._cli("show interfaces %s switchport" % port)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100265 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000266 match = regex.match(line)
267 if match:
268 mode = match.group(1)
269 if mode == 'static access':
270 return 'access'
271 if mode == 'dynamic auto':
272 return 'trunk'
273 return mode
274
275 except PExpectError:
276 # recurse on error
277 self._switch_connect()
278 return self.port_get_mode(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100279
Steve McIntyre9936d002014-10-01 15:54:10 +0100280 # Set an access port to be in a specified VLAN (tag)
281 def port_set_access_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100282 logging.debug("Setting access port %s to VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100283 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000284 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100285 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000286 raise InputError("Port %s not in access mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100287
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000288 try:
289 self._configure()
290 self._cli("interface %s" % port)
291 self._cli("switchport access vlan %d" % tag)
292 self._cli("no shutdown")
293 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100294
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000295 # Finally, validate things worked
296 read_vlan = int(self.port_get_access_vlan(port))
297 if read_vlan != tag:
298 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
299 % (port, tag, read_vlan))
300
301 except PExpectError:
302 # recurse on error
303 self._switch_connect()
304 self.port_set_access_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100305
Steve McIntyred6759dd2014-08-12 18:10:00 +0100306 # Add a trunk port to a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100307 def port_add_trunk_to_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100308 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100309 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000310 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100311 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000312 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100313
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000314 try:
315 self._configure()
316 self._cli("interface %s" % port)
317 self._cli("switchport trunk allowed vlan add %d" % tag)
318 self._end_configure()
319
320 # Validate it happened
321 read_vlans = self.port_get_trunk_vlan_list(port)
322 for vlan in read_vlans:
323 if vlan == tag or vlan == "ALL":
324 return
325 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
326
327 except PExpectError:
328 # recurse on error
329 self._switch_connect()
330 self.port_add_trunk_to_vlan(port, tag)
331
Steve McIntyred6759dd2014-08-12 18:10:00 +0100332 # Remove a trunk port from a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100333 def port_remove_trunk_from_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100334 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100335 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000336 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100337 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000338 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100339
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000340 try:
341 self._configure()
342 self._cli("interface %s" % port)
343 self._cli("switchport trunk allowed vlan remove %d" % tag)
344 self._end_configure()
345
346 # Validate it happened
347 read_vlans = self.port_get_trunk_vlan_list(port)
348 for vlan in read_vlans:
349 if vlan == tag:
350 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
351
352 except PExpectError:
353 # recurse on error
354 self._switch_connect()
355 self.port_remove_trunk_from_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100356
Steve McIntyre9936d002014-10-01 15:54:10 +0100357 # Get the configured VLAN tag for an access port (tag)
358 def port_get_access_vlan(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100359 logging.debug("Getting VLAN for access port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100360 vlan = 1
361 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000362 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100363 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000364 raise InputError("Port %s not in access mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100365 regex = re.compile('Access Mode VLAN: (\d+)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000366
367 try:
368 self._cli("show interfaces %s switchport" % port)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100369 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000370 match = regex.match(line)
371 if match:
372 vlan = match.group(1)
373 return int(vlan)
374
375 except PExpectError:
376 # recurse on error
377 self._switch_connect()
378 return self.port_get_access_vlan(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100379
380 # Get the list of configured VLAN tags for a trunk port
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100381 def port_get_trunk_vlan_list(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100382 logging.debug("Getting VLANs for trunk port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100383 vlans = [ ]
384 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000385 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100386 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000387 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100388 regex_start = re.compile('Trunking VLANs Enabled: (.*)')
389 regex_continue = re.compile('\s*(\d.*)')
Steve McIntyre3f287882014-08-18 19:02:15 +0100390
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000391 try:
392 self._cli("show interfaces %s switchport" % port)
393
394 # Horrible parsing work - VLAN list may extend over several lines
395 in_match = False
396 vlan_text = ''
397
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100398 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000399 if in_match:
400 match = regex_continue.match(line)
401 if match:
402 vlan_text += match.group(1)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000403 else:
404 in_match = False
Steve McIntyre3f287882014-08-18 19:02:15 +0100405 else:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000406 match = regex_start.match(line)
407 if match:
408 vlan_text += match.group(1)
409 in_match = True
Steve McIntyre3f287882014-08-18 19:02:15 +0100410
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000411 vlans = self._parse_vlan_list(vlan_text)
412 return vlans
Steve McIntyre3f287882014-08-18 19:02:15 +0100413
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000414 except PExpectError:
415 # recurse on error
416 self._switch_connect()
417 return self.port_get_trunk_vlan_list(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100418
419 ################################
420 ### Internal functions
421 ################################
422
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000423 # Connect to the switch and log in
424 def _switch_connect(self):
425
426 if not self.connection is None:
427 self.connection.close(True)
428 self.connection = None
429
Steve McIntyre5fa22652015-04-01 18:01:45 +0100430 logging.debug("Connecting to Switch with: %s", self.exec_string)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000431 self.connection = pexpect.spawn(self.exec_string, logfile = self.logfile)
432
433 self._login()
434
435 # Avoid paged output
436 self._cli("terminal length 0")
437
438 # And grab details about the switch. in case we need it
439 self._get_systemdata()
440
441 # And also validate them - make sure we're driving a switch of
442 # the correct model! Also store the serial number
443 descr_regex = re.compile('^cisco\s+(\S+)')
444 sn_regex = re.compile('System serial number\s+:\s+(\S+)')
445 descr = ""
446
447 for line in self._systemdata:
448 match = descr_regex.match(line)
449 if match:
450 descr = match.group(1)
451 match = sn_regex.match(line)
452 if match:
453 self.serial_number = match.group(1)
454
Steve McIntyre5fa22652015-04-01 18:01:45 +0100455 logging.debug("serial number is %s", self.serial_number)
456 logging.debug("system description is %s", descr)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000457
458 if not self._expected_descr_re.match(descr):
459 raise IOError("Switch %s not recognised by this driver: abort" % descr)
460
461 # Now build a list of our ports, for later sanity checking
462 self._ports = self._get_port_names()
463 if len(self._ports) < 4:
464 raise IOError("Not enough ports detected - problem!")
465
466 def _login(self):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100467 logging.debug("attempting login with username %s, password %s", self._username, self._password)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100468 self.connection.expect('User Access Verification')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000469 if self._username is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100470 self.connection.expect("User Name:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000471 self._cli("%s" % self._username)
472 if self._password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100473 self.connection.expect("Password:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000474 self._cli("%s" % self._password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100475 while True:
476 index = self.connection.expect(['User Name:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
477 if index != 4: # Any other means: failed to log in!
Steve McIntyre5fa22652015-04-01 18:01:45 +0100478 logging.error("Login failure: index %d\n", index)
479 logging.error("Login failure: %s\n", self.connection.match.before)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100480 raise IOError
481
482 # else
Steve McIntyre65dfe7f2015-06-09 15:57:02 +0100483 self._prompt_name = re.escape(self.connection.match.group(1).strip())
Steve McIntyred6759dd2014-08-12 18:10:00 +0100484 if self.connection.match.group(2) == ">":
485 # Need to enter "enable" mode too
486 self._cli("enable")
Steve McIntyread12cd72015-02-12 06:41:29 +0000487 if self._enable_password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100488 self.connection.expect("Password:")
Steve McIntyread12cd72015-02-12 06:41:29 +0000489 self._cli("%s" % self._enable_password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100490 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
491 if index != 3: # Any other means: failed to log in!
Steve McIntyre5fa22652015-04-01 18:01:45 +0100492 logging.error("Enable password failure: %s\n", self.connection.match)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100493 raise IOError
494 return 0
495
496 def _logout(self):
497 logging.debug("Logging out")
498 self._cli("exit", False)
499
500 def _configure(self):
501 self._cli("configure terminal")
502
503 def _end_configure(self):
504 self._cli("end")
505
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100506 def _read_long_output(self, text):
Steve McIntyre3f287882014-08-18 19:02:15 +0100507 prompt = self._prompt_name + '#'
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000508 try:
509 self.connection.expect(prompt)
510 except (pexpect.EOF, pexpect.TIMEOUT):
511 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000512 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100513 self.errors.log_error_in(text)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000514 raise PExpectError("_read_long_output failed")
515 except:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100516 logging.error("prompt is \"%s\"", prompt)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000517 raise
518
Steve McIntyre5fa22652015-04-01 18:01:45 +0100519 longbuf = []
Steve McIntyrea7cdefc2014-12-24 00:48:19 +0000520 for line in self.connection.before.split('\r\n'):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100521 longbuf.append(line.strip())
522 return longbuf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100523
524 def _get_port_names(self):
525 logging.debug("Grabbing list of ports")
526 interfaces = []
527
528 # Use "Up" or "Down" to only identify lines in the output that
529 # match interfaces that exist
530 regex = re.compile('^\s*([a-zA-Z0-9_/]*).*(connect)(.*)')
531 regex1 = re.compile('.*Not Present.*')
Steve McIntyre6c279b42014-12-23 22:09:04 +0000532 regex2 = re.compile('.*routed.*')
Steve McIntyred6759dd2014-08-12 18:10:00 +0100533
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000534 try:
535 self._cli("show interfaces status")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100536 for line in self._read_long_output("show interfaces status"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000537 match = regex.match(line)
538 if match:
539 interface = match.group(1)
540 junk = match.group(3)
541 match1 = regex1.match(junk) # Deliberately drop things
542 # marked as "Not Present"
543 match2 = regex2.match(junk) # Deliberately drop things
544 # marked as "routed"
545 if not match1 and not match2:
546 interfaces.append(interface)
547 return interfaces
548
549 except PExpectError:
550 # recurse on error
551 self._switch_connect()
552 return self._get_port_names()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100553
Steve McIntyred6759dd2014-08-12 18:10:00 +0100554 def _show_config(self):
555 logging.debug("Grabbing config")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000556 try:
557 self._cli("show running-config")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100558 return self._read_long_output("show running-config")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000559 except PExpectError:
560 # recurse on error
561 self._switch_connect()
562 return self._show_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100563
564 def _show_clock(self):
565 logging.debug("Grabbing time")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000566 try:
567 self._cli("show clock")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100568 return self._read_long_output("show clock")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000569 except PExpectError:
570 # recurse on error
571 self._switch_connect()
572 return self._show_clock()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100573
Steve McIntyred6759dd2014-08-12 18:10:00 +0100574 def _get_systemdata(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100575 logging.debug("Grabbing system sw and hw versions")
Steve McIntyreffb9b5a2014-10-10 16:31:58 +0100576
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000577 try:
578 self._systemdata = []
579 self._cli("show version")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100580 for line in self._read_long_output("show version"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000581 self._systemdata.append(line)
582
583 except PExpectError:
584 # recurse on error
585 self._switch_connect()
586 return self._get_systemdata()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100587
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000588 def _parse_vlan_list(self, inputdata):
Steve McIntyre3f287882014-08-18 19:02:15 +0100589 vlans = []
590
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000591 if inputdata == "ALL":
Steve McIntyre3f287882014-08-18 19:02:15 +0100592 return ["ALL"]
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000593 elif inputdata == "NONE":
Steve McIntyre3f287882014-08-18 19:02:15 +0100594 return []
595 else:
596 # Parse the complex list
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000597 groups = inputdata.split(',')
Steve McIntyre3f287882014-08-18 19:02:15 +0100598 for group in groups:
599 subgroups = group.split('-')
600 if len(subgroups) == 1:
601 vlans.append(int(subgroups[0]))
602 elif len(subgroups) == 2:
603 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
604 vlans.append(i)
605 else:
Steve McIntyre6d5594f2014-12-23 14:28:47 +0000606 logging.debug("Can't parse group \"" + group + "\"")
Steve McIntyre3f287882014-08-18 19:02:15 +0100607
608 return vlans
Steve McIntyred6759dd2014-08-12 18:10:00 +0100609
610 # Wrapper around connection.send - by default, expect() the same
611 # text we've sent, to remove it from the output from the
612 # switch. For the few cases where we don't need that, override
613 # this using echo=False.
614 # Horrible, but seems to work.
615 def _cli(self, text, echo=True):
616 self.connection.send(text + '\r')
617 if echo:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000618 try:
619 self.connection.expect(text)
620 except (pexpect.EOF, pexpect.TIMEOUT):
621 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000622 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000623 self.errors.log_error_out(text)
624 raise PExpectError("_cli failed on %s" % text)
625 except:
Steve McIntyrea67473a2015-02-12 08:26:33 +0000626 logging.error("Unexpected error: %s", sys.exc_info()[0])
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000627 raise
Steve McIntyred6759dd2014-08-12 18:10:00 +0100628
629if __name__ == "__main__":
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000630
631 import optparse
632
633 switch = 'vlandswitch01'
634 parser = optparse.OptionParser()
635 parser.add_option("--switch",
636 dest = "switch",
637 action = "store",
638 nargs = 1,
639 type = "string",
640 help = "specify switch to connect to for testing",
641 metavar = "<switch>")
642 (opts, args) = parser.parse_args()
643 if opts.switch:
644 switch = opts.switch
645
Steve McIntyre144d51c2015-07-07 17:56:30 +0100646 logging.basicConfig(level = logging.DEBUG,
647 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000648 p = CiscoCatalyst(switch, 23, debug=True)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100649 p.switch_connect(None, 'lngvirtual', 'lngenable')
Steve McIntyred6759dd2014-08-12 18:10:00 +0100650
651 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100652 buf = p.vlan_get_list()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100653 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100654
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100655 buf = p.vlan_get_name(2)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100656 print "VLAN 2 is named \"%s\"" % buf
657
658 print "Create VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100659 p.vlan_create(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100660
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100661 buf = p.vlan_get_name(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100662 print "VLAN 3 is named \"%s\"" % buf
663
664 print "Set name of VLAN 3 to test333"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100665 p.vlan_set_name(3, "test333")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100666
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100667 buf = p.vlan_get_name(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100668 print "VLAN 3 is named \"%s\"" % buf
669
670 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100671 buf = p.vlan_get_list()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100672 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100673
674 print "Destroy VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100675 p.vlan_destroy(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100676
677 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100678 buf = p.vlan_get_list()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100679 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100680
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100681 buf = p.port_get_mode("Gi1/0/10")
Steve McIntyreb7adc782014-08-13 00:22:21 +0100682 print "Port Gi1/0/10 is in %s mode" % buf
683
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100684 buf = p.port_get_mode("Gi1/0/11")
Steve McIntyreb7adc782014-08-13 00:22:21 +0100685 print "Port Gi1/0/11 is in %s mode" % buf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100686
Steve McIntyre9936d002014-10-01 15:54:10 +0100687 # Test access stuff
688 print "Set Gi1/0/9 to access mode"
689 p.port_set_mode("Gi1/0/9", "access")
Steve McIntyre3f287882014-08-18 19:02:15 +0100690
691 print "Move Gi1/0/9 to VLAN 4"
Steve McIntyre9936d002014-10-01 15:54:10 +0100692 p.port_set_access_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100693
Steve McIntyre9936d002014-10-01 15:54:10 +0100694 buf = p.port_get_access_vlan("Gi1/0/9")
Steve McIntyre3f287882014-08-18 19:02:15 +0100695 print "Read from switch: Gi1/0/9 is on VLAN %s" % buf
696
697 print "Move Gi1/0/9 back to VLAN 1"
Steve McIntyre9936d002014-10-01 15:54:10 +0100698 p.port_set_access_vlan("Gi1/0/9", 1)
Steve McIntyre3f287882014-08-18 19:02:15 +0100699
Steve McIntyre9936d002014-10-01 15:54:10 +0100700 # Test access stuff
Steve McIntyre3f287882014-08-18 19:02:15 +0100701 print "Set Gi1/0/9 to trunk mode"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100702 p.port_set_mode("Gi1/0/9", "trunk")
Steve McIntyre3f287882014-08-18 19:02:15 +0100703 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100704 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre5fa22652015-04-01 18:01:45 +0100705 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100706 print "Add Gi1/0/9 to VLAN 2"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100707 p.port_add_trunk_to_vlan("Gi1/0/9", 2)
Steve McIntyre3f287882014-08-18 19:02:15 +0100708 print "Add Gi1/0/9 to VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100709 p.port_add_trunk_to_vlan("Gi1/0/9", 3)
Steve McIntyre3f287882014-08-18 19:02:15 +0100710 print "Add Gi1/0/9 to VLAN 4"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100711 p.port_add_trunk_to_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100712 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100713 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre5fa22652015-04-01 18:01:45 +0100714 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100715
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100716 p.port_remove_trunk_from_vlan("Gi1/0/9", 3)
717 p.port_remove_trunk_from_vlan("Gi1/0/9", 3)
718 p.port_remove_trunk_from_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100719 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100720 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre5fa22652015-04-01 18:01:45 +0100721 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100722
Steve McIntyre7460d972014-12-23 14:45:30 +0000723# print 'Restarting switch, to explicitly reset config'
724# p.switch_restart()
Steve McIntyre3f287882014-08-18 19:02:15 +0100725
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100726# p.switch_save_running_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100727
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100728# p.switch_disconnect()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100729# p._show_config()