Welcome to Condoor’s 1.0.17 documentation!¶
Condoor is a pure Python module providing the telnet and ssh connectivity to the Cisco devices. It supports multiple jumphosts to reach the target device.
Contents¶
Overview¶
The name is taken from conglomerate of two words: connection and door. Condoor provides an easy way to connect to Cisco devices over SSH and Telnet. It provides the connection door to the Cisco devices using standard Telnet and/or SSH protocol.
Condoor supports various software platforms including:
- Cisco IOS,
- Cisco IOS XE,
- Cisco IOS XR,
- Cisco IOS XR 64 bits,
- Cisco IOS XRv,
- Cisco NX-OS.
Condoor automatically adapts to different configuration modes and shells, i.e. XR Classic Admin mode, XR 64 bits Calvados Admin mode or Windriver Linux when connecting to the Line Cards.
Here is the command line which installs together with Condoor module:
$ condoor --help
Usage: condoor [OPTIONS]
Options:
--url URL The connection url to the host (i.e.
telnet://user:pass@hostname). The --url
option can be repeated to define multiple
jumphost urls. If no --url option provided
the CONDOOR_URLS environment variable is
used. [required]
--log-path PATH The logging path. If no path specified
condoor logs are sent to stdout and session
logs are sent to stderr.
--log-level [NONE|DEBUG|INFO|ERROR]
Logging level. [default: ERROR]
--log-session Log terminal session.
--force-discovery Force full device discovery.
--cmd TEXT The command to be send to the device. The
--cmd option can be repeated multiple times.
--print-info Print the discovered information.
--help Show this message and exit.
Installation¶
Condoor is on PyPI, and can be installed with standard tools:
pip install condoor
Or:
easy_install condoor
Requirements¶
This version of Condoor requires Python 2.7.
API documentation¶
Core condoor components¶
Init file for condoor.
Connection class¶
-
class
Connection
(name, urls=[], log_dir=None, log_level=10, log_session=True)[source]¶ Connection class providing the condoor API.
Main API class providing the basic API to the physical devices. It implements the following methods:
- connect
- reconnect
- disconnect
- send
- reload
- enable
- run_fsm
-
__init__
(name, urls=[], log_dir=None, log_level=10, log_session=True)[source]¶ Initialize the
condoor.Connection
object.- Args:
name (str): The connection name.
- urls (str or list): This argument may be a string or list of strings or list of list of strings.
When urls type is string it must be valid URL in the following format:
urls = "<protocol>://<user>:<password>@<host>:<port>/<enable_password>
Example:
urls = "telnet://cisco:cisco@192.168.1.1"
The <port> can be omitted and default port for protocol will be used. When urls type is list of strings it can provide multiple intermediate hosts (jumphosts) with their credentials before making the final connection the target device. Example:
urls = ["ssh://admin:secretpass@jumphost", "telnet://cisco:cisco@192.168.1.1"] urls = ["ssh://admin:pass@jumphost1", "ssh://admin:pass@jumphost2", "telnet://cisco:cisco@192.168.1.1"]
The urls can be list of list of strings. In this case the multiple connection chains can be provided to the target device. This is used when device has two processor cards with console connected to both of them. Example:
urls = [["ssh://admin:pass@jumphost1", "telnet://cisco:cisco@termserv:2001"], ["ssh://admin:pass@jumphost1", "telnet://cisco:cisco@termserv:2002"]]
- log_dir (str): The path to the directory when session.log and condoor.log is stored. If None
- the condoor log and session log is redirected to stdout
log_level (int): The condoor logging level.
log_session (Bool): If True the terminal session is logged.
-
connect
(logfile=None, force_discovery=False, tracefile=None)[source]¶ Connect to the device.
- Args:
- logfile (file): Optional file descriptor for session logging. The file must be open for write.
- The session is logged only if
log_session=True
was passed to the constructor. If None then the default session.log file is created in log_dir.
force_discovery (Bool): Optional. If True the device discover process will start after getting connected.
- Raises:
- ConnectionError: If the discovery method was not called first or there was a problem with getting
- the connection.
ConnectionAuthenticationError: If the authentication failed.
ConnectionTimeoutError: If the connection timeout happened.
-
reconnect
(logfile=None, max_timeout=360, force_discovery=False, tracefile=None, retry=True)[source]¶ Reconnect to the device.
It can be called when after device reloads or the session was disconnected either by device or jumphost. If multiple jumphosts are used then reconnect starts from the last valid connection.
- Args:
- logfile (file): Optional file descriptor for session logging. The file must be open for write.
- The session is logged only if
log_session=True
was passed to the constructor. It the parameter is None the default session.log file is created in log_dir. - max_timeout (int): This is the maximum amount of time during the session tries to reconnect. It may take
- longer depending on the TELNET or SSH default timeout.
force_discovery (Bool): Optional. If True the device discover process will start after getting connected.
- tracefile (file): Optional file descriptor for condoor logging. The file must be open for write.
- It the parameter is None the default condoor.log file is created in log_dir.
retry (bool): Optional parameter causing the connnection to retry until timeout
- Raises:
- ConnectionError: If the discovery method was not called first or there was a problem with getting
- the connection.
ConnectionAuthenticationError: If the authentication failed.
ConnectionTimeoutError: If the connection timeout happened.
-
reload
(reload_timeout=300, save_config=True, no_reload_cmd=False)[source]¶ Reload the device and wait for device to boot up.
Returns False if reload was not successful.
-
send
(cmd='', timeout=300, wait_for_string=None, password=False)[source]¶ Send the command to the device and return the output.
- Args:
cmd (str): Command string for execution. Defaults to empty string. timeout (int): Timeout in seconds. Defaults to 300 sec (5 min) wait_for_string (str): This is optional string that driver waits for after command execution. If none the detected prompt will be used. password (bool): If true cmd representing password is not logged
and condoor waits for noecho.- Returns:
- A string containing the command output.
- Raises:
- ConnectionError: General connection error during command execution CommandSyntaxError: Command syntax error or unknown command. CommandTimeoutError: Timeout during command execution
-
enable
(enable_password=None)[source]¶ Change the device mode to privileged.
If device does not support privileged mode the the informational message to the log will be posted.
- Args:
- enable_password (str): The privileged mode password. This is optional parameter. If password is not
- provided but required the password from url will be used. Refer to
condoor.Connection
-
run_fsm
(name, command, events, transitions, timeout, max_transitions=20)[source]¶ Instantiate and run the Finite State Machine for the current device connection.
Here is the example of usage:
test_dir = "rw_test" dir = "disk0:" + test_dir REMOVE_DIR = re.compile(re.escape("Remove directory filename [{}]?".format(test_dir))) DELETE_CONFIRM = re.compile(re.escape("Delete {}/{}[confirm]".format(filesystem, test_dir))) REMOVE_ERROR = re.compile(re.escape("%Error Removing dir {} (Directory doesnot exist)".format(test_dir))) command = "rmdir {}".format(dir) events = [device.prompt, REMOVE_DIR, DELETE_CONFIRM, REMOVE_ERROR, pexpect.TIMEOUT] transitions = [ (REMOVE_DIR, [0], 1, send_newline, 5), (DELETE_CONFIRM, [1], 2, send_newline, 5), # if dir does not exist initially it's ok (REMOVE_ERROR, [0], 2, None, 0), (device.prompt, [2], -1, None, 0), (pexpect.TIMEOUT, [0, 1, 2], -1, error, 0) ] if not conn.run_fsm("DELETE_DIR", command, events, transitions, timeout=5): return False
This FSM tries to remove directory from disk0:
- Args:
- name (str): Name of the state machine used for logging purposes. Can’t be None command (str): The command sent to the device before FSM starts events (list): List of expected strings or pexpect.TIMEOUT exception expected from the device. transitions (list): List of tuples in defining the state machine transitions. timeout (int): Default timeout between states in seconds. max_transitions (int): Default maximum number of transitions allowed for FSM.
The transition tuple format is as follows:
(event, [list_of_states], next_state, action, timeout)
Where:
- event (str): string from the events list which is expected to be received from device.
- list_of_states (list): List of FSM states that triggers the action in case of event occurrence.
- next_state (int): Next state for FSM transition.
- action (func): function to be executed if the current FSM state belongs to list_of_states and the event occurred. The action can be also None then FSM transits to the next state without any action. Action can be also the exception, which is raised and FSM stops.
The example action:
def send_newline(ctx): ctx.ctrl.sendline() return True def error(ctx): ctx.message = "Filesystem error" return False def readonly(ctx): ctx.message = "Filesystem is readonly" return False
The ctx object description refer to
condoor.fsm.FSM
.If the action returns True then the FSM continues processing. If the action returns False then FSM stops and the error message passed back to the ctx object is posted to the log.
The FSM state is the integer number. The FSM starts with initial
state=0
and finishes if thenext_state
is set to -1.If action returns False then FSM returns False. FSM returns True if reaches the -1 state.
-
discovery
(logfile=None, tracefile=None)[source]¶ Discover the device details.
This method discover several device attributes.
- Args:
- logfile (file): Optional file descriptor for session logging. The file must be open for write.
- The session is logged only if
log_session=True
was passed to the constructor. It the parameter is not passed then the default session.log file is created in log_dir.
-
family
¶ Return the string representing hardware platform family.
For example: ASR9K, ASR900, NCS6K, etc.
-
platform
¶ Return the string representing hardware platform model.
For example: ASR-9010, ASR922, NCS-4006, etc.
-
os_type
¶ Return the string representing the target device OS type.
For example: IOS, XR, eXR. If not detected returns None
-
os_version
¶ Return the string representing the target device OS version.
For example 5.3.1. If not detected returns None
-
hostname
¶ Return target device hostname.
-
prompt
¶ Return target device prompt.
-
is_connected
¶ Return if target device is connected.
-
is_discovered
¶ Return if target device is discovered.
-
is_console
¶ Return if target device is connected via console.
-
mode
¶ Return the sting representing the current device mode.
For example: Calvados, Windriver, Rommon.
-
name
¶ Return the chassis name.
-
description
¶ Return the chassis description.
-
pid
¶ Return the chassis PID.
-
vid
¶ Return the chassis VID.
-
sn
¶ Return the chassis SN.
-
udi
¶ Return the dict representing the udi hardware record.
Example:
{ 'description': 'ASR-9904 AC Chassis', 'name': 'Rack 0', 'pid': 'ASR-9904-AC', 'sn': 'FOX1830GT5W ', 'vid': 'V01' }
-
device_info
¶ Return the dict representing the target device info record.
Example:
{ 'family': 'ASR9K', 'os_type': 'eXR', 'os_version': '6.1.0.06I', 'platform': 'ASR-9904' }
-
description_record
¶ Return dict describing
condoor.Connection
object.Example:
{'connections': [{'chain': [{'driver_name': 'eXR', 'family': 'ASR9K', 'hostname': 'vkg3', 'is_console': True, 'is_target': True, 'mode': 'global', 'os_type': 'eXR', 'os_version': '6.1.2.06I', 'platform': 'ASR-9904', 'prompt': 'RP/0/RSP0/CPU0:vkg3#', 'udi': {'description': 'ASR-9904 AC Chassis', 'name': 'Rack 0', 'pid': 'ASR-9904-AC', 'sn': 'FOX2024GKDE ', 'vid': 'V01'}}]}, {'chain': [{'driver_name': 'generic', 'family': None, 'hostname': '172.27.41.52:2045', 'is_console': None, 'is_target': True, 'mode': None, 'os_type': None, 'os_version': None, 'platform': None, 'prompt': None, 'udi': None}]}], 'last_chain': 0}
Exceptions¶
This chapter describes all the exceptions used by condoor module.
Init file for condoor.
-
exception
GeneralError
(message=None, host=None)[source]¶ Bases:
exceptions.Exception
General error.
This is a base class for all exceptions raised by condoor module.
-
__init__
(message=None, host=None)[source]¶ Initialize the GeneralError object.
- Args:
- message (str): Custom message to be passed to the exceptions. Defaults to None.
- If None then the general class __doc__ is used.
- host (str): Custom string which can be used to enhance the exception message by adding the “host: “
- prefix to the message string. Defaults to None. If host is None then message stays unchanged.
-
Connection exceptions¶
The exceptions below are related to connection handling events. There are covered three cases:
- general connection errors caused by device disconnect or jumphosts disconnects,
- authentication errors caused by using wrong credentials to access the device,
- timeout errors caused by lack of response within defined amount of time.
-
exception
ConnectionError
(message=None, host=None)[source]¶ General connection error.
Bases:
condoor.GeneralError
-
exception
ConnectionAuthenticationError
(message=None, host=None)[source]¶ Connection authentication error.
Bases:
condoor.ConnectionError
-
exception
ConnectionTimeoutError
(message=None, host=None)[source]¶ Connection timeout error.
Bases:
condoor.ConnectionError
Command exceptions¶
The exceptions below are related to command execution. There are covered three cases:
- generic command execution error,
- command syntax error,
- command execution timeout.
-
exception
CommandError
(message=None, host=None, command=None)[source]¶ Command execution error.
This is base class for command related exceptions which extends the standard message with a ‘command’ string for better user experience and error reporting.
Bases:
condoor.GeneralError
-
__init__
(message=None, host=None, command=None)[source]¶ Initialize CommandError object.
- Args:
- message (str): Custom message to be passed to the exceptions. Defaults to None.
- If None then the general class __doc__ is used.
- host (str): Custom string which can be used to enhance the exception message by adding the “host: “
- prefix to the message string. Defaults to None. If host is None then message stays unchanged.
- command (str): Custom string which can be used enhance the exception message by adding the
- “command” suffix to the message string. Defaults to None. If command is None then the message stays unchanged.
-
-
exception
CommandSyntaxError
(message=None, host=None, command=None)[source]¶ Command syntax error.
Bases:
condoor.CommandError
-
exception
CommandTimeoutError
(message=None, host=None, command=None)[source]¶ Command timeout error.
Bases:
condoor.CommandError
URL exceptions¶
This exception is raised when invalid URL to the condoor.Connection
class is passed.
-
exception
InvalidHopInfoError
(message=None, host=None)[source]¶ Invalid device connection parameters.
Bases:
condoor.GeneralError
Pexpect exceptions¶
Those are exceptions derived from pexpect module. This exception is used in FSM and condoor.Connection.run_fsm()
Examples¶
There is a execute_command.py example file in code repository:
#!/usr/bin/env python
# =============================================================================
# Copyright (c) 2017, Cisco Systems
# All rights reserved.
#
# # Author: Klaudiusz Staniek
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
# =============================================================================
import logging
import getpass
import optparse
import sys
import condoor
usage = '%prog -H url [-J url] [-d <level>] [-h] command'
usage += '\nCopyright (C) 2017 by Klaudiusz Staniek'
parser = optparse.OptionParser(usage=usage)
parser.add_option(
'--host_url', '-H', dest='host_url', metavar='URL',
help='''
target host url e.g.: telnet://user:pass@hostname
'''.strip())
parser.add_option(
'--jumphost_url', '-J', dest='jumphost_url', default=None, metavar='URL',
help='''
jump host url e.g.: ssh://user:pass@jumphostname
'''.strip())
parser.add_option(
'--debug', '-d', dest='debug', type='str', metavar='LEVEL',
default='CRITICAL', help='''
prints out debug information about the device connection stage.
LEVEL is a string of DEBUG, INFO, WARNING, ERROR, CRITICAL.
Default is CRITICAL.
'''.strip())
logging_map = {
0: 60, 1: 50, 2: 40, 3: 30, 4: 20, 5: 10
}
def prompt_for_password(prompt):
print("Password not specified in url.\n"
"Provided password will be stored in system KeyRing\n")
return getpass.getpass(prompt)
if __name__ == "__main__":
options, args = parser.parse_args(sys.argv)
args.pop(0)
urls = []
if options.jumphost_url:
urls.append(options.jumphost_url)
host_url = None
if not options.host_url:
parser.error('Missing host URL')
urls.append(options.host_url)
if len(args) > 0:
command = " ".join(args)
else:
parser.error("Missing command")
numeric_level = getattr(logging, options.debug.upper(), 50)
try:
import keyring
am = AccountManager(config_file='accounts.cfg', password_cb=prompt_for_password)
except ImportError:
print("No keyring library installed. Password must be provided in url.")
am = None
try:
conn = condoor.Connection('host', urls, account_manager=am, log_level=numeric_level)
conn.discovery()
conn.connect()
try:
output = conn.send(command)
print output
except condoor.CommandSyntaxError:
print "Unknown command error"
except condoor.ConnectionAuthenticationError as e:
print "Authentication error: %s" % e
except condoor.ConnectionTimeoutError as e:
print "Connection timeout: %s" % e
except condoor.ConnectionError as e:
print "Connection error: %s" % e
except condoor.GeneralError as e:
print "Error: %s" % e
The example help output:
./execute_command.py -h
Usage: execute_command.py -H url [-J url] [-d <level>] [-h] command
Copyright (C) 2015 by Klaudiusz Staniek
Options:
-h, --help show this help message and exit
-H URL, --host_url=URL
target host url e.g.: telnet://user:pass@hostname
-J URL, --jumphost_url=URL
jump host url e.g.: ssh://user:pass@jumphostname
-d LEVEL, --debug=LEVEL
prints out debug information about the device
connection stage. LEVEL is a string of DEBUG, INFO,
WARNING, ERROR, CRITICAL. Default is CRITICAL.
In below example Condoor connects to host 172.28.98.4 using username cisco. The debug level is set to INFO and command to be executed is show version brief. Condoor asks for password for username cisco and then stores it in local KeyRing. Subsequent command execution does not prompt a password again if password is already stored in the KeyRing.:
./execute_command.py -H telnet://cisco@172.28.98.4 -d INFO show version brief
2015-11-21 15:22:38,560 INFO: [host]: Connecting to telnet://cisco@172.28.98.4:23
2015-11-21 15:22:42,456 INFO: [host]: [TELNET]: telnet: 172.28.98.4: Acquiring password for cisco from system KeyRing
Password not specified in url.
Provided password will be stored in system KeyRing
cisco@172.28.98.4 Password:
2015-11-21 15:22:53,110 INFO: [host]: Connected to telnet://cisco@172.28.98.4:23
2015-11-21 15:22:53,946 INFO: [host]: Command executed successfully: 'terminal len 0'
2015-11-21 15:22:54,781 INFO: [host]: Command executed successfully: 'terminal width 0'
2015-11-21 15:22:58,741 INFO: [host]: Command executed successfully: 'show version'
2015-11-21 15:22:58,742 INFO: [host]: Disconnecting from telnet://cisco@172.28.98.4:23
2015-11-21 15:22:59,689 INFO: [host]: Disconnected
2015-11-21 15:22:59,691 INFO: Hostname: 'ASR9K-PE2-R1'
2015-11-21 15:22:59,691 INFO: Family: ASR9K
2015-11-21 15:22:59,691 INFO: Platform: ASR-9010
2015-11-21 15:22:59,691 INFO: OS: XR
2015-11-21 15:22:59,691 INFO: Version: 5.3.1[Default]
2015-11-21 15:22:59,691 INFO: Prompt: 'RP/0/RSP0/CPU0:ASR9K-PE2-R1#'
2015-11-21 15:22:59,691 INFO: [ASR9K-PE2-R1]: Connecting to telnet://cisco@172.28.98.4:23
2015-11-21 15:23:11,075 INFO: [ASR9K-PE2-R1]: Connected to telnet://cisco@172.28.98.4:23
2015-11-21 15:23:11,911 INFO: [ASR9K-PE2-R1]: Command executed successfully: 'terminal exec prompt no-timestamp'
2015-11-21 15:23:12,747 INFO: [ASR9K-PE2-R1]: Command executed successfully: 'terminal len 0'
2015-11-21 15:23:13,582 INFO: [ASR9K-PE2-R1]: Command executed successfully: 'terminal width 0'
2015-11-21 15:23:14,441 INFO: [ASR9K-PE2-R1]: Command executed successfully: 'show version brief'
Cisco IOS XR Software, Version 5.3.1[Default]
Copyright (c) 2015 by Cisco Systems, Inc.
ROM: System Bootstrap, Version 0.73(c) 1994-2012 by Cisco Systems, Inc.
ASR9K-PE2-R1 uptime is 2 days, 16 hours, 46 minutes
System image file is "disk0:asr9k-os-mbi-5.3.1/0x100305/mbiasr9k-rsp3.vm"
cisco ASR9K Series (Intel 686 F6M14S4) processor with 12582912K bytes of memory.
Intel 686 F6M14S4 processor at 2134MHz, Revision 2.174
ASR 9010 8 Line Card Slot Chassis with V1 AC PEM
2 Management Ethernet
8 TenGigE
8 DWDM controller(s)
8 WANPHY controller(s)
1 SONET/SDH
503k bytes of non-volatile configuration memory.
6271M bytes of hard disk.
11817968k bytes of disk0: (Sector size 512 bytes).
11817968k bytes of disk1: (Sector size 512 bytes).