# See the License for the specific language governing permissions and
# limitations under the License.
-"Thin layer of abstraction around NCClient"
+"This module is a thin layer of abstraction around the library. It exposes all core functionality."
import capabilities
import operations
import transport
+import logging
+logger = logging.getLogger('ncclient.manager')
+
+
+CAPABILITIES = capabilities.Capabilities([
+ "urn:ietf:params:netconf:base:1.0",
+ "urn:ietf:params:netconf:capability:writable-running:1.0",
+ "urn:ietf:params:netconf:capability:candidate:1.0",
+ "urn:ietf:params:netconf:capability:confirmed-commit:1.0",
+ "urn:ietf:params:netconf:capability:rollback-on-error:1.0",
+ "urn:ietf:params:netconf:capability:startup:1.0",
+ "urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp",
+ "urn:ietf:params:netconf:capability:validate:1.0",
+ "urn:ietf:params:netconf:capability:xpath:1.0",
+ "urn:liberouter:params:netconf:capability:power-control:1.0"
+ "urn:ietf:params:netconf:capability:interleave:1.0"
+ #'urn:ietf:params:netconf:capability:notification:1.0', # TODO
+])
+"""`~ncclient.capabilities.Capabilities` object representing the client's capabilities. This is used
+during the initial capability exchange. Modify this if you need to announce some capability not
+already included.
+"""
+
+OPERATIONS = {
+ "get": operations.Get,
+ "get_config": operations.GetConfig,
+ "edit_config": operations.EditConfig,
+ "copy_config": operations.CopyConfig,
+ "validate": operations.Validate,
+ "commit": operations.Commit,
+ "discard_changes": operations.DiscardChanges,
+ "delete_config": operations.DeleteConfig,
+ "lock": operations.Lock,
+ "unlock": operations.Unlock,
+ "close_session": operations.CloseSession,
+ "kill_session": operations.KillSession,
+ "poweroff_machine": operations.PoweroffMachine,
+ "reboot_machine": operations.RebootMachine
+}
+"""Dictionary of method names and corresponding `~ncclient.operations.RPC` subclasses. `Manager`
+uses this to lookup operations, e.g. "get_config" is mapped to `~ncclient.operations.GetConfig`. It
+is thus possible to add additional operations to the `Manager` API.
+"""
+
def connect_ssh(*args, **kwds):
- """Connect to NETCONF server over SSH. See :meth:`SSHSession.connect()
- <ncclient.transport.SSHSession.connect>` for function signature."""
- session = transport.SSHSession(capabilities.CAPABILITIES)
+ """Initializes a NETCONF session over SSH, and creates a connected `Manager` instance. *host*
+ must be specified, all the other arguments are optional and depend on the kind of host key
+ verification and user authentication you want to complete.
+
+ For the purpose of host key verification, on -NIX systems a user's :file:`~/.ssh/known_hosts`
+ file is automatically considered. The *unknown_host_cb* argument specifies a callback that will
+ be invoked when the server's host key cannot be verified. See
+ :func:`~ncclient.transport.ssh.default_unknown_host_cb` for function signature.
+
+ First, ``publickey`` authentication is attempted. If a specific *key_filename* is specified, it
+ will be loaded and authentication attempted using it. If *allow_agent* is :const:`True` and an
+ SSH agent is running, the keys provided by the agent will be tried. If *look_for_keys* is
+ :const:`True`, keys in the :file:`~/.ssh/id_rsa` and :file:`~.ssh/id_dsa` will be tried. In case
+ an encrypted key file is encountered, the *password* argument will be used as a decryption
+ passphrase.
+
+ If ``publickey`` authentication fails and the *password* argument has been supplied,
+ ``password`` / ``keyboard-interactive`` SSH authentication will be attempted.
+
+ :param host: hostname or address on which to connect
+ :type host: `string`
+
+ :param port: port on which to connect
+ :type port: `int`
+
+ :param timeout: timeout for socket connect
+ :type timeout: `int`
+
+ :param unknown_host_cb: optional; callback that is invoked when host key verification fails
+ :type unknown_host_cb: `function`
+
+ :param username: username to authenticate with, if not specified the username of the logged-in user is used
+ :type username: `string`
+
+ :param password: password for ``password`` authentication or passphrase for decrypting private key files
+ :type password: `string`
+
+ :param key_filename: location of a private key file on the file system
+ :type key_filename: `string`
+
+ :param allow_agent: whether to try connecting to SSH agent for keys
+ :type allow_agent: `bool`
+
+ :param look_for_keys: whether to look in usual locations for keys
+ :type look_for_keys: `bool`
+
+ :raises: :exc:`~ncclient.transport.SSHUnknownHostError`
+ :raises: :exc:`~ncclient.transport.AuthenticationError`
+
+ :rtype: `Manager`
+ """
+ session = transport.SSHSession(CAPABILITIES)
session.load_known_hosts()
session.connect(*args, **kwds)
return Manager(session)
-#: Same as :meth:`connect_ssh`
connect = connect_ssh
+"Same as :func:`connect_ssh`, since SSH is the default (and currently, the only) transport."
-#: Raise all :class:`~ncclient.operations.rpc.RPCError`
-RAISE_ALL = 0
-#: Only raise when *error-severity* is "error" i.e. no warnings
-RAISE_ERR = 1
-#: Don't raise any
-RAISE_NONE = 2
-
-class Manager:
-
- """API for NETCONF operations. Currently only supports making synchronous
- RPC requests.
-
- It is also a context manager, so a :class:`Manager` instance can be used
- with the *with* statement. The session is closed when the context ends. """
+class Manager(object):
def __init__(self, session):
self._session = session
- self._raise = RAISE_ALL
-
- def set_rpc_error_action(self, action):
- """Specify the action to take when an *<rpc-error>* element is encountered.
-
- :arg action: one of :attr:`RAISE_ALL`, :attr:`RAISE_ERR`, :attr:`RAISE_NONE`
- """
- self._raise = action
-
- #def __enter__(self):
- # return self
- #
- #def __exit__(self, *args):
- # self.close()
- # return False
-
- def do(self, op, *args, **kwds):
- op = operations.OPERATIONS[op](self._session)
- reply = op.request(*args, **kwds)
- if not reply.ok:
- if self._raise == RAISE_ALL:
- raise reply.error
- elif self._raise == RAISE_ERROR:
- for error in reply.errors:
- if error.severity == 'error':
- raise error
- return reply
-
- #: :see: :meth:`Get.request() <ncclient.operations.Get.request>`
- get = lambda self, *args, **kwds: self.do('get', *args, **kwds)
-
- #: :see: :meth:`GetConfig.request() <ncclient.operations.GetConfig.request>`
- get_config = lambda self, *args, **kwds: self.do('get-config', *args, **kwds)
-
- #: :see: :meth:`EditConfig.request() <ncclient.operations.EditConfig.request>`
- edit_config = lambda self, *args, **kwds: self.do('edit-config', *args, **kwds)
-
- #: :see: :meth:`CopyConfig.request() <ncclient.operations.CopyConfig.request>`
- copy_config = lambda self, *args, **kwds: self.do('copy-config', *args, **kwds)
-
- #: :see: :meth:`GetConfig.request() <ncclient.operations.Validate.request>`
- validate = lambda self, *args, **kwds: self.do('validate', *args, **kwds)
-
- #: :see: :meth:`Commit.request() <ncclient.operations.Commit.request>`
- commit = lambda self, *args, **kwds: self.do('commit', *args, **kwds)
-
- #: :see: :meth:`DiscardChanges.request() <ncclient.operations.DiscardChanges.request>`
- discard_changes = lambda self, *args, **kwds: self.do('discard-changes', *args, **kwds)
-
- #: :see: :meth:`DeleteConfig.request() <ncclient.operations.DeleteConfig.request>`
- delete_config = lambda self, *args, **kwds: self.do('delete-config', *args, **kwds)
-
- #: :see: :meth:`Lock.request() <ncclient.operations.Lock.request>`
- lock = lambda self, *args, **kwds: self.do('lock', *args, **kwds)
-
- #: :see: :meth:`DiscardChanges.request() <ncclient.operations.Unlock.request>`
- unlock = lambda self, *args, **kwds: self.do('unlock', *args, **kwds)
-
- #: :see: :meth:`CloseSession.request() <ncclient.operations.CloseSession.request>`
- close_session = lambda self, *args, **kwds: self.do('close-session', *args, **kwds)
-
- #: :see: :meth:`KillSession.request() <ncclient.operations.KillSession.request>`
- kill_session = lambda self, *args, **kwds: self.do('kill-session', *args, **kwds)
-
+ self._async_mode = False
+ self._timeout = None
+ self._raise_mode = 'all'
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *argss):
+ self.close_session()
+ return False
+
+ def __getattr__(self, name):
+ op = OPERATIONS.get(name, None)
+ if op is None:
+ raise AttributeError
+ else:
+ return op(self._session,
+ async=self._async_mode,
+ timeout=self._timeout,
+ raise_mode=self._raise_mode).request
+
def locked(self, target):
- """Returns a context manager for the *with* statement.
-
- :arg target: name of the datastore to lock
- :type target: `string`
- :rtype: :class:`~ncclient.operations.LockContext`
- """
return operations.LockContext(self._session, target)
-
- def close(self):
- """Closes the NETCONF session. First does *<close-session>* RPC."""
- try: # try doing it clean
- self.close_session()
- except Exception as e:
- logger.debug('error doing <close-session> -- %r' % e)
- if self._session.connected: # if that didn't work...
- self._session.close()
-
- @property
- def session(self, session):
- ":class:`~ncclient.transport.Session` instance"
- return self._session
-
+
@property
def client_capabilities(self):
- ":class:`~ncclient.capabilities.Capabilities` object for client"
return self._session._client_capabilities
@property
def server_capabilities(self):
- ":class:`~ncclient.capabilities.Capabilities` object for server"
return self._session._server_capabilities
@property
def session_id(self):
- "*<session-id>* as assigned by NETCONF server"
return self._session.id
@property
def connected(self):
- "Whether currently connected to NETCONF server"
return self._session.connected
+
+ def set_async_mode(self, mode):
+ self._async_mode = mode
+
+ def set_raise_mode(self, mode):
+ assert(choice in ("all", "errors", "none"))
+ self._raise_mode = mode
+
+ async_mode = property(fget=lambda self: self._async_mode, fset=set_async_mode)
+
+ raise_mode = property(fget=lambda self: self._raise_mode, fset=set_raise_mode)