Exemple #1
0
    def get_items(
        self,
        xpath: str,
        timeout_ms: int = 0,
        no_state: bool = False,
        no_config: bool = False,
        no_subs: bool = False,
        no_stored: bool = False,
    ) -> Iterator[Value]:
        """
        Retrieve an array of data elements selected by the provided XPath.

        All data elements are transferred within one message from the datastore, which
        is more efficient than multiple get_item calls.

        :arg xpath:
            XPath of the data elements to be retrieved.
        :arg timeout_ms:
            Operational callback timeout in milliseconds. If 0, default is used.
        :arg no_state:
            Return only configuration data.
        :arg no_config:
            Return only state data. If there are some state subtrees with configuration
            these are also returned (with keys if lists).
        :arg no_subs:
            Return only stored operational data (push), do not call subscriber
            callbacks (pull).
        :arg no_stored:
            Do not merge with stored operational data (push).

        :returns:
            An iterator that yields sysrepo.Value objects.
        """
        flags = _get_oper_flags(no_state=no_state,
                                no_config=no_config,
                                no_subs=no_subs,
                                no_stored=no_stored)
        val_p = ffi.new("sr_val_t **")
        count_p = ffi.new("size_t *")
        check_call(
            lib.sr_get_items,
            self.cdata,
            str2c(xpath),
            timeout_ms,
            flags,
            val_p,
            count_p,
        )
        try:
            for i in range(count_p[0]):
                yield Value.parse(val_p[0] + i)
        finally:
            lib.sr_free_values(val_p[0], count_p[0])
Exemple #2
0
 def __init__(
     self,
     cache_running: bool = False,
     no_sched_changes: bool = False,
     err_on_sched_fail: bool = False,
 ):
     """
     :arg cache_running:
         Always cache running datastore data which makes mainly repeated retrieval of
         data much faster. Affects all sessions created on this connection.
     :arg no_sched_changes:
         Do not parse internal modules data and apply any scheduled changes. Makes
         creating the connection faster but, obviously, scheduled changes are not
         applied.
     :arg err_on_sched_fail:
         If applying any of the scheduled changes fails, do not create a connection
         and return an error.
     """
     flags = 0
     if cache_running:
         flags |= lib.SR_CONN_CACHE_RUNNING
     if no_sched_changes:
         flags |= lib.SR_CONN_NO_SCHED_CHANGES
     if err_on_sched_fail:
         flags |= lib.SR_CONN_ERR_ON_SCHED_FAIL
     conn_p = ffi.new("sr_conn_ctx_t **")
     check_call(lib.sr_connect, flags, conn_p)
     self.cdata = conn_p[0]
Exemple #3
0
    def rpc_send_ly(self,
                    rpc_input: libyang.DNode,
                    timeout_ms: int = 0) -> libyang.DNode:
        """
        Send an RPC/action and wait for the result.

        RPC/action must be valid in (is validated against) the operational datastore
        context.

        :arg rpc_input:
            The RPC/action input tree. It is *NOT* spent and must be freed by the
            caller.
        :arg timeout_ms:
            RPC/action callback timeout in milliseconds. If 0, default is used.

        :returns:
            The RPC/action output tree. Allocated dynamically and must be freed by the
            caller.
        :raises SysrepoError:
            If the RPC/action callback failed.
        """
        if not isinstance(rpc_input, libyang.DNode):
            raise TypeError("rpc_input must be a libyang.DNode")
        # libyang and sysrepo bindings are different, casting is required
        in_dnode = ffi.cast("struct lyd_node *", rpc_input.cdata)
        out_dnode_p = ffi.new("struct lyd_node **")
        check_call(lib.sr_rpc_send_tree, self.cdata, in_dnode, timeout_ms,
                   out_dnode_p)
        if not out_dnode_p[0]:
            raise SysrepoInternalError("sr_rpc_send_tree returned NULL")
        return libyang.DNode.new(self.get_ly_ctx(), out_dnode_p[0])
