Update iosxr cliconf plugin (#43837)

* Update iosxr cliconf plugin

Fixes #39056

*  Update iosxr cliconf plugin
*  Modify iosxr module_utils code to support
   refactored cliconf plugin api's
*  Other minor changes

* Fix unit test failure

* Update ios, eos, nxos plugin for diff

* Fix review comment
This commit is contained in:
Ganesh Nalawade 2018-08-10 13:12:51 +05:30 committed by GitHub
commit d1de1e0449
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 217 additions and 153 deletions

View file

@ -175,7 +175,7 @@ class CliconfBase(AnsiblePlugin):
:param source: The configuration source to return from the device.
This argument accepts either `running` or `startup` as valid values.
:param flag: For devices that support configuration filtering, this
:param flags: For devices that support configuration filtering, this
keyword argument is used to filter the returned configuration.
The use of this keyword argument is device dependent adn will be
silently ignored on devices that do not support it.
@ -212,7 +212,8 @@ class CliconfBase(AnsiblePlugin):
response on executing configuration commands and platform relevant data.
{
"diff": "",
"response": []
"response": [],
"request": []
}
"""
@ -264,9 +265,11 @@ class CliconfBase(AnsiblePlugin):
'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_multiline_delimiter: <bool>, # identify if multiline demiliter is supported within config
'supports_diff_match: <bool>, # identify if match is supported
'supports_diff_ignore_lines: <bool>, # identify if ignore line in diff is supported
'supports_config_replace': <bool>, # identify if running config replace with candidate config is supported
'supports_diff_match: <bool>, # identify if match is supported
'supports_diff_ignore_lines: <bool>, # identify if ignore line in diff is supported
'supports_config_replace': <bool>, # identify if running config replace with candidate config is supported
'supports_admin': <bool>, # identify if admin configure mode is supported or not
'supports_commit_label': <bool>, # identify if commit label is supported or not
}
'format': [list of supported configuration format],
'diff_match': [list of supported match values],

View file

@ -214,7 +214,7 @@ class Cliconf(CliconfBase):
raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace'])))
# prepare candidate configuration
candidate_obj = NetworkConfig(indent=3)
candidate_obj = NetworkConfig(indent=3, ignore_lines=diff_ignore_lines)
candidate_obj.load(candidate)
if running and diff_match != 'none' and diff_replace != 'config':

View file

@ -105,7 +105,7 @@ class Cliconf(CliconfBase):
raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace'])))
# prepare candidate configuration
candidate_obj = NetworkConfig(indent=1)
candidate_obj = NetworkConfig(indent=1, ignore_lines=diff_ignore_lines)
want_src, want_banners = self._extract_banners(candidate)
candidate_obj.load(want_src)

View file

@ -19,12 +19,13 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import collections
import re
import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase
@ -56,56 +57,167 @@ class Cliconf(CliconfBase):
return device_info
def get_config(self, source='running', format='text', filter=None):
def configure(self, admin=False):
prompt = to_text(self._connection.get_prompt(), errors='surrogate_or_strict').strip()
if not prompt.endswith(')#'):
if admin and 'admin-' not in prompt:
self.send_command('admin')
self.send_command('configure terminal')
def abort(self, admin=False):
prompt = to_text(self._connection.get_prompt(), errors='surrogate_or_strict').strip()
if prompt.endswith(')#'):
self.send_command('abort')
if admin and 'admin-' in prompt:
self.send_command('exit')
def get_config(self, source='running', format='text', flags=None):
if source not in ['running']:
raise ValueError("fetching configuration from %s is not supported" % source)
lookup = {'running': 'running-config'}
if source not in lookup:
return self.invalid_params("fetching configuration from %s is not supported" % source)
if filter:
cmd = 'show {0} {1}'.format(lookup[source], filter)
else:
cmd = 'show {0}'.format(lookup[source])
cmd = 'show {0} '.format(lookup[source])
cmd += ' '.join(to_list(flags))
cmd = cmd.strip()
return self.send_command(cmd)
def edit_config(self, commands=None):
for cmd in chain(to_list(commands)):
try:
if isinstance(cmd, str):
cmd = json.loads(cmd)
command = cmd.get('command', None)
prompt = cmd.get('prompt', None)
answer = cmd.get('answer', None)
sendonly = cmd.get('sendonly', False)
newline = cmd.get('newline', True)
except:
command = cmd
prompt = None
answer = None
sendonly = None
newline = None
def edit_config(self, candidate=None, commit=True, admin=False, replace=None, comment=None, label=None):
operations = self.get_device_operations()
self.check_edit_config_capabiltiy(operations, candidate, commit, replace, comment)
self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
resp = {}
results = []
requests = []
self.configure(admin=admin)
if replace:
candidate = 'load {0}'.format(replace)
for line in to_list(candidate):
if not isinstance(line, collections.Mapping):
line = {'command': line}
cmd = line['command']
results.append(self.send_command(**line))
requests.append(cmd)
diff = self.get_diff(admin=admin)
config_diff = diff.get('config_diff')
if config_diff or replace:
resp['diff'] = config_diff
if commit:
self.commit(comment=comment, label=label, replace=replace)
else:
self.discard_changes()
self.abort(admin=admin)
resp['request'] = requests
resp['response'] = results
return resp
def get_diff(self, admin=False):
self.configure(admin=admin)
diff = {'config_diff': None}
response = self.send_command('show commit changes diff')
for item in response.splitlines():
if item and item[0] in ['<', '+', '-']:
diff['config_diff'] = response
break
return diff
def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True, output=None):
if output:
raise ValueError("'output' value %s is not supported for get" % output)
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
def commit(self, comment=None, label=None):
if comment and label:
command = 'commit label {0} comment {1}'.format(label, comment)
elif comment:
command = 'commit comment {0}'.format(comment)
elif label:
command = 'commit label {0}'.format(label)
def commit(self, comment=None, label=None, replace=None):
cmd_obj = {}
if replace:
cmd_obj['command'] = 'commit replace'
cmd_obj['prompt'] = 'This commit will replace or remove the entire running configuration'
cmd_obj['answer'] = 'yes'
else:
command = 'commit'
self.send_command(command)
if comment and label:
cmd_obj['command'] = 'commit label {0} comment {1}'.format(label, comment)
elif comment:
cmd_obj['command'] = 'commit comment {0}'.format(comment)
elif label:
cmd_obj['command'] = 'commit label {0}'.format(label)
else:
cmd_obj['command'] = 'commit'
self.send_command(**cmd_obj)
def run_commands(self, commands=None, check_rc=True):
if commands is None:
raise ValueError("'commands' value is required")
responses = list()
for cmd in to_list(commands):
if not isinstance(cmd, collections.Mapping):
cmd = {'command': cmd}
output = cmd.pop('output', None)
if output:
raise ValueError("'output' value %s is not supported for run_commands" % output)
try:
out = self.send_command(**cmd)
except AnsibleConnectionFailure as e:
if check_rc:
raise
out = getattr(e, 'err', e)
if out is not None:
try:
out = to_text(out, errors='surrogate_or_strict').strip()
except UnicodeError:
raise ConnectionError(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out)))
try:
out = json.loads(out)
except ValueError:
pass
responses.append(out)
return responses
def discard_changes(self):
self.send_command('abort')
def get_device_operations(self):
return {
'supports_diff_replace': False,
'supports_commit': True,
'supports_rollback': True,
'supports_defaults': False,
'supports_onbox_diff': True,
'supports_commit_comment': True,
'supports_multiline_delimiter': False,
'supports_diff_match': False,
'supports_diff_ignore_lines': False,
'supports_generate_diff': False,
'supports_replace': True,
'supports_admin': True,
'supports_commit_label': True
}
def get_option_values(self):
return {
'format': ['text'],
'diff_match': [],
'diff_replace': [],
'output': []
}
def get_capabilities(self):
result = {}
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes']
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'get_diff', 'configure', 'exit']
result['network_api'] = 'cliconf'
result['device_info'] = self.get_device_info()
result['device_operations'] = self.get_device_operations()
result.update(self.get_option_values())
return json.dumps(result)

View file

@ -110,10 +110,14 @@ class Cliconf(CliconfBase):
if diff:
resp['diff'] = diff
if commit:
self.commit(comment=comment)
if commit:
self.commit(comment=comment)
else:
self.discard_changes()
else:
self.discard_changes()
for cmd in ['top', 'exit']:
self.send_command(cmd)
resp['request'] = requests
resp['response'] = results
@ -166,7 +170,11 @@ class Cliconf(CliconfBase):
return resp
def get_diff(self, rollback_id=None):
return self.compare_configuration(rollback_id=rollback_id)
diff = {'config_diff': None}
response = self.compare_configuration(rollback_id=rollback_id)
if response:
diff['config_diff'] = response
return diff
def get_device_operations(self):
return {

View file

@ -115,7 +115,7 @@ class Cliconf(CliconfBase):
raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace'])))
# prepare candidate configuration
candidate_obj = NetworkConfig(indent=2)
candidate_obj = NetworkConfig(indent=2, ignore_lines=diff_ignore_lines)
candidate_obj.load(candidate)
if running and diff_match != 'none' and diff_replace != 'config':
@ -215,7 +215,7 @@ class Cliconf(CliconfBase):
try:
out = json.loads(out)
except ValueError:
out = to_text(out, errors='surrogate_or_strict').strip()
pass
responses.append(out)
return responses