summaryrefslogtreecommitdiff
path: root/doc/developer/rust-dev.rst
diff options
context:
space:
mode:
authorChristian Hopps <chopps@labn.net>2025-02-26 21:13:47 +0000
committerChristian Hopps <chopps@labn.net>2025-03-03 12:14:22 +0000
commitb4000e0ad7db49275cf3f592df3ceeb35021c296 (patch)
treefd878567ad4e49aab9483c136ead4a77363ed8a2 /doc/developer/rust-dev.rst
parent38c8603ebe694e92ef223df3a2c0c4c7fef43c13 (diff)
rustlibd: rust daemon templatedev/rust-skel
Signed-off-by: Christian Hopps <chopps@labn.net>
Diffstat (limited to 'doc/developer/rust-dev.rst')
-rw-r--r--doc/developer/rust-dev.rst177
1 files changed, 177 insertions, 0 deletions
diff --git a/doc/developer/rust-dev.rst b/doc/developer/rust-dev.rst
new file mode 100644
index 0000000000..f06f4ba54f
--- /dev/null
+++ b/doc/developer/rust-dev.rst
@@ -0,0 +1,177 @@
+.. -*- coding: utf-8 -*-
+..
+.. SPDX-License-Identifier: GPL-2.0-or-later
+..
+.. February 26 2025, Christian Hopps <chopps@labn.net>
+..
+.. Copyright (c) 2025, LabN Consulting, L.L.C.
+..
+
+.. _rust_dev:
+
+Rust Development
+================
+
+Overview
+--------
+
+The FRR project has started adding support for daemons written in rust. The
+following sections document the infrastructure to support to-date. This is the
+initial approach of rust integration, we expect changes as best-practices within
+the community evolve.
+
+General Structure
+-----------------
+
+An example template of the general structure of a rust based daemon can be found
+in ``rustlib/`` sub-directory. The recommended structure so far is to use a C
+main file and function to drive initialization of the daemon calling out to rust
+at 3 critical points. The Rust code is then built as a static library and linked
+into the daemon. Rust bindings are built for ``libfrr`` and accessed through a
+c_shim sub-module. Here's the files and as of the time of this writing:
+
+.. code-block:: make
+
+rustlibd/
+ .gitignore
+ Cargo.toml.in
+ Makefile
+ README.org
+ build.rs.in
+ c_shim.rs
+ frrutil.rs (symlink)
+ rustlib_lib.rs
+ rustlib_main.c
+ sandbox.rs
+ subdir.am
+ wrapper.h.in
+
+:file:`frrutil.rs` is a symlink to :file:`../lib/frrutil.rs` kept here to keep
+various rust tools happy about files being inside or below the main source
+directory.
+
+
+NOTE: if you use a separate build dir (named `build` in the below example) and
+you want to have your development environment proper analyze code (e.g.,
+vs-code/emacs LSP mode) you should create an additional 2 symlinks and create a
+local :file:`Cargo.toml` file like so:
+
+.. code-block:: sh
+
+ cd frr/rustlibd
+ sed -e 's,@srcdir@/,,g' < Cargo.toml.in > Cargo.toml
+ ln -s ../build/rustlibd/build.rs .
+ ln -s ../build/rustlibd/wrapper.h .
+
+Logging
+-------
+
+FRR logging is transparently supported using some bridging code that connects
+the native rust ``tracing`` calls directly to the ``zlog`` functionality in FRR.
+The only thing you have to do is call the function :func:`bridge_rust_logging`
+at startup. This is already done for you in the `rustlibd` template :func:`main`
+if you started with that code.
+
+.. code-block:: rust
+
+ use tracing::{debug, info};
+
+ fn myrustfunc(sval: &str, uval: u32) {
+ debug!("Some DEBUG level output of str value: {}", sval);
+ info!("Some INFO level output of uint value: {}", uval);
+ }
+
+Northbound Integration
+----------------------
+
+Support for the FRR northbound callback system is handled through rust macros.
+These rust macros define C shims which then call your rust functions which will
+use natural rust types. The rust macros hide the unsafe and tricky conversion
+code. You put pointers to the generated C shim functions into the
+:struct:`frr_yang_module_info` structure.
+
+NOTE: Locking will probably be important as your callbacks will be called in the
+FRR event loop main thread and your rust code is probably running in it's own
+different thread (perhaps using the tokio async runtime as setup in the
+:file:`rustlibd` template).
+
+Here's an example of defining a handler for a config leave value `enable`:
+
+.. code-block:: C
+
+ const struct frr_yang_module_info frr_my_module_nb_info = {
+ .name = "frr-my-module",
+ .nodes = {
+ {
+ .xpath = "/frr-my-module:lib/bvalue",
+ .cbs = {
+ .modify = my_module_bvalue_modify_shim,
+ .destroy = my_module_bvalue_destroy_shim
+ }
+ },
+ ...
+
+.. code-block:: rust
+
+ use crate::{define_nb_destroy_shim, define_nb_modify_shim};
+
+ pub(crate) fn my_module_bvalue_modify(
+ event: NbEvent,
+ _node: &DataNodeRef,
+ ) -> Result<(), nb_error> {
+ debug!("RUST: bvalue modify: {}", event);
+ match event {
+ NbEvent::APPLY(_) => {
+ // handle the change to the `bvalue` leaf.
+ Ok(())
+ },
+ _ => Ok(()), // All other events just return Ok.
+ }
+ }
+
+ pub(crate) fn my_module_bvalue_destroy(
+ event: NbEvent,
+ _node: &DataNodeRef,
+ ) -> Result<(), nb_error> {
+ // handle the removal of the `bvalue` leaf.
+ // ...
+ }
+
+ define_nb_modify_shim!(
+ my_module_bvalue_modify_shim,
+ my_module_bvalue_modify);
+
+ define_nb_destroy_shim!(
+ my_module_bvalue_destroy_shim,
+ my_module_bvalue_destroy);
+
+
+CLI commands
+~~~~~~~~~~~~
+
+For CLI commands you should continue to write the DEFPY_YANG() calls in C which
+simply set your YANG config data base on the args to DEFPY_YANG(). The actual
+configuration will be handled in your rust based callbacks you defined for your
+YANG model that are describe above.
+
+Operational State
+~~~~~~~~~~~~~~~~~
+
+You have 2 choices with operation state. You can implement the operation state
+callbacks in rust and use the rust macros to bridge these to the
+:struct:`frr_yang_module_info` definition as you did with your config handlers, or you
+can keep your operational state in a ``yang-rs`` (i.e., ``libyang``) based tree.
+Here's an example of using the macros:
+
+If you choose to do the latter and save all your operational state in a
+``libyang`` :struct:`DataTree`, you only need to define 2 callback functions, a
+:func:`get_tree_locked()` function which returns the :struct:`DataTree` in a
+:struct:`MutexGuard` (i.e., a held lock), and an :func:`unlock_tree()` function
+which is passed back the :struct:`MutexGuard` object for unlocking. You use 2
+macros: :func:`define_nb_get_tree_locked`, and :func:`define_nb_unlock_tree` to
+create the C based shims to plug into your :struct:`frr_yang_module_info`
+structure.
+
+NOTE: As with config, locking will probably be important as your callbacks will
+be called in the FRR event loop main thread and your rust code is probably
+running in it's own different thread.