documentation in progress
[ncclient] / ncclient / manager.py
index 19b9cd4..d305c02 100644 (file)
 # 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)