vyos and ios cliconf plugin refactor (#41846)

* vyos and ios cliconf plugin refactor

*  Refactor vyos cliconf plugin
*  Change vyos module_utils and vyos_config as per refactor
*  Minor changes in ios cliconf plugin

* Fix unit test failure

* Fix sanity issues

* Add get_diff to rpc list
pull/40936/merge
Ganesh Nalawade 6 years ago committed by GitHub
parent 9c5d40ff15
commit 773c031d33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -166,6 +166,7 @@ def load_config(module, commands):
connection = get_connection(module) connection = get_connection(module)
try: try:
return connection.edit_config(commands) diff, response = connection.edit_config(commands)
return response
except ConnectionError as exc: except ConnectionError as exc:
module.fail_json(msg=to_text(exc)) module.fail_json(msg=to_text(exc))

@ -133,29 +133,8 @@ def load_config(module, commands, commit=False, comment=None):
connection = get_connection(module) connection = get_connection(module)
try: try:
out = connection.edit_config(commands) diff_config, resp = connection.edit_config(candidate=commands, commit=commit, diff=module._diff, comment=comment)
except ConnectionError as exc: except ConnectionError as exc:
module.fail_json(msg=to_text(exc)) module.fail_json(msg=to_text(exc))
diff = None return diff_config
if module._diff:
out = connection.get('compare')
out = to_text(out, errors='surrogate_or_strict')
if not out.startswith('No changes'):
out = connection.get('show')
diff = to_text(out, errors='surrogate_or_strict').strip()
if commit:
try:
out = connection.commit(comment)
except ConnectionError:
connection.discard_changes()
module.fail_json(msg='commit failed: %s' % out)
else:
connection.get('exit')
else:
connection.discard_changes()
if diff:
return diff

@ -441,9 +441,9 @@ def main():
# them with the current running config # them with the current running config
if not module.check_mode: if not module.check_mode:
if commands: if commands:
connection.edit_config(commands) connection.edit_config(candidate=commands)
if banner_diff: if banner_diff:
connection.edit_banner(json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter']) connection.edit_banner(candidate=json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter'])
result['changed'] = True result['changed'] = True