Exemple #4
0
    def subscribe_oper_data_request(self,
                                    module: str,
                                    xpath: str,
                                    callback: OperDataCallbackType,
                                    *,
                                    no_thread: bool = False,
                                    private_data: Any = None,
                                    asyncio_register: bool = False,
                                    strict: bool = False) -> None:
        """
        Register for providing operational data at the given xpath.

        :arg module:
            Name of the affected module.
        :arg xpath:
            Xpath identifying the subtree which the provider is able to provide.
            Predicates can be used to provide only specific instances of nodes.
        :arg callback:
            Callback to be called when the operational data for the given xpath are
            requested.
        :arg no_thread:
            There will be no thread created for handling this subscription meaning no
            event will be processed! Default to `True` if asyncio_register is `True`.
        :arg private_data:
            Private context passed to the callback function, opaque to sysrepo.
        :arg asyncio_register:
            Add the created subscription event pipe into asyncio event loop monitored
            read file descriptors. Implies `no_thread=True`.
        :arg strict:
            Reject the whole data returned by callback if it contains elements without
            schema definition.
        """
        if self.is_implicit:
            raise SysrepoUnsupportedError(
                "cannot subscribe with implicit sessions")
        _check_subscription_callback(callback, self.OperDataCallbackType)

        sub = Subscription(callback,
                           private_data,
                           asyncio_register=asyncio_register,
                           strict=strict)
        sub_p = ffi.new("sr_subscription_ctx_t **")

        if asyncio_register:
            no_thread = True  # we manage our own event loop
        flags = _subscribe_flags(no_thread=no_thread)

        check_call(
            lib.sr_oper_get_items_subscribe,
            self.cdata,
            str2c(module),
            str2c(xpath),
            lib.srpy_oper_data_cb,
            sub.handle,
            flags,
            sub_p,
        )
        sub.init(sub_p[0])

        self.subscriptions.append(sub)
Exemple #5
0
 def __init__(
     self,
     cache_running: bool = False,
     no_sched_changes: bool = False,
     err_on_sched_fail: bool = False,
 ):
     """
     :arg cache_running:
         Always cache running datastore data which makes mainly repeated retrieval of
         data much faster. Affects all sessions created on this connection.
     :arg no_sched_changes:
         Do not parse internal modules data and apply any scheduled changes. Makes
         creating the connection faster but, obviously, scheduled changes are not
         applied.
     :arg err_on_sched_fail:
         If applying any of the scheduled changes fails, do not create a connection
         and return an error.
     """
     flags = 0
     if cache_running:
         flags |= lib.SR_CONN_CACHE_RUNNING
     if no_sched_changes:
         flags |= lib.SR_CONN_NO_SCHED_CHANGES
     if err_on_sched_fail:
         flags |= lib.SR_CONN_ERR_ON_SCHED_FAIL
     conn_p = ffi.new("sr_conn_ctx_t **")
     # valid_signals() is only available since python 3.8
     valid_signals = getattr(signal, "valid_signals",
                             lambda: range(1, signal.NSIG))
     sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, valid_signals())
     try:
         check_call(lib.sr_connect, flags, conn_p)
         self.cdata = ffi.gc(conn_p[0], lib.sr_disconnect)
     finally:
         signal.pthread_sigmask(signal.SIG_SETMASK, sigmask)
Exemple #6
0
    def subscribe_rpc_call(self,
                           xpath: str,
                           callback: RpcCallbackType,
                           *,
                           priority: int = 0,
                           no_thread: bool = False,
                           private_data: Any = None,
                           asyncio_register: bool = False,
                           strict: bool = False) -> None:
        """
        Subscribe for the delivery of an RPC/action.

        :arg xpath:
            XPath identifying the RPC/action. Any predicates are allowed.
        :arg callback:
            Callback to be called when the RPC/action is invoked.
        :arg priority:
            Specifies the order in which the callbacks (**within RPC/action**) will be
            called.
        :arg no_thread:
            There will be no thread created for handling this subscription meaning no
            event will be processed! Default to True if asyncio_register is True.
        :arg private_data:
            Private context passed to the callback function, opaque to sysrepo.
        :arg asyncio_register:
            Add the created subscription event pipe into asyncio event loop monitored
            read file descriptors. Implies no_thread=True.
        :arg strict:
            Reject the whole data returned by callback if it contains elements without
            schema definition.
        """
        if self.is_implicit:
            raise SysrepoUnsupportedError(
                "cannot subscribe with implicit sessions")
        _check_subscription_callback(callback, self.RpcCallbackType)

        sub = Subscription(callback,
                           private_data,
                           asyncio_register=asyncio_register,
                           strict=strict)
        sub_p = ffi.new("sr_subscription_ctx_t **")

        if asyncio_register:
            no_thread = True  # we manage our own event loop
        flags = _subscribe_flags(no_thread=no_thread)

        check_call(
            lib.sr_rpc_subscribe_tree,
            self.cdata,
            str2c(xpath),
            lib.srpy_rpc_tree_cb,
            sub.handle,
            priority,
            flags,
            sub_p,
        )
        sub.init(sub_p[0])

        self.subscriptions.append(sub)
