Beispiel #1
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])
Beispiel #2
0
    def edit_batch_ly(self,
                      edit: libyang.DNode,
                      default_operation: str = "merge") -> None:
        """
        Provide a prepared edit data tree to be applied. These changes are
        applied only after calling apply_changes().

        :arg edit:
            Data tree holding the configuration. Similar semantics to
            https://tools.ietf.org/html/rfc6241#section-7.2. The given data tree is
            *NOT* spent and must be freed by the caller.
        :arg default_operation:
            Default operation for nodes without operation on themselves or any parent.
            Possible values are `merge`, `replace`, or `none`. See RFC 6241
            https://tools.ietf.org/html/rfc6241#page-39.
        """
        # libyang and sysrepo bindings are different, casting is required
        dnode = ffi.cast("struct lyd_node *", edit.cdata)
        check_call(lib.sr_edit_batch, self.cdata, dnode,
                   str2c(default_operation))
Beispiel #3
0
    def replace_config_ly(
        self,
        config: Optional[libyang.DNode],
        module_name: Optional[str],
        timeout_ms: int = 0,
        wait: bool = False,
    ) -> None:
        """
        Replace a datastore with the contents of a data tree.

        :arg config:
            Source data to replace the datastore. Is ALWAYS spent and cannot be further
            used by the application! Can be None to completely reset the configuration.
        :arg module_name:
            The module for which to replace the configuration.
        :arg timeout_ms:
            Configuration callback timeout in milliseconds. If 0, default is used.
        :arg wait:
            Whether to wait until all callbacks on all events are finished.

        :raises SysrepoError:
            If the operation failed.
        """
        if isinstance(config, libyang.DNode):
            # libyang and sysrepo bindings are different, casting is required
            dnode = ffi.cast("struct lyd_node *", config.cdata)
        elif config is None:
            dnode = ffi.NULL
        else:
            raise TypeError(
                "config must be either a libyang.DNode object or None")
        check_call(
            lib.sr_replace_config,
            self.cdata,
            str2c(module_name),
            dnode,
            timeout_ms,
            wait,
        )
Beispiel #4
0
def oper_data_callback(session, module, xpath, req_xpath, req_id, parent,
                       priv):
    """
    Callback to be called when operational data at the selected xpath are requested.

    :arg "sr_session_ctx_t *" session:
        Implicit session (do not stop).
    :arg "const char *" module:
        Name of the affected module.
    :arg "const char *" xpath:
        XPath identifying the subtree that is supposed to be provided, same as the one
        used for the subscription.
    :arg "const char *" req_xpath:
        XPath as requested by a client. Can be NULL.
    :arg "uint32_t" req_id:
        Request ID unique for the specific module name.
    :arg "struct lyd_node **" parent:
        Pointer to an existing parent of the requested nodes. Is NULL for top-level
        nodes. Callback is supposed to append the requested nodes to this data subtree
        and return either the original parent or a top-level node.
    :arg "void *" priv:
        Private context opaque to sysrepo. Contains a CFFI handle to the Subscription
        python object.

    :returns:
        User error code (sr_error_t).
    :raises:
        IMPORTANT: This function *CANNOT* raise any exception. The C callstack does not
        handle that well and when it happens the outcome is undetermined. Make sure to
        catch all errors and log them so they are not lost.
    """
    try:
        # convert C arguments to python objects.
        from .session import SysrepoSession  # circular import

        session = SysrepoSession(session, True)
        module = c2str(module)
        xpath = c2str(xpath)
        req_xpath = c2str(req_xpath)
        subscription = ffi.from_handle(priv)
        callback = subscription.callback
        private_data = subscription.private_data

        if is_async_func(callback):
            task_id = req_id

            if task_id not in subscription.tasks:
                task = subscription.loop.create_task(
                    callback(req_xpath, private_data))
                task.add_done_callback(
                    functools.partial(subscription.task_done, task_id, "oper"))
                subscription.tasks[task_id] = task

            task = subscription.tasks[task_id]

            if not task.done():
                return lib.SR_ERR_CALLBACK_SHELVE

            del subscription.tasks[task_id]

            oper_data = task.result()

        else:
            oper_data = callback(req_xpath, private_data)

        if isinstance(oper_data, dict):
            # convert oper_data to a libyang.DNode object
            ly_ctx = session.get_ly_ctx()
            dnode = ly_ctx.get_module(module).parse_data_dict(
                oper_data,
                data=True,
                strict=subscription.strict,
                validate=False)
            if dnode is not None:
                if parent[0]:
                    root = DNode.new(ly_ctx, parent[0]).root()
                    root.merge(dnode, destruct=True)
                else:
                    # The FFI bindings of libyang and sysrepo are different.
                    # Casting is required.
                    parent[0] = ffi.cast("struct lyd_node *", dnode.cdata)
        elif oper_data is not None:
            raise TypeError("bad return type from %s (expected dict or None)" %
                            callback)

        return lib.SR_ERR_OK

    except SysrepoError as e:
        if e.msg and isinstance(session, SysrepoSession) and isinstance(
                xpath, str):
            session.set_error(xpath, e.msg)
        return e.rc

    except BaseException as e:
        # ATTENTION: catch all exceptions!
        # including KeyboardInterrupt, CancelledError, etc.
        # We are in a C callback, we cannot let any error pass
        LOG.exception("%r callback failed", locals().get("callback", priv))
        if isinstance(session, SysrepoSession) and isinstance(xpath, str):
            session.set_error(xpath, str(e))
        return lib.SR_ERR_CALLBACK_FAILED