@ -130,11 +130,11 @@ backup_path:
sample: /playbooks/ansible/backup/vyos_config.2016-07-16@22:28:34 sample: /playbooks/ansible/backup/vyos_config.2016-07-16@22:28:34
""" """
import re import re
import json
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import NetworkConfig
from ansible.module_utils.network.vyos.vyos import load_config, get_config, run_commands from ansible.module_utils.network.vyos.vyos import load_config, get_config, run_commands
from ansible.module_utils.network.vyos.vyos import vyos_argument_spec from ansible.module_utils.network.vyos.vyos import vyos_argument_spec, get_connection
DEFAULT_COMMENT = 'configured by vyos_config' DEFAULT_COMMENT = 'configured by vyos_config'
@ -144,35 +144,13 @@ CONFIG_FILTERS = [
] ]
def config_to_commands(config):
set_format = config.startswith('set') or config.startswith('delete')
candidate = NetworkConfig(indent=4, contents=config)
if not set_format:
candidate = [c.line for c in candidate.items]
commands = list()
# this filters out less specific lines
for item in candidate:
for index, entry in enumerate(commands):
if item.startswith(entry):
del commands[index]
break
commands.append(item)
commands = ['set %s' % cmd.replace(' {', '') for cmd in commands]
else:
commands = str(candidate).split('\n')
return commands
def get_candidate(module): def get_candidate(module):
contents = module.params['src'] or module.params['lines'] contents = module.params['src'] or module.params['lines']
if module.params['lines']: if module.params['lines']:
contents = '\n'.join(contents) contents = '\n'.join(contents)
return config_to_commands(contents) return contents
def diff_config(commands, config): def diff_config(commands, config):
@ -225,7 +203,10 @@ def run(module, result):
candidate = get_candidate(module) candidate = get_candidate(module)
# create loadable config that includes only the configuration updates # create loadable config that includes only the configuration updates
commands = diff_config(candidate, config) connection = get_connection(module)
response = connection.get_diff(candidate=candidate, running=config, match=module.params['match'])
diff_obj = json.loads(response)
commands = diff_obj.get('config_diff')
sanitize_config(commands, result) sanitize_config(commands, result)
result['commands'] = commands result['commands'] = commands
@ -233,8 +214,9 @@ def run(module, result):
commit = not module.check_mode commit = not module.check_mode
comment = module.params['comment'] comment = module.params['comment']
diff = None
if commands: if commands:
load_config(module, commands, commit=commit, comment=comment) diff = load_config(module, commands, commit=commit, comment=comment)
if result.get('filtered'): if result.get('filtered'):
result['warnings'].append('Some configuration commands were ' result['warnings'].append('Some configuration commands were '
@ -242,6 +224,9 @@ def run(module, result):
result['changed'] = True result['changed'] = True
if module._diff:
result['diff'] = diff
def main(): def main():
argument_spec = dict( argument_spec = dict(

@ -93,7 +93,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
display.display('closing shell due to command timeout (%s seconds).' % self._connection._play_context.timeout, log_only=True) display.display('closing shell due to command timeout (%s seconds).' % self._connection._play_context.timeout, log_only=True)
self.close() self.close()
def send_command(self, command, prompt=None, answer=None, sendonly=False, newline=True, prompt_retry_check=False): def send_command(self, command=None, prompt=None, answer=None, sendonly=False, newline=True, prompt_retry_check=False):
"""Executes a command over the device connection """Executes a command over the device connection
This method will execute a command over the device connection and This method will execute a command over the device connection and
@ -184,7 +184,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
pass pass
@abstractmethod @abstractmethod
def edit_config(self, candidate, check_mode=False, replace=None): def edit_config(self, candidate=None, commit=True, replace=False, diff=False, comment=None):
"""Loads the candidate configuration into the network device """Loads the candidate configuration into the network device
This method will load the specified candidate config into the device This method will load the specified candidate config into the device
@ -195,20 +195,22 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
:param candidate: The configuration to load into the device and merge :param candidate: The configuration to load into the device and merge
with the current running configuration with the current running configuration
:param check_mode: Boolean value that indicates if the device candidate :param commit: Boolean value that indicates if the device candidate
configuration should be pushed in the running configuration or discarded. configuration should be pushed in the running configuration or discarded.
:param replace: Specifies the way in which provided config value should replace :param replace: Boolean flag to indicate if running configuration should be completely
the configuration running on the remote device. If the device replace by candidate configuration.
doesn't support config replace, an error is return. :param diff: Boolean flag to indicate if configuration that is applied on remote host should
generated and returned in response or not
:return: Returns response of executing the configuration command received :param comment: Commit comment provided it is supported by remote host
from remote host :return: Returns a tuple, the first entry of tupe is configuration diff if diff flag is enable else
it is None. Second entry is the list of response received from remote host on executing
configuration commands.
""" """
pass pass
@abstractmethod @abstractmethod
def get(self, command, prompt=None, answer=None, sendonly=False, newline=True): def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True):
"""Execute specified command on remote device """Execute specified command on remote device
This method will retrieve the specified data and This method will retrieve the specified data and
return it to the caller as a string. return it to the caller as a string.
@ -242,7 +244,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
'network_os_platform': <str>, 'network_os_platform': <str>,
}, },
'device_operations': { 'device_operations': {
'supports_replace': <bool>, # identify if config should be merged or replaced is supported 'supports_diff_replace': <bool>, # identify if config should be merged or replaced is supported
'supports_commit': <bool>, # identify if commit is supported by device or not 'supports_commit': <bool>, # identify if commit is supported by device or not
'supports_rollback': <bool>, # identify if rollback is supported or not 'supports_rollback': <bool>, # identify if rollback is supported or not
'supports_defaults': <bool>, # identify if fetching running config with default is supported 'supports_defaults': <bool>, # identify if fetching running config with default is supported
@ -250,12 +252,13 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
'supports_onbox_diff: <bool>, # identify if on box diff capability is supported or not 'supports_onbox_diff: <bool>, # identify if on box diff capability is supported or not
'supports_generate_diff: <bool>, # identify if diff capability is supported within plugin 'supports_generate_diff: <bool>, # identify if diff capability is supported within plugin
'supports_multiline_delimiter: <bool>, # identify if multiline demiliter is supported within config 'supports_multiline_delimiter: <bool>, # identify if multiline demiliter is supported within config
'support_match: <bool>, # identify if match is supported 'support_diff_match: <bool>, # identify if match is supported
'support_diff_ignore_lines: <bool>, # identify if ignore line in diff is supported 'support_diff_ignore_lines: <bool>, # identify if ignore line in diff is supported
'support_config_replace': <bool>, # identify if running config replace with candidate config is supported
} }
'format': [list of supported configuration format], 'format': [list of supported configuration format],
'match': ['line', 'strict', 'exact', 'none'], 'diff_match': [list of supported match values],
'replace': ['line', 'block', 'config'], 'diff_replace': [list of supported replace values],
} }
:return: capability as json string :return: capability as json string
""" """
@ -326,3 +329,38 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
elif proto == 'sftp': elif proto == 'sftp':
with ssh.open_sftp() as sftp: with ssh.open_sftp() as sftp:
sftp.get(source, destination) sftp.get(source, destination)
def get_diff(self, candidate=None, running=None, match=None, diff_ignore_lines=None, path=None, replace=None):
"""
Generate diff between candidate and running configuration. If the
remote host supports onbox diff capabilities ie. supports_onbox_diff in that case
candidate and running configurations are not required to be passed as argument.
In case if onbox diff capability is not supported candidate argument is mandatory
and running argument is optional.
:param candidate: The configuration which is expected to be present on remote host.
:param running: The base configuration which is used to generate diff.
:param match: Instructs how to match the candidate configuration with current device configuration
Valid values are 'line', 'strict', 'exact', 'none'.
'line' - commands are matched line by line
'strict' - command lines are matched with respect to position
'exact' - command lines must be an equal match
'none' - will not compare the candidate configuration with the running configuration
:param diff_ignore_lines: Use this argument to specify one or more lines that should be
ignored during the diff. This is used for lines in the configuration
that are automatically updated by the system. This argument takes
a list of regular expressions or exact line matches.
:param path: The ordered set of parents that uniquely identify the section or hierarchy
the commands should be checked against. If the parents argument
is omitted, the commands are checked against the set of top
level or global commands.
:param replace: Instructs on the way to perform the configuration on the device.
If the replace argument is set to I(line) then the modified lines are
pushed to the device in configuration mode. If the replace argument is
set to I(block) then the entire command block is pushed to the device in
configuration mode if any line is not correct.
:return: Configuration and/or banner diff in json format.
{
'config_diff': ''
}
"""

