blob: 16f47db80594196b4a57c4dbf19a0a40e5b8f9de [file] [log] [blame]
Steve McIntyred6759dd2014-08-12 18:10:00 +01001#! /usr/bin/python
2
Steve McIntyre94ef65e2015-09-25 01:08:14 +01003# Copyright 2014-2015 Linaro Limited
Steve McIntyred6759dd2014-08-12 18:10:00 +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 McIntyred6759dd2014-08-12 18:10:00 +010021import sys
Steve McIntyred6759dd2014-08-12 18:10:00 +010022import re
Steve McIntyre4267c6a2018-01-24 16:57:28 +000023import pexpect
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 McIntyre1c8a3212015-07-14 17:07:31 +010049 _expected_descr_re = re.compile(r'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
Steve McIntyred6759dd2014-08-12 18:10:00 +010061 # Save the current running config into flash - we want config to
62 # remain across reboots
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010063 def switch_save_running_config(self):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000064 try:
65 self._cli("copy running-config startup-config")
66 self.connection.expect("startup-config")
67 self._cli("startup-config")
68 self.connection.expect("OK")
Steve McIntyre2d84e522015-02-12 06:44:42 +000069 except (PExpectError, pexpect.EOF):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +000070 # recurse on error
71 self._switch_connect()
72 self.switch_save_running_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +010073
Steve McIntyre095b4452014-12-19 17:53:43 +000074 # Restart the switch - we need to reload config to do a
75 # roll-back. Do NOT save running-config first if the switch asks -
76 # we're trying to dump recent changes, not save them.
77 #
Steve McIntyre1c8a3212015-07-14 17:07:31 +010078 # This will also implicitly cause a connection to be closed
Steve McIntyre095b4452014-12-19 17:53:43 +000079 def switch_restart(self):
80 self._cli("reload")
81 index = self.connection.expect(['has been modified', 'Proceed'])
82 if index == 0:
83 self._cli("n") # No, don't save
84 self.connection.expect("Proceed")
85
86 # Fall through
87 self._cli("y") # Yes, continue to reset
88 self.connection.close(True)
89
Steve McIntyre3f287882014-08-18 19:02:15 +010090 # List the capabilities of the switch (and driver) - some things
91 # make no sense to abstract. Returns a dict of strings, each one
92 # describing an extra feature that that higher levels may care
93 # about
Steve McIntyre9b09b9d2014-09-24 15:08:10 +010094 def switch_get_capabilities(self):
Steve McIntyre3f287882014-08-18 19:02:15 +010095 return self._capabilities
Steve McIntyred6759dd2014-08-12 18:10:00 +010096
97 ################################
98 ### VLAN API functions
99 ################################
100
101 # Create a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100102 def vlan_create(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100103 logging.debug("Creating VLAN %d", tag)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000104 try:
105 self._configure()
106 self._cli("vlan %d" % tag)
107 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100108
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000109 # Validate it happened
110 vlans = self.vlan_get_list()
111 for vlan in vlans:
112 if vlan == tag:
113 return
114 raise IOError("Failed to create VLAN %d" % tag)
115
116 except PExpectError:
117 # recurse on error
118 self._switch_connect()
119 self.vlan_create(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100120
121 # Destroy a VLAN with the specified tag
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100122 def vlan_destroy(self, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100123 logging.debug("Destroying VLAN %d", tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100124
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000125 try:
126 self._configure()
127 self._cli("no vlan %d" % tag)
128 self._end_configure()
129
130 # Validate it happened
131 vlans = self.vlan_get_list()
132 for vlan in vlans:
133 if vlan == tag:
134 raise IOError("Failed to destroy VLAN %d" % tag)
135
136 except PExpectError:
137 # recurse on error
138 self._switch_connect()
139 self.vlan_destroy(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100140
141 # Set the name of a VLAN
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100142 def vlan_set_name(self, tag, name):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100143 logging.debug("Setting name of VLAN %d to %s", tag, name)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100144
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000145 try:
146 self._configure()
147 self._cli("vlan %d" % tag)
148 self._cli("name %s" % name)
149 self._end_configure()
150
151 # Validate it happened
152 read_name = self.vlan_get_name(tag)
153 if read_name != name:
154 raise IOError("Failed to set name for VLAN %d (name found is \"%s\", not \"%s\")"
155 % (tag, read_name, name))
156 except PExpectError:
157 # recurse on error
158 self._switch_connect()
159 self.vlan_set_name(tag, name)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100160
161 # Get a list of the VLAN tags currently registered on the switch
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100162 def vlan_get_list(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100163 logging.debug("Grabbing list of VLANs")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100164
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000165 try:
166 vlans = []
Steve McIntyred6759dd2014-08-12 18:10:00 +0100167
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100168 regex = re.compile(r'^ *(\d+).*(active)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000169
170 self._cli("show vlan brief")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100171 for line in self._read_long_output("show vlan brief"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000172 match = regex.match(line)
173 if match:
174 vlans.append(int(match.group(1)))
175 return vlans
176
177 except PExpectError:
178 # recurse on error
179 self._switch_connect()
180 return self.vlan_get_list()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100181
182 # For a given VLAN tag, ask the switch what the associated name is
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100183 def vlan_get_name(self, tag):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000184
185 try:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100186 logging.debug("Grabbing the name of VLAN %d", tag)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000187 name = None
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100188 regex = re.compile(r'^ *\d+\s+(\S+).*(active)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000189 self._cli("show vlan id %d" % tag)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100190 for line in self._read_long_output("show vlan id"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000191 match = regex.match(line)
192 if match:
193 name = match.group(1)
194 name.strip()
195 return name
196
197 except PExpectError:
198 # recurse on error
199 self._switch_connect()
200 return self.vlan_get_name(tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100201
202 ################################
203 ### Port API functions
Steve McIntyree1bf11a2014-08-14 17:56:25 +0100204 ################################
Steve McIntyred6759dd2014-08-12 18:10:00 +0100205
Steve McIntyre9936d002014-10-01 15:54:10 +0100206 # Set the mode of a port: access or trunk
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100207 def port_set_mode(self, port, mode):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100208 logging.debug("Setting port %s to %s", port, mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100209 if not self._is_port_mode_valid(mode):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000210 raise InputError("Port mode %s is not allowed" % mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100211 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000212 raise InputError("Port name %s not recognised" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100213
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000214 try:
215 self._configure()
216 self._cli("interface %s" % port)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000217 if mode == "trunk":
218 self._cli("switchport trunk encapsulation dot1q")
Steve McIntyre747050a2015-06-09 18:04:34 +0100219 self._cli("switchport trunk native vlan 1")
Steve McIntyredba36d92015-07-24 15:38:55 +0100220 self._cli("switchport mode %s" % mode)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000221 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100222
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000223 # Validate it happened
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100224 read_mode = self._port_get_mode(port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100225
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000226 if read_mode != mode:
227 raise IOError("Failed to set mode for port %s" % port)
228
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100229 # And cache the result
230 self._port_modes[port] = mode
231
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000232 except PExpectError:
233 # recurse on error
234 self._switch_connect()
235 self.port_set_mode(port, mode)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100236
Steve McIntyre9936d002014-10-01 15:54:10 +0100237 # Set an access port to be in a specified VLAN (tag)
238 def port_set_access_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100239 logging.debug("Setting access port %s to VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100240 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000241 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100242 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000243 raise InputError("Port %s not in access mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100244
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000245 try:
246 self._configure()
247 self._cli("interface %s" % port)
248 self._cli("switchport access vlan %d" % tag)
249 self._cli("no shutdown")
250 self._end_configure()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100251
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000252 # Finally, validate things worked
253 read_vlan = int(self.port_get_access_vlan(port))
254 if read_vlan != tag:
255 raise IOError("Failed to move access port %d to VLAN %d - got VLAN %d instead"
256 % (port, tag, read_vlan))
257
258 except PExpectError:
259 # recurse on error
260 self._switch_connect()
261 self.port_set_access_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100262
Steve McIntyred6759dd2014-08-12 18:10:00 +0100263 # Add a trunk port to a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100264 def port_add_trunk_to_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100265 logging.debug("Adding trunk port %s to VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100266 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000267 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100268 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000269 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100270
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000271 try:
272 self._configure()
273 self._cli("interface %s" % port)
274 self._cli("switchport trunk allowed vlan add %d" % tag)
275 self._end_configure()
276
277 # Validate it happened
278 read_vlans = self.port_get_trunk_vlan_list(port)
279 for vlan in read_vlans:
280 if vlan == tag or vlan == "ALL":
281 return
282 raise IOError("Failed to add trunk port %s to VLAN %d" % (port, tag))
283
284 except PExpectError:
285 # recurse on error
286 self._switch_connect()
287 self.port_add_trunk_to_vlan(port, tag)
288
Steve McIntyred6759dd2014-08-12 18:10:00 +0100289 # Remove a trunk port from a specified VLAN (tag)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100290 def port_remove_trunk_from_vlan(self, port, tag):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100291 logging.debug("Removing trunk port %s from VLAN %d", port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100292 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000293 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100294 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000295 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100296
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000297 try:
298 self._configure()
299 self._cli("interface %s" % port)
300 self._cli("switchport trunk allowed vlan remove %d" % tag)
301 self._end_configure()
302
303 # Validate it happened
304 read_vlans = self.port_get_trunk_vlan_list(port)
305 for vlan in read_vlans:
306 if vlan == tag:
307 raise IOError("Failed to remove trunk port %s from VLAN %d" % (port, tag))
308
309 except PExpectError:
310 # recurse on error
311 self._switch_connect()
312 self.port_remove_trunk_from_vlan(port, tag)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100313
Steve McIntyre9936d002014-10-01 15:54:10 +0100314 # Get the configured VLAN tag for an access port (tag)
315 def port_get_access_vlan(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100316 logging.debug("Getting VLAN for access port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100317 vlan = 1
318 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000319 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9936d002014-10-01 15:54:10 +0100320 if not (self.port_get_mode(port) == "access"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000321 raise InputError("Port %s not in access mode" % port)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100322 regex = re.compile(r'Access Mode VLAN: (\d+)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000323
324 try:
325 self._cli("show interfaces %s switchport" % port)
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100326 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000327 match = regex.match(line)
328 if match:
329 vlan = match.group(1)
330 return int(vlan)
331
332 except PExpectError:
333 # recurse on error
334 self._switch_connect()
335 return self.port_get_access_vlan(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100336
337 # Get the list of configured VLAN tags for a trunk port
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100338 def port_get_trunk_vlan_list(self, port):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100339 logging.debug("Getting VLANs for trunk port %s", port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100340 vlans = [ ]
341 if not self._is_port_name_valid(port):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000342 raise InputError("Port name %s not recognised" % port)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100343 if not (self.port_get_mode(port) == "trunk"):
Steve McIntyre72a8bce2015-01-23 18:02:19 +0000344 raise InputError("Port %s not in trunk mode" % port)
Steve McIntyre3f287882014-08-18 19:02:15 +0100345 regex_start = re.compile('Trunking VLANs Enabled: (.*)')
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100346 regex_continue = re.compile(r'\s*(\d.*)')
Steve McIntyre3f287882014-08-18 19:02:15 +0100347
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000348 try:
349 self._cli("show interfaces %s switchport" % port)
350
351 # Horrible parsing work - VLAN list may extend over several lines
352 in_match = False
353 vlan_text = ''
354
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100355 for line in self._read_long_output("show interfaces switchport"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000356 if in_match:
357 match = regex_continue.match(line)
358 if match:
359 vlan_text += match.group(1)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000360 else:
361 in_match = False
Steve McIntyre3f287882014-08-18 19:02:15 +0100362 else:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000363 match = regex_start.match(line)
364 if match:
365 vlan_text += match.group(1)
366 in_match = True
Steve McIntyre3f287882014-08-18 19:02:15 +0100367
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000368 vlans = self._parse_vlan_list(vlan_text)
369 return vlans
Steve McIntyre3f287882014-08-18 19:02:15 +0100370
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000371 except PExpectError:
372 # recurse on error
373 self._switch_connect()
374 return self.port_get_trunk_vlan_list(port)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100375
376 ################################
377 ### Internal functions
378 ################################
379
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000380 # Connect to the switch and log in
381 def _switch_connect(self):
382
383 if not self.connection is None:
384 self.connection.close(True)
385 self.connection = None
386
Steve McIntyre5fa22652015-04-01 18:01:45 +0100387 logging.debug("Connecting to Switch with: %s", self.exec_string)
Steve McIntyre06c644a2015-07-17 17:07:41 +0100388 self.connection = pexpect.spawn(self.exec_string, logfile=self.logger)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000389 self._login()
390
391 # Avoid paged output
392 self._cli("terminal length 0")
393
394 # And grab details about the switch. in case we need it
395 self._get_systemdata()
396
397 # And also validate them - make sure we're driving a switch of
398 # the correct model! Also store the serial number
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100399 descr_regex = re.compile(r'^cisco\s+(\S+)')
400 sn_regex = re.compile(r'System serial number\s+:\s+(\S+)')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000401 descr = ""
402
403 for line in self._systemdata:
404 match = descr_regex.match(line)
405 if match:
406 descr = match.group(1)
407 match = sn_regex.match(line)
408 if match:
409 self.serial_number = match.group(1)
410
Steve McIntyre5fa22652015-04-01 18:01:45 +0100411 logging.debug("serial number is %s", self.serial_number)
412 logging.debug("system description is %s", descr)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000413
414 if not self._expected_descr_re.match(descr):
415 raise IOError("Switch %s not recognised by this driver: abort" % descr)
416
417 # Now build a list of our ports, for later sanity checking
418 self._ports = self._get_port_names()
419 if len(self._ports) < 4:
420 raise IOError("Not enough ports detected - problem!")
421
422 def _login(self):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100423 logging.debug("attempting login with username %s, password %s", self._username, self._password)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100424 self.connection.expect('User Access Verification')
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000425 if self._username is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100426 self.connection.expect("User Name:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000427 self._cli("%s" % self._username)
428 if self._password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100429 self.connection.expect("Password:")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000430 self._cli("%s" % self._password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100431 while True:
432 index = self.connection.expect(['User Name:', 'Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
433 if index != 4: # Any other means: failed to log in!
Steve McIntyre5fa22652015-04-01 18:01:45 +0100434 logging.error("Login failure: index %d\n", index)
435 logging.error("Login failure: %s\n", self.connection.match.before)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100436 raise IOError
437
438 # else
Steve McIntyre65dfe7f2015-06-09 15:57:02 +0100439 self._prompt_name = re.escape(self.connection.match.group(1).strip())
Steve McIntyred6759dd2014-08-12 18:10:00 +0100440 if self.connection.match.group(2) == ">":
441 # Need to enter "enable" mode too
442 self._cli("enable")
Steve McIntyread12cd72015-02-12 06:41:29 +0000443 if self._enable_password is not None:
Steve McIntyred6759dd2014-08-12 18:10:00 +0100444 self.connection.expect("Password:")
Steve McIntyread12cd72015-02-12 06:41:29 +0000445 self._cli("%s" % self._enable_password, False)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100446 index = self.connection.expect(['Password:', 'Bad passwords', 'authentication failed', r'(.*)(#|>)'])
447 if index != 3: # Any other means: failed to log in!
Steve McIntyre5fa22652015-04-01 18:01:45 +0100448 logging.error("Enable password failure: %s\n", self.connection.match)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100449 raise IOError
450 return 0
451
452 def _logout(self):
453 logging.debug("Logging out")
454 self._cli("exit", False)
Steve McIntyre26c89562015-10-09 15:02:37 +0100455 self.connection.close(True)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100456
457 def _configure(self):
458 self._cli("configure terminal")
459
460 def _end_configure(self):
461 self._cli("end")
462
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100463 def _read_long_output(self, text):
Steve McIntyre3f287882014-08-18 19:02:15 +0100464 prompt = self._prompt_name + '#'
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000465 try:
466 self.connection.expect(prompt)
467 except (pexpect.EOF, pexpect.TIMEOUT):
468 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000469 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100470 self.errors.log_error_in(text)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000471 raise PExpectError("_read_long_output failed")
472 except:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100473 logging.error("prompt is \"%s\"", prompt)
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000474 raise
475
Steve McIntyre5fa22652015-04-01 18:01:45 +0100476 longbuf = []
Steve McIntyrea7cdefc2014-12-24 00:48:19 +0000477 for line in self.connection.before.split('\r\n'):
Steve McIntyre5fa22652015-04-01 18:01:45 +0100478 longbuf.append(line.strip())
479 return longbuf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100480
481 def _get_port_names(self):
482 logging.debug("Grabbing list of ports")
483 interfaces = []
484
Steve McIntyre8400e742015-08-04 18:01:52 +0100485 # Use "connect" to only identify lines in the output that
486 # match interfaces - it will match lines with "connected" or
487 # "notconnect".
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100488 regex = re.compile(r'^\s*([a-zA-Z0-9_/]*).*(connect)(.*)')
Steve McIntyre33e10142015-08-04 18:03:11 +0100489 # Deliberately drop things marked as "routed", i.e. the
490 # management port
Steve McIntyre6c279b42014-12-23 22:09:04 +0000491 regex2 = re.compile('.*routed.*')
Steve McIntyred6759dd2014-08-12 18:10:00 +0100492
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000493 try:
494 self._cli("show interfaces status")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100495 for line in self._read_long_output("show interfaces status"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000496 match = regex.match(line)
497 if match:
498 interface = match.group(1)
499 junk = match.group(3)
Steve McIntyre33e10142015-08-04 18:03:11 +0100500 match2 = regex2.match(junk)
501 if not match2:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000502 interfaces.append(interface)
Steve McIntyred5cfde12015-08-05 13:49:42 +0100503 self._port_numbers[interface] = len(interfaces)
Steve McIntyred601ab82015-07-09 17:42:36 +0100504 logging.debug(" found %d ports on the switch", len(interfaces))
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000505 return interfaces
506
507 except PExpectError:
508 # recurse on error
509 self._switch_connect()
510 return self._get_port_names()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100511
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100512 # Get the mode of a port: access or trunk
513 def _port_get_mode(self, port):
514 logging.debug("Getting mode of port %s", port)
515 mode = ''
516 if not self._is_port_name_valid(port):
517 raise InputError("Port name %s not recognised" % port)
518 regex = re.compile('Administrative Mode: (.*)')
519
520 try:
521 self._cli("show interfaces %s switchport" % port)
522 for line in self._read_long_output("show interfaces switchport"):
523 match = regex.match(line)
524 if match:
525 mode = match.group(1)
526 if mode == 'static access':
527 return 'access'
Steve McIntyre4797a3f2015-07-24 16:20:52 +0100528 if mode == 'trunk':
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100529 return 'trunk'
Steve McIntyre4797a3f2015-07-24 16:20:52 +0100530 # Needs special handling - it's the default port
531 # mode on these switches, and it doesn't
532 # interoperate with some other vendors. Sigh.
533 if mode == 'dynamic auto':
534 return 'dynamic'
Steve McIntyre1a7bad52015-07-20 15:21:42 +0100535 return mode
536
537 except PExpectError:
538 # recurse on error
539 self._switch_connect()
540 return self.port_get_mode(port)
541
Steve McIntyred6759dd2014-08-12 18:10:00 +0100542 def _show_config(self):
543 logging.debug("Grabbing config")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000544 try:
545 self._cli("show running-config")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100546 return self._read_long_output("show running-config")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000547 except PExpectError:
548 # recurse on error
549 self._switch_connect()
550 return self._show_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100551
552 def _show_clock(self):
553 logging.debug("Grabbing time")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000554 try:
555 self._cli("show clock")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100556 return self._read_long_output("show clock")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000557 except PExpectError:
558 # recurse on error
559 self._switch_connect()
560 return self._show_clock()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100561
Steve McIntyred6759dd2014-08-12 18:10:00 +0100562 def _get_systemdata(self):
Steve McIntyred6759dd2014-08-12 18:10:00 +0100563 logging.debug("Grabbing system sw and hw versions")
Steve McIntyreffb9b5a2014-10-10 16:31:58 +0100564
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000565 try:
566 self._systemdata = []
567 self._cli("show version")
Steve McIntyref5fb22b2015-04-01 18:19:54 +0100568 for line in self._read_long_output("show version"):
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000569 self._systemdata.append(line)
570
571 except PExpectError:
572 # recurse on error
573 self._switch_connect()
574 return self._get_systemdata()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100575
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000576 def _parse_vlan_list(self, inputdata):
Steve McIntyre3f287882014-08-18 19:02:15 +0100577 vlans = []
578
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000579 if inputdata == "ALL":
Steve McIntyre3f287882014-08-18 19:02:15 +0100580 return ["ALL"]
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000581 elif inputdata == "NONE":
Steve McIntyre3f287882014-08-18 19:02:15 +0100582 return []
583 else:
584 # Parse the complex list
Steve McIntyre2b4c07b2014-12-22 16:10:04 +0000585 groups = inputdata.split(',')
Steve McIntyre3f287882014-08-18 19:02:15 +0100586 for group in groups:
587 subgroups = group.split('-')
588 if len(subgroups) == 1:
589 vlans.append(int(subgroups[0]))
590 elif len(subgroups) == 2:
591 for i in range (int(subgroups[0]), int(subgroups[1]) + 1):
592 vlans.append(i)
593 else:
Steve McIntyre6d5594f2014-12-23 14:28:47 +0000594 logging.debug("Can't parse group \"" + group + "\"")
Steve McIntyre3f287882014-08-18 19:02:15 +0100595
596 return vlans
Steve McIntyred6759dd2014-08-12 18:10:00 +0100597
598 # Wrapper around connection.send - by default, expect() the same
599 # text we've sent, to remove it from the output from the
600 # switch. For the few cases where we don't need that, override
601 # this using echo=False.
602 # Horrible, but seems to work.
603 def _cli(self, text, echo=True):
604 self.connection.send(text + '\r')
605 if echo:
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000606 try:
607 self.connection.expect(text)
608 except (pexpect.EOF, pexpect.TIMEOUT):
609 # Something went wrong; logout, log in and try again!
Steve McIntyre7a9c8192015-02-12 07:15:52 +0000610 logging.error("PEXPECT FAILURE, RECONNECT")
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000611 self.errors.log_error_out(text)
612 raise PExpectError("_cli failed on %s" % text)
613 except:
Steve McIntyrea67473a2015-02-12 08:26:33 +0000614 logging.error("Unexpected error: %s", sys.exc_info()[0])
Steve McIntyre3dfd36f2015-02-12 06:37:56 +0000615 raise
Steve McIntyred6759dd2014-08-12 18:10:00 +0100616
617if __name__ == "__main__":
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000618
Steve McIntyreda9e9c12018-01-24 16:58:13 +0000619 # Simple test harness - exercise the main working functions above to verify
620 # they work. This does *NOT* test really disruptive things like "save
621 # running-config" and "reload" - test those by hand.
622
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000623 import optparse
624
625 switch = 'vlandswitch01'
626 parser = optparse.OptionParser()
627 parser.add_option("--switch",
628 dest = "switch",
629 action = "store",
630 nargs = 1,
631 type = "string",
632 help = "specify switch to connect to for testing",
633 metavar = "<switch>")
634 (opts, args) = parser.parse_args()
635 if opts.switch:
636 switch = opts.switch
637
Steve McIntyre144d51c2015-07-07 17:56:30 +0100638 logging.basicConfig(level = logging.DEBUG,
639 format = '%(asctime)s %(levelname)-8s %(message)s')
Steve McIntyre48dc6ae2014-12-23 16:08:19 +0000640 p = CiscoCatalyst(switch, 23, debug=True)
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100641 p.switch_connect(None, 'lngvirtual', 'lngenable')
Steve McIntyred6759dd2014-08-12 18:10:00 +0100642
643 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100644 buf = p.vlan_get_list()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100645 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100646
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100647 buf = p.vlan_get_name(2)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100648 print "VLAN 2 is named \"%s\"" % buf
649
650 print "Create VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100651 p.vlan_create(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100652
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100653 buf = p.vlan_get_name(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100654 print "VLAN 3 is named \"%s\"" % buf
655
656 print "Set name of VLAN 3 to test333"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100657 p.vlan_set_name(3, "test333")
Steve McIntyred6759dd2014-08-12 18:10:00 +0100658
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100659 buf = p.vlan_get_name(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100660 print "VLAN 3 is named \"%s\"" % buf
661
662 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100663 buf = p.vlan_get_list()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100664 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100665
666 print "Destroy VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100667 p.vlan_destroy(3)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100668
669 print "VLANs are:"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100670 buf = p.vlan_get_list()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100671 p.dump_list(buf)
Steve McIntyred6759dd2014-08-12 18:10:00 +0100672
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100673 buf = p.port_get_mode("Gi1/0/10")
Steve McIntyreb7adc782014-08-13 00:22:21 +0100674 print "Port Gi1/0/10 is in %s mode" % buf
675
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100676 buf = p.port_get_mode("Gi1/0/11")
Steve McIntyreb7adc782014-08-13 00:22:21 +0100677 print "Port Gi1/0/11 is in %s mode" % buf
Steve McIntyred6759dd2014-08-12 18:10:00 +0100678
Steve McIntyre9936d002014-10-01 15:54:10 +0100679 # Test access stuff
680 print "Set Gi1/0/9 to access mode"
681 p.port_set_mode("Gi1/0/9", "access")
Steve McIntyre3f287882014-08-18 19:02:15 +0100682
683 print "Move Gi1/0/9 to VLAN 4"
Steve McIntyre9936d002014-10-01 15:54:10 +0100684 p.port_set_access_vlan("Gi1/0/9", 4)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100685
Steve McIntyre9936d002014-10-01 15:54:10 +0100686 buf = p.port_get_access_vlan("Gi1/0/9")
Steve McIntyre3f287882014-08-18 19:02:15 +0100687 print "Read from switch: Gi1/0/9 is on VLAN %s" % buf
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100688
Steve McIntyre3f287882014-08-18 19:02:15 +0100689 print "Move Gi1/0/9 back to VLAN 1"
Steve McIntyre9936d002014-10-01 15:54:10 +0100690 p.port_set_access_vlan("Gi1/0/9", 1)
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100691
Steve McIntyre9936d002014-10-01 15:54:10 +0100692 # Test access stuff
Steve McIntyre3f287882014-08-18 19:02:15 +0100693 print "Set Gi1/0/9 to trunk mode"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100694 p.port_set_mode("Gi1/0/9", "trunk")
Steve McIntyre3f287882014-08-18 19:02:15 +0100695 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100696 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre5fa22652015-04-01 18:01:45 +0100697 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100698 print "Add Gi1/0/9 to VLAN 2"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100699 p.port_add_trunk_to_vlan("Gi1/0/9", 2)
Steve McIntyre3f287882014-08-18 19:02:15 +0100700 print "Add Gi1/0/9 to VLAN 3"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100701 p.port_add_trunk_to_vlan("Gi1/0/9", 3)
Steve McIntyre3f287882014-08-18 19:02:15 +0100702 print "Add Gi1/0/9 to VLAN 4"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100703 p.port_add_trunk_to_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100704 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100705 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre5fa22652015-04-01 18:01:45 +0100706 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100707
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100708 p.port_remove_trunk_from_vlan("Gi1/0/9", 3)
709 p.port_remove_trunk_from_vlan("Gi1/0/9", 3)
710 p.port_remove_trunk_from_vlan("Gi1/0/9", 4)
Steve McIntyre3f287882014-08-18 19:02:15 +0100711 print "Read from switch: which VLANs is Gi1/0/9 on?"
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100712 buf = p.port_get_trunk_vlan_list("Gi1/0/9")
Steve McIntyre5fa22652015-04-01 18:01:45 +0100713 p.dump_list(buf)
Steve McIntyre3f287882014-08-18 19:02:15 +0100714
Steve McIntyre7460d972014-12-23 14:45:30 +0000715# print 'Restarting switch, to explicitly reset config'
716# p.switch_restart()
Steve McIntyre3f287882014-08-18 19:02:15 +0100717
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100718# p.switch_save_running_config()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100719
Steve McIntyre9b09b9d2014-09-24 15:08:10 +0100720# p.switch_disconnect()
Steve McIntyred6759dd2014-08-12 18:10:00 +0100721# p._show_config()