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])
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))
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, )
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