Exemple #7
0
    def get_changes(self, xpath: str) -> Iterator[Change]:
        """
        Return an iterator that will yield all pending changes in the current session.

        :arg xpath:
            Xpath selecting the requested changes. Be careful, you must select all the
            changes, not just subtrees! To get a full change subtree `//.` can be
            appended to the XPath.

        :returns:
            An iterator that will yield `sysrepo.Change` objects.
        """
        iter_p = ffi.new("sr_change_iter_t **")

        check_call(lib.sr_get_changes_iter, self.cdata, str2c(xpath), iter_p)

        op_p = ffi.new("sr_change_oper_t *")
        old_p = ffi.new("sr_val_t **")
        new_p = ffi.new("sr_val_t **")

        try:
            ret = check_call(
                lib.sr_get_change_next,
                self.cdata,
                iter_p[0],
                op_p,
                old_p,
                new_p,
                valid_codes=(lib.SR_ERR_OK, lib.SR_ERR_NOT_FOUND),
            )
            while ret == lib.SR_ERR_OK:
                yield Change.parse(op_p[0], old=old_p[0], new=new_p[0])
                lib.sr_free_val(old_p[0])
                lib.sr_free_val(new_p[0])
                ret = check_call(
                    lib.sr_get_change_next,
                    self.cdata,
                    iter_p[0],
                    op_p,
                    old_p,
                    new_p,
                    valid_codes=(lib.SR_ERR_OK, lib.SR_ERR_NOT_FOUND),
                )
        finally:
            lib.sr_free_change_iter(iter_p[0])
Exemple #8
0
 def get_fd(self) -> int:
     """
     Get the event pipe of a subscription. Event pipe can be used in `select()`,
     `poll()`, or similar functions to listen for new events. It will then be ready
     for reading.
     """
     fd_p = ffi.new("int *")
     check_call(lib.sr_get_event_pipe, self.cdata, fd_p)
     return fd_p[0]
Exemple #9
0
    def start_session(self, datastore: str = "running") -> SysrepoSession:
        """
        Start a new session.

        :arg datastore:
            Datastore on which all sysrepo functions within this session will operate.
            Later on, datastore can be later changed using
            `SysrepoSession.switch_datastore`.

        :returns:
            A `SysrepoSession` object that can be used as a context manager. It will be
            automatically stopped when the manager exits::

                with conn.start_session() as sess:
                    # to stuff with sess
                # sess.stop() has been called whatever happens
        """
        ds = datastore_value(datastore)
        sess_p = ffi.new("sr_session_ctx_t **")
        check_call(lib.sr_session_start, self.cdata, ds, sess_p)
        return SysrepoSession(sess_p[0])
Exemple #10
0
    def get_item(self, xpath: str, timeout_ms: int = 0) -> Value:
        """
        Retrieve a single data element selected by the provided path.

        :arg xpath:
            Path of the data element to be retrieved.
        :arg timeout_ms:
            Operational callback timeout in milliseconds. If 0, default is used.

        :returns:
            A sysrepo.Value object.
        :raises SysrepoInvalArgError:
            If multiple nodes match the path.
        :raises SysrepoNotFoundError:
            If no nodes match the path.
        """
        val_p = ffi.new("sr_val_t **")
        check_call(lib.sr_get_item, self.cdata, str2c(xpath), timeout_ms,
                   val_p)
        try:
            return Value.parse(val_p[0])
        finally:
            lib.sr_free_val(val_p[0])
