From 2b7d9532c8ab3590088aca0f22147224fce1df85 Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sat, 13 Jan 2024 22:09:18 +0200 Subject: [PATCH 1/9] lib: fix yang_lyd_trim_xpath We should traverse all top-level siblings, not only the first one. Signed-off-by: Igor Ryzhov --- lib/yang.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/yang.c b/lib/yang.c index 5b177f7fbcff..7d35fb0d3dde 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -1089,7 +1089,7 @@ LY_ERR yang_lyd_trim_xpath(struct lyd_node **root, const char *xpath) } return LY_SUCCESS; #else - struct lyd_node *node; + struct lyd_node *node, *sib; struct lyd_node **remove = NULL; struct ly_set *set = NULL; uint32_t i; @@ -1123,18 +1123,21 @@ LY_ERR yang_lyd_trim_xpath(struct lyd_node **root, const char *xpath) } darr_ensure_cap(remove, 128); - LYD_TREE_DFS_BEGIN (*root, node) { - /* - * If this is a direct matching node then include it's subtree - * which won't be marked and would otherwise be removed. - */ - if (node->priv == (void *)2) - LYD_TREE_DFS_continue = 1; - else if (!node->priv) { - *darr_append(remove) = node; - LYD_TREE_DFS_continue = 1; + LY_LIST_FOR(*root, sib) { + LYD_TREE_DFS_BEGIN (sib, node) { + /* + * If this is a direct matching node then include its + * subtree which won't be marked and would otherwise + * be removed. + */ + if (node->priv == (void *)2) + LYD_TREE_DFS_continue = 1; + else if (!node->priv) { + *darr_append(remove) = node; + LYD_TREE_DFS_continue = 1; + } + LYD_TREE_DFS_END(sib, node); } - LYD_TREE_DFS_END(*root, node); } darr_foreach_i (remove, i) { if (remove[i] == *root) From b507ad54b4baaacf796d79fbb9e6bfbe1f521405 Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sun, 14 Jan 2024 16:55:46 +0200 Subject: [PATCH 2/9] mgmtd: add option to specify netns as the vrf backend mgmtd has to know if netns is used as the vrf backend to correctly process interface names in northbound. Signed-off-by: Igor Ryzhov --- doc/user/zebra.rst | 3 ++- mgmtd/mgmt_main.c | 4 ++++ tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py | 1 + tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py | 1 + tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py | 2 +- tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py | 1 + 6 files changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst index 2b737c1a2f67..71201b42781d 100644 --- a/doc/user/zebra.rst +++ b/doc/user/zebra.rst @@ -50,7 +50,8 @@ Besides the common invocation options (:ref:`common-invocation-options`), the When *Zebra* starts with this option, the VRF backend is based on Linux network namespaces. That implies that all network namespaces discovered by ZEBRA will create an associated VRF. The other daemons will operate on the VRF - VRF defined by *Zebra*, as usual. + VRF defined by *Zebra*, as usual. If this option is specified when running + *Zebra*, one must also specify the same option for *mgmtd*. .. seealso:: :ref:`zebra-vrf` diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c index 72bb353b2095..0f2bdacdc56b 100644 --- a/mgmtd/mgmt_main.c +++ b/mgmtd/mgmt_main.c @@ -22,6 +22,7 @@ static const struct option longopts[] = { {"skip_runas", no_argument, NULL, 'S'}, {"no_zebra", no_argument, NULL, 'Z'}, {"socket_size", required_argument, NULL, 's'}, + {"vrfwnetns", no_argument, NULL, 'n'}, {0}}; static void mgmt_exit(int); @@ -237,6 +238,9 @@ int main(int argc, char **argv) case 's': buffer_size = atoi(optarg); break; + case 'n': + vrf_configure_backend(VRF_BACKEND_NETNS); + break; default: frr_help_exit(1); break; diff --git a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py index a532f3a2d7f3..9e5a68f0a313 100644 --- a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py +++ b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py @@ -84,6 +84,7 @@ def setup_module(mod): router.net.set_intf_netns(rname + "-eth2", ns, up=True) for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns") router.load_config( TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)), diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py index 7e4bcc8adaea..d9177b4d25d1 100644 --- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py @@ -132,6 +132,7 @@ def setup_module(mod): for rname, router in router_list.items(): if rname == "r1": + router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns") router.load_config( TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)), diff --git a/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py index 8457b752afaf..028bc353586b 100644 --- a/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py +++ b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py @@ -94,6 +94,7 @@ def setup_module(module): router.net.set_intf_netns("r1-eth0", ns, up=True) # run daemons + router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns") router.load_config( TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format("r1")), @@ -205,7 +206,6 @@ def test_bgp_vrf_netns(): if __name__ == "__main__": - args = ["-s"] + sys.argv[1:] ret = pytest.main(args) diff --git a/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py b/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py index 2716f63348c8..23eef8f5a633 100644 --- a/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py +++ b/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py @@ -87,6 +87,7 @@ def setup_module(mod): router.net.set_intf_netns(rname + "-eth0", ns, up=True) router.net.set_intf_netns(rname + "-eth1", ns, up=True) + router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns") router.load_config( TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)), From 88275d392ab781961f3bfa1ab773a73063b4e6bc Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sat, 13 Jan 2024 22:26:15 +0200 Subject: [PATCH 3/9] vtysh, mgmtd: send interface commands to mgmtd Signed-off-by: Igor Ryzhov --- mgmtd/mgmt_main.c | 3 +++ vtysh/vtysh.h | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c index 0f2bdacdc56b..743091e5c47b 100644 --- a/mgmtd/mgmt_main.c +++ b/mgmtd/mgmt_main.c @@ -253,6 +253,9 @@ int main(int argc, char **argv) /* VRF commands initialization. */ vrf_cmd_init(NULL); + /* Interface commands initialization. */ + if_cmd_init(NULL); + /* MGMTD related initialization. */ mgmt_init(); diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index 65733ca61b4c..11751d027e89 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -56,9 +56,9 @@ extern struct event_loop *master; VTYSH_ZEBRA | VTYSH_RIPD | VTYSH_RIPNGD | VTYSH_OSPFD | VTYSH_OSPF6D | \ VTYSH_ISISD | VTYSH_PIMD | VTYSH_PIM6D | VTYSH_NHRPD | \ VTYSH_EIGRPD | VTYSH_BABELD | VTYSH_PBRD | VTYSH_FABRICD | \ - VTYSH_VRRPD + VTYSH_VRRPD | VTYSH_MGMTD #define VTYSH_INTERFACE VTYSH_INTERFACE_SUBSET | VTYSH_BGPD -#define VTYSH_VRF VTYSH_INTERFACE_SUBSET | VTYSH_MGMTD +#define VTYSH_VRF VTYSH_INTERFACE_SUBSET #define VTYSH_KEYS VTYSH_RIPD | VTYSH_EIGRPD | VTYSH_OSPF6D | VTYSH_OSPFD /* Daemons who can process nexthop-group configs */ #define VTYSH_NH_GROUP VTYSH_PBRD|VTYSH_SHARPD From 25d79af9578ba339b105c3d65c9706ae17419527 Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sat, 13 Jan 2024 22:34:17 +0200 Subject: [PATCH 4/9] lib, mgmtd: add separate get-data request for the frontend Currently it's the same as get-tree request for the backend, but it is going to be expanded in the following commits. Signed-off-by: Igor Ryzhov --- lib/mgmt_fe_client.c | 14 +++++++------- lib/mgmt_fe_client.h | 4 ++-- lib/mgmt_msg_native.c | 1 + lib/mgmt_msg_native.h | 21 ++++++++++++++++++++- lib/vty.c | 10 +++++----- lib/vty.h | 2 +- mgmtd/mgmt_fe_adapter.c | 12 ++++++------ mgmtd/mgmt_vty.c | 2 +- 8 files changed, 43 insertions(+), 23 deletions(-) diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index 0bea66300449..16559c61031a 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -306,25 +306,25 @@ int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, } /* - * Send get-tree request. + * Send get-data request. */ -int mgmt_fe_send_get_tree_req(struct mgmt_fe_client *client, +int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, LYD_FORMAT result_type, const char *xpath) { - struct mgmt_msg_get_tree *msg; + struct mgmt_msg_get_data *msg; size_t xplen = strlen(xpath); int ret; - msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, xplen + 1, - MTYPE_MSG_NATIVE_GET_TREE); + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_data, xplen + 1, + MTYPE_MSG_NATIVE_GET_DATA); msg->refer_id = session_id; msg->req_id = req_id; - msg->code = MGMT_MSG_CODE_GET_TREE; + msg->code = MGMT_MSG_CODE_GET_DATA; msg->result_type = result_type; strlcpy(msg->xpath, xpath, xplen + 1); - MGMTD_FE_CLIENT_DBG("Sending GET_TREE_REQ session-id %" PRIu64 + MGMTD_FE_CLIENT_DBG("Sending GET_DATA_REQ session-id %" PRIu64 " req-id %" PRIu64 " xpath: %s", session_id, req_id, xpath); diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h index f3292d18fda1..95e73ca59478 100644 --- a/lib/mgmt_fe_client.h +++ b/lib/mgmt_fe_client.h @@ -367,7 +367,7 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, int num_reqs); /* - * Send GET-TREE to MGMTD daemon. + * Send GET-DATA to MGMTD daemon. * * client * Client object. @@ -387,7 +387,7 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, * Returns: * 0 on success, otherwise msg_conn_send_msg() return values. */ -extern int mgmt_fe_send_get_tree_req(struct mgmt_fe_client *client, +extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, LYD_FORMAT result_type, const char *xpath); diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c index b6dc126d49c8..a9b26718dbfe 100644 --- a/lib/mgmt_msg_native.c +++ b/lib/mgmt_msg_native.c @@ -13,6 +13,7 @@ DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_MSG, "native mgmt msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_ERROR, "native error msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_TREE, "native get tree msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_TREE_DATA, "native tree data msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg"); int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, uint64_t req_id, bool short_circuit_ok, diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h index 3f6283025c3e..93ff6f012c92 100644 --- a/lib/mgmt_msg_native.h +++ b/lib/mgmt_msg_native.h @@ -142,6 +142,7 @@ DECLARE_MTYPE(MSG_NATIVE_MSG); DECLARE_MTYPE(MSG_NATIVE_ERROR); DECLARE_MTYPE(MSG_NATIVE_GET_TREE); DECLARE_MTYPE(MSG_NATIVE_TREE_DATA); +DECLARE_MTYPE(MSG_NATIVE_GET_DATA); /* * Native message codes @@ -149,6 +150,7 @@ DECLARE_MTYPE(MSG_NATIVE_TREE_DATA); #define MGMT_MSG_CODE_ERROR 0 #define MGMT_MSG_CODE_GET_TREE 1 #define MGMT_MSG_CODE_TREE_DATA 2 +#define MGMT_MSG_CODE_GET_DATA 3 /** * struct mgmt_msg_header - Header common to all native messages. @@ -193,7 +195,7 @@ _Static_assert(sizeof(struct mgmt_msg_error) == "Size mismatch"); /** - * struct mgmt_msg_get_tree - Message carrying xpath query request. + * struct mgmt_msg_get_tree - backend oper data request. * * @result_type: ``LYD_FORMAT`` for the returned result. * @xpath: the query for the data to return. @@ -231,6 +233,23 @@ _Static_assert(sizeof(struct mgmt_msg_tree_data) == offsetof(struct mgmt_msg_tree_data, result), "Size mismatch"); +/** + * struct mgmt_msg_get_data - frontend get-data request. + * + * @result_type: ``LYD_FORMAT`` for the returned result. + * @xpath: the query for the data to return. + */ +struct mgmt_msg_get_data { + struct mgmt_msg_header; + uint8_t result_type; + uint8_t resv2[7]; + + alignas(8) char xpath[]; +}; +_Static_assert(sizeof(struct mgmt_msg_get_data) == + offsetof(struct mgmt_msg_get_data, xpath), + "Size mismatch"); + #define MGMT_MSG_VALIDATE_NUL_TERM(msgp, len) \ ((len) >= sizeof(*msg) + 1 && ((char *)msgp)[(len)-1] == 0) diff --git a/lib/vty.c b/lib/vty.c index 5f9f0dc2435b..f1b7d621b720 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -4105,23 +4105,23 @@ int vty_mgmt_send_get_req(struct vty *vty, bool is_config, return 0; } -int vty_mgmt_send_get_tree_req(struct vty *vty, LYD_FORMAT result_type, +int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type, const char *xpath) { LYD_FORMAT intern_format = result_type; vty->mgmt_req_id++; - if (mgmt_fe_send_get_tree_req(mgmt_fe_client, vty->mgmt_session_id, + if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, intern_format, xpath)) { - zlog_err("Failed to send GET-TREE to MGMTD session-id: %" PRIu64 + zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".", vty->mgmt_session_id, vty->mgmt_req_id); - vty_out(vty, "Failed to send GET-TREE to MGMTD!\n"); + vty_out(vty, "Failed to send GET-DATA to MGMTD!\n"); return -1; } - vty->mgmt_req_pending_cmd = "MESSAGE_GET_TREE_REQ"; + vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ"; vty->mgmt_req_pending_data = result_type; return 0; diff --git a/lib/vty.h b/lib/vty.h index 5866eccde04c..044e6433d177 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -420,7 +420,7 @@ extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config, Mgmtd__DatastoreId datastore, const char **xpath_list, int num_req); -extern int vty_mgmt_send_get_tree_req(struct vty *vty, LYD_FORMAT result_type, +extern int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type, const char *xpath); extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, bool lock, bool scok); diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index a69d27fc5c58..6527677d3185 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -1132,15 +1132,15 @@ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, } /** - * fe_adapter_handle_get_tree() - Handle a get-tree message from a FE client. + * fe_adapter_handle_get_data() - Handle a get-tree message from a FE client. * @session: the client session. * @msg_raw: the message data. * @msg_len: the length of the message data. */ -static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session, +static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void *__msg, size_t msg_len) { - struct mgmt_msg_get_tree *msg = __msg; + struct mgmt_msg_get_data *msg = __msg; struct lysc_node **snodes = NULL; char *xpath_resolved = NULL; uint64_t req_id = msg->req_id; @@ -1149,7 +1149,7 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session, LY_ERR err; int ret; - MGMTD_FE_ADAPTER_DBG("Received get-tree request from client %s for session-id %" PRIu64 + MGMTD_FE_ADAPTER_DBG("Received get-data request from client %s for session-id %" PRIu64 " req-id %" PRIu64, session->adapter->name, session->session_id, msg->req_id); @@ -1238,8 +1238,8 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, assert(session->adapter == adapter); switch (msg->code) { - case MGMT_MSG_CODE_GET_TREE: - fe_adapter_handle_get_tree(session, msg, msg_len); + case MGMT_MSG_CODE_GET_DATA: + fe_adapter_handle_get_data(session, msg, msg_len); break; default: MGMTD_FE_ADAPTER_ERR("unknown native message session-id %" PRIu64 diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 2591930e4a89..98e55788b435 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -272,7 +272,7 @@ DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, path = xpath; } - vty_mgmt_send_get_tree_req(vty, format, path); + vty_mgmt_send_get_data_req(vty, format, path); if (xpath) XFREE(MTYPE_TMP, xpath); From e1cdb38ee61bcd05b9648d4d60007336e5cffff4 Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sat, 13 Jan 2024 22:51:45 +0200 Subject: [PATCH 5/9] lib, mgmtd: add ability to set content type in get-data request Like in RESTCONF GET request and NETCONF get-data request, make it possible to request state-only, config-only, or all data. Signed-off-by: Igor Ryzhov --- lib/mgmt_fe_client.c | 7 +++--- lib/mgmt_fe_client.h | 7 +++++- lib/mgmt_msg_native.h | 8 +++++- lib/vty.c | 5 ++-- lib/vty.h | 2 +- mgmtd/mgmt_fe_adapter.c | 6 ++--- mgmtd/mgmt_txn.c | 54 ++++++++++++++++++++++++++++++++++++++++- mgmtd/mgmt_txn.h | 4 ++- mgmtd/mgmt_vty.c | 10 ++++++-- 9 files changed, 88 insertions(+), 15 deletions(-) diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index 16559c61031a..57ac071ecf5a 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -308,9 +308,9 @@ int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, /* * Send get-data request. */ -int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, - uint64_t session_id, uint64_t req_id, - LYD_FORMAT result_type, const char *xpath) +int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, LYD_FORMAT result_type, + uint8_t flags, const char *xpath) { struct mgmt_msg_get_data *msg; size_t xplen = strlen(xpath); @@ -322,6 +322,7 @@ int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, msg->req_id = req_id; msg->code = MGMT_MSG_CODE_GET_DATA; msg->result_type = result_type; + msg->flags = flags; strlcpy(msg->xpath, xpath, xplen + 1); MGMTD_FE_CLIENT_DBG("Sending GET_DATA_REQ session-id %" PRIu64 diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h index 95e73ca59478..3abe29b1cf39 100644 --- a/lib/mgmt_fe_client.h +++ b/lib/mgmt_fe_client.h @@ -15,6 +15,7 @@ extern "C" { #include "mgmt_pb.h" #include "frrevent.h" #include "mgmt_defines.h" +#include "mgmt_msg_native.h" /*************************************************************** * Macros @@ -381,6 +382,9 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, * result_type * The LYD_FORMAT of the result. * + * flags + * Flags to control the behavior of the request. + * * xpath * the xpath to get. * @@ -389,7 +393,8 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, */ extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, - LYD_FORMAT result_type, const char *xpath); + LYD_FORMAT result_type, uint8_t flags, + const char *xpath); /* * Destroy library and cleanup everything. diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h index 93ff6f012c92..88c1065fc84e 100644 --- a/lib/mgmt_msg_native.h +++ b/lib/mgmt_msg_native.h @@ -233,16 +233,22 @@ _Static_assert(sizeof(struct mgmt_msg_tree_data) == offsetof(struct mgmt_msg_tree_data, result), "Size mismatch"); +/* Flags for get-data request */ +#define GET_DATA_FLAG_STATE 0x01 /* get only "config false" data */ +#define GET_DATA_FLAG_CONFIG 0x02 /* get only "config true" data */ + /** * struct mgmt_msg_get_data - frontend get-data request. * * @result_type: ``LYD_FORMAT`` for the returned result. + * @flags: combination of ``GET_DATA_FLAG_*`` flags. * @xpath: the query for the data to return. */ struct mgmt_msg_get_data { struct mgmt_msg_header; uint8_t result_type; - uint8_t resv2[7]; + uint8_t flags; + uint8_t resv2[6]; alignas(8) char xpath[]; }; diff --git a/lib/vty.c b/lib/vty.c index f1b7d621b720..3fc7c380830e 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -4106,14 +4106,15 @@ int vty_mgmt_send_get_req(struct vty *vty, bool is_config, } int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type, - const char *xpath) + uint8_t flags, const char *xpath) { LYD_FORMAT intern_format = result_type; vty->mgmt_req_id++; if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id, - vty->mgmt_req_id, intern_format, xpath)) { + vty->mgmt_req_id, intern_format, flags, + xpath)) { zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".", vty->mgmt_session_id, vty->mgmt_req_id); diff --git a/lib/vty.h b/lib/vty.h index 044e6433d177..73e0d238ad1d 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -421,7 +421,7 @@ extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config, Mgmtd__DatastoreId datastore, const char **xpath_list, int num_req); extern int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type, - const char *xpath); + uint8_t flags, const char *xpath); extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, bool lock, bool scok); extern void vty_mgmt_resume_response(struct vty *vty, int ret); diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index 6527677d3185..d91987d8884f 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -1181,7 +1181,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, darr_free(snodes); clients = mgmt_be_interested_clients(msg->xpath, false); - if (!clients) { + if (!clients && !CHECK_FLAG(msg->flags, GET_DATA_FLAG_CONFIG)) { MGMTD_FE_ADAPTER_DBG("No backends provide xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64, msg->xpath, session->txn_id, @@ -1207,8 +1207,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, /* Create a GET-TREE request under the transaction */ ret = mgmt_txn_send_get_tree_oper(session->txn_id, req_id, clients, - msg->result_type, simple_xpath, - msg->xpath); + msg->result_type, msg->flags, + simple_xpath, msg->xpath); if (ret) { /* destroy the just created txn */ mgmt_destroy_txn(&session->txn_id); diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 679eaa7d7fd8..3a2a23d9e9d8 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -2364,7 +2364,8 @@ int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, */ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, uint64_t clients, LYD_FORMAT result_type, - bool simple_xpath, const char *xpath) + uint8_t flags, bool simple_xpath, + const char *xpath) { struct mgmt_msg_get_tree *msg; struct mgmt_txn_ctx *txn; @@ -2385,6 +2386,57 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, get_tree->simple_xpath = simple_xpath; get_tree->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); + if (CHECK_FLAG(flags, GET_DATA_FLAG_CONFIG)) { + struct mgmt_ds_ctx *ds = + mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); + struct nb_config *config = mgmt_ds_get_nb_config(ds); + + if (config) { + struct ly_set *set = NULL; + LY_ERR err; + + err = lyd_find_xpath(config->dnode, xpath, &set); + if (err) { + get_tree->partial_error = err; + goto state; + } + + /* + * If there's a single result, duplicate the returned + * node. If there are multiple results, duplicate the + * whole config and mark simple_xpath as false so the + * result is trimmed later in txn_get_tree_data_done. + */ + if (set->count == 1) { + err = lyd_dup_single(set->dnodes[0], NULL, + LYD_DUP_WITH_PARENTS | + LYD_DUP_WITH_FLAGS | + LYD_DUP_RECURSIVE, + &get_tree->client_results); + if (!err) + while (get_tree->client_results->parent) + get_tree->client_results = lyd_parent( + get_tree->client_results); + } else if (set->count > 1) { + err = lyd_dup_siblings(config->dnode, NULL, + LYD_DUP_RECURSIVE | + LYD_DUP_WITH_FLAGS, + &get_tree->client_results); + if (!err) + get_tree->simple_xpath = false; + } + + if (err) + get_tree->partial_error = err; + + ly_set_free(set, NULL); + } + } +state: + /* If we are only getting config, we are done */ + if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || !clients) + return txn_get_tree_data_done(txn, txn_req); + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1, MTYPE_MSG_NATIVE_GET_TREE); msg->refer_id = txn_id; diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h index 39d8cde16953..3f27f2f07b78 100644 --- a/mgmtd/mgmt_txn.h +++ b/mgmtd/mgmt_txn.h @@ -203,6 +203,7 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, * req_id: FE client request identifier. * clients: Bitmask of clients to send get-tree to. * result_type: LYD_FORMAT result format. + * flags: option flags for the request. * simple_xpath: true if xpath is simple (only key predicates). * xpath: The xpath to get the tree from. * @@ -211,7 +212,8 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, */ extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, uint64_t clients, LYD_FORMAT result_type, - bool simple_xpath, const char *xpath); + uint8_t flags, bool simple_xpath, + const char *xpath); /* * Notifiy backend adapter on connection. diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 98e55788b435..e6f74c31290e 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -251,17 +251,23 @@ DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd, } DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, - "show mgmt get-data WORD$path [json|xml]$fmt", + "show mgmt get-data WORD$path [with-config|only-config]$content [json|xml]$fmt", SHOW_STR MGMTD_STR "Get a data from the operational datastore\n" "XPath expression specifying the YANG data root\n" + "Include \"config true\" data\n" + "Get only \"config true\" data\n" "JSON output format\n" "XML output format\n") { LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; int plen = strlen(path); char *xpath = NULL; + uint8_t flags = content ? GET_DATA_FLAG_CONFIG : GET_DATA_FLAG_STATE; + + if (content && content[0] == 'w') + flags |= GET_DATA_FLAG_STATE; /* get rid of extraneous trailing slash-* or single '/' unless root */ if (plen > 2 && ((path[plen - 2] == '/' && path[plen - 1] == '*') || @@ -272,7 +278,7 @@ DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, path = xpath; } - vty_mgmt_send_get_data_req(vty, format, path); + vty_mgmt_send_get_data_req(vty, format, flags, path); if (xpath) XFREE(MTYPE_TMP, xpath); From 05c6081099d1e25e35ab57efb6c2c42d76cab44c Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sat, 13 Jan 2024 22:58:19 +0200 Subject: [PATCH 6/9] tests: add tests for mgmt get-data with config Signed-off-by: Igor Ryzhov --- tests/topotests/mgmt_oper/oper.py | 4 +-- tests/topotests/mgmt_oper/r1/frr-simple.conf | 4 ++- .../result-intf-description.json | 14 +++++++++++ .../result-intf-eth0-only-config.json | 10 ++++++++ .../result-intf-eth0-with-config.json | 25 +++++++++++++++++++ tests/topotests/mgmt_oper/test_simple.py | 15 +++++++++++ 6 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 tests/topotests/mgmt_oper/simple-results/result-intf-description.json create mode 100644 tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json create mode 100644 tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json diff --git a/tests/topotests/mgmt_oper/oper.py b/tests/topotests/mgmt_oper/oper.py index e3386067bc32..0f6c3cdf5f1e 100644 --- a/tests/topotests/mgmt_oper/oper.py +++ b/tests/topotests/mgmt_oper/oper.py @@ -66,7 +66,7 @@ def do_oper_test(tgen, query_results): r1 = tgen.gears["r1"].net qcmd = ( - r"vtysh -c 'show mgmt get-data {}' " + r"vtysh -c 'show mgmt get-data {} {}' " r"""| sed -e 's/"phy-address": ".*"/"phy-address": "rubout"/'""" r"""| sed -e 's/"uptime": ".*"/"uptime": "rubout"/'""" r"""| sed -e 's/"vrf": "[0-9]*"/"vrf": "rubout"/'""" @@ -81,7 +81,7 @@ def do_oper_test(tgen, query_results): if doreset: doreset = False expected = open(qr[1], encoding="ascii").read() - output = r1.cmd_nostatus(qcmd.format(qr[0])) + output = r1.cmd_nostatus(qcmd.format(qr[0], qr[2] if len(qr) > 2 else "")) try: ojson = json.loads(output) diff --git a/tests/topotests/mgmt_oper/r1/frr-simple.conf b/tests/topotests/mgmt_oper/r1/frr-simple.conf index cf8ba160f4b7..d262afe35945 100644 --- a/tests/topotests/mgmt_oper/r1/frr-simple.conf +++ b/tests/topotests/mgmt_oper/r1/frr-simple.conf @@ -14,10 +14,12 @@ debug mgmt client backend interface r1-eth0 ip address 1.1.1.1/24 + description r1-eth0-desc exit interface r1-eth1 vrf red ip address 3.3.3.1/24 + description r1-eth1-desc exit ip route 11.11.11.11/32 1.1.1.2 -!ip route 13.13.13.13/32 3.3.3.2 vrf red \ No newline at end of file +!ip route 13.13.13.13/32 3.3.3.2 vrf red diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-description.json b/tests/topotests/mgmt_oper/simple-results/result-intf-description.json new file mode 100644 index 000000000000..8f8092ec1a55 --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-description.json @@ -0,0 +1,14 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "r1-eth0", + "description": "r1-eth0-desc" + }, + { + "name": "r1-eth1", + "description": "r1-eth1-desc" + } + ] + } +} diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json new file mode 100644 index 000000000000..adcf99053fca --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json @@ -0,0 +1,10 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "r1-eth0", + "description": "r1-eth0-desc" + } + ] + } +} diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json new file mode 100644 index 000000000000..47c96862522f --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json @@ -0,0 +1,25 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "r1-eth0", + "vrf": "default", + "description": "r1-eth0-desc", + "state": { + "if-index": "rubout", + "mtu": 1500, + "mtu6": 1500, + "speed": 10000, + "metric": 0, + "phy-address": "rubout" + }, + "frr-zebra:zebra": { + "state": { + "up-count": 0, + "down-count": 0 + } + } + } + ] + } +} diff --git a/tests/topotests/mgmt_oper/test_simple.py b/tests/topotests/mgmt_oper/test_simple.py index 008733ee724c..2f75dfc822f2 100644 --- a/tests/topotests/mgmt_oper/test_simple.py +++ b/tests/topotests/mgmt_oper/test_simple.py @@ -112,6 +112,21 @@ def test_oper_simple(tgen): 'route[prefix="1.1.1.0/24"]/route-entry[protocol="connected"]/metric', "simple-results/result-singleton-metric.json", ), + ( + '/frr-interface:lib/interface[name="r1-eth0"]', + "simple-results/result-intf-eth0-with-config.json", + "with-config", + ), + ( + '/frr-interface:lib/interface[name="r1-eth0"]', + "simple-results/result-intf-eth0-only-config.json", + "only-config", + ), + ( + "/frr-interface:lib/interface/description", + "simple-results/result-intf-description.json", + "with-config", + ), # Interface state ( '/frr-interface:lib/interface[name="r1-eth0"]/state', From 10eac0a54dd943cabe43c59bce3f880b2e43b367 Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sun, 14 Jan 2024 00:41:54 +0200 Subject: [PATCH 7/9] lib: fix oper data leaf creation When creating an initial tree trunk for oper data walk, if the xpath represents a leaf, the leaf is created with an incorrect empty value. If it doesn't actually exist in daemon's oper data, its value is not overwritten later and an empty value is returned in the result. For example, when requesting `/frr-interface:lib/interface[name='eth0']/description`, the result is: ``` { "frr-interface:lib": { "interface": [ { "name": "eth0", "description": "" } ] } } ``` instead of an empty JSON that it should be. Signed-off-by: Igor Ryzhov --- lib/northbound_oper.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c index afce77318172..2394b5e86581 100644 --- a/lib/northbound_oper.c +++ b/lib/northbound_oper.c @@ -515,8 +515,18 @@ static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys) /* Move up to the container if on a leaf currently. */ if (node && - !CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)) + !CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)) { + struct lyd_node *leaf = node; + node = &node->parent->node; + + /* + * If the leaf is not a key, delete it, because it has a wrong + * empty value. + */ + if (!lysc_is_key(leaf->schema)) + lyd_free_tree(leaf); + } assert(!node || CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)); if (!node) From 9859f308d23bf12a7f454ae65dada6d90f4c31db Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sun, 14 Jan 2024 00:53:21 +0200 Subject: [PATCH 8/9] lib, mgmtd: add ability to request the exact node in get-data request RESTCONF expects to receive the exact node as a result, not the whole data tree. Signed-off-by: Igor Ryzhov --- lib/mgmt_msg_native.h | 1 + mgmtd/mgmt_txn.c | 10 +++++++++- mgmtd/mgmt_vty.c | 6 +++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h index 88c1065fc84e..069cb9b15075 100644 --- a/lib/mgmt_msg_native.h +++ b/lib/mgmt_msg_native.h @@ -236,6 +236,7 @@ _Static_assert(sizeof(struct mgmt_msg_tree_data) == /* Flags for get-data request */ #define GET_DATA_FLAG_STATE 0x01 /* get only "config false" data */ #define GET_DATA_FLAG_CONFIG 0x02 /* get only "config true" data */ +#define GET_DATA_FLAG_EXACT 0x04 /* get exact data node instead of the full tree */ /** * struct mgmt_msg_get_data - frontend get-data request. diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 3a2a23d9e9d8..842e13cf117b 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -176,6 +176,7 @@ struct txn_req_get_tree { uint64_t recv_clients; /* Bitmask of clients recv reply from */ int32_t partial_error; /* an error while gather results */ uint8_t result_type; /* LYD_FORMAT for results */ + uint8_t exact; /* if exact node is requested */ uint8_t simple_xpath; /* if xpath is simple */ struct lyd_node *client_results; /* result tree from clients */ }; @@ -1258,6 +1259,7 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, { struct txn_req_get_tree *get_tree = txn_req->req.get_tree; uint64_t req_id = txn_req->req_id; + struct lyd_node *result; int ret = NB_OK; /* cancel timer and send reply onward */ @@ -1272,12 +1274,17 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, ret = NB_ERR; } + result = get_tree->client_results; + + if (ret == NB_OK && result && get_tree->exact) + result = yang_dnode_get(result, get_tree->xpath); + if (ret == NB_OK) ret = mgmt_fe_adapter_send_tree_data(txn->session_id, txn->txn_id, txn_req->req_id, get_tree->result_type, - get_tree->client_results, + result, get_tree->partial_error, false); @@ -2383,6 +2390,7 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE); get_tree = txn_req->req.get_tree; get_tree->result_type = result_type; + get_tree->exact = CHECK_FLAG(flags, GET_DATA_FLAG_EXACT); get_tree->simple_xpath = simple_xpath; get_tree->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index e6f74c31290e..f4b24acf3ace 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -251,13 +251,14 @@ DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd, } DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, - "show mgmt get-data WORD$path [with-config|only-config]$content [json|xml]$fmt", + "show mgmt get-data WORD$path [with-config|only-config]$content [exact]$exact [json|xml]$fmt", SHOW_STR MGMTD_STR "Get a data from the operational datastore\n" "XPath expression specifying the YANG data root\n" "Include \"config true\" data\n" "Get only \"config true\" data\n" + "Get exact node instead of the whole data tree\n" "JSON output format\n" "XML output format\n") { @@ -269,6 +270,9 @@ DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, if (content && content[0] == 'w') flags |= GET_DATA_FLAG_STATE; + if (exact) + flags |= GET_DATA_FLAG_EXACT; + /* get rid of extraneous trailing slash-* or single '/' unless root */ if (plen > 2 && ((path[plen - 2] == '/' && path[plen - 1] == '*') || (path[plen - 2] != '/' && path[plen - 1] == '/'))) { From 2764344bcbd37c5ea69231d6e51ba9cd58edbb44 Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Sun, 14 Jan 2024 00:54:08 +0200 Subject: [PATCH 9/9] tests: add tests for mgmt get-data exact node request Signed-off-by: Igor Ryzhov --- .../result-intf-eth0-description-exact.json | 3 +++ .../result-intf-eth0-exact.json | 22 +++++++++++++++++++ tests/topotests/mgmt_oper/test_simple.py | 10 +++++++++ 3 files changed, 35 insertions(+) create mode 100644 tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json create mode 100644 tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json new file mode 100644 index 000000000000..e00f23c3ddee --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json @@ -0,0 +1,3 @@ +{ + "frr-interface:description": "r1-eth0-desc" +} diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json new file mode 100644 index 000000000000..f04e3a55e1b2 --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json @@ -0,0 +1,22 @@ +{ + "frr-interface:interface": [ + { + "name": "r1-eth0", + "vrf": "default", + "state": { + "if-index": "rubout", + "mtu": 1500, + "mtu6": 1500, + "speed": 10000, + "metric": 0, + "phy-address": "rubout" + }, + "frr-zebra:zebra": { + "state": { + "up-count": 0, + "down-count": 0 + } + } + } + ] +} diff --git a/tests/topotests/mgmt_oper/test_simple.py b/tests/topotests/mgmt_oper/test_simple.py index 2f75dfc822f2..a52d125ecfa4 100644 --- a/tests/topotests/mgmt_oper/test_simple.py +++ b/tests/topotests/mgmt_oper/test_simple.py @@ -127,6 +127,16 @@ def test_oper_simple(tgen): "simple-results/result-intf-description.json", "with-config", ), + ( + '/frr-interface:lib/interface[name="r1-eth0"]', + "simple-results/result-intf-eth0-exact.json", + "exact", + ), + ( + '/frr-interface:lib/interface[name="r1-eth0"]/description', + "simple-results/result-intf-eth0-description-exact.json", + "with-config exact", + ), # Interface state ( '/frr-interface:lib/interface[name="r1-eth0"]/state',