├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── ciscolib ├── __init__.py ├── compat.py ├── device.py ├── errors.py └── helpers.py ├── examples ├── cdp-port-descriptioner.py └── ios-version-getter.py ├── setup.py └── tests ├── fixtures └── switches.txt ├── get_switch_data.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | *pyc 3 | build 4 | dist 5 | test.py 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Nick Pegg 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation Notice 2 | 3 | This project is officially deprecated. I haven't updated it in years, and it's 4 | horribly basic (it only supports telent, which you should _not_ be using!). 5 | 6 | I'm keeping this code around since someone might find it useful, but I don't 7 | recommend using it as-is. 8 | 9 | For doing network device automation, here are a couple of projects that I 10 | recommend you check out: 11 | 12 | * [Trigger](https://github.com/trigger/trigger) 13 | * [Napalm](https://github.com/napalm-automation/napalm) 14 | 15 | # ciscolib 16 | 17 | A Python library for interacting with Cisco devices via command line. Only 18 | telnet is supported at this time. 19 | 20 | There is a lack of documentation at the moment, but this library is fairly 21 | simple. If you dig through device.py, it should be fairly self-explanatory. 22 | Just keep in mind that functions prefixed with an underscore are not meant 23 | to be called directly. 24 | 25 | See the LICENSE file for license information. 26 | 27 | ## Basic Usage 28 | 29 | import ciscolib 30 | switch = ciscolib.Device("hostname or ip", "login password", "optional login username") 31 | switch.connect() # Defaults to port 23 32 | 33 | # There are some helper commands for common tasks 34 | print(switch.get_model()) 35 | print(switch.get_ios_version()) 36 | print(switch.get_neighbors()) 37 | 38 | switch.enable("enable_password") 39 | 40 | # Or you can throw plain commands at the switch 41 | print(switch.cmd("show run")) 42 | switch.cmd("reload\n") 43 | -------------------------------------------------------------------------------- /ciscolib/__init__.py: -------------------------------------------------------------------------------- 1 | from .device import * 2 | from .errors import * 3 | from .helpers import * 4 | 5 | __version__ = "0.1.1" 6 | -------------------------------------------------------------------------------- /ciscolib/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info[0] < 3: 4 | PY3 = False 5 | input = raw_input 6 | 7 | def s(a): 8 | return a 9 | else: 10 | PY3 = True 11 | 12 | def s(a): 13 | return a.decode('ascii') 14 | -------------------------------------------------------------------------------- /ciscolib/device.py: -------------------------------------------------------------------------------- 1 | import telnetlib 2 | import re 3 | import time 4 | 5 | from .errors import * 6 | from .compat import * 7 | 8 | class Device(object): 9 | """ Connects to a Cisco device through telnet """ 10 | 11 | def __init__(self, host=None, password=None, username=None, enable_password=None): 12 | self.host = host 13 | self.username = username 14 | self.password = password 15 | self.enable_password = enable_password 16 | 17 | self.connected = False 18 | self._connection = None 19 | 20 | if self.username == '': 21 | self.username = None 22 | 23 | 24 | def connect(self, host=None, port=23, timeout=5): 25 | if host is None: 26 | host = self.host 27 | 28 | self._connection = telnetlib.Telnet(host, port, timeout) 29 | self._authenticate() 30 | self._get_hostname() 31 | 32 | self.cmd("term len 0") 33 | 34 | self.connected = True 35 | 36 | 37 | def disconnect(self): 38 | if self._connection is not None: 39 | self._connection.write(b"exit\n") 40 | self._connection.close() 41 | 42 | self._connection = None 43 | self.connected = False 44 | 45 | 46 | def _authenticate(self): 47 | idx, match, text = self.expect(['sername:', 'assword:'], 5) 48 | 49 | if match is None: 50 | raise AuthenticationError("Unable to get a username or password prompt when trying to authenticate.", text) 51 | elif match.group().count(b'assword:'): 52 | self.write(self.password + "\n") 53 | 54 | # Another password prompt means a bad password 55 | idx, match, text = self.expect(['assword', '>', '#'], 5) 56 | if match.group() is not None and match.group().count(b'assword'): 57 | raise AuthenticationError("Incorrect login password") 58 | elif match.group().count(b'sername') > 0: 59 | if self.username is None: 60 | raise AuthenticationError("A username is required but none is supplied.") 61 | else: 62 | self.write(self.username + "\n") 63 | idx, match, text = self.expect(['assword:'], 5) 64 | 65 | if match is None: 66 | raise AuthenticationError("Unexpected text when trying to enter password", text) 67 | elif match.group().count(b'assword'): 68 | self.write(self.password + "\n") 69 | 70 | # Check for an valid login 71 | idx, match, text = self.expect(['#', '>', "Login invalid", "Authentication failed"], 2) 72 | if match is None: 73 | raise AuthenticationError("Unexpected text post-login", text) 74 | elif b"invalid" in match.group() or b"failed" in match.group(): 75 | raise AuthenticationError("Unable to login. Your username or password are incorrect.") 76 | else: 77 | raise AuthenticationError("Unable to get a login prompt") 78 | 79 | 80 | def _get_hostname(self): 81 | self.write("\n") 82 | 83 | idx, match, text = self.expect(['#', '>'], 2) 84 | 85 | if match is not None: 86 | self.hostname = text.replace('>', '').replace('#', '').strip() 87 | else: 88 | raise CiscoError("Unable to get device hostname") 89 | 90 | 91 | def _get_truncated_hostname(self): 92 | """ Returns a truncated version of the hostname suitable for prompt-searching """ 93 | return self.hostname[:15] 94 | 95 | 96 | def enable(self, password=None, level=-1): 97 | if password is not None: 98 | self.enable_password = password 99 | 100 | if level < 0: 101 | self.write("enable\n") 102 | elif level > 15: 103 | raise CiscoError("Enable level %s is out of the permitted range (0-15)" % (level)) 104 | else: 105 | self.write("enable %s\n" % (level)) 106 | 107 | idx, match, text = self.expect(['#', 'assword:'], 1) 108 | if match is None: 109 | raise CiscoError("I tried to enable, but didn't get a command nor a password prompt") 110 | else: 111 | if '#' in text: 112 | return # We're already enabled, dummy! 113 | elif 'assword' in text: 114 | self.write(self.enable_password + "\n") 115 | 116 | idx, match, text = self.expect(["#", 'assword:'], 1) 117 | 118 | if match.group() is None: 119 | raise CiscoError("Unexpected output when trying to enter enable mode", text=None) 120 | elif match.group().count(b'assword') > 0: 121 | self.write("\n\n\n") # Get back to the prompt 122 | raise CiscoError("Incorrect enable password") 123 | elif not match.group().count(b"#"): 124 | raise CiscoError("Unexpected output when trying to enter enable mode", text=match.group()) 125 | 126 | def expect(self, asearch, ind): 127 | 128 | idx, match, text = self._connection.expect([needle.encode('ascii') for needle in asearch], ind) 129 | return idx, match, s(text) 130 | 131 | def write(self, text): 132 | """ Do a raw write on the telnet connection. No newline implied. """ 133 | 134 | if self._connection is None: 135 | self.connect() 136 | raise CiscoError("Not connected") 137 | 138 | self._connection.write(text.encode('ascii')) 139 | 140 | 141 | def read_until_prompt(self, prompt=None, timeout=5): 142 | thost = self._get_truncated_hostname() 143 | 144 | if prompt is None: 145 | expect_re = [thost + ".*>$", thost + ".*#$"] 146 | else: 147 | expect_re = [thost + ".*" + prompt + "$"] 148 | 149 | # TODO: Error instead of timing out 150 | idx, match, ret_text = self.expect(expect_re, 10) 151 | 152 | return ret_text 153 | 154 | 155 | def cmd(self, cmd_text): 156 | """ Send a command to the switch and return the resulting text. Given 157 | command should NOT have a newline in it.""" 158 | 159 | self.write(cmd_text + "\n") 160 | text = self.read_until_prompt() 161 | 162 | # Get rid of the prompt (the last line) 163 | ret_text = "" 164 | for a in text.split('\n')[:-1]: 165 | ret_text += a + "\n" 166 | 167 | # If someone changed the hostname, we need to update that 168 | if 'hostname' in cmd_text: 169 | self._get_hostname() 170 | 171 | if "Invalid input" in ret_text or "Incomplete command" in ret_text: 172 | raise InvalidCommand(cmd_text) 173 | 174 | return ret_text 175 | 176 | 177 | def get_neighbors(self): 178 | """ Returns a list of dicts of the switch's neighbors: 179 | {hostname, ip, local_port, remote_port} """ 180 | 181 | re_text = "-+\r?\nDevice ID: (.+)\\b\r?\n.+\s+\r?\n\s*IP address:\s+(\d+\.\d+\.\d+\.\d+)\s*\r?\n.*\r?\nInterface: (.+),.+Port ID.+: (.+)\\b\r?\n" 182 | 183 | neighbors = list() 184 | for neighbor in re.findall(re_text, self.cmd('show cdp neighbors detail')): 185 | n_dict = dict() 186 | 187 | n_dict['hostname'], n_dict['ip'], n_dict['local_port'], n_dict['remote_port'] = neighbor 188 | 189 | neighbors.append(n_dict) 190 | 191 | return neighbors 192 | 193 | 194 | def get_model(self): 195 | """ Gets the model number of the switch using the `get version` command """ 196 | 197 | re_text = '(?:Model number\s*:\s+(.+))|(?:cisco (.+?) \(.+\) processor)' 198 | 199 | cmd_output = self.cmd('show version') 200 | match = re.search(re_text, cmd_output) 201 | 202 | if match is not None: 203 | one, two = match.groups() 204 | if two is None: 205 | model = one.strip() 206 | elif one is None: 207 | model = two.strip() 208 | else: 209 | model = None 210 | 211 | else: 212 | model = None 213 | raise ModelNotSupported("Unable to do `show version`", cmd_output) 214 | 215 | 216 | return model 217 | 218 | def get_ios_version(self): 219 | """ Gets the IOS software version """ 220 | 221 | needle = "IOS.*Software.*Version ([\w\.\(\)]+)" 222 | haystack = self.cmd('show version') 223 | 224 | match = re.search(needle, haystack) 225 | 226 | if match is not None: 227 | version = match.group(1) 228 | else: 229 | version = None 230 | raise ModelNotSupported("Unable to do `show version`", cmd_output) 231 | 232 | return version 233 | 234 | 235 | def get_interface(self, interface): 236 | """ 237 | Gets information on one interface and returns it as a dict. 238 | Input interface name must be in a form that Cisco likes 239 | 240 | Returned fields: name, description, status, vlan, duplex, speed, media 241 | """ 242 | #TODO: Implement this 243 | pass 244 | 245 | 246 | def get_interfaces(self): 247 | detail_re = "((?:\w+|-|/!)+\d+(?:/\d+)*) is (?:up|(?:administratively )?down).+? \((.+)\)\s?\r?\n(?:.+\r?\n)(?:\s+Description: (.+)\r?\n)?" 248 | 249 | status_re = "(\w{2}\d+(?:/\d+)+)\s+(.+?)\s+(\w+)\s+(\d+|trunk)\s+((?:\d|\w|-)+)\s+((?:\d|\w|-)+)\s+(.+)" 250 | 251 | ports = [] 252 | 253 | interface_data = self.cmd('show interfaces') 254 | port_matches = re.findall(detail_re, interface_data) 255 | if port_matches == []: 256 | raise ModelNotSupported("Unable to parse `show interfaces`", interface_data) 257 | 258 | 259 | for match in port_matches: 260 | port = dict() 261 | port['name'], port['status'], port['description'] = match 262 | 263 | # Not exactly the most efficient, but it guarantees that we get the right ports 264 | status_match = re.search(status_re, self.cmd('show interface ' + port['name'] + ' status')) 265 | if status_match is None: 266 | port['vlan'] = None 267 | port['duplex'] = None 268 | port['speed'] = None 269 | port['media'] = None 270 | else: 271 | port['vlan'], port['duplex'], port['speed'], port['media'] = status_match.groups()[3:] 272 | 273 | port['media'] = port['media'].strip() 274 | port['description'] = port['description'].strip() 275 | 276 | ports.append(port) 277 | 278 | return ports 279 | 280 | def get_arp_table(self): 281 | """ 282 | Returns the ARP table from the device as a list of dicts. 283 | Only retreives IP and ARPA addresses at the moment. 284 | 285 | {ip, age, mac, interface} 286 | """ 287 | re_text = 'Internet\s+(?P\d+\.\d+\.\d+\.\d+)\s+(\d+|-)\s+((?:\d|\w){4}\.(?:\d|\w){4}\.(?:\d|\w){4})\s+ARPA\s+(.+)\r?\n?' 288 | 289 | table = [] 290 | for item in re.findall(re_text, self.cmd("show arp")): 291 | table.append({ 292 | "ip": item[0], 293 | "age": item[1], 294 | "mac": item[2], 295 | "interface": item[3].strip() 296 | }) 297 | 298 | return table 299 | 300 | 301 | def get_mac_table(self): 302 | """ Returns the mac address table from the device """ 303 | re_text = '\*?\s+(\d+|All)\s+((?:\d|\w){4}\.(?:\d|\w){4}\.(?:\d|\w){4})\s+(static|dynamic)\s+(?:(?:Yes|No)\s+(?:-|\d+)\s+)?(.+?)\r?\n' 304 | 305 | try: 306 | data = self.cmd("show mac address-table") 307 | except: 308 | try: 309 | data = self.cmd("show mac-address-table") 310 | except: 311 | raise ModelNotSupported("No MAC address table command") 312 | 313 | rows = re.findall(re_text, data, flags=re.I) 314 | 315 | return rows 316 | -------------------------------------------------------------------------------- /ciscolib/errors.py: -------------------------------------------------------------------------------- 1 | class CiscoError(Exception): 2 | def __init__(self, value, text=''): 3 | self.value = value 4 | self.text = text 5 | 6 | def __str__(self): 7 | ret = self.value 8 | 9 | if self.text is not None and self.text != '': 10 | ret += "\nText returned from switch: " + str(self.text) 11 | 12 | return ret 13 | 14 | class AuthenticationError(CiscoError): 15 | pass 16 | 17 | class AuthorizationError(CiscoError): 18 | def __init__(self, cmd): 19 | self.cmd = cmd 20 | 21 | def __str__(self): 22 | return "Authorization error on command: " + str(self.cmd) 23 | 24 | class InvalidCommand(CiscoError): 25 | def __init__(self, cmd): 26 | self.cmd = cmd 27 | 28 | def __str__(self): 29 | ret = "Invalid command: " + str(self.cmd) 30 | return ret 31 | 32 | class ModelNotSupported(CiscoError): 33 | def __init__(self, reason): 34 | self.value = value 35 | self.reason = reason 36 | def __str__(self): 37 | ret = "This model of switch is not supported by this version of CiscoLib\n" 38 | ret += "Please contact the CiscoLib developer for help.\n\n" 39 | ret += "Reason: %s\n" % self.reason 40 | 41 | return ret 42 | -------------------------------------------------------------------------------- /ciscolib/helpers.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def shorten_int_name(interface_name): 4 | """ 5 | Returns the Cisco shortened interface name from a full one. 6 | If the full interface name is invalid, this will return None 7 | """ 8 | 9 | short = None 10 | regex = "(\w{2}).*?(\d+(?:/\d+)?(?:/\d+)?)" 11 | 12 | match = re.match(regex, interface_name) 13 | 14 | if match is not None: 15 | short = "" 16 | for group in match.groups(): 17 | short += group 18 | 19 | return short 20 | 21 | -------------------------------------------------------------------------------- /examples/cdp-port-descriptioner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Runs through a list of switches and if there is a neighbor on a given port, 4 | # set the port's description to that neighbor's name 5 | 6 | from getpass import getpass 7 | 8 | import ciscolib 9 | from ciscolib.compat import * 10 | 11 | def main(): 12 | # These variables can be assigned in-code, or will be asked at runtime 13 | USERNAME = '' 14 | PASSWORD = '' 15 | ENABLE_PWD = '' 16 | 17 | DESCRIPTION_PREFIX = '' 18 | DESCRIPTION_SUFFIX = ' Uplink' 19 | 20 | DOMAIN_NAME = '' # Domain name of the switches. This will get dropped. 21 | 22 | 23 | # Get credentials if they aren't already supplied 24 | if USERNAME is None or USERNAME == '': 25 | USERNAME = input("What is your switch username (blank for none)? ") 26 | if PASSWORD is None or PASSWORD == '': 27 | PASSWORD = getpass("What is your switch password? ") 28 | if ENABLE_PWD is None or ENABLE_PWD == '': 29 | ENABLE_PWD = getpass("What is your enable password? ") 30 | 31 | 32 | # Load the switch file and iterate through the switches 33 | for ip in open('switches.txt').readlines(): 34 | ip = ip.strip() # Endline characters are for the birds 35 | 36 | if USERNAME != "": 37 | switch = ciscolib.Device(ip, PASSWORD, USERNAME, ENABLE_PWD) 38 | else: 39 | switch = ciscolib.Device(ip, PASSWORD, enable_password=ENABLE_PWD) 40 | 41 | try: 42 | switch.connect() 43 | print("Logged into %s" % ip) 44 | except ciscolib.AuthenticationError as e: 45 | print("Couldn't connect to %s: %s" % (ip, e.value)) 46 | continue 47 | except Exception as e: 48 | print("Couldn't connect to %s: %s" % (ip, str(e))) 49 | continue 50 | 51 | 52 | neighbors = switch.get_neighbors() 53 | 54 | switch.enable(ENABLE_PWD) 55 | switch.cmd("conf terminal") 56 | 57 | # Iterate through the neighbors and set the port descriptions 58 | for neighbor in neighbors: 59 | name = neighbor['hostname'] 60 | port = neighbor['local_port'] 61 | 62 | # If there is a domain name in the hostname, get rid of it 63 | if DOMAIN_NAME == '': 64 | name = name.split('.', 1)[0] 65 | else: 66 | name = name.replace(DOMAIN_NAME, '') 67 | 68 | if "SEP" in name or "ap" in name or "AP" in name: 69 | # Specific for my case. Feel free to remove this if statement. 70 | print("\tFound an AP or Phone on port %s. Ignoring." % port) 71 | else: 72 | print("\tFound %s on %s" % (name, port)) 73 | switch.cmd("int %s" % port) 74 | switch.cmd("desc %s%s%s" % (DESCRIPTION_PREFIX, name, DESCRIPTION_SUFFIX)) 75 | switch.cmd("exit") # Get out of the interface config 76 | 77 | 78 | switch.cmd("exit") # Get out of config mode 79 | switch.cmd("write mem") # Save it! 80 | switch.disconnect() 81 | print('') 82 | 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | 88 | -------------------------------------------------------------------------------- /examples/ios-version-getter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Gets IOS versions of switches and dumps them into a CSV file 4 | 5 | import ciscolib 6 | import csv 7 | import traceback 8 | 9 | infile = csv.DictReader(open("switches.csv")) # Columns: ip, username, password 10 | outfile = csv.DictWriter(open("ios_versions.csv", 'w'), ["Hostname", "IP", "Model", "IOS Version"]) 11 | 12 | outfile.writeheader() 13 | 14 | 15 | for row in infile: 16 | switch = {'IP': row['ip']} 17 | 18 | d = ciscolib.Device(row['ip'], row['password'], row['username']) 19 | try: 20 | d.connect() 21 | except: 22 | print("Could not connect to %s" % switch['IP']) 23 | continue 24 | 25 | print("Connected to %s (%s)" % (d.host, d.hostname)) 26 | 27 | switch['Hostname'] = d.hostname 28 | 29 | try: 30 | switch['Model'] = d.get_model() 31 | except: 32 | traceback.print_exc() 33 | 34 | try: 35 | switch['IOS Version'] = d.get_ios_version() 36 | except: 37 | traceback.print_exc() 38 | 39 | outfile.writerow(switch) 40 | d.disconnect() 41 | print('') 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | import ciscolib 4 | 5 | setup(name="ciscolib", 6 | packages=['ciscolib'], 7 | version=ciscolib.__version__, 8 | description="Interacts with Cisco devices via command line", 9 | author="Nick Pegg", 10 | author_email="nick@nickpegg.com", 11 | url="https://github.com/nickpegg/ciscolib", 12 | classifiers = [ 13 | "Programming Language :: Python", 14 | "Development Status :: 3 - Alpha", 15 | "Intended Audience :: Developers", 16 | "License :: OSI Approved :: BSD License", 17 | "Operating System :: OS Independent", 18 | "Topic :: Terminals", 19 | "Topic :: Terminals :: Telnet", 20 | "Topic :: Software Development :: Libraries :: Python Modules", 21 | ] 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /tests/fixtures/switches.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickpegg/ciscolib/5bda0338693c9268fd8455d6f5c75e4d0c0809b6/tests/fixtures/switches.txt -------------------------------------------------------------------------------- /tests/get_switch_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # get_switch_data.py 4 | # 5 | # Gets fixture data out of a set of switches (located in switches.txt) 6 | 7 | import threading 8 | import time 9 | import datetime 10 | import re 11 | import os 12 | 13 | 14 | import ciscolib 15 | 16 | 17 | PASSWORD = '' 18 | 19 | USERNAME = '' 20 | USER_PASSWORD = '' # Password to use if we need to use a username 21 | 22 | models = [] 23 | models_lock = threading.Lock() 24 | 25 | 26 | class Grabber(threading.Thread): 27 | def __init__(self, host): 28 | threading.Thread.__init__(self) 29 | 30 | self.host = host 31 | 32 | 33 | def run(self): 34 | # Connect up 35 | try: 36 | device = ciscolib.Device(self.host, PASSWORD) 37 | device.connect() 38 | except ciscolib.AuthenticationError: 39 | try: 40 | device = ciscolib.Device(self.host, USER_PASSWORD, USERNAME) 41 | device.connect() 42 | except: 43 | print("Unable to connect to %s" % self.host) 44 | return 45 | except: 46 | print("Unable to connect to %s" % self.host) 47 | return 48 | 49 | try: 50 | model = device.get_model() 51 | except ciscolib.ModelNotSupported: 52 | print("!! Model of %s is not supported!" % self.host) 53 | return 54 | 55 | if model in models: 56 | return # We already have a data for this switch model 57 | else: 58 | with models_lock: 59 | models.append(model) 60 | 61 | output_dir = 'fixtures/%s/' % model 62 | try: 63 | os.mkdir(output_dir) 64 | except OSError as e: 65 | if e.errno != 17: 66 | print("!! Unable to create model directory %s" % output_dir) 67 | return 68 | 69 | open(output_dir + "show_int_status.txt", 'w').write(device.cmd("show int status")) 70 | open(output_dir + "show_cdp_neighbors_detail.txt", 'w').write(device.cmd("show cdp neighbors detail")) 71 | open(output_dir + "show_version.txt", 'w').write(device.cmd("show version")) 72 | open(output_dir + "show_arp.txt", 'w').write(device.cmd("show arp")) 73 | 74 | 75 | mac_data = '' 76 | mac_cmd = '' 77 | try: 78 | mac_cmd = 'show mac-address-table' 79 | mac_data = device.cmd(mac_cmd) 80 | except ciscolib.InvalidCommand: 81 | try: 82 | mac_cmd = 'show mac address-table' 83 | mac_data = device.cmd(mac_cmd) 84 | except: 85 | print("!! Unable to get mac data for %s" % self.host) 86 | 87 | if mac_data != '': 88 | open(output_dir + mac_cmd.replace(' ', '_') + ".txt", 'w').write(mac_data) 89 | 90 | 91 | interfaces_re = "((?:\w+|-|/!)+\d+(?:/\d+)*) is (?:up|down)" 92 | interfaces = device.cmd("show interfaces") 93 | open(output_dir + "show_interfaces.txt", 'w').write(interfaces) 94 | 95 | for result in re.findall(interfaces_re, interfaces): 96 | filename = "show_interface_%s_status.txt" % result 97 | filename = filename.replace("/", "+") 98 | open(output_dir + filename, 'w').write(device.cmd("show interface %s status" % result)) 99 | 100 | 101 | 102 | grabber_pool = [] 103 | 104 | for ip in open('fixtures/switches.txt'): 105 | print("Connecting to %s" % ip) 106 | t = Grabber(ip) 107 | t.start() 108 | grabber_pool.append(t) 109 | 110 | while len(grabber_pool) >= 20: 111 | [grabber_pool.remove(thread) for thread in grabber_pool if not thread.is_alive()] 112 | time.sleep(0.01) 113 | 114 | # Wait for the threads to die out 115 | while len(grabber_pool) > 0: 116 | [grabber_pool.remove(thread) for thread in grabber_pool if not thread.is_alive()] 117 | time.sleep(0.01) 118 | 119 | print("Models found: %s" % str(models)) 120 | 121 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | # This is unfortunately mostly unused at the moment. I would like to set up 2 | # some sort of test framework which tests helper functions based on pre-fetched 3 | # switch output 4 | 5 | import unittest 6 | import telnetlib 7 | 8 | from device import Device 9 | from errors import CiscoError 10 | 11 | ## Configuration 12 | # You must supply a valid switch and credentials for testing 13 | host = '' 14 | username = '' 15 | password = '' 16 | enable_password = '' 17 | 18 | class TestCiscoDevice(unittest.TestCase): 19 | def setUp(self): 20 | self.device = CiscoDevice(host, username, password, enable_password) 21 | self.device._connection = telnetlib.Telnet(host, 5) 22 | 23 | def tearDown(self): 24 | self.device.disconnect() 25 | 26 | def test_disconnect(self): 27 | self.device.disconnect() 28 | self.assertIsNone(self.device._connection) 29 | 30 | def test_authenticate(self): 31 | # Set an incorrect password and check that it barfs 32 | self.device.password = self.device.password + "test" 33 | self.assertRaises(CiscoError, self.device._authenticate) 34 | self.device.password = self.device.password[:-3] 35 | 36 | try: 37 | self.tearDown() 38 | self.setUp() 39 | except: 40 | pass 41 | 42 | class TestAuthenticatedCiscoDevice(TestCiscoDevice): 43 | def setUp(self): 44 | super(TestAuthenticatedCiscoDevice, self).setUp() 45 | self.device._authenticate() 46 | 47 | def test_enable(self): 48 | self.device.enable_password = self.device.enable_password + "test" 49 | self.assertRaises(CiscoError, self.device.enable) 50 | self.device.enable_password = self.device.enable_password[:-3] 51 | 52 | def test_read_until_prompt(self): 53 | ret = self.device.read_until_prompt() 54 | self.assertNotEqual(ret, '') 55 | 56 | 57 | 58 | if __name__ == '__main__': 59 | unittest.main() 60 | 61 | --------------------------------------------------------------------------------