Exemple #11
0
def _get_error_msg(session) -> Optional[str]:
    """
    Get the error message information from the given session C pointer.

    :arg "sr_session_ctx_t *" session:
        A session C pointer allocated by libsysrepo.so.
    """
    msg = None
    err_info_p = ffi.new("sr_error_info_t **")
    if lib.sr_get_error(session, err_info_p) == lib.SR_ERR_OK:
        err_info = err_info_p[0]
        error_strings = []
        if err_info != ffi.NULL:
            for i in range(err_info.err_count):
                err = err_info.err[i]
                strings = []
                if err.xpath:
                    strings.append(c2str(err.xpath))
                if err.message:
                    strings.append(c2str(err.message))
                if strings:
                    error_strings.append(": ".join(strings))
        msg = ", ".join(error_strings)
    return msg
Exemple #12
0
    def get_data_ly(
        self,
        xpath: str,
        max_depth: int = 0,
        timeout_ms: int = 0,
        no_state: bool = False,
        no_config: bool = False,
        no_subs: bool = False,
        no_stored: bool = False,
    ) -> libyang.DNode:
        """
        Retrieve a tree whose root nodes match the provided XPath.

        Top-level trees are always returned so if an inner node is selected, all of its
        descendants and its direct parents (lists also with keys) are returned.

        If the subtree selection process results in too many node overlaps, the cost of
        the operation may be unnecessarily big. As an example, a common XPath expression
        `//.` is normally used to select all nodes in a data tree, but for this
        operation it would result in an excessive duplication of data nodes. Since all
        the descendants of each matched node are returned implicitly, `//` in the XPath
        should never be used (i.e. `/*` is the correct XPath for all the nodes).

        :arg xpath:
            Path selecting the root nodes of subtrees to be retrieved.
        :arg max_depth:
            Maximum depth of the selected subtrees. 0 is unlimited, 1 will not return
            any descendant nodes. If a list should be returned, its keys are always
            returned as well.
        :arg timeout_ms:
            Operational callback timeout in milliseconds. If 0, default is used.
        :arg no_state:
            Return only configuration data.
        :arg no_config:
            Return only state data. If there are some state subtrees with configuration
            these are also returned (with keys if lists).
        :arg no_subs:
            Return only stored operational data (push), do not call subscriber callbacks
            (pull).
        :arg no_stored:
            Do not merge with stored operational data (push).

        :returns:
            A libyang.DNode object with all the requested data, allocated dynamically.
            It must be freed manually by the caller with the libyang.DNode.free()
            method.
        :raises SysrepoNotFoundError:
            If no nodes match the path.
        """
        flags = _get_oper_flags(no_state=no_state,
                                no_config=no_config,
                                no_subs=no_subs,
                                no_stored=no_stored)
        if flags and lib.sr_session_get_ds(
                self.cdata) != lib.SR_DS_OPERATIONAL:
            raise ValueError(
                '"no_*" arguments are only valid for the "operational" datastore'
            )
        dnode_p = ffi.new("struct lyd_node **")
        check_call(
            lib.sr_get_data,
            self.cdata,
            str2c(xpath),
            max_depth,
            timeout_ms,
            flags,
            dnode_p,
        )
        if not dnode_p[0]:
            raise SysrepoNotFoundError(xpath)
        return libyang.DNode.new(self.get_ly_ctx(), dnode_p[0]).root()
