You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ansible/lib/ansible/plugins/netconf/__init__.py

249 lines
10 KiB
Python

#
# (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.errors import AnsibleError
from ansible.module_utils.six import with_metaclass
from ansible.module_utils._text import to_bytes
try:
from ncclient.operations import RPCError
from ncclient.xml_ import to_xml, to_ele
except ImportError:
raise AnsibleError("ncclient is not installed")
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: Retrieves running configuration and device state information
:get_config: Retrieves the specified configuration from the device
:edit_config: Loads the specified commands into the remote device
: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.
:get-schema: Retrieves the required schema from the device
:get_capabilities: Retrieves device information and supported rpc methods
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 rpc(self, name):
"""RPC to be execute on remote device
:name: Name of rpc in string format"""
try:
obj = to_ele(name)
resp = self.m.rpc(obj)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
except RPCError as exc:
msg = exc.data_xml if hasattr(exc, 'data_xml') else exc.xml
raise Exception(to_xml(msg))
@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)"""
resp = self.m.get_config(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.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)
"""
resp = self.m.get(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.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.
"""
resp = self.m.edit_config(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.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
"""
resp = self.m.validate(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.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"""
resp = self.m.copy_config(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.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
"""
resp = self.m.lock(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.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
"""
resp = self.m.unlock(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def discard_changes(self, *args, **kwargs):
"""Revert the candidate configuration to the currently running configuration.
Any uncommitted changes are discarded."""
resp = self.m.discard_changes(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.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
"""
resp = self.m.commit(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def validate(self, *args, **kwargs):
"""Validate the contents of the specified configuration.
:source: name of configuration data store"""
resp = self.m.validate(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def get_schema(self, *args, **kwargs):
"""Retrieves the required schema from the device
"""
resp = self.m.get_schema(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def locked(self, *args, **kwargs):
resp = self.m.locked(*args, **kwargs)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@abstractmethod
def get_capabilities(self):
"""Retrieves device information and supported
rpc methods by device platform and return result
as a string
"""
pass
@staticmethod
def guess_network_os(obj):
"""Identifies the operating system of
network device.
"""
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
# TODO Restore .xml, when ncclient supports it for all platforms