mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-25 11:51:26 -07:00
Remove cliconf from httpapi connection (#46813)
* Bare minimum rip out cliconf * nxapi changeover * Update documentation, move options * Memoize device_info * Gratuitous rename to underscore use of local api implementation Fixup eos module_utils like nxos * Streamline version and image scans * Expose get_capabilities through module_utils * Add load_config to module_utils * Support rpcs using both args and kwargs * Add get_config for nxos * Add get_diff * module context, pulled from nxapi We could probably do this correctly later * Fix eos issues * Limit connection._sub_plugin to only one plugin
This commit is contained in:
parent
32dbb99bb8
commit
02432565cd
14 changed files with 568 additions and 255 deletions
|
@ -900,7 +900,7 @@ class TaskExecutor:
|
||||||
final_vars = combine_vars(variables, variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict()))
|
final_vars = combine_vars(variables, variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict()))
|
||||||
|
|
||||||
option_vars = C.config.get_plugin_vars('connection', connection._load_name)
|
option_vars = C.config.get_plugin_vars('connection', connection._load_name)
|
||||||
for plugin in connection._sub_plugins:
|
plugin = connection._sub_plugin
|
||||||
if plugin['type'] != 'external':
|
if plugin['type'] != 'external':
|
||||||
option_vars.extend(C.config.get_plugin_vars(plugin['type'], plugin['name']))
|
option_vars.extend(C.config.get_plugin_vars(plugin['type'], plugin['name']))
|
||||||
|
|
||||||
|
|
|
@ -100,10 +100,7 @@ def exec_command(module, command):
|
||||||
def request_builder(method_, *args, **kwargs):
|
def request_builder(method_, *args, **kwargs):
|
||||||
reqid = str(uuid.uuid4())
|
reqid = str(uuid.uuid4())
|
||||||
req = {'jsonrpc': '2.0', 'method': method_, 'id': reqid}
|
req = {'jsonrpc': '2.0', 'method': method_, 'id': reqid}
|
||||||
|
req['params'] = (args, kwargs)
|
||||||
params = args or kwargs or None
|
|
||||||
if params:
|
|
||||||
req['params'] = params
|
|
||||||
|
|
||||||
return req
|
return req
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
# 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.
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -99,10 +100,15 @@ def get_connection(module):
|
||||||
global _DEVICE_CONNECTION
|
global _DEVICE_CONNECTION
|
||||||
if not _DEVICE_CONNECTION:
|
if not _DEVICE_CONNECTION:
|
||||||
load_params(module)
|
load_params(module)
|
||||||
if is_eapi(module):
|
if is_local_eapi(module):
|
||||||
conn = Eapi(module)
|
conn = LocalEapi(module)
|
||||||
else:
|
else:
|
||||||
|
connection_proxy = Connection(module._socket_path)
|
||||||
|
cap = json.loads(connection_proxy.get_capabilities())
|
||||||
|
if cap['network_api'] == 'cliconf':
|
||||||
conn = Cli(module)
|
conn = Cli(module)
|
||||||
|
elif cap['network_api'] == 'eapi':
|
||||||
|
conn = HttpApi(module)
|
||||||
_DEVICE_CONNECTION = conn
|
_DEVICE_CONNECTION = conn
|
||||||
return _DEVICE_CONNECTION
|
return _DEVICE_CONNECTION
|
||||||
|
|
||||||
|
@ -180,7 +186,7 @@ class Cli:
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
|
|
||||||
class Eapi:
|
class LocalEapi:
|
||||||
|
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
self._module = module
|
self._module = module
|
||||||
|
@ -394,18 +400,187 @@ class Eapi:
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
|
|
||||||
|
class HttpApi:
|
||||||
|
def __init__(self, module):
|
||||||
|
self._module = module
|
||||||
|
self._device_configs = {}
|
||||||
|
self._session_support = None
|
||||||
|
self._connection_obj = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _connection(self):
|
||||||
|
if not self._connection_obj:
|
||||||
|
self._connection_obj = Connection(self._module._socket_path)
|
||||||
|
|
||||||
|
return self._connection_obj
|
||||||
|
|
||||||
|
def run_commands(self, commands, check_rc=True):
|
||||||
|
"""Runs list of commands on remote device and returns results
|
||||||
|
"""
|
||||||
|
output = None
|
||||||
|
queue = list()
|
||||||
|
responses = list()
|
||||||
|
|
||||||
|
def run_queue(queue, output):
|
||||||
|
try:
|
||||||
|
response = to_list(self._connection.send_request(queue, output=output))
|
||||||
|
except Exception as exc:
|
||||||
|
if check_rc:
|
||||||
|
raise
|
||||||
|
return to_text(exc)
|
||||||
|
|
||||||
|
if output == 'json':
|
||||||
|
response = [json.loads(item) for item in response]
|
||||||
|
return response
|
||||||
|
|
||||||
|
for item in to_list(commands):
|
||||||
|
cmd_output = 'text'
|
||||||
|
if isinstance(item, dict):
|
||||||
|
command = item['command']
|
||||||
|
if 'output' in item:
|
||||||
|
cmd_output = item['output']
|
||||||
|
else:
|
||||||
|
command = item
|
||||||
|
|
||||||
|
# Emulate '| json' from CLI
|
||||||
|
if is_json(command):
|
||||||
|
command = command.rsplit('|', 1)[0]
|
||||||
|
cmd_output = 'json'
|
||||||
|
|
||||||
|
if output and output != cmd_output:
|
||||||
|
responses.extend(run_queue(queue, output))
|
||||||
|
queue = list()
|
||||||
|
|
||||||
|
output = cmd_output
|
||||||
|
queue.append(command)
|
||||||
|
|
||||||
|
if queue:
|
||||||
|
responses.extend(run_queue(queue, output))
|
||||||
|
|
||||||
|
return responses
|
||||||
|
|
||||||
|
def get_config(self, flags=None):
|
||||||
|
"""Retrieves the current config from the device or cache
|
||||||
|
"""
|
||||||
|
flags = [] if flags is None else flags
|
||||||
|
|
||||||
|
cmd = 'show running-config '
|
||||||
|
cmd += ' '.join(flags)
|
||||||
|
cmd = cmd.strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._device_configs[cmd]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
out = self._connection.send_request(cmd)
|
||||||
|
except ConnectionError as exc:
|
||||||
|
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||||
|
|
||||||
|
cfg = to_text(out).strip()
|
||||||
|
self._device_configs[cmd] = cfg
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
|
||||||
|
diff = {}
|
||||||
|
|
||||||
|
# prepare candidate configuration
|
||||||
|
candidate_obj = NetworkConfig(indent=3)
|
||||||
|
candidate_obj.load(candidate)
|
||||||
|
|
||||||
|
if running and diff_match != 'none' and diff_replace != 'config':
|
||||||
|
# running configuration
|
||||||
|
running_obj = NetworkConfig(indent=3, contents=running, ignore_lines=diff_ignore_lines)
|
||||||
|
configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace)
|
||||||
|
|
||||||
|
else:
|
||||||
|
configdiffobjs = candidate_obj.items
|
||||||
|
|
||||||
|
diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else {}
|
||||||
|
return diff
|
||||||
|
|
||||||
|
def load_config(self, config, commit=False, replace=False):
|
||||||
|
"""Loads the configuration onto the remote devices
|
||||||
|
|
||||||
|
If the device doesn't support configuration sessions, this will
|
||||||
|
fallback to using configure() to load the commands. If that happens,
|
||||||
|
there will be no returned diff or session values
|
||||||
|
"""
|
||||||
|
return self.edit_config(config, commit, replace)
|
||||||
|
|
||||||
|
def edit_config(self, config, commit=False, replace=False):
|
||||||
|
"""Loads the configuration onto the remote devices
|
||||||
|
|
||||||
|
If the device doesn't support configuration sessions, this will
|
||||||
|
fallback to using configure() to load the commands. If that happens,
|
||||||
|
there will be no returned diff or session values
|
||||||
|
"""
|
||||||
|
session = 'ansible_%s' % int(time.time())
|
||||||
|
result = {'session': session}
|
||||||
|
banner_cmd = None
|
||||||
|
banner_input = []
|
||||||
|
|
||||||
|
commands = ['configure session %s' % session]
|
||||||
|
if replace:
|
||||||
|
commands.append('rollback clean-config')
|
||||||
|
|
||||||
|
for command in config:
|
||||||
|
if command.startswith('banner'):
|
||||||
|
banner_cmd = command
|
||||||
|
banner_input = []
|
||||||
|
elif banner_cmd:
|
||||||
|
if command == 'EOF':
|
||||||
|
command = {'cmd': banner_cmd, 'input': '\n'.join(banner_input)}
|
||||||
|
banner_cmd = None
|
||||||
|
commands.append(command)
|
||||||
|
else:
|
||||||
|
banner_input.append(command)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
commands.append(command)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self._connection.send_request(commands)
|
||||||
|
except Exception:
|
||||||
|
commands = ['configure session %s' % session, 'abort']
|
||||||
|
response = self._connection.send_request(commands, output='text')
|
||||||
|
raise
|
||||||
|
|
||||||
|
commands = ['configure session %s' % session, 'show session-config diffs']
|
||||||
|
if commit:
|
||||||
|
commands.append('commit')
|
||||||
|
else:
|
||||||
|
commands.append('abort')
|
||||||
|
|
||||||
|
response = self._connection.send_request(commands, output='text')
|
||||||
|
diff = response[1].strip()
|
||||||
|
if diff:
|
||||||
|
result['diff'] = diff
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
"""Returns platform info of the remove device
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
capabilities = self._connection.get_capabilities()
|
||||||
|
except ConnectionError as exc:
|
||||||
|
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||||
|
|
||||||
|
return json.loads(capabilities)
|
||||||
|
|
||||||
|
|
||||||
def is_json(cmd):
|
def is_json(cmd):
|
||||||
return to_native(cmd, errors='surrogate_then_replace').endswith('| json')
|
return to_text(cmd, errors='surrogate_then_replace').endswith('| json')
|
||||||
|
|
||||||
|
|
||||||
def is_eapi(module):
|
def is_local_eapi(module):
|
||||||
transport = module.params['transport']
|
transport = module.params['transport']
|
||||||
provider_transport = (module.params['provider'] or {}).get('transport')
|
provider_transport = (module.params['provider'] or {}).get('transport')
|
||||||
return 'eapi' in (transport, provider_transport)
|
return 'eapi' in (transport, provider_transport)
|
||||||
|
|
||||||
|
|
||||||
def to_command(module, commands):
|
def to_command(module, commands):
|
||||||
if is_eapi(module):
|
if is_local_eapi(module):
|
||||||
default_output = 'json'
|
default_output = 'json'
|
||||||
else:
|
else:
|
||||||
default_output = 'text'
|
default_output = 'text'
|
||||||
|
|
|
@ -105,10 +105,15 @@ def get_connection(module):
|
||||||
global _DEVICE_CONNECTION
|
global _DEVICE_CONNECTION
|
||||||
if not _DEVICE_CONNECTION:
|
if not _DEVICE_CONNECTION:
|
||||||
load_params(module)
|
load_params(module)
|
||||||
if is_nxapi(module):
|
if is_local_nxapi(module):
|
||||||
conn = Nxapi(module)
|
conn = LocalNxapi(module)
|
||||||
else:
|
else:
|
||||||
|
connection_proxy = Connection(module._socket_path)
|
||||||
|
cap = json.loads(connection_proxy.get_capabilities())
|
||||||
|
if cap['network_api'] == 'cliconf':
|
||||||
conn = Cli(module)
|
conn = Cli(module)
|
||||||
|
elif cap['network_api'] == 'nxapi':
|
||||||
|
conn = HttpApi(module)
|
||||||
_DEVICE_CONNECTION = conn
|
_DEVICE_CONNECTION = conn
|
||||||
return _DEVICE_CONNECTION
|
return _DEVICE_CONNECTION
|
||||||
|
|
||||||
|
@ -244,7 +249,7 @@ class Cli:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Nxapi:
|
class LocalNxapi:
|
||||||
|
|
||||||
OUTPUT_TO_COMMAND_TYPE = {
|
OUTPUT_TO_COMMAND_TYPE = {
|
||||||
'text': 'cli_show_ascii',
|
'text': 'cli_show_ascii',
|
||||||
|
@ -496,22 +501,178 @@ class Nxapi:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class HttpApi:
|
||||||
|
def __init__(self, module):
|
||||||
|
self._module = module
|
||||||
|
self._device_configs = {}
|
||||||
|
self._module_context = {}
|
||||||
|
self._connection_obj = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _connection(self):
|
||||||
|
if not self._connection_obj:
|
||||||
|
self._connection_obj = Connection(self._module._socket_path)
|
||||||
|
|
||||||
|
return self._connection_obj
|
||||||
|
|
||||||
|
def run_commands(self, commands, check_rc=True):
|
||||||
|
"""Runs list of commands on remote device and returns results
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
out = self._connection.send_request(commands)
|
||||||
|
except ConnectionError as exc:
|
||||||
|
if check_rc is True:
|
||||||
|
raise
|
||||||
|
out = to_text(exc)
|
||||||
|
|
||||||
|
out = to_list(out)
|
||||||
|
if not out[0]:
|
||||||
|
return out
|
||||||
|
|
||||||
|
for index, response in enumerate(out):
|
||||||
|
if response[0] == '{':
|
||||||
|
out[index] = json.loads(response)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def get_config(self, flags=None):
|
||||||
|
"""Retrieves the current config from the device or cache
|
||||||
|
"""
|
||||||
|
flags = [] if flags is None else flags
|
||||||
|
|
||||||
|
cmd = 'show running-config '
|
||||||
|
cmd += ' '.join(flags)
|
||||||
|
cmd = cmd.strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._device_configs[cmd]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
out = self._connection.send_request(cmd)
|
||||||
|
except ConnectionError as exc:
|
||||||
|
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||||
|
|
||||||
|
cfg = to_text(out).strip()
|
||||||
|
self._device_configs[cmd] = cfg
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
|
||||||
|
diff = {}
|
||||||
|
|
||||||
|
# prepare candidate configuration
|
||||||
|
candidate_obj = NetworkConfig(indent=2)
|
||||||
|
candidate_obj.load(candidate)
|
||||||
|
|
||||||
|
if running and diff_match != 'none' and diff_replace != 'config':
|
||||||
|
# running configuration
|
||||||
|
running_obj = NetworkConfig(indent=2, contents=running, ignore_lines=diff_ignore_lines)
|
||||||
|
configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace)
|
||||||
|
|
||||||
|
else:
|
||||||
|
configdiffobjs = candidate_obj.items
|
||||||
|
|
||||||
|
diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else ''
|
||||||
|
return diff
|
||||||
|
|
||||||
|
def load_config(self, commands, return_error=False, opts=None, replace=None):
|
||||||
|
"""Sends the ordered set of commands to the device
|
||||||
|
"""
|
||||||
|
if opts is None:
|
||||||
|
opts = {}
|
||||||
|
|
||||||
|
responses = []
|
||||||
|
try:
|
||||||
|
resp = self.edit_config(commands, replace=replace)
|
||||||
|
except ConnectionError as exc:
|
||||||
|
code = getattr(exc, 'code', 1)
|
||||||
|
message = getattr(exc, 'err', exc)
|
||||||
|
err = to_text(message, errors='surrogate_then_replace')
|
||||||
|
if opts.get('ignore_timeout') and code:
|
||||||
|
responses.append(code)
|
||||||
|
return responses
|
||||||
|
elif code and 'no graceful-restart' in err:
|
||||||
|
if 'ISSU/HA will be affected if Graceful Restart is disabled' in err:
|
||||||
|
msg = ['']
|
||||||
|
responses.extend(msg)
|
||||||
|
return responses
|
||||||
|
else:
|
||||||
|
self._module.fail_json(msg=err)
|
||||||
|
elif code:
|
||||||
|
self._module.fail_json(msg=err)
|
||||||
|
|
||||||
|
responses.extend(resp)
|
||||||
|
return responses
|
||||||
|
|
||||||
|
def edit_config(self, candidate=None, commit=True, replace=None, comment=None):
|
||||||
|
resp = list()
|
||||||
|
|
||||||
|
self.check_edit_config_capability(candidate, commit, replace, comment)
|
||||||
|
|
||||||
|
if replace:
|
||||||
|
candidate = 'config replace {0}'.format(replace)
|
||||||
|
|
||||||
|
responses = self._connection.send_request(candidate, output='config')
|
||||||
|
for response in to_list(responses):
|
||||||
|
if response != '{}':
|
||||||
|
resp.append(response)
|
||||||
|
if not resp:
|
||||||
|
resp = ['']
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
"""Returns platform info of the remove device
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
capabilities = self._connection.get_capabilities()
|
||||||
|
except ConnectionError as exc:
|
||||||
|
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||||
|
|
||||||
|
return json.loads(capabilities)
|
||||||
|
|
||||||
|
def check_edit_config_capability(self, candidate=None, commit=True, replace=None, comment=None):
|
||||||
|
operations = self._connection.get_device_operations()
|
||||||
|
|
||||||
|
if not candidate and not replace:
|
||||||
|
raise ValueError("must provide a candidate or replace to load configuration")
|
||||||
|
|
||||||
|
if commit not in (True, False):
|
||||||
|
raise ValueError("'commit' must be a bool, got %s" % commit)
|
||||||
|
|
||||||
|
if replace and not operations.get('supports_replace'):
|
||||||
|
raise ValueError("configuration replace is not supported")
|
||||||
|
|
||||||
|
if comment and not operations.get('supports_commit_comment', False):
|
||||||
|
raise ValueError("commit comment is not supported")
|
||||||
|
|
||||||
|
def read_module_context(self, module_key):
|
||||||
|
if self._module_context.get(module_key):
|
||||||
|
return self._module_context[module_key]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_module_context(self, module_key, module_context):
|
||||||
|
self._module_context[module_key] = module_context
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def is_json(cmd):
|
def is_json(cmd):
|
||||||
return str(cmd).endswith('| json')
|
return to_text(cmd).endswith('| json')
|
||||||
|
|
||||||
|
|
||||||
def is_text(cmd):
|
def is_text(cmd):
|
||||||
return not is_json(cmd)
|
return not is_json(cmd)
|
||||||
|
|
||||||
|
|
||||||
def is_nxapi(module):
|
def is_local_nxapi(module):
|
||||||
transport = module.params['transport']
|
transport = module.params['transport']
|
||||||
provider_transport = (module.params['provider'] or {}).get('transport')
|
provider_transport = (module.params['provider'] or {}).get('transport')
|
||||||
return 'nxapi' in (transport, provider_transport)
|
return 'nxapi' in (transport, provider_transport)
|
||||||
|
|
||||||
|
|
||||||
def to_command(module, commands):
|
def to_command(module, commands):
|
||||||
if is_nxapi(module):
|
if is_local_nxapi(module):
|
||||||
default_output = 'json'
|
default_output = 'json'
|
||||||
else:
|
else:
|
||||||
default_output = 'text'
|
default_output = 'text'
|
||||||
|
|
|
@ -50,8 +50,6 @@ from ansible.module_utils.common._collections_compat import Mapping
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
from ansible.module_utils.network.common.utils import to_list
|
||||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
||||||
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||||
from ansible.plugins.connection.network_cli import Connection as NetworkCli
|
|
||||||
from ansible.plugins.connection.httpapi import Connection as HttpApi
|
|
||||||
|
|
||||||
|
|
||||||
class Cliconf(CliconfBase):
|
class Cliconf(CliconfBase):
|
||||||
|
@ -60,20 +58,6 @@ class Cliconf(CliconfBase):
|
||||||
super(Cliconf, self).__init__(*args, **kwargs)
|
super(Cliconf, self).__init__(*args, **kwargs)
|
||||||
self._session_support = None
|
self._session_support = None
|
||||||
|
|
||||||
def send_command(self, command, **kwargs):
|
|
||||||
"""Executes a cli command and returns the results
|
|
||||||
This method will execute the CLI command on the connection and return
|
|
||||||
the results to the caller. The command output will be returned as a
|
|
||||||
string
|
|
||||||
"""
|
|
||||||
if isinstance(self._connection, NetworkCli):
|
|
||||||
resp = super(Cliconf, self).send_command(command, **kwargs)
|
|
||||||
elif isinstance(self._connection, HttpApi):
|
|
||||||
resp = self._connection.send_request(command, **kwargs)
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid connection type")
|
|
||||||
return resp
|
|
||||||
|
|
||||||
@enable_mode
|
@enable_mode
|
||||||
def get_config(self, source='running', format='text', flags=None):
|
def get_config(self, source='running', format='text', flags=None):
|
||||||
options_values = self.get_option_values()
|
options_values = self.get_option_values()
|
||||||
|
@ -294,13 +278,8 @@ class Cliconf(CliconfBase):
|
||||||
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())
|
||||||
|
|
||||||
if isinstance(self._connection, NetworkCli):
|
|
||||||
result['network_api'] = 'cliconf'
|
result['network_api'] = 'cliconf'
|
||||||
elif isinstance(self._connection, HttpApi):
|
|
||||||
result['network_api'] = 'eapi'
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid connection type")
|
|
||||||
return json.dumps(result)
|
return json.dumps(result)
|
||||||
|
|
||||||
def _get_command_with_output(self, command, output):
|
def _get_command_with_output(self, command, output):
|
||||||
|
|
|
@ -29,8 +29,6 @@ from ansible.module_utils.connection import ConnectionError
|
||||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
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, enable_mode
|
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||||
from ansible.plugins.connection.network_cli import Connection as NetworkCli
|
|
||||||
from ansible.plugins.connection.httpapi import Connection as HttpApi
|
|
||||||
|
|
||||||
|
|
||||||
class Cliconf(CliconfBase):
|
class Cliconf(CliconfBase):
|
||||||
|
@ -50,20 +48,6 @@ class Cliconf(CliconfBase):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def send_command(self, command, **kwargs):
|
|
||||||
"""Executes a cli command and returns the results
|
|
||||||
This method will execute the CLI command on the connection and return
|
|
||||||
the results to the caller. The command output will be returned as a
|
|
||||||
string
|
|
||||||
"""
|
|
||||||
if isinstance(self._connection, NetworkCli):
|
|
||||||
resp = super(Cliconf, self).send_command(command, **kwargs)
|
|
||||||
elif isinstance(self._connection, HttpApi):
|
|
||||||
resp = self._connection.send_request(command, **kwargs)
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid connection type")
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def get_device_info(self):
|
def get_device_info(self):
|
||||||
device_info = {}
|
device_info = {}
|
||||||
|
|
||||||
|
@ -261,13 +245,8 @@ class Cliconf(CliconfBase):
|
||||||
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())
|
||||||
|
|
||||||
if isinstance(self._connection, NetworkCli):
|
|
||||||
result['network_api'] = 'cliconf'
|
result['network_api'] = 'cliconf'
|
||||||
elif isinstance(self._connection, HttpApi):
|
|
||||||
result['network_api'] = 'nxapi'
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid connection type")
|
|
||||||
return json.dumps(result)
|
return json.dumps(result)
|
||||||
|
|
||||||
def _get_command_with_output(self, command, output):
|
def _get_command_with_output(self, command, output):
|
||||||
|
|
|
@ -297,7 +297,7 @@ class NetworkConnectionBase(ConnectionBase):
|
||||||
self._local = connection_loader.get('local', play_context, '/dev/null')
|
self._local = connection_loader.get('local', play_context, '/dev/null')
|
||||||
self._local.set_options()
|
self._local.set_options()
|
||||||
|
|
||||||
self._sub_plugins = []
|
self._sub_plugin = {}
|
||||||
self._cached_variables = (None, None, None)
|
self._cached_variables = (None, None, None)
|
||||||
|
|
||||||
# reconstruct the socket_path and set instance values accordingly
|
# reconstruct the socket_path and set instance values accordingly
|
||||||
|
@ -309,8 +309,9 @@ class NetworkConnectionBase(ConnectionBase):
|
||||||
return self.__dict__[name]
|
return self.__dict__[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if not name.startswith('_'):
|
if not name.startswith('_'):
|
||||||
for plugin in self._sub_plugins:
|
plugin = self._sub_plugin.get('obj')
|
||||||
method = getattr(plugin['obj'], name, None)
|
if plugin:
|
||||||
|
method = getattr(plugin, name, None)
|
||||||
if method is not None:
|
if method is not None:
|
||||||
return method
|
return method
|
||||||
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
|
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
|
||||||
|
@ -342,10 +343,9 @@ class NetworkConnectionBase(ConnectionBase):
|
||||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||||
super(NetworkConnectionBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
super(NetworkConnectionBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||||
|
|
||||||
for plugin in self._sub_plugins:
|
if self._sub_plugin.get('obj') and self._sub_plugin.get('type') != 'external':
|
||||||
if plugin['type'] != 'external':
|
|
||||||
try:
|
try:
|
||||||
plugin['obj'].set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
self._sub_plugin['obj'].set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ options:
|
||||||
network_os:
|
network_os:
|
||||||
description:
|
description:
|
||||||
- Configures the device platform network operating system. This value is
|
- Configures the device platform network operating system. This value is
|
||||||
used to load the correct httpapi and cliconf plugins to communicate
|
used to load the correct httpapi plugin to communicate with the remote
|
||||||
with the remote device
|
device
|
||||||
vars:
|
vars:
|
||||||
- name: ansible_network_os
|
- name: ansible_network_os
|
||||||
remote_user:
|
remote_user:
|
||||||
|
@ -154,7 +154,7 @@ from ansible.module_utils.six.moves import cPickle
|
||||||
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
|
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
|
||||||
from ansible.module_utils.urls import open_url
|
from ansible.module_utils.urls import open_url
|
||||||
from ansible.playbook.play_context import PlayContext
|
from ansible.playbook.play_context import PlayContext
|
||||||
from ansible.plugins.loader import cliconf_loader, httpapi_loader
|
from ansible.plugins.loader import httpapi_loader
|
||||||
from ansible.plugins.connection import NetworkConnectionBase
|
from ansible.plugins.connection import NetworkConnectionBase
|
||||||
from ansible.utils.display import Display
|
from ansible.utils.display import Display
|
||||||
|
|
||||||
|
@ -177,17 +177,11 @@ class Connection(NetworkConnectionBase):
|
||||||
|
|
||||||
self.httpapi = httpapi_loader.get(self._network_os, self)
|
self.httpapi = httpapi_loader.get(self._network_os, self)
|
||||||
if self.httpapi:
|
if self.httpapi:
|
||||||
self._sub_plugins.append({'type': 'httpapi', 'name': self._network_os, 'obj': self.httpapi})
|
self._sub_plugin = {'type': 'httpapi', 'name': self._network_os, 'obj': self.httpapi}
|
||||||
display.vvvv('loaded API plugin for network_os %s' % self._network_os)
|
display.vvvv('loaded API plugin for network_os %s' % self._network_os)
|
||||||
else:
|
else:
|
||||||
raise AnsibleConnectionFailure('unable to load API plugin for network_os %s' % self._network_os)
|
raise AnsibleConnectionFailure('unable to load API plugin for network_os %s' % self._network_os)
|
||||||
|
|
||||||
self.cliconf = cliconf_loader.get(self._network_os, self)
|
|
||||||
if self.cliconf:
|
|
||||||
self._sub_plugins.append({'type': 'cliconf', 'name': self._network_os, 'obj': self.cliconf})
|
|
||||||
display.vvvv('loaded cliconf plugin for network_os %s' % self._network_os)
|
|
||||||
else:
|
|
||||||
display.vvvv('unable to load cliconf for network_os %s' % self._network_os)
|
|
||||||
else:
|
else:
|
||||||
raise AnsibleConnectionFailure(
|
raise AnsibleConnectionFailure(
|
||||||
'Unable to automatically determine host network os. Please '
|
'Unable to automatically determine host network os. Please '
|
||||||
|
|
|
@ -183,7 +183,7 @@ class Connection(NetworkConnectionBase):
|
||||||
|
|
||||||
self.napalm.open()
|
self.napalm.open()
|
||||||
|
|
||||||
self._sub_plugins.append({'type': 'external', 'name': 'napalm', 'obj': self.napalm})
|
self._sub_plugin = {'type': 'external', 'name': 'napalm', 'obj': self.napalm}
|
||||||
display.vvvv('created napalm device for network_os %s' % self._network_os, host=host)
|
display.vvvv('created napalm device for network_os %s' % self._network_os, host=host)
|
||||||
self._connected = True
|
self._connected = True
|
||||||
|
|
||||||
|
|
|
@ -217,11 +217,11 @@ class Connection(NetworkConnectionBase):
|
||||||
|
|
||||||
netconf = netconf_loader.get(self._network_os, self)
|
netconf = netconf_loader.get(self._network_os, self)
|
||||||
if netconf:
|
if netconf:
|
||||||
self._sub_plugins.append({'type': 'netconf', 'name': self._network_os, 'obj': netconf})
|
self._sub_plugin = {'type': 'netconf', 'name': self._network_os, 'obj': netconf}
|
||||||
display.display('loaded netconf plugin for network_os %s' % self._network_os, log_only=True)
|
display.display('loaded netconf plugin for network_os %s' % self._network_os, log_only=True)
|
||||||
else:
|
else:
|
||||||
netconf = netconf_loader.get("default", self)
|
netconf = netconf_loader.get("default", self)
|
||||||
self._sub_plugins.append({'type': 'netconf', 'name': 'default', 'obj': netconf})
|
self._sub_plugin = {'type': 'netconf', 'name': 'default', 'obj': netconf}
|
||||||
display.display('unable to load netconf plugin for network_os %s, falling back to default plugin' % self._network_os)
|
display.display('unable to load netconf plugin for network_os %s, falling back to default plugin' % self._network_os)
|
||||||
display.display('network_os is set to %s' % self._network_os, log_only=True)
|
display.display('network_os is set to %s' % self._network_os, log_only=True)
|
||||||
|
|
||||||
|
|
|
@ -231,7 +231,7 @@ class Connection(NetworkConnectionBase):
|
||||||
self.cliconf = cliconf_loader.get(self._network_os, self)
|
self.cliconf = cliconf_loader.get(self._network_os, self)
|
||||||
if self.cliconf:
|
if self.cliconf:
|
||||||
display.vvvv('loaded cliconf plugin for network_os %s' % self._network_os)
|
display.vvvv('loaded cliconf plugin for network_os %s' % self._network_os)
|
||||||
self._sub_plugins.append({'type': 'cliconf', 'name': self._network_os, 'obj': self.cliconf})
|
self._sub_plugin = {'type': 'cliconf', 'name': self._network_os, 'obj': self.cliconf}
|
||||||
else:
|
else:
|
||||||
display.vvvv('unable to load cliconf for network_os %s' % self._network_os)
|
display.vvvv('unable to load cliconf for network_os %s' % self._network_os)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -4,8 +4,29 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
---
|
||||||
|
author: Ansible Networking Team
|
||||||
|
httpapi: eos
|
||||||
|
short_description: Use eAPI to run command on eos platform
|
||||||
|
description:
|
||||||
|
- This eos plugin provides low level abstraction api's for
|
||||||
|
sending and receiving CLI commands with eos network devices.
|
||||||
|
version_added: "2.6"
|
||||||
|
options:
|
||||||
|
eos_use_sessions:
|
||||||
|
type: int
|
||||||
|
default: 1
|
||||||
|
description:
|
||||||
|
- Specifies if sessions should be used on remote host or not
|
||||||
|
env:
|
||||||
|
- name: ANSIBLE_EOS_USE_SESSIONS
|
||||||
|
vars:
|
||||||
|
- name: ansible_eos_use_sessions
|
||||||
|
version_added: '2.8'
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text
|
from ansible.module_utils._text import to_text
|
||||||
from ansible.module_utils.connection import ConnectionError
|
from ansible.module_utils.connection import ConnectionError
|
||||||
|
@ -16,7 +37,39 @@ from ansible.utils.display import Display
|
||||||
display = Display()
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
OPTIONS = {
|
||||||
|
'format': ['text', 'json'],
|
||||||
|
'diff_match': ['line', 'strict', 'exact', 'none'],
|
||||||
|
'diff_replace': ['line', 'block', 'config'],
|
||||||
|
'output': ['text', 'json']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class HttpApi(HttpApiBase):
|
class HttpApi(HttpApiBase):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(HttpApi, self).__init__(*args, **kwargs)
|
||||||
|
self._device_info = None
|
||||||
|
self._session_support = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supports_sessions(self):
|
||||||
|
use_session = self.get_option('eos_use_sessions')
|
||||||
|
try:
|
||||||
|
use_session = int(use_session)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not bool(use_session):
|
||||||
|
self._session_support = False
|
||||||
|
else:
|
||||||
|
if self._session_support:
|
||||||
|
return self._session_support
|
||||||
|
|
||||||
|
response = self.send_request('show configuration sessions')
|
||||||
|
self._session_support = 'error' not in response
|
||||||
|
|
||||||
|
return self._session_support
|
||||||
|
|
||||||
def send_request(self, data, **message_kwargs):
|
def send_request(self, data, **message_kwargs):
|
||||||
data = to_list(data)
|
data = to_list(data)
|
||||||
if self._become:
|
if self._become:
|
||||||
|
@ -45,117 +98,51 @@ class HttpApi(HttpApiBase):
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def get_prompt(self):
|
def get_device_info(self):
|
||||||
# Fake a prompt for @enable_mode
|
if self._device_info:
|
||||||
if self._become:
|
return self._device_info
|
||||||
return '#'
|
|
||||||
return '>'
|
|
||||||
|
|
||||||
# Imported from module_utils
|
device_info = {}
|
||||||
def edit_config(self, config, commit=False, replace=False):
|
|
||||||
"""Loads the configuration onto the remote devices
|
|
||||||
|
|
||||||
If the device doesn't support configuration sessions, this will
|
device_info['network_os'] = 'eos'
|
||||||
fallback to using configure() to load the commands. If that happens,
|
reply = self.send_request('show version | json')
|
||||||
there will be no returned diff or session values
|
data = json.loads(reply)
|
||||||
"""
|
|
||||||
session = 'ansible_%s' % int(time.time())
|
|
||||||
result = {'session': session}
|
|
||||||
banner_cmd = None
|
|
||||||
banner_input = []
|
|
||||||
|
|
||||||
commands = ['configure session %s' % session]
|
device_info['network_os_version'] = data['version']
|
||||||
if replace:
|
device_info['network_os_model'] = data['modelName']
|
||||||
commands.append('rollback clean-config')
|
|
||||||
|
|
||||||
for command in config:
|
reply = self.send_request('show hostname | json')
|
||||||
if command.startswith('banner'):
|
data = json.loads(reply)
|
||||||
banner_cmd = command
|
|
||||||
banner_input = []
|
|
||||||
elif banner_cmd:
|
|
||||||
if command == 'EOF':
|
|
||||||
command = {'cmd': banner_cmd, 'input': '\n'.join(banner_input)}
|
|
||||||
banner_cmd = None
|
|
||||||
commands.append(command)
|
|
||||||
else:
|
|
||||||
banner_input.append(command)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
commands.append(command)
|
|
||||||
|
|
||||||
try:
|
device_info['network_os_hostname'] = data['hostname']
|
||||||
response = self.send_request(commands)
|
|
||||||
except Exception:
|
|
||||||
commands = ['configure session %s' % session, 'abort']
|
|
||||||
response = self.send_request(commands, output='text')
|
|
||||||
raise
|
|
||||||
|
|
||||||
commands = ['configure session %s' % session, 'show session-config diffs']
|
self._device_info = device_info
|
||||||
if commit:
|
return self._device_info
|
||||||
commands.append('commit')
|
|
||||||
else:
|
|
||||||
commands.append('abort')
|
|
||||||
|
|
||||||
response = self.send_request(commands, output='text')
|
def get_device_operations(self):
|
||||||
diff = response[1].strip()
|
return {
|
||||||
if diff:
|
'supports_diff_replace': True,
|
||||||
result['diff'] = diff
|
'supports_commit': bool(self.supports_sessions),
|
||||||
|
'supports_rollback': False,
|
||||||
|
'supports_defaults': False,
|
||||||
|
'supports_onbox_diff': bool(self.supports_sessions),
|
||||||
|
'supports_commit_comment': False,
|
||||||
|
'supports_multiline_delimiter': False,
|
||||||
|
'supports_diff_match': True,
|
||||||
|
'supports_diff_ignore_lines': True,
|
||||||
|
'supports_generate_diff': not bool(self.supports_sessions),
|
||||||
|
'supports_replace': bool(self.supports_sessions),
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = []
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
result['device_operations'] = self.get_device_operations()
|
||||||
|
result.update(OPTIONS)
|
||||||
|
result['network_api'] = 'eapi'
|
||||||
|
|
||||||
def run_commands(self, commands, check_rc=True):
|
return json.dumps(result)
|
||||||
"""Runs list of commands on remote device and returns results
|
|
||||||
"""
|
|
||||||
output = None
|
|
||||||
queue = list()
|
|
||||||
responses = list()
|
|
||||||
|
|
||||||
def run_queue(queue, output):
|
|
||||||
try:
|
|
||||||
response = to_list(self.send_request(queue, output=output))
|
|
||||||
except Exception as exc:
|
|
||||||
if check_rc:
|
|
||||||
raise
|
|
||||||
return to_text(exc)
|
|
||||||
|
|
||||||
if output == 'json':
|
|
||||||
response = [json.loads(item) for item in response]
|
|
||||||
return response
|
|
||||||
|
|
||||||
for item in to_list(commands):
|
|
||||||
cmd_output = 'text'
|
|
||||||
if isinstance(item, dict):
|
|
||||||
command = item['command']
|
|
||||||
if 'output' in item:
|
|
||||||
cmd_output = item['output']
|
|
||||||
else:
|
|
||||||
command = item
|
|
||||||
|
|
||||||
# Emulate '| json' from CLI
|
|
||||||
if command.endswith('| json'):
|
|
||||||
command = command.rsplit('|', 1)[0]
|
|
||||||
cmd_output = 'json'
|
|
||||||
|
|
||||||
if output and output != cmd_output:
|
|
||||||
responses.extend(run_queue(queue, output))
|
|
||||||
queue = list()
|
|
||||||
|
|
||||||
output = cmd_output
|
|
||||||
queue.append(command)
|
|
||||||
|
|
||||||
if queue:
|
|
||||||
responses.extend(run_queue(queue, output))
|
|
||||||
|
|
||||||
return responses
|
|
||||||
|
|
||||||
def load_config(self, config, commit=False, replace=False):
|
|
||||||
"""Loads the configuration onto the remote devices
|
|
||||||
|
|
||||||
If the device doesn't support configuration sessions, this will
|
|
||||||
fallback to using configure() to load the commands. If that happens,
|
|
||||||
there will be no returned diff or session values
|
|
||||||
"""
|
|
||||||
return self.edit_config(config, commit, replace)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_response(response):
|
def handle_response(response):
|
||||||
|
@ -170,6 +157,7 @@ def handle_response(response):
|
||||||
raise ConnectionError(error_text, code=error['code'])
|
raise ConnectionError(error_text, code=error['code'])
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
for result in response['result']:
|
for result in response['result']:
|
||||||
if 'messages' in result:
|
if 'messages' in result:
|
||||||
results.append(result['messages'][0])
|
results.append(result['messages'][0])
|
||||||
|
|
|
@ -4,7 +4,19 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
---
|
||||||
|
author: Ansible Networking Team
|
||||||
|
httpapi: nxos
|
||||||
|
short_description: Use NX-API to run command on nxos platform
|
||||||
|
description:
|
||||||
|
- This eos plugin provides low level abstraction api's for
|
||||||
|
sending and receiving CLI commands with nxos network devices.
|
||||||
|
version_added: "2.6"
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text
|
from ansible.module_utils._text import to_text
|
||||||
from ansible.module_utils.connection import ConnectionError
|
from ansible.module_utils.connection import ConnectionError
|
||||||
|
@ -15,29 +27,18 @@ from ansible.utils.display import Display
|
||||||
display = Display()
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
OPTIONS = {
|
||||||
|
'format': ['text', 'json'],
|
||||||
|
'diff_match': ['line', 'strict', 'exact', 'none'],
|
||||||
|
'diff_replace': ['line', 'block', 'config'],
|
||||||
|
'output': ['text', 'json']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class HttpApi(HttpApiBase):
|
class HttpApi(HttpApiBase):
|
||||||
def _run_queue(self, queue, output):
|
def __init__(self, *args, **kwargs):
|
||||||
if self._become:
|
super(HttpApi, self).__init__(*args, **kwargs)
|
||||||
display.vvvv('firing event: on_become')
|
self._device_info = None
|
||||||
queue.insert(0, 'enable')
|
|
||||||
|
|
||||||
request = request_builder(queue, output)
|
|
||||||
headers = {'Content-Type': 'application/json'}
|
|
||||||
|
|
||||||
response, response_data = self.connection.send('/ins', request, headers=headers, method='POST')
|
|
||||||
|
|
||||||
try:
|
|
||||||
response_data = json.loads(to_text(response_data.getvalue()))
|
|
||||||
except ValueError:
|
|
||||||
raise ConnectionError('Response was not valid JSON, got {0}'.format(
|
|
||||||
to_text(response_data.getvalue())
|
|
||||||
))
|
|
||||||
|
|
||||||
results = handle_response(response_data)
|
|
||||||
|
|
||||||
if self._become:
|
|
||||||
results = results[1:]
|
|
||||||
return results
|
|
||||||
|
|
||||||
def send_request(self, data, **message_kwargs):
|
def send_request(self, data, **message_kwargs):
|
||||||
output = None
|
output = None
|
||||||
|
@ -72,46 +73,93 @@ class HttpApi(HttpApiBase):
|
||||||
return responses[0]
|
return responses[0]
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
def edit_config(self, candidate=None, commit=True, replace=None, comment=None):
|
def _run_queue(self, queue, output):
|
||||||
resp = list()
|
if self._become:
|
||||||
|
display.vvvv('firing event: on_become')
|
||||||
|
queue.insert(0, 'enable')
|
||||||
|
|
||||||
operations = self.connection.get_device_operations()
|
request = request_builder(queue, output)
|
||||||
self.connection.check_edit_config_capability(operations, candidate, commit, replace, comment)
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
if replace:
|
response, response_data = self.connection.send('/ins', request, headers=headers, method='POST')
|
||||||
device_info = self.connection.get_device_info()
|
|
||||||
if '9K' not in device_info.get('network_os_platform', ''):
|
|
||||||
raise ConnectionError(msg=u'replace is supported only on Nexus 9K devices')
|
|
||||||
candidate = 'config replace {0}'.format(replace)
|
|
||||||
|
|
||||||
responses = self.send_request(candidate, output='config')
|
|
||||||
for response in to_list(responses):
|
|
||||||
if response != '{}':
|
|
||||||
resp.append(response)
|
|
||||||
if not resp:
|
|
||||||
resp = ['']
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def run_commands(self, commands, check_rc=True):
|
|
||||||
"""Runs list of commands on remote device and returns results
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
out = self.send_request(commands)
|
response_data = json.loads(to_text(response_data.getvalue()))
|
||||||
except ConnectionError as exc:
|
except ValueError:
|
||||||
if check_rc is True:
|
raise ConnectionError('Response was not valid JSON, got {0}'.format(
|
||||||
raise
|
to_text(response_data.getvalue())
|
||||||
out = to_text(exc)
|
))
|
||||||
|
|
||||||
out = to_list(out)
|
results = handle_response(response_data)
|
||||||
if not out[0]:
|
|
||||||
return out
|
|
||||||
|
|
||||||
for index, response in enumerate(out):
|
if self._become:
|
||||||
if response[0] == '{':
|
results = results[1:]
|
||||||
out[index] = json.loads(response)
|
return results
|
||||||
|
|
||||||
return out
|
def get_device_info(self):
|
||||||
|
if self._device_info:
|
||||||
|
return self._device_info
|
||||||
|
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'nxos'
|
||||||
|
reply = self.send_request('show version')
|
||||||
|
platform_reply = self.send_request('show inventory')
|
||||||
|
|
||||||
|
find_os_version = [r'\s+system:\s+version\s*(\S+)', r'\s+kickstart:\s+version\s*(\S+)', r'\s+NXOS:\s+version\s*(\S+)']
|
||||||
|
for regex in find_os_version:
|
||||||
|
match_ver = re.search(regex, reply, re.M)
|
||||||
|
if match_ver:
|
||||||
|
device_info['network_os_version'] = match_ver.group(1)
|
||||||
|
break
|
||||||
|
|
||||||
|
match_chassis_id = re.search(r'Hardware\n\s+cisco\s*(\S+\s+\S+)', reply, re.M)
|
||||||
|
if match_chassis_id:
|
||||||
|
device_info['network_os_model'] = match_chassis_id.group(1)
|
||||||
|
|
||||||
|
match_host_name = re.search(r'\s+Device name:\s*(\S+)', reply, re.M)
|
||||||
|
if match_host_name:
|
||||||
|
device_info['network_os_hostname'] = match_host_name.group(1)
|
||||||
|
|
||||||
|
find_os_image = [r'\s+system image file is:\s*(\S+)', r'\s+kickstart image file is:\s*(\S+)', r'\s+NXOS image file is:\s*(\S+)']
|
||||||
|
for regex in find_os_image:
|
||||||
|
match_file_name = re.search(regex, reply, re.M)
|
||||||
|
if match_file_name:
|
||||||
|
device_info['network_os_image'] = match_file_name.group(1)
|
||||||
|
break
|
||||||
|
|
||||||
|
match_os_platform = re.search(r'NAME: "Chassis",\s*DESCR:.*\nPID:\s*(\S+)', platform_reply, re.M)
|
||||||
|
if match_os_platform:
|
||||||
|
device_info['network_os_platform'] = match_os_platform.group(1)
|
||||||
|
|
||||||
|
self._device_info = device_info
|
||||||
|
return self._device_info
|
||||||
|
|
||||||
|
def get_device_operations(self):
|
||||||
|
platform = self.get_device_info().get('network_os_platform', '')
|
||||||
|
return {
|
||||||
|
'supports_diff_replace': True,
|
||||||
|
'supports_commit': False,
|
||||||
|
'supports_rollback': False,
|
||||||
|
'supports_defaults': True,
|
||||||
|
'supports_onbox_diff': False,
|
||||||
|
'supports_commit_comment': False,
|
||||||
|
'supports_multiline_delimiter': False,
|
||||||
|
'supports_diff_match': True,
|
||||||
|
'supports_diff_ignore_lines': True,
|
||||||
|
'supports_generate_diff': True,
|
||||||
|
'supports_replace': True if '9K' in platform else False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = []
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
result['device_operations'] = self.get_device_operations()
|
||||||
|
result.update(OPTIONS)
|
||||||
|
result['network_api'] = 'nxapi'
|
||||||
|
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
|
||||||
def handle_response(response):
|
def handle_response(response):
|
||||||
|
|
|
@ -27,17 +27,9 @@ class JsonRpcServer(object):
|
||||||
error = self.invalid_request()
|
error = self.invalid_request()
|
||||||
return json.dumps(error)
|
return json.dumps(error)
|
||||||
|
|
||||||
params = request.get('params')
|
args, kwargs = request.get('params')
|
||||||
setattr(self, '_identifier', request.get('id'))
|
setattr(self, '_identifier', request.get('id'))
|
||||||
|
|
||||||
args = []
|
|
||||||
kwargs = {}
|
|
||||||
|
|
||||||
if all((params, isinstance(params, list))):
|
|
||||||
args = params
|
|
||||||
elif all((params, isinstance(params, dict))):
|
|
||||||
kwargs = params
|
|
||||||
|
|
||||||
rpc_method = None
|
rpc_method = None
|
||||||
for obj in self._objects:
|
for obj in self._objects:
|
||||||
rpc_method = getattr(obj, method, None)
|
rpc_method = getattr(obj, method, None)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue