Develop & Run Unittests¶
It is strongly recommended to write unittest for plugins to ensure proper test coverage.
asyncssh is required for running unittest.
run the following command on the shell:
pip install asyncssh
A mock device framework is available that uses YAML files as its primary source of ‘mocked data’. You can also create python methods as part of the mock data class to create specific device behavior. For more information on YAML syntax, see yaml.org.
The mock device class is part of the unicon.mock.mock_device
module. The YAML files are located under the
unicon.plugins.tests.mock_data
directory. Each OS type has its own sub-directory for mock data.
Creating Mock Device¶
A new mock device can be created by executing the mock_device_cli
command with the --os
and --state
options or by creating a new module with the name
mock_device_<os_type>.py
and sub classing the mock device class MockDevice.
Mock data needs to be available in YAML files before the mock device can be executed.
Example mock device sub class for IOS:
# mock_device_ios.py
import argparse
from unicon.mock.mock_device import MockDevice
class MockDeviceIOS(MockDevice):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def main(args=None):
if not args:
parser = argparse.ArgumentParser()
parser.add_argument('--state', help='initial state')
parser.add_argument('--ha', action='store_true', help='HA mode')
parser.add_argument('--hostname', help='Device hostname (default: Router')
args = parser.parse_args()
if args.state:
state = args.state
else:
state = 'exec,exec_standby'
if args.hostname:
hostname = args.hostname
else:
hostname = 'Router'
if args.ha:
md = MockDeviceTcpWrapperIOS(hostname=hostname, state=state)
md.run()
else:
md = MockDeviceIOS(hostname=hostname, state=state)
md.run()
if __name__ == "__main__":
main()
Running the mock device:
# Using device specific mock_device:
mock_device_cli --os ios --state exec
Router> enable
Router#
# Using the generic mock_device:
mock_device_cli --os ios --state exec -generic_main
Router> enable
Router#
High Availability (HA) mock device
To create a High Availability (HA) mock device that simulates multiple RPs or a stack of devices, you need to specify the ‘–ha’ option with multiple states specified using the ‘–state’ option, separated by a comma, for example:
$ mock_device_cli --os iosxr --state login,console_standby --ha
2017-08-31 09:41:39,886 [ INFO]: Server 0 listening on port 8266
2017-08-31 09:41:39,888 [ INFO]: Server 1 listening on port 8267
This will start the mock device that listens on TCP ports, one port for each RP.
By default, the HA option creates TCP listeners. To use SSH instead of TCP,
you can use the ‘–ssh’ option instead of ‘–ha’. To run the SSH service,
the file ssh_host_key
must exist with an SSH private key in it to use
as a server host key. You can generate the file using the command
ssh-keygen -f ssh_host_key
.
Mock Device with vty
To create a vty type mock device, use --vty
option.
Currently, this is available for simplex mock device.
Supported only for TCP mock device and not require on SSH type mock device.
With --vty
option, when we telnet to vty mock device, no need to press enter key to get the prompt.
$ mock_device_cli --os ios --state login --vty
2019-02-05 12:55:19,954 [ INFO]: Server 0 listening on port 8266
$ telnet 127.0.0.1 8266
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Username:
Mock data
The state and response data is captured in YAML files. The syntax for the mock
data YAML file is shown below. If the prompt changes with the state, the prompt
can be specified as part of the YAML data. If the prompt is shown after another
output (e.g. banner), preface
data can be specified as a string or text block.
The filename of the YAML data is not important, all .yaml files that are part of the os sub directory are loaded.
To make sure that block text is correctly parsed, a block indentation indicator
may be necessary. This indicator is specified with |n
after the node name
where n
is the number of indentation spaces used.
In case you want to emulate delay in responses, you can use the timing
option
to specify how quickly the data should be returned. Time is specified in
seconds and can be specified as 0.01 for 10ms.
There are three timing variables that can be specified:
start delay
line interval (optional)
char interval (optional)
The start delay specifies the amount of time to wait before the output is printed to the terminal. The line interval specifies the delay between each line that is printed. The char interval specifies the time between characters of a line. The line and char interval timings are optional and can be omitted.
Mock device data schema
<state>:
# (optional)
preface: |2
<text before prompt>
# (optional)
# preface with timing
preface:
response: |2
<text before prompt>
timing:
# line range uses python 'slice' syntax
# <start line>:<end line>
# e.g. "0:" for all lines
- "<line range>,<start delay>,<line interval>,<char interval>"
# (optional)
# prompt may contain %N which will be replaced by the device hostname,
# by default the hostname is 'Router'
prompt: <prompt text>
commands:
# simple response string
"<cmd>": ""
# the response can be loaded from file
# by using the `file|` prefix
"<cmd>": file|<relative/path/to/file>
# Multi-line response (block text)
"<cmd>": |2
<response data>
# response with additional options
"<cmd>":
# (optional) state transition
new_state: <state>
# (optional) block text response
response: |2
<response text>
# (optional) list of responses
# The default behavior is to walk the list and stick to
# the last entry when reached.
response:
- "abc"
- "def"
# (optional)
# For list responses, you can specify response type 'circular'.
# When circular type is enabled, the command response will
# start again from the first entry after reaching the end of the list.
response_type: circular
# (optional)
timing:
# line range uses python 'slice' syntax
# <start line>:<end line>
# e.g. "0:" for all lines
- "<line range>,<start delay>,<line interval>,<char interval>"
- "<line range>,<start delay>,<line interval>,<char interval>"
keys:
# same kind of response structure as commands
"<key>": ""
"<key>": |2
<response data>
# response with additional options
"<cmd>":
# (optional) state transition
new_state: <state>
# ... etc, see above
# special keys: Control-X where X is one of 0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
# example: ctrl-y
"ctrl-y": "Control Y pressed"
Example data:
--
exec:
prompt: "Router> "
commands:
"enable":
new_state: enable
Example: using mock device¶
Create YAML data with the state, prompt and command(s) that you want to match.
--
login:
prompt: "Username: "
commands:
"cisco":
new_state: password
password:
prompt: "Password: "
commands:
"cisco":
new_state: exec
exec:
prompt: "Router>"
Note: the above example data is incomplete, see
ios_mock_data.yaml
for a more complete example.
Create a unittest that executes the mock device with the state that you created. Execute the commands or service and verify the response data.
import unittest
from unicon import Connection
class TestIosPluginConnect(unittest.TestCase):
def test_login_connect(self):
c = Connection(hostname='Router',
start=['mock_device_cli --os ios --state login'],
os='ios',
username='cisco',
tacacs_password='cisco',
enable_password='cisco')
c.connect()
assert c.spawn.match.match_output == 'end\r\nRouter#'
Example: using HA mock device¶
from unicon.plugins.tests.mock.mock_device_ios import MockDeviceTcpWrapperIOS
class TestIosPluginHAConnect(unittest.TestCase):
def setUp(self):
self.md = MockDeviceTcpWrapperIOS(port=0, state='login,exec_standby')
self.md.start()
self.testbed = """
devices:
Router:
os: ios
type: router
tacacs:
username: cisco
passwords:
tacacs: cisco
connections:
defaults:
class: unicon.Unicon
a:
protocol: telnet
ip: localhost
port: {}
b:
protocol: telnet
ip: localhost
port: {}
""".format(self.md.ports[0], self.md.ports[1])
def tearDown(self):
self.md.stop()
def test_connect(self):
tb = loader.load(self.testbed)
r = tb.devices.Router
r.connect()
return r
def test_switchover(self):
r = self.test_connect()
r.switchover()
Known Limitations¶
The current mock device has a number of limitations.
no support for time mocking
no support for random variation of response time
no command completion
Section author: Dave Wapstra <dwapstra@cisco.com>