blob: ac54fa04b8af89d74490f391a3251340d04c273c [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 McIntyre48dc6ae2014-12-23 16:08:19 +000051 def __init__(self, switch_hostname, switch_telnetport=23, debug = False):
Steve McIntyrebb58a272015-07-14 15:39:54 +010052 SwitchDriver.__init__(self, switch_hostname, debug)
Steve McIntyre5fa22652015-04-01 18:01:45 +010053 self._systemdata = []
Steve McIntyred6759dd2014-08-12 18:10:00 +010054 self.exec_string = "/usr/bin/telnet %s %d" % (switch_hostname, switch_telnetport)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000055 self.errors = SwitchErrors()
Steve McIntyred6759dd2014-08-12 18:10:00 +010056
57 ################################
58 ### Switch-level API functions
59 ################################
60
61 # Connect to the switch and log in
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010062 def switch_connect(self, username, password, enablepassword):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000063 self._username = username
64 self._password = password
65 self._enable_password = enablepassword
66 self._switch_connect()
Steve McIntyred6759dd2014-08-12 18:10:00 +010067
68 # Log out of the switch and drop the connection and all state
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010069 def switch_disconnect(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +010070 self._logout()
Steve McIntyre5fa22652015-04-01 18:01:45 +010071 logging.debug("Closing connection: %s", self.connection)
Steve McIntyred6759dd2014-08-12 18:10:00 +010072 self.connection.close(True)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000073 self._ports = []
74 self._prompt_name = ''
75 self._systemdata = []
Steve McIntyred6759dd2014-08-12 18:10:00 +010076 del(self)
77
78 # Save the current running config into flash - we want config to
79 # remain across reboots
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010080 def switch_save_running_config(self):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000081 try:
82 self._cli("copy running-config startup-config")
83 self.connection.expect("startup-config")
84 self._cli("startup-config")
85 self.connection.expect("OK")
Steve McIntyre2d84e522015-02-12 06:44:42 +000086 except (PExpectError, pexpect.EOF):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000087 # recurse on error
88 self._switch_connect()
89 self.switch_save_running_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +010090
Steve McIntyre095b4452014-12-19 17:53:43 +000091 # Restart the switch - we need to reload config to do a
92 # roll-back. Do NOT save running-config first if the switch asks -
93 # we're trying to dump recent changes, not save them.
94 #
95 # This will also implicitly cause a connection to be closed
96 def switch_restart(self):
97 self._cli("reload")
98 index = self.connection.expect(['has been modified', 'Proceed'])
99 if index == 0:
100 self._cli("n") # No, don't save
101 self.connection.expect("Proceed")
102
103 # Fall through
104 self._cli("y") # Yes, continue to reset
105 self.connection.close(True)
106
Steve McIntyre3f287882014-08-18 19:02:15 +0100107 # List the capabilities of the switch (and driver) - some things
108 # make no sense to abstract. Returns a dict of strings, each one
109 # describing an extra feature that that higher levels may care
110 # about
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100111 def switch_get_capabilities(self):
Steve McIntyre3f287882014-08-18 19:02:15 +0100112 return self._capabilities
Steve McIntyred6759dd2014-08-12 18:10:00 +0100113
114 ################################
115 ### VLAN API functions
116 ################################
117
118 # Create a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100119 def vlan_create(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100120 logging.debug("Creating VLAN %d", tag)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000121 try:
122 self._configure()
123 self._cli("vlan %d" % tag)
124 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100125
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000126 # Validate it happened
127 vlans = self.vlan_get_list()
128 for vlan in vlans:
129 if vlan == tag:
130 return
131 raise IOError("Failed to create VLAN %d" % tag)
132
133 except PExpectError:
134 # recurse on error
135 self._switch_connect()
136 self.vlan_create(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100137
138 # Destroy a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100139 def vlan_destroy(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100140 logging.debug("Destroying VLAN %d", tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100141
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000142 try:
143 self._configure()
144 self._cli("no vlan %d" % tag)
145 self._end_configure()
146
147 # Validate it happened
148 vlans = self.vlan_get_list()
149 for vlan in vlans:
150 if vlan == tag:
151 raise IOError("Failed to destroy VLAN %d" % tag)
152
153 except PExpectError:
154 # recurse on error
155 self._switch_connect()
156 self.vlan_destroy(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100157
158 # Set the name of a VLAN
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100159 def vlan_set_name(self, tag, name):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100160 logging.debug("Setting name of VLAN %d to %s", tag, name)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100161
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000162 try:
163 self._configure()
164 self._cli("vlan %d" % tag)
165 self._cli("name %s" % name)
166 self._end_configure()
167
168 # Validate it happened
169 read_name = self.vlan_get_name(tag)
170 if read_name != name:
171 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
172 % (tag, read_name, name))
173 except PExpectError:
174 # recurse on error
175 self._switch_connect()
176 self.vlan_set_name(tag, name)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100177
178 # Get a list of the VLAN tags currently registered on the switch
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100179 def vlan_get_list(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100180 logging.debug("Grabbing list of VLANs")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100181
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000182 try:
183 vlans = []
Steve McIntyred6759dd2014-08-12 18:10:00 +0100184
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000185 regex = re.compile('^ *(\d+).*(active)')
186
187 self._cli("show vlan brief")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100188 for line in self._read_long_output("show vlan brief"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000189 match = regex.match(line)
190 if match:
191 vlans.append(int(match.group(1)))
192 return vlans
193
194 except PExpectError:
195 # recurse on error
196 self._switch_connect()
197 return self.vlan_get_list()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100198
199 # For a given VLAN tag, ask the switch what the associated name is
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100200 def vlan_get_name(self, tag):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000201
202 try:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100203 logging.debug("Grabbing the name of VLAN %d", tag)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000204 name = None
205 regex = re.compile('^ *\d+\s+(\S+).*(active)')
206 self._cli("show vlan id %d" % tag)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100207 for line in self._read_long_output("show vlan id"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000208 match = regex.match(line)
209 if match:
210 name = match.group(1)
211 name.strip()
212 return name
213
214 except PExpectError:
215 # recurse on error
216 self._switch_connect()
217 return self.vlan_get_name(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100218
219 ################################
220 ### Port API functions
Steve McIntyree1bf11a2014-08-14 17:56:25 +0100221 ################################
Steve McIntyred6759dd2014-08-12 18:10:00 +0100222
Steve McIntyre9936d002014-10-01 15:54:10 +0100223 # Set the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100224 def port_set_mode(self, port, mode):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100225 logging.debug("Setting port %s to %s", port, mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100226 if not self._is_port_mode_valid(mode):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000227 raise InputError("Port mode %s is not allowed" % mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100228 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000229 raise InputError("Port name %s not recognised" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100230
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000231 try:
232 self._configure()
233 self._cli("interface %s" % port)
234 self._cli("switchport mode %s" % mode)
235 if mode == "trunk":
236 self._cli("switchport trunk encapsulation dot1q")
Steve McIntyre747050a2015-06-09 18:04:34 +0100237 self._cli("switchport trunk native vlan 1")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000238 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100239
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000240 # Validate it happened
241 read_mode = self.port_get_mode(port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100242
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000243 if read_mode != mode:
244 raise IOError("Failed to set mode for port %s" % port)
245
246 except PExpectError:
247 # recurse on error
248 self._switch_connect()
249 self.port_set_mode(port, mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100250
Steve McIntyre9936d002014-10-01 15:54:10 +0100251 # Get the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100252 def port_get_mode(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100253 logging.debug("Getting mode of port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100254 mode = ''
255 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 McIntyre9936d002014-10-01 15:54:10 +0100257 regex = re.compile('Administrative Mode: (.*)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000258
259 try:
260 self._cli("show interfaces %s switchport" % port)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100261 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000262 match = regex.match(line)
263 if match:
264 mode = match.group(1)
265 if mode == 'static access':
266 return 'access'
267 if mode == 'dynamic auto':
268 return 'trunk'
269 return mode
270
271 except PExpectError:
272 # recurse on error
273 self._switch_connect()
274 return self.port_get_mode(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100275
Steve McIntyre9936d002014-10-01 15:54:10 +0100276 # Set an access port to be in a specified VLAN (tag)
277 def port_set_access_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100278 logging.debug("Setting access port %s to VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100279 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000280 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100281 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000282 raise InputError("Port %s not in access mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100283
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000284 try:
285 self._configure()
286 self._cli("interface %s" % port)
287 self._cli("switchport access vlan %d" % tag)
288 self._cli("no shutdown")
289 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100290
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000291 # Finally, validate things worked
292 read_vlan = int(self.port_get_access_vlan(port))
293 if read_vlan != tag:
294 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
295 % (port, tag, read_vlan))
296
297 except PExpectError:
298 # recurse on error
299 self._switch_connect()
300 self.port_set_access_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100301
Steve McIntyred6759dd2014-08-12 18:10:00 +0100302 # Add a trunk port to a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100303 def port_add_trunk_to_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100304 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100305 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000306 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100307 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000308 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100309
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000310 try:
311 self._configure()
312 self._cli("interface %s" % port)
313 self._cli("switchport trunk allowed vlan add %d" % tag)
314 self._end_configure()
315
316 # Validate it happened
317 read_vlans = self.port_get_trunk_vlan_list(port)
318 for vlan in read_vlans:
319 if vlan == tag or vlan == "ALL":
320 return
321 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
322
323 except PExpectError:
324 # recurse on error
325 self._switch_connect()
326 self.port_add_trunk_to_vlan(port, tag)
327
Steve McIntyred6759dd2014-08-12 18:10:00 +0100328 # Remove a trunk port from a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100329 def port_remove_trunk_from_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100330 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100331 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000332 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100333 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000334 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100335
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000336 try:
337 self._configure()
338 self._cli("interface %s" % port)
339 self._cli("switchport trunk allowed vlan remove %d" % tag)
340 self._end_configure()
341
342 # Validate it happened
343 read_vlans = self.port_get_trunk_vlan_list(port)
344 for vlan in read_vlans:
345 if vlan == tag:
346 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
347
348 except PExpectError:
349 # recurse on error
350 self._switch_connect()
351 self.port_remove_trunk_from_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100352
Steve McIntyre9936d002014-10-01 15:54:10 +0100353 # Get the configured VLAN tag for an access port (tag)
354 def port_get_access_vlan(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100355 logging.debug("Getting VLAN for access port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100356 vlan = 1
357 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000358 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100359 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000360 raise InputError("Port %s not in access mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100361 regex = re.compile('Access Mode VLAN: (\d+)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000362
363 try:
364 self._cli("show interfaces %s switchport" % port)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100365 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000366 match = regex.match(line)
367 if match:
368 vlan = match.group(1)
369 return int(vlan)
370
371 except PExpectError:
372 # recurse on error
373 self._switch_connect()
374 return self.port_get_access_vlan(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100375
376 # Get the list of configured VLAN tags for a trunk port
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100377 def port_get_trunk_vlan_list(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100378 logging.debug("Getting VLANs for trunk port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100379 vlans = [ ]
380 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000381 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100382 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000383 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100384 regex_start = re.compile('Trunking VLANs Enabled: (.*)')
385 regex_continue = re.compile('\s*(\d.*)')
Steve McIntyre3f287882014-08-18 19:02:15 +0100386
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000387 try:
388 self._cli("show interfaces %s switchport" % port)
389
390 # Horrible parsing work - VLAN list may extend over several lines
391 in_match = False
392 vlan_text = ''
393
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100394 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000395 if in_match:
396 match = regex_continue.match(line)
397 if match:
398 vlan_text += match.group(1)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000399 else:
400 in_match = False
Steve McIntyre3f287882014-08-18 19:02:15 +0100401 else:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000402 match = regex_start.match(line)
403 if match:
404 vlan_text += match.group(1)
405 in_match = True
Steve McIntyre3f287882014-08-18 19:02:15 +0100406
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000407 vlans = self._parse_vlan_list(vlan_text)
408 return vlans
Steve McIntyre3f287882014-08-18 19:02:15 +0100409
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000410 except PExpectError:
411 # recurse on error
412 self._switch_connect()
413 return self.port_get_trunk_vlan_list(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100414
415 ################################
416 ### Internal functions
417 ################################
418
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000419 # Connect to the switch and log in
420 def _switch_connect(self):
421
422 if not self.connection is None:
423 self.connection.close(True)
424 self.connection = None
425
Steve McIntyre5fa22652015-04-01 18:01:45 +0100426 logging.debug("Connecting to Switch with: %s", self.exec_string)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000427 self.connection = pexpect.spawn(self.exec_string, logfile = self.logfile)
428
429 self._login()
430
431 # Avoid paged output
432 self._cli("terminal length 0")
433
434 # And grab details about the switch. in case we need it
435 self._get_systemdata()
436
437 # And also validate them - make sure we're driving a switch of
438 # the correct model! Also store the serial number
439 descr_regex = re.compile('^cisco\s+(\S+)')
440 sn_regex = re.compile('System serial number\s+:\s+(\S+)')
441 descr = ""
442
443 for line in self._systemdata:
444 match = descr_regex.match(line)
445 if match:
446 descr = match.group(1)
447 match = sn_regex.match(line)
448 if match:
449 self.serial_number = match.group(1)
450
Steve McIntyre5fa22652015-04-01 18:01:45 +0100451 logging.debug("serial number is %s", self.serial_number)
452 logging.debug("system description is %s", descr)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000453
454 if not self._expected_descr_re.match(descr):
455 raise IOError("Switch %s not recognised by this driver: abort" % descr)
456
457 # Now build a list of our ports, for later sanity checking
458 self._ports = self._get_port_names()
459 if len(self._ports) < 4:
460 raise IOError("Not enough ports detected - problem!")
461
462 def _login(self):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100463 logging.debug("attempting login with username %s, password %s", self._username, self._password)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100464 self.connection.expect('User Access Verification')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000465 if self._username is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100466 self.connection.expect("User Name:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000467 self._cli("%s" % self._username)
468 if self._password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100469 self.connection.expect("Password:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000470 self._cli("%s" % self._password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100471 while True:
472 index = self.connection.expect(['User Name:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
473 if index != 4: # Any other means: failed to log in!
Steve McIntyre5fa22652015-04-01 18:01:45 +0100474 logging.error("Login failure: index %d\n", index)
475 logging.error("Login failure: %s\n", self.connection.match.before)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100476 raise IOError
477
478 # else
Steve McIntyre65dfe7f2015-06-09 15:57:02 +0100479 self._prompt_name = re.escape(self.connection.match.group(1).strip())
Steve McIntyred6759dd2014-08-12 18:10:00 +0100480 if self.connection.match.group(2) == ">":
481 # Need to enter "enable" mode too
482 self._cli("enable")
Steve McIntyread12cd72015-02-12 06:41:29 +0000483 if self._enable_password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100484 self.connection.expect("Password:")
Steve McIntyread12cd72015-02-12 06:41:29 +0000485 self._cli("%s" % self._enable_password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100486 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
487 if index != 3: # Any other means: failed to log in!
Steve McIntyre5fa22652015-04-01 18:01:45 +0100488 logging.error("Enable password failure: %s\n", self.connection.match)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100489 raise IOError
490 return 0
491
492 def _logout(self):
493 logging.debug("Logging out")
494 self._cli("exit", False)
495
496 def _configure(self):
497 self._cli("configure terminal")
498
499 def _end_configure(self):
500 self._cli("end")
501
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100502 def _read_long_output(self, text):
Steve McIntyre3f287882014-08-18 19:02:15 +0100503 prompt = self._prompt_name + '#'
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000504 try:
505 self.connection.expect(prompt)
506 except (pexpect.EOF, pexpect.TIMEOUT):
507 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000508 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100509 self.errors.log_error_in(text)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000510 raise PExpectError("_read_long_output failed")
511 except:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100512 logging.error("prompt is \"%s\"", prompt)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000513 raise
514
Steve McIntyre5fa22652015-04-01 18:01:45 +0100515 longbuf = []
Steve McIntyrea7cdefc2014-12-24 00:48:19 +0000516 for line in self.connection.before.split('\r\n'):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100517 longbuf.append(line.strip())
518 return longbuf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100519
520 def _get_port_names(self):
521 logging.debug("Grabbing list of ports")
522 interfaces = []
523
524 # Use "Up" or "Down" to only identify lines in the output that
525 # match interfaces that exist
526 regex = re.compile('^\s*([a-zA-Z0-9_/]*).*(connect)(.*)')
527 regex1 = re.compile('.*Not Present.*')
Steve McIntyre6c279b42014-12-23 22:09:04 +0000528 regex2 = re.compile('.*routed.*')
Steve McIntyred6759dd2014-08-12 18:10:00 +0100529
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000530 try:
531 self._cli("show interfaces status")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100532 for line in self._read_long_output("show interfaces status"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000533 match = regex.match(line)
534 if match:
535 interface = match.group(1)
536 junk = match.group(3)
537 match1 = regex1.match(junk) # Deliberately drop things
538 # marked as "Not Present"
539 match2 = regex2.match(junk) # Deliberately drop things
540 # marked as "routed"
541 if not match1 and not match2:
542 interfaces.append(interface)
Steve McIntyred601ab82015-07-09 17:42:36 +0100543 logging.debug(" found %d ports on the switch", len(interfaces))
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000544 return interfaces
545
546 except PExpectError:
547 # recurse on error
548 self._switch_connect()
549 return self._get_port_names()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100550
Steve McIntyred6759dd2014-08-12 18:10:00 +0100551 def _show_config(self):
552 logging.debug("Grabbing config")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000553 try:
554 self._cli("show running-config")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100555 return self._read_long_output("show running-config")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000556 except PExpectError:
557 # recurse on error
558 self._switch_connect()
559 return self._show_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100560
561 def _show_clock(self):
562 logging.debug("Grabbing time")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000563 try:
564 self._cli("show clock")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100565 return self._read_long_output("show clock")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000566 except PExpectError:
567 # recurse on error
568 self._switch_connect()
569 return self._show_clock()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100570
Steve McIntyred6759dd2014-08-12 18:10:00 +0100571 def _get_systemdata(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100572 logging.debug("Grabbing system sw and hw versions")
Steve McIntyreffb9b5a2014-10-10 16:31:58 +0100573
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000574 try:
575 self._systemdata = []
576 self._cli("show version")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100577 for line in self._read_long_output("show version"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000578 self._systemdata.append(line)
579
580 except PExpectError:
581 # recurse on error
582 self._switch_connect()
583 return self._get_systemdata()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100584
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000585 def _parse_vlan_list(self, inputdata):
Steve McIntyre3f287882014-08-18 19:02:15 +0100586 vlans = []
587
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000588 if inputdata == "ALL":
Steve McIntyre3f287882014-08-18 19:02:15 +0100589 return ["ALL"]
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000590 elif inputdata == "NONE":
Steve McIntyre3f287882014-08-18 19:02:15 +0100591 return []
592 else:
593 # Parse the complex list
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000594 groups = inputdata.split(',')
Steve McIntyre3f287882014-08-18 19:02:15 +0100595 for group in groups:
596 subgroups = group.split('-')
597 if len(subgroups) == 1:
598 vlans.append(int(subgroups[0]))
599 elif len(subgroups) == 2:
600 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
601 vlans.append(i)
602 else:
Steve McIntyre6d5594f2014-12-23 14:28:47 +0000603 logging.debug("Can't parse group \"" + group + "\"")
Steve McIntyre3f287882014-08-18 19:02:15 +0100604
605 return vlans
Steve McIntyred6759dd2014-08-12 18:10:00 +0100606
607 # Wrapper around connection.send - by default, expect() the same
608 # text we've sent, to remove it from the output from the
609 # switch. For the few cases where we don't need that, override
610 # this using echo=False.
611 # Horrible, but seems to work.
612 def _cli(self, text, echo=True):
613 self.connection.send(text + '\r')
614 if echo:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000615 try:
616 self.connection.expect(text)
617 except (pexpect.EOF, pexpect.TIMEOUT):
618 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000619 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000620 self.errors.log_error_out(text)
621 raise PExpectError("_cli failed on %s" % text)
622 except:
Steve McIntyrea67473a2015-02-12 08:26:33 +0000623 logging.error("Unexpected error: %s", sys.exc_info()[0])
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000624 raise
Steve McIntyred6759dd2014-08-12 18:10:00 +0100625
626if __name__ == "__main__":
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000627
628 import optparse
629
630 switch = 'vlandswitch01'
631 parser = optparse.OptionParser()
632 parser.add_option("--switch",
633 dest = "switch",
634 action = "store",
635 nargs = 1,
636 type = "string",
637 help = "specify switch to connect to for testing",
638 metavar = "<switch>")
639 (opts, args) = parser.parse_args()
640 if opts.switch:
641 switch = opts.switch
642
Steve McIntyre144d51c2015-07-07 17:56:30 +0100643 logging.basicConfig(level = logging.DEBUG,
644 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000645 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 McIntyre5fa22652015-04-01 18:01:45 +0100650 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100651
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 McIntyre5fa22652015-04-01 18:01:45 +0100669 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100670
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 McIntyre5fa22652015-04-01 18:01:45 +0100676 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100677
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 McIntyre5fa22652015-04-01 18:01:45 +0100702 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100703 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 McIntyre5fa22652015-04-01 18:01:45 +0100711 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100712
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 McIntyre5fa22652015-04-01 18:01:45 +0100718 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100719
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()