Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add banner support to system api #75

Merged
merged 10 commits into from Jan 25, 2016
7 changes: 5 additions & 2 deletions pyeapi/api/abstract.py
Expand Up @@ -96,7 +96,7 @@ def get_block(self, parent, config='running_config'):
except TypeError:
return None

def configure(self, commands):
def configure(self, commands, stdin=False):
"""Sends the commands list to the node in config mode

This method performs configuration the node using the array of
Expand All @@ -117,7 +117,10 @@ def configure(self, commands):
False is returned
"""
try:
self.node.config(commands)
if stdin:
self.node.config_with_input(commands)
else:
self.node.config(commands)
return True
except (CommandError, ConnectionError):
return False
Expand Down
45 changes: 45 additions & 0 deletions pyeapi/api/system.py
Expand Up @@ -67,6 +67,8 @@ def get(self):
resource = dict()
resource.update(self._parse_hostname())
resource.update(self._parse_iprouting())
resource.update(self._parse_banners())

return resource

def _parse_hostname(self):
Expand All @@ -92,6 +94,27 @@ def _parse_iprouting(self):
value = 'no ip routing' not in self.config
return dict(iprouting=value)

def _parse_banners(self):
"""Parses the global config and returns the value for both motd
and login banners.

Returns:
dict: The configure value for modtd and login banners. If the
banner is not set it will return a value of None for that
key. The returned dict object is intendd to be merged
into the resource dict
"""
motd_value = login_value = None
matches = re.findall('^banner ([a-z]*[A-Z]*[0-9]*\n)(.*?)EOF', self.config,
re.DOTALL | re.M)
for match in matches:
if match[0].strip() == "motd":
motd_value = match[1].strip()
elif match[0].strip() == "login":
login_value = match[1].strip()

return dict(banner_motd=motd_value, banner_login=login_value)

def set_hostname(self, value=None, default=False, disable=False):
"""Configures the global system hostname setting

Expand Down Expand Up @@ -130,7 +153,29 @@ def set_iprouting(self, value=None, default=False, disable=False):
cmd = self.command_builder('ip routing', value=value, default=default,
disable=disable)
return self.configure(cmd)

def set_banner(self, banner_type, value=None, default=False, disable=False):
"""Configures system banners