@ -66,8 +66,7 @@ class Cliconf(CliconfBase):
'line' - commands are matched line by line 'line' - commands are matched line by line
'strict' - command lines are matched with respect to position 'strict' - command lines are matched with respect to position
'exact' - command lines must be an equal match 'exact' - command lines must be an equal match
'none' - will not compare the candidate configuration with 'none' - will not compare the candidate configuration with the running configuration
the running configuration on the remote device
:param diff_ignore_lines: Use this argument to specify one or more lines that should be :param diff_ignore_lines: Use this argument to specify one or more lines that should be
ignored during the diff. This is used for lines in the configuration ignored during the diff. This is used for lines in the configuration
that are automatically updated by the system. This argument takes that are automatically updated by the system. This argument takes
@ -90,9 +89,16 @@ class Cliconf(CliconfBase):
""" """
diff = {} diff = {}
device_operations = self.get_device_operations() device_operations = self.get_device_operations()
option_values = self.get_option_values()
if candidate is None and not device_operations['supports_onbox_diff']: if candidate is None and not device_operations['supports_onbox_diff']:
raise ValueError('candidate configuration is required to generate diff') raise ValueError("candidate configuration is required to generate diff")
if match not in option_values['diff_match']:
raise ValueError("'match' value %s in invalid, valid values are %s" % (match, option_values['diff_match']))
if replace not in option_values['diff_replace']:
raise ValueError("'replace' value %s in invalid, valid values are %s" % (replace, option_values['diff_replace']))
# prepare candidate configuration # prepare candidate configuration
candidate_obj = NetworkConfig(indent=1) candidate_obj = NetworkConfig(indent=1)
@ -118,19 +124,22 @@ class Cliconf(CliconfBase):
return json.dumps(diff) return json.dumps(diff)
@enable_mode @enable_mode
def edit_config(self, candidate, check_mode=False, replace=None): def edit_config(self, candidate=None, commit=True, replace=False, diff=False, comment=None):
if not candidate: if not candidate:
raise ValueError('must provide a candidate config to load') raise ValueError("must provide a candidate config to load")
if commit not in (True, False):
raise ValueError("'commit' must be a bool, got %s" % commit)
if check_mode not in (True, False): if replace not in (True, False):
raise ValueError('`check_mode` must be a bool, got %s' % check_mode) raise ValueError("'replace' must be a bool, got %s" % replace)
options = self.get_option_values() operations = self.get_device_operations()
if replace and replace not in options['replace']: if replace and not operations['supports_replace']:
raise ValueError('`replace` value %s in invalid, valid values are %s' % (replace, options['replace'])) raise ValueError("configuration replace is not supported on ios")
results = [] results = []
if not check_mode: if commit:
for line in chain(['configure terminal'], to_list(candidate)): for line in chain(['configure terminal'], to_list(candidate)):
if not isinstance(line, collections.Mapping): if not isinstance(line, collections.Mapping):
line = {'command': line} line = {'command': line}
@ -140,16 +149,23 @@ class Cliconf(CliconfBase):
results.append(self.send_command(**line)) results.append(self.send_command(**line))
results.append(self.send_command('end')) results.append(self.send_command('end'))
return results[1:-1]
def get(self, command, prompt=None, answer=None, sendonly=False): diff_config = None
return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly) if diff:
diff_config = candidate
return diff_config, results[1:-1]
def get(self, command=None, prompt=None, answer=None, sendonly=False):
if not command:
raise ValueError('must provide value of command to execute')
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly)
def get_device_info(self): def get_device_info(self):
device_info = {} device_info = {}
device_info['network_os'] = 'ios' device_info['network_os'] = 'ios'
reply = self.get('show version') reply = self.get(command='show version')
data = to_text(reply, errors='surrogate_or_strict').strip() data = to_text(reply, errors='surrogate_or_strict').strip()
match = re.search(r'Version (\S+)', data) match = re.search(r'Version (\S+)', data)
@ -168,47 +184,50 @@ class Cliconf(CliconfBase):
def get_device_operations(self): def get_device_operations(self):
return { return {
'supports_replace': True, 'supports_diff_replace': True,
'supports_commit': False, 'supports_commit': False,
'supports_rollback': False, 'supports_rollback': False,
'supports_defaults': True, 'supports_defaults': True,
'supports_onbox_diff': False, 'supports_onbox_diff': False,
'supports_commit_comment': False, 'supports_commit_comment': False,
'supports_multiline_delimiter': False, 'supports_multiline_delimiter': False,
'support_match': True, 'support_diff_match': True,
'support_diff_ignore_lines': True, 'support_diff_ignore_lines': True,
'supports_generate_diff': True, 'supports_generate_diff': True,
'supports_replace': False
} }
def get_option_values(self): def get_option_values(self):
return { return {
'format': ['text'], 'format': ['text'],
'match': ['line', 'strict', 'exact', 'none'], 'diff_match': ['line', 'strict', 'exact', 'none'],
'replace': ['line', 'block'] 'diff_replace': ['line', 'block']
} }
def get_capabilities(self): def get_capabilities(self):
result = dict() result = dict()
result['rpc'] = self.get_base_rpc() + ['edit_banner'] result['rpc'] = self.get_base_rpc() + ['edit_banner', 'get_diff']
result['network_api'] = 'cliconf' result['network_api'] = 'cliconf'
result['device_info'] = self.get_device_info() result['device_info'] = self.get_device_info()
result['device_operations'] = self.get_device_operations() result['device_operations'] = self.get_device_operations()
result.update(self.get_option_values()) result.update(self.get_option_values())
return json.dumps(result) return json.dumps(result)
def edit_banner(self, banners, multiline_delimiter="@", check_mode=False): def edit_banner(self, candidate=None, multiline_delimiter="@", commit=True, diff=False):
""" """
Edit banner on remote device Edit banner on remote device
:param banners: Banners to be loaded in json format :param banners: Banners to be loaded in json format
:param multiline_delimiter: Line delimiter for banner :param multiline_delimiter: Line delimiter for banner
:param check_mode: Boolean value that indicates if the device candidate :param commit: Boolean value that indicates if the device candidate
configuration should be pushed in the running configuration or discarded. configuration should be pushed in the running configuration or discarded.
:param diff: Boolean flag to indicate if configuration that is applied on remote host should
generated and returned in response or not
:return: Returns response of executing the configuration command received :return: Returns response of executing the configuration command received
from remote host from remote host
""" """
banners_obj = json.loads(banners) banners_obj = json.loads(candidate)
results = [] results = []
if not check_mode: if commit:
for key, value in iteritems(banners_obj): for key, value in iteritems(banners_obj):
key += ' %s' % multiline_delimiter key += ' %s' % multiline_delimiter
for cmd in ['config terminal', key, value, multiline_delimiter, 'end']: for cmd in ['config terminal', key, value, multiline_delimiter, 'end']:
@ -218,7 +237,11 @@ class Cliconf(CliconfBase):
time.sleep(0.1) time.sleep(0.1)
results.append(self.send_command('\n')) results.append(self.send_command('\n'))
return results[1:-1] diff_banner = None
if diff:
diff_banner = candidate
return diff_banner, results[1:-1]
def _extract_banners(self, config): def _extract_banners(self, config):
banners = {} banners = {}