Exemple #13
0
    def get_changes(
            self,
            xpath: str,
            include_implicit_defaults: bool = True) -> Iterator[Change]:
        """
        Return an iterator that will yield all pending changes in the current session.

        :arg xpath:
            Xpath selecting the requested changes. Be careful, you must select all the
            changes, not just subtrees! To get a full change subtree `//.` can be
            appended to the XPath.
        :arg include_implicit_defaults:
            Include implicit default nodes.

        :returns:
            An iterator that will yield `sysrepo.Change` objects.
        """
        iter_p = ffi.new("sr_change_iter_t **")

        check_call(lib.sr_get_changes_iter, self.cdata, str2c(xpath), iter_p)

        op_p = ffi.new("sr_change_oper_t *")
        node_p = ffi.new("struct lyd_node **")
        prev_val_p = ffi.new("char **")
        prev_list_p = ffi.new("char **")
        prev_dflt_p = ffi.new("bool *")
        ctx = self.get_ly_ctx()

        try:
            ret = check_call(
                lib.sr_get_change_tree_next,
                self.cdata,
                iter_p[0],
                op_p,
                node_p,
                prev_val_p,
                prev_list_p,
                prev_dflt_p,
                valid_codes=(lib.SR_ERR_OK, lib.SR_ERR_NOT_FOUND),
            )
            while ret == lib.SR_ERR_OK:
                try:
                    yield Change.parse(
                        operation=op_p[0],
                        node=libyang.DNode.new(ctx, node_p[0]),
                        prev_val=c2str(prev_val_p[0]),
                        prev_list=c2str(prev_list_p[0]),
                        prev_dflt=bool(prev_dflt_p[0]),
                        include_implicit_defaults=include_implicit_defaults,
                    )
                except Change.Skip:
                    pass
                ret = check_call(
                    lib.sr_get_change_tree_next,
                    self.cdata,
                    iter_p[0],
                    op_p,
                    node_p,
                    prev_val_p,
                    prev_list_p,
                    prev_dflt_p,
                    valid_codes=(lib.SR_ERR_OK, lib.SR_ERR_NOT_FOUND),
                )
        finally:
            lib.sr_free_change_iter(iter_p[0])
Exemple #14
0
    def subscribe_module_change(
            self,
            module: str,
            xpath: Optional[str],
            callback: ModuleChangeCallbackType,
            *,
            priority: int = 0,
            no_thread: bool = False,
            passive: bool = False,
            done_only: bool = False,
            enabled: bool = False,
            private_data: Any = None,
            asyncio_register: bool = False,
            include_implicit_defaults: bool = True) -> None:
        """
        Subscribe for changes made in the specified module.

        :arg module:
            Name of the module of interest for change notifications.
        :arg xpath:
            Optional xpath further filtering the changes that will be handled
            by this subscription.
        :arg callback:
            Callback to be called when the change in the datastore occurs.
        :arg priority:
            Specifies the order in which the callbacks (**within module**) will
            be called.
        :arg no_thread:
            There will be no thread created for handling this subscription
            meaning no event will be processed! Default to `True` if
            asyncio_register is `True`.
        :arg passive:
            The subscriber is not the "owner" of the subscribed data tree, just
            a passive watcher for changes.
        :arg done_only:
            The subscriber does not support verification of the changes and
            wants to be notified only after the changes has been applied in the
            datastore, without the possibility to deny them.
        :arg enabled:
            The subscriber wants to be notified about the current configuration
            at the moment of subscribing.
        :arg private_data:
            Private context passed to the callback function, opaque to sysrepo.
        :arg asyncio_register:
            Add the created subscription event pipe into asyncio event loop
            monitored read file descriptors. Implies `no_thread=True`.
        :arg include_implicit_defaults:
            Include implicit default nodes in changes.
        """
        if self.is_implicit:
            raise SysrepoUnsupportedError(
                "cannot subscribe with implicit sessions")
        _check_subscription_callback(callback, self.ModuleChangeCallbackType)

        sub = Subscription(
            callback,
            private_data,
            asyncio_register=asyncio_register,
            include_implicit_defaults=include_implicit_defaults,
        )
        sub_p = ffi.new("sr_subscription_ctx_t **")

        if asyncio_register:
            no_thread = True  # we manage our own event loop
        flags = _subscribe_flags(no_thread=no_thread,
                                 passive=passive,
                                 done_only=done_only,
                                 enabled=enabled)

        check_call(
            lib.sr_module_change_subscribe,
            self.cdata,
            str2c(module),
            str2c(xpath),
            lib.srpy_module_change_cb,
            sub.handle,
            priority,
            flags,
            sub_p,
        )
        sub.init(sub_p[0])

        self.subscriptions.append(sub)
Exemple #15
0
def str2c(s: Optional[str]):
    if s is None:
        return ffi.NULL
    if hasattr(s, "encode"):
        s = s.encode("utf-8")
    return ffi.new("char []", s)