Args:
banner_type(str): banner to be changed (likely either login or motd)
value(str): value to set for the banner
default (bool): Controls the use of the default keyword
disable (bool): Controls the use of the no keyword`

Returns:
bool: True if the commands completed successfully otherwise False
"""

command_string = "banner %s" % banner_type
if default is True or disable is True:
cmd = self.command_builder(command_string, value=None, default=default,
disable=disable)
return self.configure(cmd)
else:
command_input = dict(command=command_string, value=value)
return self.configure(command_input, True)


def instance(api):
"""Returns an instance of System
Expand Down
37 changes: 37 additions & 0 deletions pyeapi/client.py
Expand Up @@ -521,6 +521,43 @@ def config(self, commands):
# pop the configure command output off the stack
response.pop(0)

return response
def config_with_input(self, commands):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this method anymore.

"""Configures the node with a command that would normally take stdin

This method will send a configuration change to the node for commands
to use std input. Banners are an example of such a configuration.
It will take a dictionary where the key is the command and the value
would be entered as if it where coming from standard input

Args:
commands(dict or list): the dict should have a key of command
and value. Where command is configuration item to change with
string set for the key value.
These commands will be constructed into the
correct form to be sent to node to make the desired config
changes. The function will preprend the required commands
to put the session in config mode

Returns:
The config method will return a list of dictionaries with
the ouptut from each command
"""
command_list = []
command_list.append('configure terminal')
if isinstance(commands, list):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can make the change to the config method by checking for the value key for each command. If the value key is defined then you would add the input key when building the command list. Note that you can leverage the make_iterable call in the config method.

for com in commands:
command_list.append({'cmd': com['command'], 'input': com['value']})
else:
command_list.append({'cmd': commands['command'], 'input': commands['value']})

response = self.run_commands(command_list)

if self.autorefresh:
self.refresh()

response.pop(0)

return response

def section(self, regex, config='running_config'):
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/dut.conf
Expand Up @@ -3,3 +3,4 @@ host: 192.168.1.16
username: eapi
password: password
transport: http

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary empty line, there shouldn't be any changes to the dut.conf.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks John. Sorry about the oversights here.

29 changes: 22 additions & 7 deletions test/lib/testlib.py
Expand Up @@ -75,42 +75,57 @@ def setUp(self):

self.mock_config = Mock(name='node.config')
self.node.config = self.mock_config
self.mock_config_with_input = Mock(name='node.conifg_with_input')
self.node.config_with_input = self.mock_config_with_input

self.mock_enable = Mock(name='node.enable')
self.node.enable = self.mock_enable

self.assertIsNotNone(self.instance)
self.instance.node = self.node

def eapi_config_test(self, func, cmds=None, *args, **kwargs):
def eapi_config_test(self, func, stdin=False, cmds=None, *args, **kwargs):
func, fargs, fkwargs = func
func = getattr(self.instance, func)

if cmds is not None:
lcmds = len([cmds]) if isinstance(cmds, str) else len(cmds)
self.mock_config.return_value = [{} for i in range(0, lcmds)]
if not stdin:
self.mock_config.return_value = [{} for i in range(0, lcmds)]
else:
self.mock_config_with_input.return_value = [{} for i in range(0, lcmds)]

result = func(*fargs, **fkwargs)

if cmds is not None:
self.node.config.assert_called_with(cmds)
if not stdin:
self.node.config.assert_called_with(cmds)
else:
self.node.config_with_input.assert_called_with(cmds)
else:
self.assertEqual(self.node.config.call_count, 0)
if not stdin:
self.assertEqual(self.node.config.call_count, 0)
else:
self.assertEqual(self.node.config_with_input.call_count, 0)

return result

def eapi_positive_config_test(self, func, cmds=None, *args, **kwargs):
result = self.eapi_config_test(func, cmds, *args, **kwargs)
result = self.eapi_config_test(func, False, cmds, *args, **kwargs)
self.assertTrue(result)

def eapi_negative_config_test(self, func, cmds=None, *args, **kwargs):
result = self.eapi_config_test(func, cmds, *args, **kwargs)
result = self.eapi_config_test(func, False, cmds, *args, **kwargs)
self.assertFalse(result)

def eapi_exception_config_test(self, func, exc, *args, **kwargs):
with self.assertRaises(exc):
self.eapi_config_test(func, *args, **kwargs)
self.eapi_config_test(func, False, *args, **kwargs)

def eapi_positive_config_with_input_test(self, func, cmds=None,
*args, **kwargs):
result = self.eapi_config_test(func, True, cmds, *args, **kwargs)
self.assertTrue(result)



Expand Down
94 changes: 93 additions & 1 deletion test/system/test_api_system.py
Expand Up @@ -45,9 +45,10 @@ def test_get(self):
for dut in self.duts:
dut.config('default hostname')
resp = dut.api('system').get()
keys = ['hostname', 'iprouting']
keys = ['hostname', 'iprouting', 'banner_motd', 'banner_login']
self.assertEqual(sorted(keys), sorted(resp.keys()))


def test_get_with_period(self):
for dut in self.duts:
dut.config('hostname host.domain.net')
Expand All @@ -59,6 +60,21 @@ def test_get_check_hostname(self):
dut.config('hostname teststring')
response = dut.api('system').get()
self.assertEqual(response['hostname'], 'teststring')
def test_get_check_banners(self):
for dut in self.duts:
motd_banner_value = random_string()
login_banner_value = random_string()
dut.config_with_input(dict(command="banner motd",value=motd_banner_value))
dut.config_with_input(dict(command="banner login",value=login_banner_value))
resp = dut.api('system').get()
self.assertEqual(resp['banner_login'], login_banner_value)
self.assertEqual(resp['banner_motd'], motd_banner_value)
def test_get_banner_with_newline(self):
for dut in self.duts:
motd_banner_value = '!!!newlinebaner\n\n!!!newlinebanner'
dut.config_with_input(dict(command="banner motd", value=motd_banner_value))
resp = dut.api('system').get()
self.assertEqual(resp['banner_motd'], motd_banner_value)

def test_set_hostname_with_value(self):
for dut in self.duts:
Expand Down Expand Up @@ -129,6 +145,82 @@ def test_set_hostname_with_period(self):
value = 'hostname host.domain.net'
self.assertIn(value, dut.running_config)

def test_set_banner_motd(self):
for dut in self.duts:
banner_value = random_string()
dut.config_with_input(dict(command="banner motd",
value=banner_value))
self.assertIn(banner_value, dut.running_config)
banner_api_value = random_string()
resp = dut.api('system').set_banner("motd", banner_api_value)
self.assertTrue(resp, 'dut=%s' % dut)
self.assertIn(banner_api_value, dut.running_config)

def test_set_banner_motd_donkey(self):
for dut in self.duts:
donkey_chicken = """
/\ /\
( \\ // )
\ \\ // /
\_\\||||//_/
\/ _ _ \
\/|(o)(O)|
\/ | |
___________________\/ \ /
// // |____| Cluck cluck cluck!
// || / \
//| \| \ 0 0 /
// \ ) V / \____/
// \ / ( /
"" \ /_________| |_/
/ /\ / | ||
/ / / / \ ||
| | | | | ||
| | | | | ||
|_| |_| |_||
\_\ \_\ \_\\
"""

resp = dut.api('system').set_banner("motd", donkey_chicken)
self.assertTrue(resp, 'dut=%s' % dut)
self.assertIn(donkey_chicken, dut.running_config)



def test_set_banner_motd_default(self):
for dut in self.duts:
dut.config_with_input(dict(command="banner motd",
value="!!!!REMOVE BANNER TEST!!!!"))
resp = dut.api('system').set_banner('motd', None, True)
self.assertIn('no banner motd', dut.running_config)

def test_set_banner_login(self):
for dut in self.duts:
banner_value = random_string()
dut.config_with_input(dict(command="banner login",
value=banner_value))
self.assertIn(banner_value, dut.running_config)
banner_api_value = random_string()
resp = dut.api('system').set_banner("login", banner_api_value)
self.assertTrue(resp, 'dut=%s' % dut)
self.assertIn(banner_api_value, dut.running_config)

def test_set_banner_login_default(self):
for dut in self.duts:
dut.config_with_input(dict(command="banner login",
value="!!!!REMOVE LOGIN BANNER TEST!!!!"))
resp = dut.api('system').set_banner('login', None, True)
self.assertIn('no banner login', dut.running_config)

def test_set_banner_login_negate(self):
for dut in self.duts:
dut.config_with_input(dict(command="banner login",
value="!!!!REMOVE LOGIN BANNER TEST!!!!"))
resp = dut.api('system').set_banner('login', None, False, True)
self.assertIn('no banner login', dut.running_config)




if __name__ == '__main__':
unittest.main()
23 changes: 20 additions & 3 deletions test/unit/test_api_system.py
Expand Up @@ -46,11 +46,14 @@ def __init__(self, *args, **kwargs):
super(TestApiSystem, self).__init__(*args, **kwargs)
self.instance = pyeapi.api.system.instance(None)
self.config = open(get_fixture('running_config.text')).read()


def test_get(self):
keys = ['hostname', 'iprouting']
keys = ['hostname', 'iprouting', 'banner_motd', 'banner_login']
result = self.instance.get()
self.assertEqual(sorted(keys), sorted(list(result.keys())))
self.assertIsNotNone(self.instance.get()['banner_motd'])
self.assertIsNotNone(self.instance.get()['banner_login'])

def test_set_hostname(self):
for state in ['config', 'negate', 'default']:
Expand Down Expand Up @@ -78,9 +81,23 @@ def test_set_iprouting(self):
cmds = 'default ip routing'
func = function('set_iprouting', default=True)
self.eapi_positive_config_test(func, cmds)
def test_set_banner(self):
banner_value = random_string()
func = function('set_banner', banner_type='motd',
value=banner_value)
cmds = dict(command='banner motd', value=banner_value)
self.eapi_positive_config_with_input_test(func, cmds)
def test_set_banner_default_disable(self):
func = function('set_banner', banner_type='motd',
value=None, default=True)
cmds = 'default banner motd'
self.eapi_positive_config_test(func, cmds)
func = function('set_banner', banner_type='motd',
value=None, disable=True)
cmds = 'no banner motd'
self.eapi_positive_config_test(func, cmds)





if __name__ == '__main__':
unittest.main()
6 changes: 6 additions & 0 deletions test/unit/test_client.py
Expand Up @@ -92,6 +92,12 @@ def test_config_with_multiple_commands(self):
self.node.run_commands = Mock(return_value=[{}, {}, {}])
result = self.node.config(commands)
self.assertEqual(result, [{}, {}])

def test_config_with_input(self):
command = dict(command=random_string(), value=random_string())
self.node.run_commands = Mock(return_value=[{}, {}])
result = self.node.config_with_input(command)
self.assertEqual(result, [{}])

def test_get_config(self):
config = [dict(output='test\nconfig')]
Expand Down