mirror of https://github.com/ansible/ansible.git
Add support for cliconf and netconf plugin (#25093)
* ansible-connection refactor and action plugin changes * Add cliconf plugin for eos, ios, iosxr, junos, nxos, vyos * Add netconf plugin for junos * Add jsonrpc support * Modify network_cli and netconf connection plugin * Fix py3 unit test failure * Fix review comment * Minor fixes * Fix ansible-connection review comments * Fix CI issue * platform_agnostic related changespull/12880/merge
parent
c20285782d
commit
6215922889
@ -0,0 +1,188 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import signal
|
||||||
|
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError, AnsibleConnectionFailure
|
||||||
|
from ansible.module_utils.six import with_metaclass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from __main__ import display
|
||||||
|
except ImportError:
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
def enable_mode(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapped(self, *args, **kwargs):
|
||||||
|
prompt = self.get_prompt()
|
||||||
|
if not str(prompt).strip().endswith('#'):
|
||||||
|
raise AnsibleError('operation requires privilege escalation')
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
class CliconfBase(with_metaclass(ABCMeta, object)):
|
||||||
|
"""
|
||||||
|
A base class for implementing cli connections
|
||||||
|
|
||||||
|
.. note:: Unlike most of Ansible, nearly all strings in
|
||||||
|
:class:`CliconfBase` plugins are byte strings. This is because of
|
||||||
|
how close to the underlying platform these plugins operate. Remember
|
||||||
|
to mark literal strings as byte string (``b"string"``) and to use
|
||||||
|
:func:`~ansible.module_utils._text.to_bytes` and
|
||||||
|
:func:`~ansible.module_utils._text.to_text` to avoid unexpected
|
||||||
|
problems.
|
||||||
|
|
||||||
|
List of supported rpc's:
|
||||||
|
:get_config: Retrieves the specified configuration from the device
|
||||||
|
:edit_config: Loads the specified commands into the remote device
|
||||||
|
:get: Execute specified command on remote device
|
||||||
|
:get_capabilities: Retrieves device information and supported rpc methods
|
||||||
|
:commit: Load configuration from candidate to running
|
||||||
|
:discard_changes: Discard changes to candidate datastore
|
||||||
|
|
||||||
|
Note: List of supported rpc's for remote device can be extracted from
|
||||||
|
output of get_capabilities()
|
||||||
|
|
||||||
|
:returns: Returns output received from remote device as byte string
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
from ansible.module_utils.connection import Connection
|
||||||
|
|
||||||
|
conn = Connection()
|
||||||
|
conn.get('show lldp neighbors detail'')
|
||||||
|
conn.get_config('running')
|
||||||
|
conn.edit_config(['hostname test', 'netconf ssh'])
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection):
|
||||||
|
self._connection = connection
|
||||||
|
|
||||||
|
def _alarm_handler(self, signum, frame):
|
||||||
|
raise AnsibleConnectionFailure('timeout waiting for command to complete')
|
||||||
|
|
||||||
|
def send_command(self, command, prompt=None, answer=None, sendonly=False):
|
||||||
|
"""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
|
||||||
|
"""
|
||||||
|
timeout = self._connection._play_context.timeout or 30
|
||||||
|
signal.signal(signal.SIGALRM, self._alarm_handler)
|
||||||
|
signal.alarm(timeout)
|
||||||
|
display.display("command: %s" % command, log_only=True)
|
||||||
|
resp = self._connection.send(command, prompt, answer, sendonly)
|
||||||
|
signal.alarm(0)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def get_prompt(self):
|
||||||
|
"""Returns the current prompt from the device"""
|
||||||
|
return self._connection._matched_prompt
|
||||||
|
|
||||||
|
def get_base_rpc(self):
|
||||||
|
"""Returns list of base rpc method supported by remote device"""
|
||||||
|
return ['get_config', 'edit_config', 'get_capabilities', 'get']
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_config(self, source='running', format='text'):
|
||||||
|
"""Retrieves the specified configuration from the device
|
||||||
|
This method will retrieve the configuration specified by source and
|
||||||
|
return it to the caller as a string. Subsequent calls to this method
|
||||||
|
will retrieve a new configuration from the device
|
||||||
|
:args:
|
||||||
|
arg[0] source: Datastore from which configuration should be retrieved eg: running/candidate/startup. (optional)
|
||||||
|
default is running.
|
||||||
|
arg[1] format: Output format in which configuration is retrieved
|
||||||
|
Note: Specified datastore should be supported by remote device.
|
||||||
|
:kwargs:
|
||||||
|
Keywords supported
|
||||||
|
:command: the command string to execute
|
||||||
|
:source: Datastore from which configuration should be retrieved
|
||||||
|
:format: Output format in which configuration is retrieved
|
||||||
|
:returns: Returns output received from remote device as byte string
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def edit_config(self, commands):
|
||||||
|
"""Loads the specified commands into the remote device
|
||||||
|
This method will load the commands into the remote device. This
|
||||||
|
method will make sure the device is in the proper context before
|
||||||
|
send the commands (eg config mode)
|
||||||
|
:args:
|
||||||
|
arg[0] command: List of configuration commands
|
||||||
|
:kwargs:
|
||||||
|
Keywords supported
|
||||||
|
:command: the command string to execute
|
||||||
|
:returns: Returns output received from remote device as byte string
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
"""Execute specified command on remote device
|
||||||
|
This method will retrieve the specified data and
|
||||||
|
return it to the caller as a string.
|
||||||
|
:args:
|
||||||
|
arg[0] command: command in string format to be executed on remote device
|
||||||
|
arg[1] prompt: the expected prompt generated by executing command.
|
||||||
|
This can be a string or a list of strings (optional)
|
||||||
|
arg[2] answer: the string to respond to the prompt with (optional)
|
||||||
|
arg[3] sendonly: bool to disable waiting for response, default is false (optional)
|
||||||
|
:kwargs:
|
||||||
|
:command: the command string to execute
|
||||||
|
:prompt: the expected prompt generated by executing command.
|
||||||
|
This can be a string or a list of strings
|
||||||
|
:answer: the string to respond to the prompt with
|
||||||
|
:sendonly: bool to disable waiting for response
|
||||||
|
:returns: Returns output received from remote device as byte string
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_capabilities(self):
|
||||||
|
"""Retrieves device information and supported
|
||||||
|
rpc methods by device platform and return result
|
||||||
|
as a string
|
||||||
|
:returns: Returns output received from remote device as byte string
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def commit(self, comment=None):
|
||||||
|
"""Commit configuration changes"""
|
||||||
|
return self._connection.method_not_found("commit is not supported by network_os %s" % self._play_context.network_os)
|
||||||
|
|
||||||
|
def discard_changes(self):
|
||||||
|
"Discard changes in candidate datastore"
|
||||||
|
return self._connection.method_not_found("discard_changes is not supported by network_os %s" % self._play_context.network_os)
|
||||||
|
|
||||||
|
def put_file(self, source, destination):
|
||||||
|
"""Copies file over scp to remote device"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def fetch_file(self, source, destination):
|
||||||
|
"""Fetch file over scp from remote device"""
|
||||||
|
pass
|
@ -0,0 +1,73 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from ansible.module_utils.network_common import to_list
|
||||||
|
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||||
|
|
||||||
|
|
||||||
|
class Cliconf(CliconfBase):
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'eos'
|
||||||
|
reply = self.get(b'show version | json')
|
||||||
|
data = json.loads(reply)
|
||||||
|
|
||||||
|
device_info['network_os_version'] = data['version']
|
||||||
|
device_info['network_os_model'] = data['modelName']
|
||||||
|
|
||||||
|
reply = self.get(b'show hostname | json')
|
||||||
|
data = json.loads(reply)
|
||||||
|
|
||||||
|
device_info['network_os_hostname'] = data['hostname']
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
@enable_mode
|
||||||
|
def get_config(self, source='running', format='text'):
|
||||||
|
lookup = {'running': 'running-config', 'startup': 'startup-config'}
|
||||||
|
if source not in lookup:
|
||||||
|
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
||||||
|
if format == 'text':
|
||||||
|
cmd = b'show %s' % lookup[source]
|
||||||
|
else:
|
||||||
|
cmd = b'show %s | %s' % (lookup[source], format)
|
||||||
|
return self.send_command(cmd)
|
||||||
|
|
||||||
|
@enable_mode
|
||||||
|
def edit_config(self, command):
|
||||||
|
for cmd in chain([b'configure'], to_list(command), [b'end']):
|
||||||
|
self.send_command(cmd)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = self.get_base_rpc()
|
||||||
|
result['network_api'] = 'cliconf'
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
return json.dumps(result)
|
@ -0,0 +1,78 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
from ansible.module_utils.network_common import to_list
|
||||||
|
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||||
|
|
||||||
|
|
||||||
|
class Cliconf(CliconfBase):
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'ios'
|
||||||
|
reply = self.get(b'show version')
|
||||||
|
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||||
|
|
||||||
|
match = re.search(r'Version (\S+),', data)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_version'] = match.group(1)
|
||||||
|
|
||||||
|
match = re.search(r'^Cisco (.+) \(revision', data, re.M)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_model'] = match.group(1)
|
||||||
|
|
||||||
|
match = re.search(r'^(.+) uptime', data, re.M)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_hostname'] = match.group(1)
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
@enable_mode
|
||||||
|
def get_config(self, source='running'):
|
||||||
|
if source not in ('running', 'startup'):
|
||||||
|
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
||||||
|
if source == 'running':
|
||||||
|
cmd = b'show running-config all'
|
||||||
|
else:
|
||||||
|
cmd = b'show startup-config'
|
||||||
|
return self.send_command(cmd)
|
||||||
|
|
||||||
|
@enable_mode
|
||||||
|
def edit_config(self, command):
|
||||||
|
for cmd in chain([b'configure terminal'], to_list(command), [b'end']):
|
||||||
|
self.send_command(cmd)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = self.get_base_rpc()
|
||||||
|
result['network_api'] = 'cliconf'
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
return json.dumps(result)
|
@ -0,0 +1,87 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
from ansible.module_utils.network_common import to_list
|
||||||
|
from ansible.plugins.cliconf import CliconfBase
|
||||||
|
|
||||||
|
|
||||||
|
class Cliconf(CliconfBase):
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'iosxr'
|
||||||
|
reply = self.get(b'show version brief')
|
||||||
|
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||||
|
|
||||||
|
match = re.search(r'Version (\S+)$', data, re.M)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_version'] = match.group(1)
|
||||||
|
|
||||||
|
match = re.search(r'image file is "(.+)"', data)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_image'] = match.group(1)
|
||||||
|
|
||||||
|
match = re.search(r'^Cisco (.+) \(revision', data, re.M)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_model'] = match.group(1)
|
||||||
|
|
||||||
|
match = re.search(r'^(.+) uptime', data, re.M)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_hostname'] = match.group(1)
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
def get_config(self, source='running'):
|
||||||
|
lookup = {'running': 'running-config'}
|
||||||
|
if source not in lookup:
|
||||||
|
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
||||||
|
return self.send_command(to_bytes(b'show %s' % lookup[source], errors='surrogate_or_strict'))
|
||||||
|
|
||||||
|
def edit_config(self, command):
|
||||||
|
for cmd in chain([b'configure'], to_list(command), [b'end']):
|
||||||
|
self.send_command(cmd)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
|
def commit(self, comment=None):
|
||||||
|
if comment:
|
||||||
|
command = b'commit comment {0}'.format(comment)
|
||||||
|
else:
|
||||||
|
command = b'commit'
|
||||||
|
self.send_command(command)
|
||||||
|
|
||||||
|
def discard_changes(self):
|
||||||
|
self.send_command(b'abort')
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes']
|
||||||
|
result['network_api'] = 'cliconf'
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
return json.dumps(result)
|
@ -0,0 +1,87 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
from xml.etree.ElementTree import fromstring
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
from ansible.module_utils.network_common import to_list
|
||||||
|
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||||
|
|
||||||
|
|
||||||
|
class Cliconf(CliconfBase):
|
||||||
|
|
||||||
|
def get_text(self, ele, tag):
|
||||||
|
try:
|
||||||
|
return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'junos'
|
||||||
|
reply = self.get(b'show version | display xml')
|
||||||
|
data = fromstring(to_text(reply, errors='surrogate_then_replace').strip())
|
||||||
|
|
||||||
|
sw_info = data.find('.//software-information')
|
||||||
|
|
||||||
|
device_info['network_os_version'] = self.get_text(sw_info, 'junos-version')
|
||||||
|
device_info['network_os_hostname'] = self.get_text(sw_info, 'host-name')
|
||||||
|
device_info['network_os_model'] = self.get_text(sw_info, 'product-model')
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
def get_config(self, source='running', format='text'):
|
||||||
|
if source != 'running':
|
||||||
|
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
||||||
|
if format == 'text':
|
||||||
|
cmd = b'show configuration'
|
||||||
|
else:
|
||||||
|
cmd = b'show configuration | display %s' % format
|
||||||
|
return self.send_command(to_bytes(cmd), errors='surrogate_or_strict')
|
||||||
|
|
||||||
|
def edit_config(self, command):
|
||||||
|
for cmd in chain([b'configure'], to_list(command)):
|
||||||
|
self.send_command(cmd)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
|
def commit(self, comment=None):
|
||||||
|
if comment:
|
||||||
|
command = b'commit comment {0}'.format(comment)
|
||||||
|
else:
|
||||||
|
command = b'commit'
|
||||||
|
self.send_command(command)
|
||||||
|
|
||||||
|
def discard_changes(self):
|
||||||
|
self.send_command(b'rollback')
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes']
|
||||||
|
result['network_api'] = 'cliconf'
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
return json.dumps(result)
|
@ -0,0 +1,62 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from ansible.module_utils.network_common import to_list
|
||||||
|
from ansible.plugins.cliconf import CliconfBase
|
||||||
|
|
||||||
|
|
||||||
|
class Cliconf(CliconfBase):
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'nxos'
|
||||||
|
reply = self.get(b'show version | json')
|
||||||
|
data = json.loads(reply)
|
||||||
|
|
||||||
|
device_info['network_os_version'] = data['sys_ver_str']
|
||||||
|
device_info['network_os_model'] = data['chassis_id']
|
||||||
|
device_info['network_os_hostname'] = data['host_name']
|
||||||
|
device_info['network_os_image'] = data['isan_file_name']
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
def get_config(self, source='running'):
|
||||||
|
lookup = {'running': 'running-config', 'startup': 'startup-config'}
|
||||||
|
return self.send_command(b'show %s' % lookup[source])
|
||||||
|
|
||||||
|
def edit_config(self, command):
|
||||||
|
for cmd in chain([b'configure'], to_list(command), [b'end']):
|
||||||
|
self.send_command(cmd)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = self.get_base_rpc()
|
||||||
|
result['network_api'] = 'cliconf'
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
return json.dumps(result)
|
@ -0,0 +1,79 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
from ansible.module_utils.network_common import to_list
|
||||||
|
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||||
|
|
||||||
|
|
||||||
|
class Cliconf(CliconfBase):
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'vyos'
|
||||||
|
reply = self.get(b'show version')
|
||||||
|
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||||
|
|
||||||
|
match = re.search(r'Version:\s*(\S+)', data)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_version'] = match.group(1)
|
||||||
|
|
||||||
|
match = re.search(r'HW model:\s*(\S+)', data)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_model'] = match.group(1)
|
||||||
|
|
||||||
|
reply = self.get(b'show host name')
|
||||||
|
device_info['network_os_hostname'] = to_text(reply, errors='surrogate_or_strict').strip()
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
return self.send_command(b'show configuration all')
|
||||||
|
|
||||||
|
def edit_config(self, command):
|
||||||
|
for cmd in chain([b'configure'], to_list(command)):
|
||||||
|
self.send_command(cmd)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
|
def commit(self, comment=None):
|
||||||
|
if comment:
|
||||||
|
command = b'commit comment {0}'.format(comment)
|
||||||
|
else:
|
||||||
|
command = b'commit'
|
||||||
|
self.send_command(command)
|
||||||
|
|
||||||
|
def discard_changes(self, *args, **kwargs):
|
||||||
|
self.send_command(b'discard')
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes']
|
||||||
|
result['network_api'] = 'cliconf'
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
return json.dumps(result)
|
@ -0,0 +1,189 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from ansible.module_utils.six import with_metaclass
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_connected(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapped(self, *args, **kwargs):
|
||||||
|
if not self._connection._connected:
|
||||||
|
self._connection._connect()
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
|
"""
|
||||||
|
A base class for implementing Netconf connections
|
||||||
|
|
||||||
|
.. note:: Unlike most of Ansible, nearly all strings in
|
||||||
|
:class:`TerminalBase` plugins are byte strings. This is because of
|
||||||
|
how close to the underlying platform these plugins operate. Remember
|
||||||
|
to mark literal strings as byte string (``b"string"``) and to use
|
||||||
|
:func:`~ansible.module_utils._text.to_bytes` and
|
||||||
|
:func:`~ansible.module_utils._text.to_text` to avoid unexpected
|
||||||
|
problems.
|
||||||
|
|
||||||
|
List of supported rpc's:
|
||||||
|
:get_config: Retrieves the specified configuration from the device
|
||||||
|
:edit_config: Loads the specified commands into the remote device
|
||||||
|
:get: Execute specified command on remote device
|
||||||
|
:get_capabilities: Retrieves device information and supported rpc methods
|
||||||
|
:commit: Load configuration from candidate to running
|
||||||
|
:discard_changes: Discard changes to candidate datastore
|
||||||
|
:validate: Validate the contents of the specified configuration.
|
||||||
|
:lock: Allows the client to lock the configuration system of a device.
|
||||||
|
:unlock: Release a configuration lock, previously obtained with the lock operation.
|
||||||
|
:copy_config: create or replace an entire configuration datastore with the contents of another complete
|
||||||
|
configuration datastore.
|
||||||
|
For JUNOS:
|
||||||
|
:execute_rpc: RPC to be execute on remote device
|
||||||
|
:load_configuration: Loads given configuration on device
|
||||||
|
|
||||||
|
Note: rpc support depends on the capabilites of remote device.
|
||||||
|
|
||||||
|
:returns: Returns output received from remote device as byte string
|
||||||
|
Note: the 'result' or 'error' from response should to be converted to object
|
||||||
|
of ElementTree using 'fromstring' to parse output as xml doc
|
||||||
|
|
||||||
|
'get_capabilities()' returns 'result' as a json string.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
from ansible.module_utils.connection import Connection
|
||||||
|
|
||||||
|
conn = Connection()
|
||||||
|
data = conn.execute_rpc(rpc)
|
||||||
|
reply = fromstring(reply)
|
||||||
|
|
||||||
|
data = conn.get_capabilities()
|
||||||
|
json.loads(data)
|
||||||
|
|
||||||
|
conn.load_configuration(config=[''set system ntp server 1.1.1.1''], action='set', format='text')
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection):
|
||||||
|
self._connection = connection
|
||||||
|
self.m = self._connection._manager
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def get_config(self, *args, **kwargs):
|
||||||
|
"""Retrieve all or part of a specified configuration.
|
||||||
|
:source: name of the configuration datastore being queried
|
||||||
|
:filter: specifies the portion of the configuration to retrieve
|
||||||
|
(by default entire configuration is retrieved)"""
|
||||||
|
return self.m.get_config(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
"""Retrieve running configuration and device state information.
|
||||||
|
*filter* specifies the portion of the configuration to retrieve
|
||||||
|
(by default entire configuration is retrieved)
|
||||||
|
"""
|
||||||
|
return self.m.get(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def edit_config(self, *args, **kwargs):
|
||||||
|
"""Loads all or part of the specified *config* to the *target* configuration datastore.
|
||||||
|
|
||||||
|
:target: is the name of the configuration datastore being edited
|
||||||
|
:config: is the configuration, which must be rooted in the `config` element.
|
||||||
|
It can be specified either as a string or an :class:`~xml.etree.ElementTree.Element`.
|
||||||
|
:default_operation: if specified must be one of { `"merge"`, `"replace"`, or `"none"` }
|
||||||
|
:test_option: if specified must be one of { `"test_then_set"`, `"set"` }
|
||||||
|
:error_option: if specified must be one of { `"stop-on-error"`, `"continue-on-error"`, `"rollback-on-error"` }
|
||||||
|
The `"rollback-on-error"` *error_option* depends on the `:rollback-on-error` capability.
|
||||||
|
"""
|
||||||
|
return self.m.get_config(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def validate(self, *args, **kwargs):
|
||||||
|
"""Validate the contents of the specified configuration.
|
||||||
|
:source: is the name of the configuration datastore being validated or `config`
|
||||||
|
element containing the configuration subtree to be validated
|
||||||
|
"""
|
||||||
|
return self.m.validate(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def copy_config(self, *args, **kwargs):
|
||||||
|
"""Create or replace an entire configuration datastore with the contents of another complete
|
||||||
|
configuration datastore.
|
||||||
|
:source: is the name of the configuration datastore to use as the source of the
|
||||||
|
copy operation or `config` element containing the configuration subtree to copy
|
||||||
|
:target: is the name of the configuration datastore to use as the destination of the copy operation"""
|
||||||
|
return self.m.copy_config(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def lock(self, *args, **kwargs):
|
||||||
|
"""Allows the client to lock the configuration system of a device.
|
||||||
|
*target* is the name of the configuration datastore to lock
|
||||||
|
"""
|
||||||
|
return self.m.lock(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def unlock(self, *args, **kwargs):
|
||||||
|
"""Release a configuration lock, previously obtained with the lock operation.
|
||||||
|
:target: is the name of the configuration datastore to unlock
|
||||||
|
"""
|
||||||
|
return self.m.lock(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def discard_changes(self, *args, **kwargs):
|
||||||
|
"""Revert the candidate configuration to the currently running configuration.
|
||||||
|
Any uncommitted changes are discarded."""
|
||||||
|
return self.m.discard_changes(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def commit(self, *args, **kwargs):
|
||||||
|
"""Commit the candidate configuration as the device's new current configuration.
|
||||||
|
Depends on the `:candidate` capability.
|
||||||
|
A confirmed commit (i.e. if *confirmed* is `True`) is reverted if there is no
|
||||||
|
followup commit within the *timeout* interval. If no timeout is specified the
|
||||||
|
confirm timeout defaults to 600 seconds (10 minutes).
|
||||||
|
A confirming commit may have the *confirmed* parameter but this is not required.
|
||||||
|
Depends on the `:confirmed-commit` capability.
|
||||||
|
:confirmed: whether this is a confirmed commit
|
||||||
|
:timeout: specifies the confirm timeout in seconds
|
||||||
|
"""
|
||||||
|
return self.m.commit(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_capabilities(self, commands):
|
||||||
|
"""Retrieves device information and supported
|
||||||
|
rpc methods by device platform and return result
|
||||||
|
as a string
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_base_rpc(self):
|
||||||
|
"""Returns list of base rpc method supported by remote device"""
|
||||||
|
return ['get_config', 'edit_config', 'get_capabilities', 'get']
|
||||||
|
|
||||||
|
def put_file(self, source, destination):
|
||||||
|
"""Copies file over scp to remote device"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def fetch_file(self, source, destination):
|
||||||
|
"""Fetch file over scp from remote device"""
|
||||||
|
pass
|
@ -0,0 +1,79 @@
|
|||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from xml.etree.ElementTree import fromstring
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
from ansible.plugins.netconf import NetconfBase
|
||||||
|
from ansible.plugins.netconf import ensure_connected
|
||||||
|
|
||||||
|
from ncclient.xml_ import new_ele
|
||||||
|
|
||||||
|
|
||||||
|
class Netconf(NetconfBase):
|
||||||
|
|
||||||
|
def get_text(self, ele, tag):
|
||||||
|
try:
|
||||||
|
return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'junos'
|
||||||
|
data = self.execute_rpc('get-software-information')
|
||||||
|
reply = fromstring(data)
|
||||||
|
sw_info = reply.find('.//software-information')
|
||||||
|
|
||||||
|
device_info['network_os_version'] = self.get_text(sw_info, 'junos-version')
|
||||||
|
device_info['network_os_hostname'] = self.get_text(sw_info, 'host-name')
|
||||||
|
device_info['network_os_model'] = self.get_text(sw_info, 'product-model')
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def execute_rpc(self, rpc):
|
||||||
|
"""RPC to be execute on remote device
|
||||||
|
:rpc: Name of rpc in string format"""
|
||||||
|
name = new_ele(rpc)
|
||||||
|
return self.m.rpc(name).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def load_configuration(self, *args, **kwargs):
|
||||||
|
"""Loads given configuration on device
|
||||||
|
:format: Format of configuration (xml, text, set)
|
||||||
|
:action: Action to be performed (merge, replace, override, update)
|
||||||
|
:target: is the name of the configuration datastore being edited
|
||||||
|
:config: is the configuration in string format."""
|
||||||
|
return self.m.load_configuration(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = {}
|
||||||
|
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'validate', 'lock', 'unlock', 'copy_copy']
|
||||||
|
result['network_api'] = 'netconf'
|
||||||
|
result['device_info'] = self.get_device_info()
|
||||||
|
result['server_capabilities'] = [c for c in self.m.server_capabilities]
|
||||||
|
result['client_capabilities'] = [c for c in self.m.client_capabilities]
|
||||||
|
result['session_id'] = self.m.session_id
|
||||||
|
return json.dumps(result)
|
@ -0,0 +1,115 @@
|
|||||||
|
#
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
|
||||||
|
try:
|
||||||
|
from __main__ import display
|
||||||
|
except ImportError:
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
class Rpc:
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._rpc = set()
|
||||||
|
super(Rpc, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _exec_rpc(self, request):
|
||||||
|
method = request.get('method')
|
||||||
|
|
||||||
|
if method.startswith('rpc.') or method.startswith('_'):
|
||||||
|
error = self.invalid_request()
|
||||||
|
return json.dumps(error)
|
||||||
|
|
||||||
|
params = request.get('params')
|
||||||
|
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
|
||||||
|
for obj in self._rpc:
|
||||||
|
rpc_method = getattr(obj, method, None)
|
||||||
|
if rpc_method:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not rpc_method:
|
||||||
|
error = self.method_not_found()
|
||||||
|
response = json.dumps(error)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
result = rpc_method(*args, **kwargs)
|
||||||
|
display.display(" -- result -- %s" % result, log_only=True)
|
||||||
|
except Exception as exc:
|
||||||
|
display.display(traceback.format_exc(), log_only=True)
|
||||||
|
error = self.internal_error(data=to_text(exc, errors='surrogate_then_replace'))
|
||||||
|
response = json.dumps(error)
|
||||||
|
else:
|
||||||
|
if isinstance(result, dict) and 'jsonrpc' in result:
|
||||||
|
response = result
|
||||||
|
else:
|
||||||
|
response = self.response(result)
|
||||||
|
|
||||||
|
response = json.dumps(response)
|
||||||
|
|
||||||
|
display.display(" -- response -- %s" % response, log_only=True)
|
||||||
|
delattr(self, '_identifier')
|
||||||
|
return response
|
||||||
|
|
||||||
|
def header(self):
|
||||||
|
return {'jsonrpc': '2.0', 'id': self._identifier}
|
||||||
|
|
||||||
|
def response(self, result=None):
|
||||||
|
response = self.header()
|
||||||
|
response['result'] = result or 'ok'
|
||||||
|
return response
|
||||||
|
|
||||||
|
def error(self, code, message, data=None):
|
||||||
|
response = self.header()
|
||||||
|
error = {'code': code, 'message': message}
|
||||||
|
if data:
|
||||||
|
error['data'] = data
|
||||||
|
response['error'] = error
|
||||||
|
return response
|
||||||
|
|
||||||
|
# json-rpc standard errors (-32768 .. -32000)
|
||||||
|
def parse_error(self, data=None):
|
||||||
|
return self.error(-32700, 'Parse error', data)
|
||||||
|
|
||||||
|
def method_not_found(self, data=None):
|
||||||
|
return self.error(-32601, 'Method not found', data)
|
||||||
|
|
||||||
|
def invalid_request(self, data=None):
|
||||||
|
return self.error(-32600, 'Invalid request', data)
|
||||||
|
|
||||||
|
def invalid_params(self, data=None):
|
||||||
|
return self.error(-32602, 'Invalid params', data)
|
||||||
|
|
||||||
|
def internal_error(self, data=None):
|
||||||
|
return self.error(-32603, 'Internal error', data)
|
Loading…
Reference in New Issue