@ -24,7 +24,9 @@ import json
from itertools import chain from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.config import NetworkConfig, dumps
from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase from ansible.plugins.cliconf import CliconfBase
@ -51,14 +53,56 @@ class Cliconf(CliconfBase):
return device_info return device_info
def get_config(self, source='running', format='text'): def get_config(self, filter=None, format='set'):
return self.send_command('show configuration commands') if format == 'text':
out = self.send_command('show configuration')
else:
out = self.send_command('show configuration commands')
return out
def edit_config(self, candidate=None, commit=True, replace=False, diff=False, comment=None):
if not candidate:
raise ValueError('must provide a candidate config to load')
if commit not in (True, False):
raise ValueError("'commit' must be a bool, got %s" % commit)
if replace not in (True, False):
raise ValueError("'replace' must be a bool, got %s" % replace)
operations = self.get_device_operations()
if replace and not operations['supports_replace']:
raise ValueError("configuration replace is not supported on vyos")
results = []
for cmd in chain(['configure'], to_list(candidate)):
results.append(self.send_command(cmd))
diff_config = None
if diff:
out = self.get('compare')
out = to_text(out, errors='surrogate_or_strict')
if not out.startswith('No changes'):
diff_config = out
if commit:
try:
self.commit(comment)
except AnsibleConnectionFailure as e:
msg = 'commit failed: %s' % e.message
self.discard_changes()
raise AnsibleConnectionFailure(msg)
else:
self.get('exit')
else:
self.discard_changes()
def edit_config(self, command): return diff_config, results[1:]
for cmd in chain(['configure'], to_list(command)):
self.send_command(cmd)
def get(self, command, prompt=None, answer=None, sendonly=False): def get(self, command=None, prompt=None, answer=None, sendonly=False):
if not command:
raise ValueError('must provide value of command to execute')
return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly) return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly)
def commit(self, comment=None): def commit(self, comment=None):
@ -68,12 +112,105 @@ class Cliconf(CliconfBase):
command = 'commit' command = 'commit'
self.send_command(command) self.send_command(command)
def discard_changes(self, *args, **kwargs): def discard_changes(self):
self.send_command('exit discard') self.send_command('exit discard')
def get_diff(self, candidate=None, running=None, match='line', diff_ignore_lines=None, path=None, replace=None):
diff = {}
device_operations = self.get_device_operations()
option_values = self.get_option_values()
if candidate is None and not device_operations['supports_onbox_diff']:
raise ValueError("candidate configuration is required to generate diff")
if match not in option_values['diff_match']:
raise ValueError("'match' value %s in invalid, valid values are %s" % (match, option_values['diff_match']))
if replace:
raise ValueError("'replace' in diff is not supported on vyos")
if diff_ignore_lines:
raise ValueError("'diff_ignore_lines' in diff is not supported on vyos")
if path:
raise ValueError("'path' in diff is not supported on vyos")
set_format = candidate.startswith('set') or candidate.startswith('delete')
candidate_obj = NetworkConfig(indent=4, contents=candidate)
if not set_format:
config = [c.line for c in candidate_obj.items]
commands = list()
# this filters out less specific lines
for item in config:
for index, entry in enumerate(commands):
if item.startswith(entry):
del commands[index]
break
commands.append(item)
candidate_commands = ['set %s' % cmd.replace(' {', '') for cmd in commands]
else:
candidate_commands = str(candidate).strip().split('\n')
if match == 'none':
diff['config_diff'] = list(candidate_commands)
return json.dumps(diff)
running_commands = [str(c).replace("'", '') for c in running.splitlines()]
updates = list()
visited = set()
for line in candidate_commands:
item = str(line).replace("'", '')
if not item.startswith('set') and not item.startswith('delete'):
raise ValueError('line must start with either `set` or `delete`')
elif item.startswith('set') and item not in running_commands:
updates.append(line)
elif item.startswith('delete'):
if not running_commands:
updates.append(line)
else:
item = re.sub(r'delete', 'set', item)
for entry in running_commands:
if entry.startswith(item) and line not in visited:
updates.append(line)
visited.add(line)
diff['config_diff'] = list(updates)
return json.dumps(diff)
def get_device_operations(self):
return {
'supports_diff_replace': False,
'supports_commit': True,
'supports_rollback': True,
'supports_defaults': False,
'supports_onbox_diff': False,
'supports_commit_comment': True,
'supports_multiline_delimiter': False,
'support_diff_match': True,
'support_diff_ignore_lines': False,
'supports_generate_diff': True,
'supports_replace': False
}
def get_option_values(self):
return {
'format': ['set', 'text'],
'diff_match': ['line', 'none'],
'diff_replace': [],
}
def get_capabilities(self): def get_capabilities(self):
result = {} result = {}
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes'] result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'get_diff']
result['network_api'] = 'cliconf' result['network_api'] = 'cliconf'
result['device_info'] = self.get_device_info() result['device_info'] = self.get_device_info()
result['device_operations'] = self.get_device_operations()
result.update(self.get_option_values())
return json.dumps(result) return json.dumps(result)

@ -20,8 +20,9 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible.compat.tests.mock import patch from ansible.compat.tests.mock import patch, MagicMock
from ansible.modules.network.vyos import vyos_config from ansible.modules.network.vyos import vyos_config
from ansible.plugins.cliconf.vyos import Cliconf
from units.modules.utils import set_module_args from units.modules.utils import set_module_args
from .vyos_module import TestVyosModule, load_fixture from .vyos_module import TestVyosModule, load_fixture
@ -42,12 +43,23 @@ class TestVyosConfigModule(TestVyosModule):
self.mock_run_commands = patch('ansible.modules.network.vyos.vyos_config.run_commands') self.mock_run_commands = patch('ansible.modules.network.vyos.vyos_config.run_commands')
self.run_commands = self.mock_run_commands.start() self.run_commands = self.mock_run_commands.start()
self.mock_get_connection = patch('ansible.modules.network.vyos.vyos_config.get_connection')
self.get_connection = self.mock_get_connection.start()
self.cliconf_obj = Cliconf(MagicMock())
self.running_config = load_fixture('vyos_config_config.cfg')
self.conn = self.get_connection()
self.conn.edit_config = MagicMock()
self.running_config = load_fixture('vyos_config_config.cfg')
def tearDown(self): def tearDown(self):
super(TestVyosConfigModule, self).tearDown() super(TestVyosConfigModule, self).tearDown()
self.mock_get_config.stop() self.mock_get_config.stop()
self.mock_load_config.stop() self.mock_load_config.stop()
self.mock_run_commands.stop() self.mock_run_commands.stop()
self.mock_get_connection.stop()
def load_fixtures(self, commands=None): def load_fixtures(self, commands=None):
config_file = 'vyos_config_config.cfg' config_file = 'vyos_config_config.cfg'
@ -56,6 +68,7 @@ class TestVyosConfigModule(TestVyosModule):
def test_vyos_config_unchanged(self): def test_vyos_config_unchanged(self):
src = load_fixture('vyos_config_config.cfg') src = load_fixture('vyos_config_config.cfg')
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, src))
set_module_args(dict(src=src)) set_module_args(dict(src=src))
self.execute_module() self.execute_module()
@ -63,12 +76,14 @@ class TestVyosConfigModule(TestVyosModule):
src = load_fixture('vyos_config_src.cfg') src = load_fixture('vyos_config_src.cfg')
set_module_args(dict(src=src)) set_module_args(dict(src=src))
commands = ['set system host-name foo', 'delete interfaces ethernet eth0 address'] commands = ['set system host-name foo', 'delete interfaces ethernet eth0 address']
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, self.running_config))
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_vyos_config_src_brackets(self): def test_vyos_config_src_brackets(self):
src = load_fixture('vyos_config_src_brackets.cfg') src = load_fixture('vyos_config_src_brackets.cfg')
set_module_args(dict(src=src)) set_module_args(dict(src=src))
commands = ['set interfaces ethernet eth0 address 10.10.10.10/24', 'set system host-name foo'] commands = ['set interfaces ethernet eth0 address 10.10.10.10/24', 'set system host-name foo']
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, self.running_config))
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_vyos_config_backup(self): def test_vyos_config_backup(self):
@ -79,16 +94,22 @@ class TestVyosConfigModule(TestVyosModule):
def test_vyos_config_lines(self): def test_vyos_config_lines(self):
commands = ['set system host-name foo'] commands = ['set system host-name foo']
set_module_args(dict(lines=commands)) set_module_args(dict(lines=commands))
candidate = '\n'.join(commands)
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(candidate, self.running_config))
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_vyos_config_config(self): def test_vyos_config_config(self):
config = 'set system host-name localhost' config = 'set system host-name localhost'
new_config = ['set system host-name router'] new_config = ['set system host-name router']
set_module_args(dict(lines=new_config, config=config)) set_module_args(dict(lines=new_config, config=config))
candidate = '\n'.join(new_config)
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(candidate, config))
self.execute_module(changed=True, commands=new_config) self.execute_module(changed=True, commands=new_config)
def test_vyos_config_match_none(self): def test_vyos_config_match_none(self):
lines = ['set system interfaces ethernet eth0 address 1.2.3.4/24', lines = ['set system interfaces ethernet eth0 address 1.2.3.4/24',
'set system interfaces ethernet eth0 description test string'] 'set system interfaces ethernet eth0 description test string']
set_module_args(dict(lines=lines, match='none')) set_module_args(dict(lines=lines, match='none'))
candidate = '\n'.join(lines)
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(candidate, None, match='none'))
self.execute_module(changed=True, commands=lines, sort=False) self.execute_module(changed=True, commands=lines, sort=False)

Loading…
Cancel
Save