diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-01-21 20:46:15 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-21 20:46:15 +0100 |
| commit | 7550fb68071fb141730361c4ea94061a5c73b4fc (patch) | |
| tree | eca8edb91e1dba708cf9369eb757d06f75d18fb1 /scripts/package-build | |
| parent | fef0fd0377d38626f44bd7482b042af42ee6f57e (diff) | |
| parent | 93d32a8e83a3d91abb8e990dc84a99ab80895dc4 (diff) | |
| download | vyos-build-7550fb68071fb141730361c4ea94061a5c73b4fc.tar.gz vyos-build-7550fb68071fb141730361c4ea94061a5c73b4fc.zip | |
Merge pull request #1090 from hedrok/T7664-upgrade-frr-10.5
T7664: Upgrade frr 10.5
Diffstat (limited to 'scripts/package-build')
12 files changed, 12294 insertions, 496 deletions
diff --git a/scripts/package-build/frr/package.toml b/scripts/package-build/frr/package.toml index 669e64b8..247228d5 100644 --- a/scripts/package-build/frr/package.toml +++ b/scripts/package-build/frr/package.toml @@ -1,12 +1,12 @@ [[packages]] name = "libyang" -commit_id = "v3.4.2" +commit_id = "v3.13.6" scm_url = "https://github.com/CESNET/libyang.git" build_cmd = "pipx run apkg build -i && find pkg/pkgs -type f -name *.deb -exec mv -t .. {} +" [[packages]] name = "frr" -commit_id = "stable/10.2" +commit_id = "stable/10.5" scm_url = "https://github.com/FRRouting/frr.git" build_cmd = "sudo dpkg -i ../*.deb; dpkg-buildpackage -us -uc -tc -b -Ppkg.frr.rtrlib,pkg.frr.lua" diff --git a/scripts/package-build/frr/patches/frr/0001-ldpd-Option-for-disabled-LDP-hello-message-during-TC.patch b/scripts/package-build/frr/patches/frr/0001-ldpd-Option-for-disabled-LDP-hello-message-during-TC.patch deleted file mode 100644 index 67f85d01..00000000 --- a/scripts/package-build/frr/patches/frr/0001-ldpd-Option-for-disabled-LDP-hello-message-during-TC.patch +++ /dev/null @@ -1,176 +0,0 @@ -From 945eff42df61982585011fa8427050c74ca90c6b Mon Sep 17 00:00:00 2001 -From: Andrii Melnychenko <a.melnychenko@vyos.io> -Date: Mon, 17 Mar 2025 13:25:20 +0100 -Subject: [PATCH 1/1] ldpd: Option for disabled LDP hello message during TCP - -Added option "disable-establish-hello" that disableds -sending additional LDP hello multicast messages during -TCP session establishment. -This option enables per interface: "(config-ldp-af-if)". - -Signed-off-by: Andrii Melnychenko <a.melnychenko@vyos.io> ---- - ldpd/interface.c | 2 ++ - ldpd/ldp_vty.h | 1 + - ldpd/ldp_vty_cmds.c | 11 +++++++++++ - ldpd/ldp_vty_conf.c | 32 ++++++++++++++++++++++++++++++++ - ldpd/ldpd.c | 1 + - ldpd/ldpd.h | 1 + - ldpd/neighbor.c | 5 +++-- - 7 files changed, 51 insertions(+), 2 deletions(-) - -diff --git a/ldpd/interface.c b/ldpd/interface.c -index f0e70cbac..6fccd4af5 100644 ---- a/ldpd/interface.c -+++ b/ldpd/interface.c -@@ -63,11 +63,13 @@ if_new(const char *name) - iface->ipv4.af = AF_INET; - iface->ipv4.iface = iface; - iface->ipv4.enabled = 0; -+ iface->ipv4.disable_establish_hello = 0; - - /* ipv6 */ - iface->ipv6.af = AF_INET6; - iface->ipv6.iface = iface; - iface->ipv6.enabled = 0; -+ iface->ipv6.disable_establish_hello = 0; - - return (iface); - } -diff --git a/ldpd/ldp_vty.h b/ldpd/ldp_vty.h -index 5c83d1c56..196d05c93 100644 ---- a/ldpd/ldp_vty.h -+++ b/ldpd/ldp_vty.h -@@ -24,6 +24,7 @@ int ldp_vty_allow_broken_lsp(struct vty *, const char *); - int ldp_vty_address_family (struct vty *, const char *, const char *); - int ldp_vty_disc_holdtime(struct vty *, const char *, enum hello_type, long); - int ldp_vty_disc_interval(struct vty *, const char *, enum hello_type, long); -+int ldp_vty_disable_establish_hello(struct vty *, const char *); - int ldp_vty_targeted_hello_accept(struct vty *, const char *, const char *); - int ldp_vty_nbr_session_holdtime(struct vty *, const char *, struct in_addr, long); - int ldp_vty_af_session_holdtime(struct vty *, const char *, long); -diff --git a/ldpd/ldp_vty_cmds.c b/ldpd/ldp_vty_cmds.c -index e046ae996..d6c36c35b 100644 ---- a/ldpd/ldp_vty_cmds.c -+++ b/ldpd/ldp_vty_cmds.c -@@ -122,6 +122,15 @@ DEFPY (ldp_discovery_link_interval, - return (ldp_vty_disc_interval(vty, no, HELLO_LINK, interval)); - } - -+DEFPY (ldp_disable_establish_hello, -+ ldp_disable_establish_hello_cmd, -+ "[no] disable-establish-hello", -+ NO_STR -+ "Disable sending additional LDP hello message on establishing LDP tcp connection\n") -+{ -+ return ldp_vty_disable_establish_hello(vty, no); -+} -+ - DEFPY (ldp_discovery_targeted_interval, - ldp_discovery_targeted_interval_cmd, - "[no] discovery targeted-hello interval (1-65535)$interval", -@@ -866,9 +875,11 @@ ldp_vty_init (void) - - install_element(LDP_IPV4_IFACE_NODE, &ldp_discovery_link_holdtime_cmd); - install_element(LDP_IPV4_IFACE_NODE, &ldp_discovery_link_interval_cmd); -+ install_element(LDP_IPV4_IFACE_NODE, &ldp_disable_establish_hello_cmd); - - install_element(LDP_IPV6_IFACE_NODE, &ldp_discovery_link_holdtime_cmd); - install_element(LDP_IPV6_IFACE_NODE, &ldp_discovery_link_interval_cmd); -+ install_element(LDP_IPV6_IFACE_NODE, &ldp_disable_establish_hello_cmd); - - install_element(LDP_L2VPN_NODE, &ldp_bridge_cmd); - install_element(LDP_L2VPN_NODE, &ldp_mtu_cmd); -diff --git a/ldpd/ldp_vty_conf.c b/ldpd/ldp_vty_conf.c -index ffff67683..56ad071c8 100644 ---- a/ldpd/ldp_vty_conf.c -+++ b/ldpd/ldp_vty_conf.c -@@ -119,6 +119,8 @@ ldp_af_iface_config_write(struct vty *vty, int af) - ia->hello_interval != 0) - vty_out (vty, " discovery hello interval %u\n", - ia->hello_interval); -+ if (ia->disable_establish_hello) -+ vty_out (vty, " disable-establish-hello\n"); - - vty_out (vty, " exit\n"); - } -@@ -632,6 +634,36 @@ ldp_vty_disc_interval(struct vty *vty, const char *negate, - return (CMD_SUCCESS); - } - -+int -+ldp_vty_disable_establish_hello(struct vty *vty, -+ const char *negate) -+{ -+ struct iface *iface; -+ struct iface_af *ia; -+ int af; -+ -+ switch (vty->node) { -+ case LDP_IPV4_IFACE_NODE: -+ case LDP_IPV6_IFACE_NODE: -+ af = ldp_vty_get_af(vty); -+ iface = VTY_GET_CONTEXT(iface); -+ VTY_CHECK_CONTEXT(iface); -+ -+ ia = iface_af_get(iface, af); -+ if (negate) -+ ia->disable_establish_hello = 0; -+ else -+ ia->disable_establish_hello = 1; -+ -+ ldp_config_apply(vty, vty_conf); -+ break; -+ default: -+ fatalx("ldp_vty_disable_establish_hello: unexpected node"); -+ } -+ -+ return (CMD_SUCCESS); -+} -+ - int - ldp_vty_targeted_hello_accept(struct vty *vty, const char *negate, - const char *acl_from_str) -diff --git a/ldpd/ldpd.c b/ldpd/ldpd.c -index 4d38fdcd0..9a5667c26 100644 ---- a/ldpd/ldpd.c -+++ b/ldpd/ldpd.c -@@ -1604,6 +1604,7 @@ merge_iface_af(struct iface_af *ia, struct iface_af *xi) - } - ia->hello_holdtime = xi->hello_holdtime; - ia->hello_interval = xi->hello_interval; -+ ia->disable_establish_hello = xi->disable_establish_hello; - } - - static void -diff --git a/ldpd/ldpd.h b/ldpd/ldpd.h -index ad831a6ea..40a1e8c3c 100644 ---- a/ldpd/ldpd.h -+++ b/ldpd/ldpd.h -@@ -332,6 +332,7 @@ struct iface_af { - struct event *hello_timer; - uint16_t hello_holdtime; - uint16_t hello_interval; -+ int disable_establish_hello; - }; - - struct iface_ldp_sync { -diff --git a/ldpd/neighbor.c b/ldpd/neighbor.c -index 2596c7948..00a809186 100644 ---- a/ldpd/neighbor.c -+++ b/ldpd/neighbor.c -@@ -630,8 +630,9 @@ nbr_establish_connection(struct nbr *nbr) - * an adjacency as well. - */ - RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) -- send_hello(adj->source.type, adj->source.link.ia, -- adj->source.target); -+ if (!(adj->source.type == HELLO_LINK && adj->source.link.ia->disable_establish_hello)) -+ send_hello(adj->source.type, adj->source.link.ia, -+ adj->source.target); - - if (connect(nbr->fd, &remote_su.sa, sockaddr_len(&remote_su.sa)) == -1) { - if (errno == EINPROGRESS) { --- -2.43.0 - diff --git a/scripts/package-build/frr/patches/frr/0003-Clear-Babel-Config-On-Stop.patch b/scripts/package-build/frr/patches/frr/0003-Clear-Babel-Config-On-Stop.patch deleted file mode 100644 index fea45891..00000000 --- a/scripts/package-build/frr/patches/frr/0003-Clear-Babel-Config-On-Stop.patch +++ /dev/null @@ -1,29 +0,0 @@ -From c3c70e87b040233263b9594d14582dfedfecc92e Mon Sep 17 00:00:00 2001 -From: Yaroslav Kholod <y.kholod@vyos.io> -Date: Wed, 18 Dec 2024 11:48:29 +0200 -Subject: [PATCH] #17413: Clean babeld config on stop - ---- - babeld/babeld.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/babeld/babeld.c b/babeld/babeld.c -index b562f0b70..6f1a9a3d7 100644 ---- a/babeld/babeld.c -+++ b/babeld/babeld.c -@@ -304,6 +304,12 @@ void babel_clean_routing_process(void) - flush_all_routes(); - babel_interface_close_all(); - -+ /* Clean babel config */ -+ diversity_kind = DIVERSITY_NONE; -+ diversity_factor = BABEL_DEFAULT_DIVERSITY_FACTOR; -+ resend_delay = BABEL_DEFAULT_RESEND_DELAY; -+ smoothing_half_life = BABEL_DEFAULT_SMOOTHING_HALF_LIFE; -+ - /* cancel events */ - event_cancel(&babel_routing_process->t_read); - event_cancel(&babel_routing_process->t_update); --- -2.43.0 - diff --git a/scripts/package-build/frr/patches/frr/0004-tools-Add-missing-keyword-segment-routing-srv6-in-fr.patch b/scripts/package-build/frr/patches/frr/0004-tools-Add-missing-keyword-segment-routing-srv6-in-fr.patch deleted file mode 100644 index 6344cfb4..00000000 --- a/scripts/package-build/frr/patches/frr/0004-tools-Add-missing-keyword-segment-routing-srv6-in-fr.patch +++ /dev/null @@ -1,39 +0,0 @@ -From b4babae93b81be46c105e59c74b1708f00b68704 Mon Sep 17 00:00:00 2001 -From: Carmine Scarpitta <cscarpit@cisco.com> -Date: Sat, 23 Nov 2024 08:46:00 +0100 -Subject: [PATCH 4/5] tools: Add missing keyword `segment-routing srv6` in - frr-reload - -Fix the error below: - -``` -[58564|mgmtd] sending configuration -[58565|zebra] sending configuration, -line 10: % Unknown command[52]: node-msd -[58573|isisd] sending configuration -[58565|zebra] Configuration file[/etc/frr/frr.conf] processing failure: 2 -``` - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> ---- - tools/frr-reload.py | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/tools/frr-reload.py b/tools/frr-reload.py -index 08a1f1e07e..baa6285f45 100755 ---- a/tools/frr-reload.py -+++ b/tools/frr-reload.py -@@ -255,7 +255,9 @@ ctx_keywords = { - }, - "router rip": {}, - "router ripng": {}, -- "router isis ": {}, -+ "router isis ": { -+ "segment-routing srv6": {}, -+ }, - "router openfabric ": {}, - "router ospf": {}, - "router ospf6": {}, --- -2.39.5 - diff --git a/scripts/package-build/frr/patches/frr/0005-tools-Add-missing-keyword-node-msd-in-frr-reload.patch b/scripts/package-build/frr/patches/frr/0005-tools-Add-missing-keyword-node-msd-in-frr-reload.patch deleted file mode 100644 index 559caead..00000000 --- a/scripts/package-build/frr/patches/frr/0005-tools-Add-missing-keyword-node-msd-in-frr-reload.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 458e8a851608b8308b564e6cbfe95314629dccda Mon Sep 17 00:00:00 2001 -From: Carmine Scarpitta <cscarpit@cisco.com> -Date: Sat, 23 Nov 2024 08:46:50 +0100 -Subject: [PATCH 5/5] tools: Add missing keyword `node-msd` in frr-reload - -Fix the error below: - -``` -[58564|mgmtd] sending configuration -[58565|zebra] sending configuration, -line 14: % Unknown command[52]: max-segs-left 3 -line 18: % Unknown command[52]: max-end-pop 3 -line 22: % Unknown command[52]: max-h-encaps 2 -line 26: % Unknown command[52]: max-end-d 5 -[58573|isisd] sending configuration -[58565|zebra] Configuration file[/etc/frr/frr.conf] processing failure: 2 -``` - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> ---- - tools/frr-reload.py | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/tools/frr-reload.py b/tools/frr-reload.py -index baa6285f45..33d6cf0444 100755 ---- a/tools/frr-reload.py -+++ b/tools/frr-reload.py -@@ -256,7 +256,9 @@ ctx_keywords = { - "router rip": {}, - "router ripng": {}, - "router isis ": { -- "segment-routing srv6": {}, -+ "segment-routing srv6": { -+ "node-msd": {}, -+ }, - }, - "router openfabric ": {}, - "router ospf": {}, --- -2.39.5 - diff --git a/scripts/package-build/frr/patches/frr/0006-ospfd-support-table-direct-redistribution.patch b/scripts/package-build/frr/patches/frr/0006-ospfd-support-table-direct-redistribution.patch deleted file mode 100644 index 5bdbe966..00000000 --- a/scripts/package-build/frr/patches/frr/0006-ospfd-support-table-direct-redistribution.patch +++ /dev/null @@ -1,58 +0,0 @@ -From cbc1602bc6be411bebce2fd9cef712622a1b1799 Mon Sep 17 00:00:00 2001 -From: Rafael Zalamena <rzalamena@opensourcerouting.org> -Date: Fri, 15 Dec 2023 14:24:17 -0300 -Subject: [PATCH] ospfd: support table-direct redistribution - -Add the 'table-direct' option to the redistribute command in OSPF. - -Signed-off-by: Rafael Zalamena <rzalamena@opensourcerouting.org> ---- - ospfd/ospf_vty.c | 11 +++++------ - 1 file changed, 5 insertions(+), 6 deletions(-) - -diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c -index ccc46424bc..3594ad5e2c 100644 ---- a/ospfd/ospf_vty.c -+++ b/ospfd/ospf_vty.c -@@ -9321,10 +9321,11 @@ DEFUN (no_ospf_redistribute_source, - - DEFUN (ospf_redistribute_instance_source, - ospf_redistribute_instance_source_cmd, -- "redistribute <ospf|table> (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", -+ "redistribute <ospf|table|table-direct> (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", - REDIST_STR - "Open Shortest Path First\n" - "Non-main Kernel Routing Table\n" -+ "Non-main Kernel Routing Table - Direct\n" - "Instance ID/Table ID\n" - "Metric for redistributed routes\n" - "OSPF default metric\n" -@@ -9397,11 +9398,12 @@ DEFUN (ospf_redistribute_instance_source, - - DEFUN (no_ospf_redistribute_instance_source, - no_ospf_redistribute_instance_source_cmd, -- "no redistribute <ospf|table> (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", -+ "no redistribute <ospf|table|table-direct> (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", - NO_STR - REDIST_STR - "Open Shortest Path First\n" - "Non-main Kernel Routing Table\n" -+ "Non-main Kernel Routing Table - Direct\n" - "Instance ID/Table Id\n" - "Metric for redistributed routes\n" - "OSPF default metric\n" -@@ -9417,10 +9419,7 @@ DEFUN (no_ospf_redistribute_instance_source, - struct ospf_redist *red; - int source; - -- if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) -- source = ZEBRA_ROUTE_OSPF; -- else -- source = ZEBRA_ROUTE_TABLE; -+ source = proto_redistnum(AFI_IP, argv[idx_ospf_table]->text); - - instance = strtoul(argv[idx_number]->arg, NULL, 10); - --- -2.50.1 - diff --git a/scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch b/scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch index a0ecbce2..55646666 100644 --- a/scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch +++ b/scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch @@ -1,9 +1,9 @@ -From 1ba7319656c548075e466a8ad1d068f7b1d66756 Mon Sep 17 00:00:00 2001 +From 0e94af0907446266b28d40b1115144aa12aeb7c8 Mon Sep 17 00:00:00 2001 From: Kyrylo Yatsenko <hedrok@gmail.com> Date: Thu, 11 Sep 2025 17:43:08 +0300 Subject: [PATCH] zebra: remove kernel route on last address deletion -Fixes issue #13561 +Fixes: #13561 Linux kernel deletes IPv4 routes when last interface IPv4 address is deleted, but intentionally doesn't send RTM_DELROUTE in this case. @@ -38,17 +38,17 @@ Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> --- lib/if.c | 12 +++++++++++ lib/if.h | 1 + - zebra/interface.c | 8 ++++--- + zebra/interface.c | 8 +++++--- zebra/rib.h | 1 + - zebra/zebra_rib.c | 55 +++++++++++++++++++++++++++++++++++++++-------- - 5 files changed, 65 insertions(+), 12 deletions(-) + zebra/zebra_rib.c | 51 +++++++++++++++++++++++++++++++++++++---------- + 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/lib/if.c b/lib/if.c -index 8f15230f23..2402d17992 100644 +index 798bcd3da1..0991b765bb 100644 --- a/lib/if.c +++ b/lib/if.c -@@ -899,6 +899,18 @@ nbr_connected_log(struct nbr_connected *connected, char *str) - zlog_info("%s", logbuf); +@@ -972,6 +972,18 @@ struct nbr_connected *nbr_connected_check(struct interface *ifp, + return NULL; } +/* Return true if there is at least one connected address in the given family */ @@ -67,10 +67,10 @@ index 8f15230f23..2402d17992 100644 unsigned int connected_count_by_family(struct interface *ifp, int family) { diff --git a/lib/if.h b/lib/if.h -index 0dc56bd210..233213ca84 100644 +index fce6705c23..97f1f13a15 100644 --- a/lib/if.h +++ b/lib/if.h -@@ -606,6 +606,7 @@ extern struct connected *connected_lookup_prefix(struct interface *ifp, +@@ -609,6 +609,7 @@ extern struct connected *connected_lookup_prefix(struct interface *ifp, const struct prefix *p); extern struct connected *connected_lookup_prefix_exact(struct interface *ifp, const struct prefix *p); @@ -79,10 +79,10 @@ index 0dc56bd210..233213ca84 100644 extern struct nbr_connected *nbr_connected_new(void); extern void nbr_connected_free(struct nbr_connected *connected); diff --git a/zebra/interface.c b/zebra/interface.c -index e2c2b4a80c..48343355ba 100644 +index 47850c7f89..095ad355a2 100644 --- a/zebra/interface.c +++ b/zebra/interface.c -@@ -1313,11 +1313,13 @@ static void zebra_if_addr_update_ctx(struct zebra_dplane_ctx *ctx, +@@ -1280,11 +1280,13 @@ static void zebra_if_addr_update_ctx(struct zebra_dplane_ctx *ctx, } /* @@ -100,10 +100,10 @@ index e2c2b4a80c..48343355ba 100644 static void zebra_if_update_ctx(struct zebra_dplane_ctx *ctx, diff --git a/zebra/rib.h b/zebra/rib.h -index 5fedb07335..2f1954de9d 100644 +index f46c372335..230d304ba2 100644 --- a/zebra/rib.h +++ b/zebra/rib.h -@@ -330,6 +330,7 @@ enum rib_update_event { +@@ -350,6 +350,7 @@ enum rib_update_event { RIB_UPDATE_KERNEL, RIB_UPDATE_RMAP_CHANGE, RIB_UPDATE_OTHER, @@ -112,10 +112,10 @@ index 5fedb07335..2f1954de9d 100644 }; void rib_update_finish(void); diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c -index 752f8282df..eaba976265 100644 +index f1cc5ab085..b1eee9875f 100644 --- a/zebra/zebra_rib.c +++ b/zebra/zebra_rib.c -@@ -4465,6 +4465,9 @@ static const char *rib_update_event2str(enum rib_update_event event) +@@ -4717,6 +4717,9 @@ static const char *rib_update_event2str(enum rib_update_event event) case RIB_UPDATE_OTHER: ret = "RIB_UPDATE_OTHER"; break; @@ -125,16 +125,16 @@ index 752f8282df..eaba976265 100644 case RIB_UPDATE_MAX: break; } -@@ -4487,13 +4490,45 @@ rib_update_handle_kernel_route_down_possibility(struct route_node *rn, +@@ -4739,7 +4742,7 @@ rib_update_handle_kernel_route_down_possibility(struct route_node *rn, bool alive = false; for (ALL_NEXTHOPS(re->nhe->nhg, nexthop)) { +- if (!nexthop->ifindex) { + if (!nexthop->ifindex || nexthop->type == NEXTHOP_TYPE_BLACKHOLE) { -+ /* blackhole nexthops have no interfaces */ -+ alive = true; -+ break; -+ } -+ + /* blackhole nexthops have no interfaces */ + alive = true; + break; +@@ -4748,10 +4751,36 @@ rib_update_handle_kernel_route_down_possibility(struct route_node *rn, struct interface *ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id); @@ -172,7 +172,7 @@ index 752f8282df..eaba976265 100644 } if (!alive) { -@@ -4518,8 +4553,9 @@ static void rib_update_route_node(struct route_node *rn, int type, +@@ -4776,8 +4805,9 @@ static void rib_update_route_node(struct route_node *rn, int type, bool re_changed = false; RNODE_FOREACH_RE_SAFE (rn, re, next) { @@ -184,7 +184,7 @@ index 752f8282df..eaba976265 100644 rib_update_handle_kernel_route_down_possibility(rn, re); else if (type == ZEBRA_ROUTE_ALL || type == re->type) { SET_FLAG(re->status, ROUTE_ENTRY_CHANGED); -@@ -4562,17 +4598,18 @@ void rib_update_table(struct route_table *table, enum rib_update_event event, +@@ -4820,17 +4850,18 @@ void rib_update_table(struct route_table *table, enum rib_update_event event, * If we are looking at a route node and the node * has already been queued we don't * need to queue it up again, unless it is diff --git a/scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch b/scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch index ba37123f..3e94d7a6 100644 --- a/scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch +++ b/scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch @@ -1,4 +1,4 @@ -From 2fc836b3a2f7778d311e385966e3b2f00eebba1b Mon Sep 17 00:00:00 2001 +From 60664bb4d86102f4786ce3882e686d6dfd6c998a Mon Sep 17 00:00:00 2001 From: Kyrylo Yatsenko <hedrok@gmail.com> Date: Fri, 31 Oct 2025 21:20:20 +0200 Subject: [PATCH] bgpd: Support multiple labels in BGP-LU @@ -16,26 +16,23 @@ One of uses of multiple labels is SR-TE with BGP-LU. Resolves #19506 -cherry-picked-from: 17a082ee3a5d1295c27323773c523c945af12c3d - Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 17a082ee3a5d1295c27323773c523c945af12c3d) --- - bgpd/bgp_attr.c | 13 +++- + bgpd/bgp_attr.c | 13 ++++- bgpd/bgp_debug.c | 6 +- - bgpd/bgp_label.c | 142 ++++++++++++++++++++++++++++++--------- - bgpd/bgp_label.h | 30 +++++++-- - bgpd/bgp_mplsvpn.c | 25 ------- - bgpd/bgp_mplsvpn.h | 2 - - bgpd/bgp_route.c | 56 +++++++++------ - bgpd/bgp_updgrp_packet.c | 14 ++-- - bgpd/bgp_zebra.c | 47 +++++++++---- - 9 files changed, 223 insertions(+), 112 deletions(-) + bgpd/bgp_label.c | 120 ++++++++++++++++++++++++++++----------- + bgpd/bgp_label.h | 28 +++++++-- + bgpd/bgp_route.c | 56 +++++++++++------- + bgpd/bgp_updgrp_packet.c | 14 ++--- + bgpd/bgp_zebra.c | 47 +++++++++++---- + 7 files changed, 197 insertions(+), 87 deletions(-) diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c -index 1a2fa8318e..53e54b9c12 100644 +index 3b154c28a3..0af57bfae2 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c -@@ -4280,6 +4280,8 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, +@@ -4721,6 +4721,8 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, uint8_t num_labels, bool addpath_capable, uint32_t addpath_tx_id, struct attr *attr) { @@ -44,7 +41,7 @@ index 1a2fa8318e..53e54b9c12 100644 switch (safi) { case SAFI_UNSPEC: case SAFI_MAX: -@@ -4304,9 +4306,16 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, +@@ -4745,9 +4747,16 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, assert(!"Add encoding bits here for other AFI's"); break; case SAFI_LABELED_UNICAST: @@ -64,10 +61,10 @@ index 1a2fa8318e..53e54b9c12 100644 case SAFI_FLOWSPEC: stream_putc(s, p->u.prefix_flowspec.prefixlen); diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c -index 97c3e5740f..06b9543114 100644 +index 530203d860..aaabd26ee0 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c -@@ -2767,11 +2767,7 @@ const char *bgp_debug_rdpfxpath2str(afi_t afi, safi_t safi, +@@ -2924,11 +2924,7 @@ const char *bgp_debug_rdpfxpath2str(afi_t afi, safi_t safi, snprintf(tag_buf, sizeof(tag_buf), " label %s", tag_buf2); } else { @@ -81,7 +78,7 @@ index 97c3e5740f..06b9543114 100644 } diff --git a/bgpd/bgp_label.c b/bgpd/bgp_label.c -index 8ed9584b0a..fc2ab78371 100644 +index ae521a5db6..4a13d4b395 100644 --- a/bgpd/bgp_label.c +++ b/bgpd/bgp_label.c @@ -198,19 +198,20 @@ int bgp_parse_fec_update(void) @@ -186,15 +183,15 @@ index 8ed9584b0a..fc2ab78371 100644 - zlog_info("%pBP rcvd UPDATE with label stack %d deep", peer, - label_depth); + *num_labels = label_depth; - -- if (!(bgp_is_withdraw_label(label) || label_bos(label))) ++ + if (label_depth > BGP_MAX_LABELS) { + *num_labels = BGP_MAX_LABELS; + label_set_bos(&labels[*num_labels - 1]); + zlog_info("%pBP rcvd UPDATE with label stack %d deep, using only first %d labels", + peer, label_depth, BGP_MAX_LABELS); + } -+ + +- if (!(bgp_is_withdraw_label(label) || label_bos(label))) + if (!(bgp_is_withdraw_label(label_pnt) || label_bos(label_pnt))) flog_warn( EC_BGP_INVALID_LABEL_STACK, @@ -236,47 +233,32 @@ index 8ed9584b0a..fc2ab78371 100644 } } -@@ -596,6 +618,37 @@ int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, - return BGP_NLRI_PARSE_OK; +@@ -607,7 +629,7 @@ uint32_t decode_label(mpls_label_t *label_pnt) + return l; } -+uint32_t decode_label(mpls_label_t *label_pnt) -+{ -+ uint32_t l; -+ uint8_t *pnt = (uint8_t *)label_pnt; -+ -+ l = ((uint32_t)*pnt++ << 12); -+ l |= (uint32_t)*pnt++ << 4; -+ l |= (uint32_t)((*pnt & 0xf0) >> 4); -+ return l; -+} -+ +-void encode_label(mpls_label_t label, mpls_label_t *label_pnt) +void encode_label_bos(mpls_label_t label, mpls_label_t *label_pnt, bool bos) -+{ -+ uint8_t *pnt = (uint8_t *)label_pnt; -+ -+ if (pnt == NULL) -+ return; -+ if (label == BGP_PREVENT_VRF_2_VRF_LEAK) { -+ *label_pnt = label; -+ return; -+ } -+ *pnt++ = (label >> 12) & 0xff; -+ *pnt++ = (label >> 4) & 0xff; + { + uint8_t *pnt = (uint8_t *)label_pnt; + +@@ -619,7 +641,12 @@ void encode_label(mpls_label_t label, mpls_label_t *label_pnt) + } + *pnt++ = (label >> 12) & 0xff; + *pnt++ = (label >> 4) & 0xff; +- *pnt++ = ((label << 4) + 1) & 0xff; /* S=1 */ + *pnt++ = ((label << 4) + (bos ? 1 : 0)) & 0xff; +} + +void encode_label(mpls_label_t label, mpls_label_t *label_pnt) +{ + encode_label_bos(label, label_pnt, true); -+} -+ + } + bool bgp_labels_same(const mpls_label_t *tbl_a, const uint8_t num_labels_a, - const mpls_label_t *tbl_b, const uint8_t num_labels_b) - { -@@ -612,3 +665,30 @@ bool bgp_labels_same(const mpls_label_t *tbl_a, const uint8_t num_labels_a, - } - return true; +@@ -647,3 +674,30 @@ bool bgp_labels_is_implicit_null(struct bgp_path_info *pi) + return true; + return false; } + +char *mpls_labels2str(mpls_label_t *labels, uint8_t num_labels, const char *prefix, char *buf, @@ -306,10 +288,10 @@ index 8ed9584b0a..fc2ab78371 100644 + return buf; +} diff --git a/bgpd/bgp_label.h b/bgpd/bgp_label.h -index 2ffd5b699d..ab972c4830 100644 +index a467bc1130..2793aef1c8 100644 --- a/bgpd/bgp_label.h +++ b/bgpd/bgp_label.h -@@ -16,9 +16,15 @@ struct bgp_path_info; +@@ -14,9 +14,15 @@ struct bgp_path_info; struct peer; /* Maximum number of labels we can process or send with a prefix. We @@ -327,7 +309,7 @@ index 2ffd5b699d..ab972c4830 100644 /* MPLS label(s) - VNI(s) for EVPN-VxLAN */ struct bgp_labels { -@@ -40,16 +46,21 @@ extern int bgp_reg_for_label_callback(mpls_label_t new_label, void *labelid, +@@ -38,19 +44,22 @@ extern int bgp_reg_for_label_callback(mpls_label_t new_label, void *labelid, extern void bgp_reg_dereg_for_label(struct bgp_dest *dest, struct bgp_path_info *pi, bool reg); extern int bgp_parse_fec_update(void); @@ -339,20 +321,21 @@ index 2ffd5b699d..ab972c4830 100644 extern int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, struct bgp_nlri *packet); -+extern uint32_t decode_label(mpls_label_t *label); + extern uint32_t decode_label(mpls_label_t *label); +extern void encode_label_bos(mpls_label_t label_in, mpls_label_t *label_out, bool bos); -+extern void encode_label(mpls_label_t label_in, mpls_label_t *label_out); + extern void encode_label(mpls_label_t label_in, mpls_label_t *label_out); extern bool bgp_labels_same(const mpls_label_t *tbl_a, const uint8_t num_labels_a, const mpls_label_t *tbl_b, const uint8_t num_labels_b); + extern bool bgp_labels_is_implicit_null(struct bgp_path_info *pi); +/* Write labels to str of format "prefixLABEL1/LABEL2/.../LABELN", prefix may be NULL */ +extern char *mpls_labels2str(mpls_label_t *labels, uint8_t num_labels, const char *prefix, + char *buf, int size); static inline int bgp_labeled_safi(safi_t safi) { -@@ -114,4 +125,13 @@ static inline uint8_t label_bos(mpls_label_t *label) +@@ -115,4 +124,13 @@ static inline uint8_t label_bos(mpls_label_t *label) return (t[2] & 0x01); }; @@ -366,60 +349,11 @@ index 2ffd5b699d..ab972c4830 100644 +} + #endif /* _BGP_LABEL_H */ -diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c -index a35b4037bb..057ecb318e 100644 ---- a/bgpd/bgp_mplsvpn.c -+++ b/bgpd/bgp_mplsvpn.c -@@ -64,31 +64,6 @@ extern int argv_find_and_parse_vpnvx(struct cmd_token **argv, int argc, - return ret; - } - --uint32_t decode_label(mpls_label_t *label_pnt) --{ -- uint32_t l; -- uint8_t *pnt = (uint8_t *)label_pnt; -- -- l = ((uint32_t)*pnt++ << 12); -- l |= (uint32_t)*pnt++ << 4; -- l |= (uint32_t)((*pnt & 0xf0) >> 4); -- return l; --} -- --void encode_label(mpls_label_t label, mpls_label_t *label_pnt) --{ -- uint8_t *pnt = (uint8_t *)label_pnt; -- if (pnt == NULL) -- return; -- if (label == BGP_PREVENT_VRF_2_VRF_LEAK) { -- *label_pnt = label; -- return; -- } -- *pnt++ = (label >> 12) & 0xff; -- *pnt++ = (label >> 4) & 0xff; -- *pnt++ = ((label << 4) + 1) & 0xff; /* S=1 */ --} -- - int bgp_nlri_parse_vpn(struct peer *peer, struct attr *attr, - struct bgp_nlri *packet) - { -diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h -index 3b036d2167..ab84cc2118 100644 ---- a/bgpd/bgp_mplsvpn.h -+++ b/bgpd/bgp_mplsvpn.h -@@ -34,8 +34,6 @@ - extern void bgp_mplsvpn_init(void); - extern void bgp_mplsvpn_path_nh_label_unlink(struct bgp_path_info *pi); - extern int bgp_nlri_parse_vpn(struct peer *, struct attr *, struct bgp_nlri *); --extern uint32_t decode_label(mpls_label_t *); --extern void encode_label(mpls_label_t, mpls_label_t *); - - extern int argv_find_and_parse_vpnvx(struct cmd_token **argv, int argc, - int *index, afi_t *afi); diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c -index 85cad88979..867b4a7cea 100644 +index 307e6fe94d..1857e2f2c0 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c -@@ -2145,8 +2145,9 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, +@@ -2170,8 +2170,9 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, int samepeer_safe = 0; /* for synthetic mplsvpns routes */ bool nh_reset = false; uint64_t cum_bw; @@ -430,7 +364,7 @@ index 85cad88979..867b4a7cea 100644 if (DISABLE_BGP_ANNOUNCE) return false; -@@ -2231,13 +2232,11 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, +@@ -2256,13 +2257,11 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, /* If it's labeled safi, make sure the route has a valid label. */ if (safi == SAFI_LABELED_UNICAST) { @@ -448,7 +382,7 @@ index 85cad88979..867b4a7cea 100644 return false; } } else if (safi == SAFI_MPLS_VPN && -@@ -2254,8 +2253,8 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, +@@ -2279,8 +2278,8 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, * then get appropriate mpls local label * and check its validity */ @@ -459,15 +393,15 @@ index 85cad88979..867b4a7cea 100644 if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " %pFX is filtered - no valid label", -@@ -10493,6 +10492,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, - json_object *json_paths) +@@ -11553,6 +11552,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, + struct attr *pattr, uint16_t show_opts) { char buf[INET6_ADDRSTRLEN]; + char labels_buf[9 * BGP_MAX_LABELS]; /* 8 per label + / or \0 for each */ char vni_buf[30] = {}; - struct attr *attr = path->attr; + struct attr *attr = pattr ? pattr : path->attr; time_t tbuf; -@@ -10500,6 +10500,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, +@@ -11560,6 +11560,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_bestpath = NULL; json_object *json_cluster_list = NULL; json_object *json_cluster_list_list = NULL; @@ -475,7 +409,7 @@ index 85cad88979..867b4a7cea 100644 json_object *json_ext_community = NULL; json_object *json_ext_ipv6_community = NULL; json_object *json_last_update = NULL; -@@ -10510,6 +10511,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, +@@ -11570,6 +11571,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_path = NULL; json_object *json_peer = NULL; json_object *json_string = NULL; @@ -483,7 +417,7 @@ index 85cad88979..867b4a7cea 100644 json_object *json_adv_to = NULL; int first = 0; struct listnode *node, *nnode; -@@ -10520,12 +10522,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, +@@ -11580,12 +11582,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, bool nexthop_self = CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; int i; @@ -497,7 +431,7 @@ index 85cad88979..867b4a7cea 100644 struct bgp_path_info *bpi_ultimate = bgp_get_imported_bpi_ultimate(path); struct bgp_route_evpn *bre = bgp_attr_get_evpn_overlay(attr); -@@ -11338,13 +11335,28 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, +@@ -12418,13 +12415,28 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, /* Remote Label */ if (bgp_path_info_has_valid_label(path) && (safi != SAFI_EVPN && !is_route_parent_evpn(path))) { @@ -534,10 +468,10 @@ index 85cad88979..867b4a7cea 100644 /* Remote SID */ diff --git a/bgpd/bgp_updgrp_packet.c b/bgpd/bgp_updgrp_packet.c -index 3ce136ef87..4e0702aeb4 100644 +index 141cb81efc..449173d93f 100644 --- a/bgpd/bgp_updgrp_packet.c +++ b/bgpd/bgp_updgrp_packet.c -@@ -664,7 +664,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) +@@ -681,7 +681,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) int addpath_overhead = 0; uint32_t addpath_tx_id = 0; struct prefix_rd *prd = NULL; @@ -547,7 +481,7 @@ index 3ce136ef87..4e0702aeb4 100644 uint8_t num_labels = 0; if (!subgrp) -@@ -785,10 +786,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) +@@ -804,10 +805,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) dest->pdest); if (safi == SAFI_LABELED_UNICAST) { @@ -560,7 +494,7 @@ index 3ce136ef87..4e0702aeb4 100644 } else if (safi == SAFI_MPLS_VPN && path && CHECK_FLAG(path->flags, BGP_PATH_MPLSVPN_NH_LABEL_BIND) && -@@ -808,9 +807,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) +@@ -827,9 +826,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) * called here, 'get_label()' returns a valid * label. */ @@ -573,10 +507,10 @@ index 3ce136ef87..4e0702aeb4 100644 } else { num_labels = BGP_PATH_INFO_NUM_LABELS(path); diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c -index 51af57ac68..ecfc278f2c 100644 +index 0658beffa6..1d33883e87 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c -@@ -1328,11 +1328,10 @@ static void bgp_zebra_announce_parse_nexthop( +@@ -1314,11 +1314,10 @@ static void bgp_zebra_announce_parse_nexthop( if (bgp_debug_zebra(&api->prefix)) { if (BGP_PATH_INFO_NUM_LABELS(mpinfo)) { @@ -591,7 +525,7 @@ index 51af57ac68..ecfc278f2c 100644 } else { zlog_debug("%s: p=%pFX, no label", __func__, p); } -@@ -1411,15 +1410,28 @@ static void bgp_zebra_announce_parse_nexthop( +@@ -1397,15 +1396,28 @@ static void bgp_zebra_announce_parse_nexthop( nh_label = *bgp_evpn_path_info_labels_get_l3vni( labels, num_labels); nh_label_type = ZEBRA_LSP_EVPN; @@ -624,7 +558,7 @@ index 51af57ac68..ecfc278f2c 100644 } if (is_evpn && !(bre && bre->type == OVERLAY_INDEX_GATEWAY_IP)) -@@ -1475,7 +1487,8 @@ static void bgp_debug_zebra_nh(struct zapi_route *api) +@@ -1462,7 +1474,8 @@ static void bgp_debug_zebra_nh(struct zapi_route *api) char nh_buf[INET6_ADDRSTRLEN]; char eth_buf[ETHER_ADDR_STRLEN + 7] = { '\0' }; char buf1[ETHER_ADDR_STRLEN]; @@ -634,7 +568,7 @@ index 51af57ac68..ecfc278f2c 100644 char sid_buf[20]; char segs_buf[256]; struct zapi_nexthop *api_nh; -@@ -1513,9 +1526,19 @@ static void bgp_debug_zebra_nh(struct zapi_route *api) +@@ -1500,9 +1513,19 @@ static void bgp_debug_zebra_nh(struct zapi_route *api) eth_buf[0] = '\0'; segs_buf[0] = '\0'; if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL) && diff --git a/scripts/package-build/frr/patches/frr/0012-Revert-tools-Allow-deleting-of-interfaces.patch b/scripts/package-build/frr/patches/frr/0012-Revert-tools-Allow-deleting-of-interfaces.patch new file mode 100644 index 00000000..4a61dad8 --- /dev/null +++ b/scripts/package-build/frr/patches/frr/0012-Revert-tools-Allow-deleting-of-interfaces.patch @@ -0,0 +1,37 @@ +From 3b720b6e9b4a6a6f53f00c01536efb7a4e013c94 Mon Sep 17 00:00:00 2001 +From: Kyrylo Yatsenko <hedrok@gmail.com> +Date: Tue, 23 Dec 2025 09:35:14 +0200 +Subject: [PATCH] Revert "tools: Allow deleting of interfaces" + +This reverts commit f1a1703a0a2c2ecd81f50ccd384dcdb7e0c1a793. +--- + tools/frr-reload.py | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/tools/frr-reload.py b/tools/frr-reload.py +index fcd64bae3e..2ac7c6cc17 100755 +--- a/tools/frr-reload.py ++++ b/tools/frr-reload.py +@@ -1799,13 +1799,12 @@ def compare_context_objects(newconf, running): + delete_bgpd = True + lines_to_del.append((running_ctx_keys, None)) + +- elif running_ctx_keys[0].startswith("interface"): +- lines_to_del.append((running_ctx_keys, None)) +- +- # We cannot do 'no vrf' in FRR, and so deal with it +- elif running_ctx_keys[0].startswith("vrf") or running_ctx_keys[ +- 0 +- ].startswith("router pim"): ++ # We cannot do 'no interface' or 'no vrf' in FRR, and so deal with it ++ elif ( ++ running_ctx_keys[0].startswith("interface") ++ or running_ctx_keys[0].startswith("vrf") ++ or running_ctx_keys[0].startswith("router pim") ++ ): + for line in running_ctx.lines: + lines_to_del.append((running_ctx_keys, line)) + +-- +2.50.1 + diff --git a/scripts/package-build/frr/patches/frr/0013-Revert-bgpd-Ensure-incorrect-GR-config-isn-t-display.patch b/scripts/package-build/frr/patches/frr/0013-Revert-bgpd-Ensure-incorrect-GR-config-isn-t-display.patch new file mode 100644 index 00000000..2628580d --- /dev/null +++ b/scripts/package-build/frr/patches/frr/0013-Revert-bgpd-Ensure-incorrect-GR-config-isn-t-display.patch @@ -0,0 +1,62 @@ +From 2b1622ab1c521ef8959b1edabf685b03394a8224 Mon Sep 17 00:00:00 2001 +From: Kyrylo Yatsenko <hedrok@gmail.com> +Date: Sun, 28 Dec 2025 19:37:15 +0200 +Subject: [PATCH] Revert "bgpd: Ensure incorrect GR config isn't displayed for + peer-groups" + +This reverts commit d34b97374b02a88be43c132d676d6c87869ece4c. + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +--- + bgpd/bgp_vty.c | 35 ++++++++++++++++++++--------------- + 1 file changed, 20 insertions(+), 15 deletions(-) + +diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c +index 4050a8cd50..e3ff8b7f48 100644 +--- a/bgpd/bgp_vty.c ++++ b/bgpd/bgp_vty.c +@@ -19646,21 +19646,26 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp, + " neighbor %s path-attribute treat-as-withdraw %s\n", + addr, withdraw_attrs_str); + +- if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { +- if (!CHECK_FLAG(peer->peer_gr_new_status_flag, +- PEER_GRACEFUL_RESTART_NEW_STATE_INHERIT)) { +- if (CHECK_FLAG(peer->peer_gr_new_status_flag, +- PEER_GRACEFUL_RESTART_NEW_STATE_HELPER)) { +- vty_out(vty, " neighbor %s graceful-restart-helper\n", addr); +- } else if (CHECK_FLAG(peer->peer_gr_new_status_flag, +- PEER_GRACEFUL_RESTART_NEW_STATE_RESTART)) { +- vty_out(vty, " neighbor %s graceful-restart\n", addr); +- } else if ((!(CHECK_FLAG(peer->peer_gr_new_status_flag, +- PEER_GRACEFUL_RESTART_NEW_STATE_HELPER)) && +- !(CHECK_FLAG(peer->peer_gr_new_status_flag, +- PEER_GRACEFUL_RESTART_NEW_STATE_RESTART)))) { +- vty_out(vty, " neighbor %s graceful-restart-disable\n", addr); +- } ++ if (!CHECK_FLAG(peer->peer_gr_new_status_flag, ++ PEER_GRACEFUL_RESTART_NEW_STATE_INHERIT)) { ++ ++ if (CHECK_FLAG(peer->peer_gr_new_status_flag, ++ PEER_GRACEFUL_RESTART_NEW_STATE_HELPER)) { ++ vty_out(vty, ++ " neighbor %s graceful-restart-helper\n", addr); ++ } else if (CHECK_FLAG( ++ peer->peer_gr_new_status_flag, ++ PEER_GRACEFUL_RESTART_NEW_STATE_RESTART)) { ++ vty_out(vty, ++ " neighbor %s graceful-restart\n", addr); ++ } else if ( ++ (!(CHECK_FLAG(peer->peer_gr_new_status_flag, ++ PEER_GRACEFUL_RESTART_NEW_STATE_HELPER)) ++ && !(CHECK_FLAG( ++ peer->peer_gr_new_status_flag, ++ PEER_GRACEFUL_RESTART_NEW_STATE_RESTART)))) { ++ vty_out(vty, " neighbor %s graceful-restart-disable\n", ++ addr); + } + } + +-- +2.50.1 + diff --git a/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch b/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch new file mode 100644 index 00000000..3e646328 --- /dev/null +++ b/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch @@ -0,0 +1,12048 @@ +From acc1741e372a209a2c0583b11eeee8409f07e264 Mon Sep 17 00:00:00 2001 +From: Christian Hopps <chopps@labn.net> +Date: Sun, 16 Nov 2025 07:57:08 +0000 +Subject: [PATCH] Simplify and cleanup mgmtd + +PR https://github.com/FRRouting/frr/pull/20088 + +Squashed all 18 commits for easier patch manipulation. + +Used commits: + +* d241d414ea mgmtd: fixes post reorganization +* 47f5704bc2 mgmtd: reorganize and cleanup the code +* 91f71c916d mgmtd: txn: remove copies of globals and simplify init/cleanup +* ec273b53c8 mgmtd: move mgmt vty code out of share lib and into mgmtd +* db9dc758c3 mgmtd: backend cleanup, remove locks and unused functions +* a9d106ce9d mgmtd: remove unneeded lock/unlock (ref-count) on front-end adapters +* 0c0a438ef4 mgmtd: front-end locking and candidate restore cleanup +* 51351b8e23 mgmtd: rename MGMT_TXN_LOCK to TXN_INCREF +* 5357a1befe mgmtd: standardize the clients sent and clients wait logic +* fc7fc581e2 mgmtd: txn: refactor txn req structs +* b7e995f72a mgmtd: simplify the config code/state machine +* 9c0a02d598 lib: adapt mgmtd backend client code to simplification changes +* 26190c2a4b lib: mgmtd: improve debug statement in front-end client code +* bbc2792605 lib: mgmt_msg: add msg code to string function +* 0dbc0c73bf lib: mgmt_msg: turn down the debug output +* 8fde52f524 lib: change define so it can be passed as function "ptr". +* 2345e038ab tools: recognize new FOREACH_BE_ADAPTER_BITS macro +* 3e379b2a89 lib: add the `vty` arg to the start/end config callbacks +--- + .clang-format | 1 + + bgpd/bgp_vty.c | 4 +- + isisd/isis_main.c | 4 +- + lib/command.h | 4 +- + lib/lib_vty.c | 22 +- + lib/mgmt_be_client.c | 353 +++--- + lib/mgmt_be_client.h | 8 +- + lib/mgmt_fe_client.c | 16 +- + lib/mgmt_fe_client.h | 8 +- + lib/mgmt_msg.c | 95 +- + lib/mgmt_msg_native.c | 64 +- + lib/mgmt_msg_native.h | 12 +- + lib/northbound_cli.c | 43 +- + lib/vty.c | 780 +----------- + lib/vty.h | 41 +- + mgmtd/mgmt.c | 6 +- + mgmtd/mgmt.h | 33 +- + mgmtd/mgmt_be_adapter.c | 973 +++++++-------- + mgmtd/mgmt_be_adapter.h | 118 +- + mgmtd/mgmt_ds.c | 98 +- + mgmtd/mgmt_ds.h | 26 +- + mgmtd/mgmt_fe_adapter.c | 1848 ++++++++++++++-------------- + mgmtd/mgmt_fe_adapter.h | 87 +- + mgmtd/mgmt_history.c | 16 +- + mgmtd/mgmt_main.c | 2 + + mgmtd/mgmt_memory.c | 3 - + mgmtd/mgmt_memory.h | 3 - + mgmtd/mgmt_txn.c | 2421 ++++++++----------------------------- + mgmtd/mgmt_txn.h | 282 ++--- + mgmtd/mgmt_txn_cfg.c | 1096 +++++++++++++++++ + mgmtd/mgmt_txn_priv.h | 80 ++ + mgmtd/mgmt_vty.c | 66 +- + mgmtd/mgmt_vty_frontend.c | 898 ++++++++++++++ + mgmtd/subdir.am | 3 + + ospf6d/ospf6_main.c | 4 +- + ospfd/ospf_main.c | 4 +- + sharpd/sharp_main.c | 4 +- + tools/checkpatch.pl | 2 +- + watchfrr/watchfrr.c | 4 +- + 39 files changed, 4604 insertions(+), 4928 deletions(-) + create mode 100644 mgmtd/mgmt_txn_cfg.c + create mode 100644 mgmtd/mgmt_txn_priv.h + create mode 100644 mgmtd/mgmt_vty_frontend.c + +diff --git a/.clang-format b/.clang-format +index 89f554508a..f92767967a 100644 +--- a/.clang-format ++++ b/.clang-format +@@ -105,6 +105,7 @@ ForEachMacros: + - 'FOREACH_AFI_SAFI' + - 'FOREACH_AFI_SAFI_NSF' + - 'FOREACH_BE_APPLY_BATCH_IN_LIST' ++ - 'FOREACH_BE_ADAPTER_BITS' + - 'FOREACH_BE_CLIENT_BITS' + - 'FOREACH_BE_TXN_BATCH_IN_LIST' + - 'FOREACH_BE_TXN_IN_LIST' +diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c +index e21eeae317..a953790112 100644 +--- a/bgpd/bgp_vty.c ++++ b/bgpd/bgp_vty.c +@@ -20668,7 +20668,7 @@ static void bgp_config_end_timeout(struct event *t) + bgp_config_finish(t); + } + +-static void bgp_config_start(void) ++static void bgp_config_start(struct vty *vty) + { + event_cancel(&t_bgp_cfg); + event_add_timer(bm->master, bgp_config_end_timeout, NULL, +@@ -20680,7 +20680,7 @@ static void bgp_config_start(void) + * EoR before route-maps are processed. + * This is especially valid if using `bgp route-map delay-timer`. + */ +-static void bgp_config_end(void) ++static void bgp_config_end(struct vty *vty) + { + #define BGP_POST_CONFIG_DELAY_SECONDS 1 + uint32_t bgp_post_config_delay = +diff --git a/isisd/isis_main.c b/isisd/isis_main.c +index a38e4ae54e..042cfd7466 100644 +--- a/isisd/isis_main.c ++++ b/isisd/isis_main.c +@@ -214,14 +214,14 @@ static void isis_config_end_timeout(struct event *t) + isis_config_finish(t); + } + +-static void isis_config_start(void) ++static void isis_config_start(struct vty *vty) + { + event_cancel(&t_isis_cfg); + event_add_timer(im->master, isis_config_end_timeout, NULL, + ISIS_PRE_CONFIG_MAX_WAIT_SECONDS, &t_isis_cfg); + } + +-static void isis_config_end(void) ++static void isis_config_end(struct vty *vty) + { + /* If ISIS config processing thread isn't running, then + * we can return and rely it's properly handled. +diff --git a/lib/command.h b/lib/command.h +index ea08a74d6a..d5119fd97c 100644 +--- a/lib/command.h ++++ b/lib/command.h +@@ -611,8 +611,8 @@ extern int cmd_execute_command(vector, struct vty *, + extern int cmd_execute_command_strict(vector, struct vty *, + const struct cmd_element **); + extern void cmd_init(int terminal); +-extern void cmd_init_config_callbacks(void (*start_config_cb)(void), +- void (*end_config_cb)(void)); ++extern void cmd_init_config_callbacks(void (*start_config_cb)(struct vty *vty), ++ void (*end_config_cb)(struct vty *vty)); + extern void cmd_terminate(void); + extern void cmd_exit(struct vty *vty); + extern int cmd_list_cmds(struct vty *vty, int do_permute); +diff --git a/lib/lib_vty.c b/lib/lib_vty.c +index 66dda7d0b4..1b476bca8b 100644 +--- a/lib/lib_vty.c ++++ b/lib/lib_vty.c +@@ -207,8 +207,8 @@ DEFUN (frr_version, + static struct call_back { + time_t readin_time; + +- void (*start_config)(void); +- void (*end_config)(void); ++ void (*start_config)(struct vty *vty); ++ void (*end_config)(struct vty *vty); + } callback; + + +@@ -220,7 +220,7 @@ DEFUN_NOSH(start_config, start_config_cmd, "XFRR_start_configuration", + vty->pending_allowed = 1; + + if (callback.start_config) +- (*callback.start_config)(); ++ (*callback.start_config)(vty); + + return CMD_SUCCESS; + } +@@ -238,6 +238,7 @@ DEFUN_NOSH(end_config, end_config_cmd, "XFRR_end_configuration", + frrtime_to_interval(readin_time, readin_time_str, + sizeof(readin_time_str)); + ++ /* This is also getting cleared in the config node exit */ + vty->pending_allowed = 0; + ret = nb_cli_pending_commit_check(vty); + +@@ -245,23 +246,14 @@ DEFUN_NOSH(end_config, end_config_cmd, "XFRR_end_configuration", + zlog_debug("%s: VTY:%p, pending SET-CFG: %u", __func__, vty, + (uint32_t)vty->mgmt_num_pending_setcfg); + +- /* +- * If (and only if) we have sent any CLI config commands to MGMTd +- * FE interface using vty_mgmt_send_config_data() without implicit +- * commit before, should we need to send an explicit COMMIT-REQ now +- * to apply all those commands at once. +- */ +- if (vty->mgmt_num_pending_setcfg && vty_mgmt_fe_enabled()) +- vty_mgmt_send_commit_config(vty, false, false, false); +- + if (callback.end_config) +- (*callback.end_config)(); ++ (*callback.end_config)(vty); + + return ret; + } + +-void cmd_init_config_callbacks(void (*start_config_cb)(void), +- void (*end_config_cb)(void)) ++void cmd_init_config_callbacks(void (*start_config_cb)(struct vty *vty), ++ void (*end_config_cb)(struct vty *vty)) + { + callback.start_config = start_config_cb; + callback.end_config = end_config_cb; +diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c +index 6a5ca6649f..6a611d7611 100644 +--- a/lib/mgmt_be_client.c ++++ b/lib/mgmt_be_client.c +@@ -33,7 +33,6 @@ struct be_oper_iter_arg { + struct lyd_node *hint; /* last node added */ + }; + +-PREDECL_LIST(mgmt_be_txns); + struct mgmt_be_txn_ctx { + /* Txn-Id as assigned by MGMTD */ + uint64_t txn_id; +@@ -42,21 +41,17 @@ struct mgmt_be_txn_ctx { + struct mgmt_be_client_txn_ctx client_data; + struct mgmt_be_client *client; + +- struct mgmt_be_txns_item list_linkage; +- ++ struct nb_config *candidate_config; + struct nb_transaction *nb_txn; + uint32_t nb_txn_id; + }; + #define MGMTD_BE_TXN_FLAGS_CFGPREP_FAILED (1U << 1) + +-DECLARE_LIST(mgmt_be_txns, struct mgmt_be_txn_ctx, list_linkage); +- + struct mgmt_be_client { + struct msg_client client; + + char *name; + +- struct nb_config *candidate_config; + struct nb_config *running_config; + + uint64_t num_edit_nb_cfg; +@@ -66,15 +61,12 @@ struct mgmt_be_client { + uint64_t num_apply_nb_cfg; + uint64_t avg_apply_nb_cfg_tm; + +- struct mgmt_be_txns_head txn_head; ++ struct mgmt_be_txn_ctx *config_txn; + + struct mgmt_be_client_cbs cbs; + uintptr_t user_data; + }; + +-#define FOREACH_BE_TXN_IN_LIST(client_ctx, txn) \ +- frr_each_safe (mgmt_be_txns, &(client_ctx)->txn_head, (txn)) +- + struct debug mgmt_dbg_be_client = { + .conf = "debug mgmt client backend", + .desc = "Management backend client operations", +@@ -96,15 +88,10 @@ static struct mgmt_be_txn_ctx * + mgmt_be_find_txn_by_id(struct mgmt_be_client *client_ctx, uint64_t txn_id, + bool warn) + { +- struct mgmt_be_txn_ctx *txn = NULL; +- +- FOREACH_BE_TXN_IN_LIST (client_ctx, txn) +- if (txn->txn_id == txn_id) +- return txn; ++ if (client_ctx->config_txn && client_ctx->config_txn->txn_id == txn_id) ++ return client_ctx->config_txn; + if (warn) +- log_err_be_client("client %s unkonwn txn-id: %" PRIu64, +- client_ctx->name, txn_id); +- ++ log_err_be_client("client %s unkonwn txn-id: %Lu", client_ctx->name, txn_id); + return NULL; + } + +@@ -123,53 +110,54 @@ mgmt_be_txn_create(struct mgmt_be_client *client_ctx, uint64_t txn_id) + txn = XCALLOC(MTYPE_MGMTD_BE_TXN, sizeof(struct mgmt_be_txn_ctx)); + txn->txn_id = txn_id; + txn->client = client_ctx; +- mgmt_be_txns_add_tail(&client_ctx->txn_head, txn); ++ /* ++ * NOTE: If this is ends up being expensive then we should just offload ++ * creating one in a thread whenever updating running_config. ++ */ ++ txn->candidate_config = nb_config_dup(client_ctx->running_config); ++ if (!txn->candidate_config) { ++ log_err_be_client("Failed to create candidate config for txn-id: %Lu", txn_id); ++ XFREE(MTYPE_MGMTD_BE_TXN, txn); ++ return NULL; ++ } ++ /* record that we have one going */ ++ client_ctx->config_txn = txn; + + debug_be_client("Created new txn-id: %" PRIu64, txn_id); + + return txn; + } + +-static void mgmt_be_txn_delete(struct mgmt_be_client *client_ctx, +- struct mgmt_be_txn_ctx **txn) ++static void mgmt_be_txn_delete(struct mgmt_be_txn_ctx *txn) + { ++ struct mgmt_be_client *client_ctx; + char err_msg[] = "MGMT Transaction Delete"; + + if (!txn) + return; +- + /* +- * Remove the transaction from the list of transactions +- * so that future lookups with the same transaction id +- * does not return this one. ++ * Stop tracking this transaction from the client context + */ +- mgmt_be_txns_del(&client_ctx->txn_head, *txn); ++ client_ctx = txn->client; ++ assert(client_ctx->config_txn == txn); ++ client_ctx->config_txn = NULL; + + /* +- * Time to delete the transaction which should also take care of +- * cleaning up all batches created via CFG_REQs. But first notify the +- * client about the transaction delete. ++ * Delete the northbound transaction which should also take care ++ * of cleaning up any allocations made so far. + */ +- if (client_ctx->cbs.txn_notify) +- (void)(*client_ctx->cbs.txn_notify)(client_ctx, +- client_ctx->user_data, +- &(*txn)->client_data, true); ++ if (txn->nb_txn) ++ nb_candidate_commit_abort(txn->nb_txn, err_msg, sizeof(err_msg)); + +- if ((*txn)->nb_txn) +- nb_candidate_commit_abort((*txn)->nb_txn, err_msg, +- sizeof(err_msg)); +- XFREE(MTYPE_MGMTD_BE_TXN, *txn); ++ nb_config_free(txn->candidate_config); + +- *txn = NULL; ++ XFREE(MTYPE_MGMTD_BE_TXN, txn); + } + + static void mgmt_be_cleanup_all_txns(struct mgmt_be_client *client_ctx) + { +- struct mgmt_be_txn_ctx *txn = NULL; +- +- FOREACH_BE_TXN_IN_LIST (client_ctx, txn) { +- mgmt_be_txn_delete(client_ctx, &txn); +- } ++ mgmt_be_txn_delete(client_ctx->config_txn); ++ assert(client_ctx->config_txn == NULL); + } + + +@@ -308,65 +296,10 @@ static int mgmt_be_send_notification(void *__client, const char *path, const str + return 0; + } + +-static int mgmt_be_send_txn_reply(struct mgmt_be_client *client, uint64_t txn_id, bool create) +-{ +- struct mgmt_msg_txn_reply *msg; +- int ret; +- +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_reply, 0, MTYPE_MSG_NATIVE_TXN_REPLY); +- msg->code = MGMT_MSG_CODE_TXN_REPLY; +- msg->refer_id = txn_id; +- msg->created = create; +- +- debug_be_client("Sending TXN_REPLY txn-id %Lu", txn_id); +- +- ret = be_client_send_native_msg(client, msg, mgmt_msg_native_get_msg_len(msg), false); +- mgmt_msg_native_free_msg(msg); +- return ret; +-} +- + /* ===================== */ + /* CONFIGURATION PROCESS */ + /* ===================== */ + +-/* ------------------ */ +-/* Create Transaction */ +-/* ------------------ */ +- +-/* +- * Process transaction create message +- */ +-static void be_client_handle_txn_req(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf, +- size_t msg_len) +-{ +- struct mgmt_msg_txn_req *msg = msgbuf; +- struct mgmt_be_txn_ctx *txn; +- +- debug_be_client("Got TXN_REQ for client %s txn-id %Lu req-id %Lu", client->name, txn_id, +- msg->req_id); +- +- if (msg->create) { +- debug_be_client("Creating new txn-id %" PRIu64, txn_id); +- +- txn = mgmt_be_txn_create(client, txn_id); +- if (!txn) { +- msg_conn_disconnect(&client->client.conn, true); +- return; +- } +- +- if (client->cbs.txn_notify) +- (*client->cbs.txn_notify)(client, client->user_data, &txn->client_data, +- false); +- } else { +- debug_be_client("Deleting txn-id: %" PRIu64, txn_id); +- txn = mgmt_be_find_txn_by_id(client, txn_id, false); +- if (txn) +- mgmt_be_txn_delete(client, &txn); +- } +- +- mgmt_be_send_txn_reply(client, txn_id, msg->create); +-} +- + /* ------------------------- */ + /* Receive Config from MgmtD */ + /* ------------------------- */ +@@ -405,17 +338,6 @@ static void mgmt_be_txn_cfg_abort(struct mgmt_be_txn_ctx *txn) + nb_candidate_commit_abort(txn->nb_txn, errmsg, sizeof(errmsg)); + txn->nb_txn = 0; + } +- +- /* +- * revert candidate back to running +- * +- * This is one txn ctx but the candidate_config is per client ctx, how +- * does that work? +- */ +- debug_be_client("Reset candidate configurations after abort of txn-id: %" PRIu64, +- txn->txn_id); +- nb_config_replace(txn->client->candidate_config, +- txn->client->running_config, true); + } + + /* ------------------------------------------------ */ +@@ -470,10 +392,29 @@ static bool be_client_change_edit_config(struct nb_config *candidate_config, con + return error; + } + +-static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **config) ++/** ++ * be_txn_config_prepare() - Prepare to apply the given configuration. ++ * @client_ctx: The backend client context. ++ * @txn_id: The transaction id for this configuration change. ++ * ++ * The configuration changes are applied to the candidate configuration and ++ * validated. If the configuration is valid it is saved in a northbound ++ * transaction that is saved in a mgmtd txn associated with the @txn_id. ++ * ++ * If there is a config validation error the error is returned to mgmtd. For any ++ * other error the connection is dropped. ++ * ++ * Currently the global candidate configuration is used. This will be changed to ++ * a per TXN candidate eventually. For now it is assumed that we only have one ++ * change TXN at a time. ++ * ++ * Return: true if the connection should be dropped. ++ */ ++static bool mgmt_be_txn_cfg_prepare(struct mgmt_be_client *client_ctx, uint64_t txn_id, ++ const char **config) + { +- struct mgmt_be_client *client_ctx; +- struct nb_context nb_ctx = {0}; ++ struct mgmt_be_txn_ctx *txn; ++ struct nb_context nb_ctx = { 0 }; + struct timeval edit_nb_cfg_start; + struct timeval edit_nb_cfg_end; + unsigned long edit_nb_cfg_tm; +@@ -483,58 +424,76 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **con + bool error; + char err_buf[BUFSIZ]; + size_t num_processed; ++ bool disconnect = false; + int err; + +- assert(txn && txn->client); +- client_ctx = txn->client; +- + num_processed = 0; +- error = false; + nb_ctx.client = NB_CLIENT_CLI; + nb_ctx.user = (void *)client_ctx->user_data; + err_buf[0] = 0; + ++ debug_be_client("Creating new txn-id %Lu", txn_id); ++ + /* +- * this is always true, right?! ++ * First validate there's no other config txn right now. Eventually we ++ * may want to support multiple CFG_REQ prioer to a CFG_APPLY. + */ +- assert(!txn->nb_txn); ++ if (client_ctx->config_txn) { ++ log_err_be_client("Cannot prepare cfg for txn-id: %Lu, another txn-id: %Lu was in progress", ++ txn_id, client_ctx->config_txn->txn_id); ++ return true; ++ } + +- if (!txn->nb_txn) { +- /* +- * XXX, is this just an old crusty comment? +- * +- * This happens when the current backend client is only +- * interested in consuming the config items but is not +- * interested in validating it. +- */ +- gettimeofday(&edit_nb_cfg_start, NULL); +- +- error = be_client_change_edit_config(client_ctx->candidate_config, config, err_buf, +- sizeof(err_buf)); +- if (error) { +- log_err_be_client("Failed to update configs for txn-id: %" PRIu64 +- " to candidate, err: '%s'", +- txn->txn_id, err_buf); +- return -1; +- } ++ /* Create a txn to track the config changes for later apply */ ++ txn = mgmt_be_txn_create(client_ctx, txn_id); ++ if (!txn) { ++ log_err_be_client("Failed to create txn for txn_id: %Lu", txn_id); ++ return true; ++ } + +- gettimeofday(&edit_nb_cfg_end, NULL); +- edit_nb_cfg_tm = timeval_elapsed(edit_nb_cfg_end, edit_nb_cfg_start); +- client_ctx->avg_edit_nb_cfg_tm = +- ((client_ctx->avg_edit_nb_cfg_tm * client_ctx->num_edit_nb_cfg) + +- edit_nb_cfg_tm) / +- (client_ctx->num_edit_nb_cfg + 1); +- client_ctx->num_edit_nb_cfg++; ++ /* Apply the list of config changes to the candidate config */ ++ gettimeofday(&edit_nb_cfg_start, NULL); ++ error = be_client_change_edit_config(txn->candidate_config, config, err_buf, ++ sizeof(err_buf)); ++ if (error) { ++ log_err_be_client("Failed to update configs for txn-id: %" PRIu64 ++ " to candidate, err: '%s'", ++ txn->txn_id, err_buf); ++ disconnect = true; ++ goto done; + } + ++ gettimeofday(&edit_nb_cfg_end, NULL); ++ edit_nb_cfg_tm = timeval_elapsed(edit_nb_cfg_end, edit_nb_cfg_start); ++ client_ctx->avg_edit_nb_cfg_tm = ++ ((client_ctx->avg_edit_nb_cfg_tm * client_ctx->num_edit_nb_cfg) + edit_nb_cfg_tm) / ++ (client_ctx->num_edit_nb_cfg + 1); ++ client_ctx->num_edit_nb_cfg++; ++ ++ /* Create a northbound transaction and prepare for the changes */ ++ ++ /* ++ * XXX this takes the candidate config we just applied a list of config ++ * changes to, creates a diff, then creates a new list of changes, ++ * again, so that we can use that to call the callbacks. Would should ++ * just use the original list! ++ * ++ * We do need the changes applied to the candidate config for validation ++ * and to eventually replace the running config, but we should be able ++ * to go back and just use the original list. The only possible issue is ++ * if the list includes any new changes. We should maybe build a new ++ * list and then validate that it is the same as the original lsit (or a ++ * subset of it) in development mode, but not in production build. ++ * ++ * Anyway we should time it to see if this is a big deal or not. ++ */ + gettimeofday(&prep_nb_cfg_start, NULL); +- err = nb_candidate_commit_prepare(nb_ctx, client_ctx->candidate_config, +- "MGMTD Backend Txn", &txn->nb_txn, +- false, true, +- err_buf, sizeof(err_buf) - 1); ++ err = nb_candidate_commit_prepare(nb_ctx, txn->candidate_config, "MGMTD Backend Txn", ++ &txn->nb_txn, false, true, err_buf, sizeof(err_buf) - 1); + if (err != NB_OK) { + err_buf[sizeof(err_buf) - 1] = 0; + if (err == NB_ERR_VALIDATION) { ++ /* maybe symantic validation error isn't a LOGERR? */ + log_err_be_client("Failed to validate configs txn-id: %" PRIu64 + " %zu batches, err: '%s'", + txn->txn_id, num_processed, err_buf); +@@ -556,38 +515,39 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **con + + gettimeofday(&prep_nb_cfg_end, NULL); + prep_nb_cfg_tm = timeval_elapsed(prep_nb_cfg_end, prep_nb_cfg_start); +- client_ctx->avg_prep_nb_cfg_tm = ((client_ctx->avg_prep_nb_cfg_tm * +- client_ctx->num_prep_nb_cfg) + +- prep_nb_cfg_tm) / +- (client_ctx->num_prep_nb_cfg + 1); ++ client_ctx->avg_prep_nb_cfg_tm = ++ ((client_ctx->avg_prep_nb_cfg_tm * client_ctx->num_prep_nb_cfg) + prep_nb_cfg_tm) / ++ (client_ctx->num_prep_nb_cfg + 1); + client_ctx->num_prep_nb_cfg++; + +- mgmt_be_send_cfg_reply(client_ctx, txn->txn_id, !error, error ? err_buf : NULL); ++ /* ++ * Replying to mgmtd ++ */ ++ disconnect = !!mgmt_be_send_cfg_reply(client_ctx, txn->txn_id, !error, ++ error ? err_buf : NULL); + + debug_be_client("Avg-nb-edit-duration %Lu uSec, nb-prep-duration %lu (avg: %Lu) uSec, batch size %u", + client_ctx->avg_edit_nb_cfg_tm, prep_nb_cfg_tm, + client_ctx->avg_prep_nb_cfg_tm, (uint32_t)num_processed); +- +- if (error) ++done: ++ /* ++ * If we have any error we abort the new transaction and delete the txn ++ */ ++ if (error || disconnect) { + mgmt_be_txn_cfg_abort(txn); ++ mgmt_be_txn_delete(txn); ++ } + +- return 0; ++ return disconnect; + } + + static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf, + size_t msg_len) + { + struct mgmt_msg_cfg_req *msg = msgbuf; +- struct mgmt_be_txn_ctx *txn; + const char **config = NULL; + +- debug_be_client("Got CFG_DATA_REQ txn-id: %Lu", txn_id); +- +- txn = mgmt_be_find_txn_by_id(client, txn_id, true); +- if (!txn) { +- log_err_be_client("Can't find TXN for txn_id: %Lu", txn_id); +- goto failed; +- } ++ debug_be_client("Got CFG_REQ txn-id: %Lu", txn_id); + + config = mgmt_msg_native_strings_decode(msg, msg_len, msg->config); + if (darr_len(config) == 0) { +@@ -595,9 +555,9 @@ static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id, + goto failed; + } + +- debug_be_client("End of data; CFG_PREPARE_REQ processing"); ++ debug_be_client("CFG_REQ processing"); + +- if (mgmt_be_txn_cfg_prepare(txn, config)) ++ if (mgmt_be_txn_cfg_prepare(client, txn_id, config)) + goto failed; + + darr_free_free(config); +@@ -605,6 +565,7 @@ static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id, + + failed: + darr_free_free(config); ++ log_err_be_client("Disconnecting client %s due to error handling CFG_REQ", client->name); + msg_conn_disconnect(&client->client.conn, true); + } + +@@ -631,16 +592,18 @@ static int mgmt_be_send_apply_reply(struct mgmt_be_client *client_ctx, + return ret; + } + +-static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) ++static bool mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) + { + struct mgmt_be_client *client_ctx; + struct timeval apply_nb_cfg_start; + struct timeval apply_nb_cfg_end; + unsigned long apply_nb_cfg_tm; + char err_buf[BUFSIZ]; ++ bool disconnect; + + assert(txn && txn->client); + client_ctx = txn->client; ++ assert(client_ctx->config_txn == txn); + + assert(txn->nb_txn); + +@@ -648,8 +611,7 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) + * Now apply all the batches we have applied in one go. + */ + gettimeofday(&apply_nb_cfg_start, NULL); +- (void)nb_candidate_commit_apply(txn->nb_txn, true, &txn->nb_txn_id, +- err_buf, sizeof(err_buf) - 1); ++ nb_candidate_commit_apply(txn->nb_txn, true, &txn->nb_txn_id, err_buf, sizeof(err_buf) - 1); + gettimeofday(&apply_nb_cfg_end, NULL); + + apply_nb_cfg_tm = timeval_elapsed(apply_nb_cfg_end, apply_nb_cfg_start); +@@ -659,25 +621,54 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) + client_ctx->num_apply_nb_cfg++; + txn->nb_txn = NULL; + +- mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL); ++ disconnect = !!mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL); + + debug_be_client("Nb-apply-duration %lu (avg: %Lu) uSec", apply_nb_cfg_tm, + client_ctx->avg_apply_nb_cfg_tm); + +- return 0; ++ mgmt_be_txn_delete(txn); ++ ++ return disconnect; + } + + static void be_client_handle_cfg_apply(struct mgmt_be_client *client, uint64_t txn_id) + { +- struct mgmt_be_txn_ctx *txn; ++ struct mgmt_be_txn_ctx *txn = NULL; + + debug_be_client("Got CFG_APPLY_REQ for client %s txn-id %Lu", client->name, txn_id); + +- txn = mgmt_be_find_txn_by_id(client, txn_id, true); ++ if (client->config_txn && client->config_txn->txn_id == txn_id) ++ txn = client->config_txn; ++ else ++ log_err_be_client("client %s unkonwn config txn-id: %Lu", client->name, txn_id); ++ + if (!txn || mgmt_be_txn_proc_cfgapply(txn)) + msg_conn_disconnect(&client->client.conn, true); + } + ++/* ++ * This currently serves as the CFG_ABORT ++ */ ++static void be_client_handle_txn_req(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf, ++ size_t msg_len) ++{ ++ struct mgmt_msg_txn_req *msg = msgbuf; ++ ++ debug_be_client("Got TXN_DELETE txn-id: %Lu", txn_id); ++ if (msg->create) { ++ log_err_be_client("Unsupported TXN_REQ create for txn-id: %Lu", txn_id); ++ goto failed; ++ } ++ if (!client->config_txn || client->config_txn->txn_id != txn_id) { ++ log_err_be_client("Cannot find TXN for TXN_DELETE txn-id: %Lu", txn_id); ++ goto failed; ++ } ++ mgmt_be_txn_delete(client->config_txn); ++ return; ++failed: ++ msg_conn_disconnect(&client->client.conn, true); ++} ++ + + struct be_client_tree_data_batch_args { + struct mgmt_be_client *client; +@@ -1009,6 +1000,7 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client, + + switch (msg->code) { + case MGMT_MSG_CODE_TXN_REQ: ++ /* XXX used as a CFG_ABORT_REQ */ + be_client_handle_txn_req(client, txn_id, msg, msg_len); + break; + case MGMT_MSG_CODE_CFG_REQ: +@@ -1119,8 +1111,12 @@ static int _notify_conenct_disconnect(struct msg_client *msg_client, + if (connected) { + assert(msg_client->conn.fd != -1); + ret = mgmt_be_send_subscribe(client); +- if (ret) ++ if (ret) { ++ log_err_be_client("Failed to send subscribe on connect: %s", client->name); + return ret; ++ } ++ debug_be_client("Sent subscribe on connect: %s: fd: %d", client->name, ++ msg_client->conn.fd); + } + + /* Notify BE client through registered callback (if any) */ +@@ -1227,7 +1223,12 @@ static enum nb_error clients_client_state_candidate_config_version_get( + const struct nb_node *nb_node, const void *parent_list_entry, struct lyd_node *parent) + { + const struct lysc_node *snode = nb_node->snode; +- uint64_t value = __be_client->candidate_config->version; ++ uint64_t value; ++ ++ if (__be_client->config_txn) ++ value = __be_client->config_txn->candidate_config->version; ++ else ++ value = __be_client->running_config->version; + + if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value), + LYD_NEW_PATH_UPDATE, NULL)) +@@ -1376,10 +1377,9 @@ struct mgmt_be_client *mgmt_be_client_create(const char *client_name, + + client->name = XSTRDUP(MTYPE_MGMTD_BE_CLIENT_NAME, client_name); + client->running_config = running_config; +- client->candidate_config = vty_shared_candidate_config; ++ // client->candidate_config = vty_shared_candidate_config; + if (cbs) + client->cbs = *cbs; +- mgmt_be_txns_init(&client->txn_head); + + snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); + +@@ -1417,7 +1417,6 @@ void mgmt_be_client_destroy(struct mgmt_be_client *client) + nb_oper_cancel_all_walks(); + msg_client_cleanup(&client->client); + mgmt_be_cleanup_all_txns(client); +- mgmt_be_txns_fini(&client->txn_head); + + XFREE(MTYPE_MGMTD_BE_CLIENT_NAME, client->name); + XFREE(MTYPE_MGMTD_BE_CLIENT, client); +diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h +index d0b1f12ceb..5d1aeb7b97 100644 +--- a/lib/mgmt_be_client.h ++++ b/lib/mgmt_be_client.h +@@ -54,12 +54,8 @@ struct mgmt_be_client_txn_ctx { + * + */ + struct mgmt_be_client_cbs { +- void (*client_connect_notify)(struct mgmt_be_client *client, +- uintptr_t usr_data, bool connected); +- void (*txn_notify)(struct mgmt_be_client *client, uintptr_t usr_data, +- struct mgmt_be_client_txn_ctx *txn_ctx, +- bool destroyed); +- ++ void (*client_connect_notify)(struct mgmt_be_client *client, uintptr_t usr_data, ++ bool connected); + const char **config_xpaths; + uint nconfig_xpaths; + const char **oper_xpaths; +diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c +index 4fed87f0d3..95d0bafa6b 100644 +--- a/lib/mgmt_fe_client.c ++++ b/lib/mgmt_fe_client.c +@@ -266,9 +266,9 @@ int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, uint64_t session_id, + return ret; + } + +-int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, +- enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dest_ds_id, +- bool validate_only, bool abort, bool unlock) ++int mgmt_fe_send_commit_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, ++ enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dest_ds_id, ++ bool validate_only, bool abort, bool unlock) + { + struct mgmt_msg_commit *msg; + int ret; +@@ -287,8 +287,12 @@ int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_i + msg->action = MGMT_MSG_COMMIT_APPLY; + msg->unlock = unlock; + +- debug_fe_client("Sending COMMIT message for Src-DS:%s, Dst-DS:%s session-id %Lu", +- dsid2name(src_ds_id), dsid2name(dest_ds_id), session_id); ++ debug_fe_client("Sending COMMIT message for src: %s dst: %s session-id %Lu action: %s unlock: %d", ++ dsid2name(src_ds_id), dsid2name(dest_ds_id), session_id, ++ msg->action == MGMT_MSG_COMMIT_VALIDATE ? "validate" ++ : msg->action == MGMT_MSG_COMMIT_ABORT ? "abort" ++ : "apply", ++ unlock); + + ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false); + ret = fe_client_push_sent_msg(client, (struct mgmt_msg_header *)msg, ret); +@@ -695,7 +699,7 @@ static void mgmt_fe_client_process_msg(uint8_t version, uint8_t *data, + return; + } + +- log_err_fe_client("Protobuf no longer used in backend API"); ++ log_err_fe_client("Protobuf no longer used in frontend API"); + msg_conn_disconnect(&client->client.conn, true); + } + +diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h +index 22b167453a..8ff08b566a 100644 +--- a/lib/mgmt_fe_client.h ++++ b/lib/mgmt_fe_client.h +@@ -232,10 +232,10 @@ extern int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, uint64_t sessi + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +-extern int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_id, +- uint64_t req_id, enum mgmt_ds_id src_ds_id, +- enum mgmt_ds_id dst_ds_id, bool validate_only, bool abort, +- bool unlock); ++extern int mgmt_fe_send_commit_req(struct mgmt_fe_client *client, uint64_t session_id, ++ uint64_t req_id, enum mgmt_ds_id src_ds_id, ++ enum mgmt_ds_id dst_ds_id, bool validate_only, bool abort, ++ bool unlock); + + /* + * Send GET-DATA to MGMTD daemon. +diff --git a/lib/mgmt_msg.c b/lib/mgmt_msg.c +index 8f22b31231..615e353217 100644 +--- a/lib/mgmt_msg.c ++++ b/lib/mgmt_msg.c +@@ -17,6 +17,14 @@ + #include "mgmt_msg.h" + #include "mgmt_msg_native.h" + ++static bool trace; ++ ++#define MGMT_MSG_TRACE(dbgtag, fmt, ...) \ ++ do { \ ++ if (dbgtag && trace) \ ++ zlog_debug("%s: %s: " fmt, dbgtag, __func__, ##__VA_ARGS__); \ ++ } while (0) ++ + + #define MGMT_MSG_DBG(dbgtag, fmt, ...) \ + do { \ +@@ -56,6 +64,7 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, + ssize_t n, left; + + assert(ms && fd != -1); ++ MGMT_MSG_TRACE(dbgtag, "enter with %zu bytes available to read on fd %d", avail, fd); + + /* + * Read as much as we can into the stream. +@@ -65,19 +74,18 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, + + /* -2 is normal nothing read, and to retry */ + if (n == -2) { +- MGMT_MSG_DBG(dbgtag, "nothing more to read"); ++ MGMT_MSG_TRACE(dbgtag, "nothing more to read on fd %d", fd); + break; + } + if (n <= 0) { + if (n == 0) +- MGMT_MSG_ERR(ms, "got EOF/disconnect"); ++ MGMT_MSG_ERR(ms, "got EOF/disconnect on fd %d", fd); + else +- MGMT_MSG_ERR(ms, +- "got error while reading: '%s'", ++ MGMT_MSG_ERR(ms, "got error while reading on fd %d: '%s'", fd, + safe_strerror(errno)); + return MSR_DISCONNECT; + } +- MGMT_MSG_DBG(dbgtag, "read %zd bytes", n); ++ MGMT_MSG_TRACE(dbgtag, "read %zd bytes on fd %d", n, fd); + ms->nrxb += n; + avail -= n; + } +@@ -90,13 +98,13 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, + while (left > (ssize_t)sizeof(struct mgmt_msg_hdr)) { + mhdr = (struct mgmt_msg_hdr *)(STREAM_DATA(ms->ins) + total); + if (!MGMT_MSG_IS_MARKER(mhdr->marker)) { +- MGMT_MSG_DBG(dbgtag, "recv corrupt buffer, disconnect"); ++ MGMT_MSG_DBG(dbgtag, "recv corrupt buffer on fd %d, disconnect", fd); + return MSR_DISCONNECT; + } + if ((ssize_t)mhdr->len > left) + break; + +- MGMT_MSG_DBG(dbgtag, "read full message len %u", mhdr->len); ++ MGMT_MSG_TRACE(dbgtag, "read full message on fd %d len %u", fd, mhdr->len); + total += mhdr->len; + left -= mhdr->len; + mcount++; +@@ -166,7 +174,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, + uint8_t *data; + size_t left, nproc; + +- MGMT_MSG_DBG(dbgtag, "Have %zu streams to process", ms->inq.count); ++ MGMT_MSG_TRACE(dbgtag, "Have %zu streams to process", ms->inq.count); + + nproc = 0; + while (nproc < ms->max_read_buf) { +@@ -176,7 +184,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, + + data = STREAM_DATA(work); + left = stream_get_endp(work); +- MGMT_MSG_DBG(dbgtag, "Processing stream of len %zu", left); ++ MGMT_MSG_TRACE(dbgtag, "Processing stream of len %zu", left); + + for (; left > sizeof(struct mgmt_msg_hdr); + left -= mhdr->len, data += mhdr->len) { +@@ -185,10 +193,11 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, + assert(MGMT_MSG_IS_MARKER(mhdr->marker)); + assert(left >= mhdr->len); + +- handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker), +- (uint8_t *)(mhdr + 1), +- mhdr->len - sizeof(struct mgmt_msg_hdr), +- user); ++ /* ++ * Q: if the handler disconnects should stop/flush? ++ */ ++ handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker), (uint8_t *)(mhdr + 1), ++ mhdr->len - sizeof(struct mgmt_msg_hdr), user); + ms->nrxm++; + nproc++; + } +@@ -230,9 +239,8 @@ enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd, + ssize_t n; + + if (ms->outs) { +- MGMT_MSG_DBG(dbgtag, +- "found unqueued stream with %zu bytes, queueing", +- stream_get_endp(ms->outs)); ++ MGMT_MSG_TRACE(dbgtag, "found unqueued stream with %zu bytes on fd %d, queueing", ++ stream_get_endp(ms->outs), fd); + stream_fifo_push(&ms->outq, ms->outs); + ms->outs = NULL; + } +@@ -245,46 +253,40 @@ enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd, + n = stream_flush(s, fd); + if (n <= 0) { + if (n == 0) +- MGMT_MSG_ERR(ms, +- "connection closed while writing"); ++ MGMT_MSG_ERR(ms, "connection closed while writing on fd %d", fd); + else if (ERRNO_IO_RETRY(errno)) { +- MGMT_MSG_DBG( +- dbgtag, +- "retry error while writing %zd bytes: %s (%d)", +- left, safe_strerror(errno), errno); ++ MGMT_MSG_DBG(dbgtag, ++ "retry error while writing %zd bytes on fd %d: %s (%d)", ++ left, fd, safe_strerror(errno), errno); + return MSW_SCHED_STREAM; + } else +- MGMT_MSG_ERR( +- ms, +- "error while writing %zd bytes: %s (%d)", +- left, safe_strerror(errno), errno); ++ MGMT_MSG_ERR(ms, "error while writing %zd bytes on fd %d: %s (%d)", ++ left, fd, safe_strerror(errno), errno); + + n = mgmt_msg_reset_writes(ms); +- MGMT_MSG_DBG(dbgtag, "drop and freed %zd streams", n); ++ MGMT_MSG_ERR(ms, "drop, freed %zd streams on fd %d for disconnect", n, fd); + + return MSW_DISCONNECT; + } + + ms->ntxb += n; + if (n != left) { +- MGMT_MSG_DBG(dbgtag, "short stream write %zd of %zd", n, +- left); ++ MGMT_MSG_DBG(dbgtag, "short stream write %zd of %zd on fd %d", n, left, fd); + stream_forward_getp(s, n); + return MSW_SCHED_STREAM; + } + + stream_free(stream_fifo_pop(&ms->outq)); +- MGMT_MSG_DBG(dbgtag, "wrote stream of %zd bytes", n); ++ MGMT_MSG_TRACE(dbgtag, "wrote stream of %zd bytes on fd %d", n, fd); + nproc++; + } + if (s) { +- MGMT_MSG_DBG( +- dbgtag, +- "reached %zu buffer writes, pausing with %zu streams left", +- ms->max_write_buf, ms->outq.count); ++ MGMT_MSG_DBG(dbgtag, ++ "reached %zu buffer writes on fd %d, pausing with %zu streams left", ++ ms->max_write_buf, fd, ms->outq.count); + return MSW_SCHED_STREAM; + } +- MGMT_MSG_DBG(dbgtag, "flushed all streams from output q"); ++ MGMT_MSG_TRACE(dbgtag, "flushed all streams from output q for fd %d", fd); + return MSW_SCHED_NONE; + } + +@@ -317,30 +319,27 @@ int mgmt_msg_send_msg(struct mgmt_msg_state *ms, uint8_t version, void *msg, + size_t mlen = len + sizeof(*mhdr); + + if (mlen > ms->max_msg_sz) +- MGMT_MSG_DBG(dbgtag, "Sending large msg size %zu > max size %zu", +- mlen, ms->max_msg_sz); ++ MGMT_MSG_TRACE(dbgtag, "Sending large msg size %zu > max size %zu", mlen, ++ ms->max_msg_sz); + + if (!ms->outs) { +- MGMT_MSG_DBG(dbgtag, "creating new stream for msg len %zu", mlen); ++ MGMT_MSG_TRACE(dbgtag, "creating new stream for msg len %zu", mlen); + ms->outs = stream_new(MAX(ms->max_msg_sz, mlen)); + } else if (mlen > ms->max_msg_sz && ms->outs->endp == 0) { + /* msg is larger than stream max size get a fit-to-size stream */ +- MGMT_MSG_DBG(dbgtag, +- "replacing old stream with fit-to-size for msg len %zu", +- mlen); ++ MGMT_MSG_TRACE(dbgtag, "replacing old stream with fit-to-size for msg len %zu", ++ mlen); + stream_free(ms->outs); + ms->outs = stream_new(mlen); + } else if (STREAM_WRITEABLE(ms->outs) < mlen) { +- MGMT_MSG_DBG(dbgtag, +- "enq existing stream len %zu and creating new stream for msg len %zu", +- STREAM_WRITEABLE(ms->outs), mlen); ++ MGMT_MSG_TRACE(dbgtag, ++ "enq existing stream len %zu and creating new stream for msg len %zu", ++ STREAM_WRITEABLE(ms->outs), mlen); + stream_fifo_push(&ms->outq, ms->outs); + ms->outs = stream_new(MAX(ms->max_msg_sz, mlen)); + } else { +- MGMT_MSG_DBG( +- dbgtag, +- "using existing stream with avail %zu for msg len %zu", +- STREAM_WRITEABLE(ms->outs), mlen); ++ MGMT_MSG_TRACE(dbgtag, "using existing stream with avail %zu for msg len %zu", ++ STREAM_WRITEABLE(ms->outs), mlen); + } + s = ms->outs; + +diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c +index 21ca54c3a4..763dbcc73f 100644 +--- a/lib/mgmt_msg_native.c ++++ b/lib/mgmt_msg_native.c +@@ -69,9 +69,67 @@ size_t mgmt_msg_get_min_size(uint code) + return mgmt_msg_min_sizes[code]; + } + +-int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, +- uint64_t req_id, bool short_circuit_ok, +- int16_t error, const char *errfmt, va_list ap) ++const char *mgmt_msg_code_name(uint code) ++{ ++ static char buf[48]; ++ ++ switch (code) { ++ case MGMT_MSG_CODE_ERROR: ++ return "ERROR"; ++ case MGMT_MSG_CODE_GET_TREE: ++ return "GET_TREE"; ++ case MGMT_MSG_CODE_TREE_DATA: ++ return "TREE_DATA"; ++ case MGMT_MSG_CODE_GET_DATA: ++ return "GET_DATA"; ++ case MGMT_MSG_CODE_NOTIFY: ++ return "NOTIFY"; ++ case MGMT_MSG_CODE_EDIT: ++ return "EDIT"; ++ case MGMT_MSG_CODE_EDIT_REPLY: ++ return "EDIT_REPLY"; ++ case MGMT_MSG_CODE_RPC: ++ return "RPC"; ++ case MGMT_MSG_CODE_RPC_REPLY: ++ return "RPC_REPLY"; ++ case MGMT_MSG_CODE_NOTIFY_SELECT: ++ return "NOTIFY_SELECT"; ++ case MGMT_MSG_CODE_SESSION_REQ: ++ return "SESSION_REQ"; ++ case MGMT_MSG_CODE_SESSION_REPLY: ++ return "SESSION_REPLY"; ++ case MGMT_MSG_CODE_LOCK: ++ return "LOCK"; ++ case MGMT_MSG_CODE_LOCK_REPLY: ++ return "LOCK_REPLY"; ++ case MGMT_MSG_CODE_COMMIT: ++ return "COMMIT"; ++ case MGMT_MSG_CODE_COMMIT_REPLY: ++ return "COMMIT_REPLY"; ++ case MGMT_MSG_CODE_SUBSCRIBE: ++ return "SUBSCRIBE"; ++ case MGMT_MSG_CODE_TXN_REQ: ++ return "TXN_REQ"; ++ case MGMT_MSG_CODE_TXN_REPLY: ++ return "TXN_REPLY"; ++ case MGMT_MSG_CODE_CFG_REQ: ++ return "CFG_REQ"; ++ case MGMT_MSG_CODE_CFG_REPLY: ++ return "CFG_REPLY"; ++ case MGMT_MSG_CODE_CFG_APPLY_REQ: ++ return "CFG_APPLY_REQ"; ++ case MGMT_MSG_CODE_CFG_APPLY_REPLY: ++ return "CFG_APPLY_REPLY"; ++ default: ++ snprintf(buf, sizeof(buf), "UNKNOWN(%u)", code); ++ return buf; ++ } ++} ++ ++ ++int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, uint64_t req_id, ++ bool short_circuit_ok, int16_t error, const char *errfmt, ++ va_list ap) + { + struct mgmt_msg_error *msg; + char *errstr; +diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h +index 99ce1e17fa..f6a0ce4d1a 100644 +--- a/lib/mgmt_msg_native.h ++++ b/lib/mgmt_msg_native.h +@@ -215,6 +215,8 @@ DECLARE_MTYPE(MSG_NATIVE_COMMIT_REPLY); + #define MGMT_MSG_DATASTORE_CANDIDATE 2 + #define MGMT_MSG_DATASTORE_OPERATIONAL 3 + ++const char *mgmt_msg_code_name(uint code); ++ + /* + * Formats + */ +@@ -375,7 +377,13 @@ struct mgmt_msg_notify_data { + _Static_assert(sizeof(struct mgmt_msg_notify_data) == offsetof(struct mgmt_msg_notify_data, data), + "Size mismatch"); + +-#define EDIT_FLAG_IMPLICIT_LOCK 0x01 ++/* ++ * deprecated -- can simply do the locks as needed, if the session holds the ++ * locks then they aren't needed, otherwise an attempt to acquire them is made ++ * and if that fails the edit fails. ++ */ ++#define EDIT_FLAG_IMPLICIT_LOCK 0x01 ++/* also deprecated -- unneeded, if DS is candidate, then no "commit", if running then "commit" */ + #define EDIT_FLAG_IMPLICIT_COMMIT 0x02 + + #define EDIT_OP_CREATE 0 +@@ -777,7 +785,7 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn, + * mgmt_msg_free_native_msg() - Free a native msg. + * @msg: pointer to message allocated by mgmt_msg_create_native_msg(). + */ +-#define mgmt_msg_native_free_msg(msg) darr_free(msg) ++#define mgmt_msg_native_free_msg darr_free + + /** + * mgmt_msg_native_get_msg_len() - Get the total length of the msg. +diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c +index 81ed51e518..348cf2cc41 100644 +--- a/lib/northbound_cli.c ++++ b/lib/northbound_cli.c +@@ -218,35 +218,12 @@ static void create_xpath_base_abs(struct vty *vty, char *xpath_base_abs, + static int _nb_cli_apply_changes(struct vty *vty, const char *xpath_base, bool clear_pending) + { + char xpath_base_abs[XPATH_MAXLEN] = {}; +- bool implicit_commit; + + create_xpath_base_abs(vty, xpath_base_abs, sizeof(xpath_base_abs), + xpath_base); + +- if (vty_mgmt_should_process_cli_apply_changes(vty)) { +- VTY_CHECK_XPATH; +- +- assert(vty->type != VTY_FILE); +- /* +- * The legacy user wanted to clear pending (i.e., perform a +- * commit immediately) due to some non-yang compatible +- * functionality. This new mgmtd code however, continues to send +- * changes putting off the commit until XFRR_end is received +- * (i.e., end-of-config-file). This should be fine b/c all +- * conversions to mgmtd require full proper implementations. +- */ +- if (!vty->num_cfg_changes) +- return CMD_SUCCESS; +- +- implicit_commit = vty_needs_implicit_commit(vty); +- if (vty_mgmt_send_config_data(vty, xpath_base_abs, implicit_commit) < 0) { +- vty_out(vty, "%% Failed to apply configuration data.\n"); +- return CMD_WARNING_CONFIG_FAILED; +- } +- if (!implicit_commit) +- ++vty->mgmt_num_pending_setcfg; +- return CMD_SUCCESS; +- } ++ if (vty->type != VTY_FILE && nb_cli_apply_changes_mgmt_cb) ++ return nb_cli_apply_changes_mgmt_cb(vty, xpath_base_abs); + + return nb_cli_apply_changes_internal(vty, xpath_base_abs, clear_pending); + } +@@ -330,20 +307,10 @@ int nb_cli_rpc(struct vty *vty, const char *xpath, struct lyd_node **output_p) + assert(err == LY_SUCCESS); + } + +- if (vty_mgmt_fe_enabled()) { +- char *data = NULL; +- +- err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK); +- assert(err == LY_SUCCESS); +- +- ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data); +- +- free(data); ++ if (nb_cli_rpc_mgmt_cb) { ++ ret = nb_cli_rpc_mgmt_cb(vty, xpath, input); + lyd_free_all(input); +- +- if (ret < 0) +- return CMD_WARNING; +- return CMD_SUCCESS; ++ return ret; + } + + /* validate input tree to create implicit defaults */ +diff --git a/lib/vty.c b/lib/vty.c +index b4e59a2a7b..41319afd0d 100644 +--- a/lib/vty.c ++++ b/lib/vty.c +@@ -73,9 +73,14 @@ enum vty_event { + + struct nb_config *vty_mgmt_candidate_config; + +-static struct mgmt_fe_client *mgmt_fe_client; +-static bool mgmt_fe_connected; +-static uint64_t mgmt_client_id_next; ++void (*vty_new_mgmt_cb)(struct vty *vty); ++void (*vty_close_mgmt_cb)(struct vty *vty); ++int (*vty_config_enter_mgmt_cb)(struct vty *vty, bool private_config, bool exclusive, ++ bool file_lock); ++void (*vty_config_node_exit_mgmt_cb)(struct vty *vty); ++int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs); ++int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input); ++ + + PREDECL_DLIST(vtyservs); + +@@ -128,66 +133,10 @@ static bool vty_log_commands_perm; + /* Vty incremental flush threshold size */ + static const int VTY_MAX_INCREMENTAL_FLUSH = 1024 * 128; + +-char const *const mgmt_daemons[] = { +- "zebra", +-#ifdef HAVE_RIPD +- "ripd", +-#endif +-#ifdef HAVE_RIPNGD +- "ripngd", +-#endif +-#ifdef HAVE_STATICD +- "staticd", +-#endif +-}; +-uint mgmt_daemons_count = array_size(mgmt_daemons); +- +- +-static int vty_mgmt_lock_candidate_inline(struct vty *vty) +-{ +- assert(!vty->mgmt_locked_candidate_ds); +- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true); +- return vty->mgmt_locked_candidate_ds ? 0 : -1; +-} +- +-static int vty_mgmt_unlock_candidate_inline(struct vty *vty) +-{ +- assert(vty->mgmt_locked_candidate_ds); +- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true); +- return vty->mgmt_locked_candidate_ds ? -1 : 0; +-} +- +-static int vty_mgmt_lock_running_inline(struct vty *vty) +-{ +- assert(!vty->mgmt_locked_running_ds); +- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true); +- return vty->mgmt_locked_running_ds ? 0 : -1; +-} +- +-static int vty_mgmt_unlock_running_inline(struct vty *vty) +-{ +- assert(vty->mgmt_locked_running_ds); +- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true); +- return vty->mgmt_locked_running_ds ? -1 : 0; +-} +- +-void vty_mgmt_resume_response(struct vty *vty, int ret) ++void vty_resume_response(struct vty *vty, int ret) + { + uint8_t header[4] = {0, 0, 0, 0}; + +- if (!vty->mgmt_req_pending_cmd) { +- zlog_err( +- "vty resume response called without mgmt_req_pending_cmd"); +- return; +- } +- +- debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64 +- " with '%s'", +- vty->mgmt_req_pending_cmd, vty->mgmt_session_id, +- ret == CMD_SUCCESS ? "success" : "failed"); +- +- vty->mgmt_req_pending_cmd = NULL; +- + if (vty->type != VTY_FILE) { + header[3] = ret; + buffer_put(vty->obuf, header, 4); +@@ -1724,17 +1673,8 @@ struct vty *vty_new(void) + new->max = VTY_BUFSIZ; + new->pass_fd = -1; + +- if (mgmt_fe_client) { +- if (!mgmt_client_id_next) +- mgmt_client_id_next++; +- new->mgmt_client_id = mgmt_client_id_next++; +- new->mgmt_session_id = 0; +- mgmt_fe_create_client_session( +- mgmt_fe_client, new->mgmt_client_id, (uintptr_t) new); +- /* we short-circuit create the session so it must be set now */ +- assertf(new->mgmt_session_id != 0, +- "Failed to create client session for VTY"); +- } ++ if (vty_new_mgmt_cb) ++ vty_new_mgmt_cb(new); + + return new; + } +@@ -2297,77 +2237,6 @@ void vty_pass_fd(struct vty *vty, int fd) + vty->pass_fd = fd; + } + +-bool mgmt_vty_read_configs(void) +-{ +- char path[PATH_MAX]; +- struct vty *vty; +- FILE *confp; +- uint line_num = 0; +- uint count = 0; +- uint index; +- +- vty = vty_new(); +- vty->wfd = STDERR_FILENO; +- vty->type = VTY_FILE; +- vty->node = CONFIG_NODE; +- vty->config = true; +- vty->pending_allowed = true; +- +- vty->candidate_config = vty_shared_candidate_config; +- +- vty_mgmt_lock_candidate_inline(vty); +- vty_mgmt_lock_running_inline(vty); +- +- for (index = 0; index < array_size(mgmt_daemons); index++) { +- snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir, +- mgmt_daemons[index]); +- +- confp = vty_open_config(path, config_default); +- if (!confp) +- continue; +- +- zlog_info("mgmtd: reading config file: %s", path); +- +- /* Execute configuration file */ +- line_num = 0; +- (void)config_from_file(vty, confp, &line_num); +- count++; +- +- fclose(confp); +- } +- +- snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir); +- confp = vty_open_config(path, config_default); +- if (confp) { +- zlog_info("mgmtd: reading config file: %s", path); +- +- line_num = 0; +- (void)config_from_file(vty, confp, &line_num); +- count++; +- +- fclose(confp); +- } +- +- /* Conditionally unlock as the config file may have "exit"d early which +- * would then have unlocked things. +- */ +- if (vty->mgmt_locked_running_ds) +- vty_mgmt_unlock_running_inline(vty); +- if (vty->mgmt_locked_candidate_ds) +- vty_mgmt_unlock_candidate_inline(vty); +- +- vty->pending_allowed = false; +- +- if (!count) +- vty_close(vty); +- else +- vty_read_file_finish(vty, NULL); +- +- zlog_info("mgmtd: finished reading config files"); +- +- return true; +-} +- + static void vtysh_read(struct event *thread) + { + int ret; +@@ -2568,12 +2437,8 @@ void vty_close(struct vty *vty) + /* Drop out of configure / transaction if needed. */ + vty_config_exit(vty); + +- if (mgmt_fe_client && vty->mgmt_session_id) { +- debug_fe_client("closing vty session"); +- mgmt_fe_destroy_client_session(mgmt_fe_client, +- vty->mgmt_client_id); +- vty->mgmt_session_id = 0; +- } ++ if (vty_close_mgmt_cb) ++ vty_close_mgmt_cb(vty); + + /* Cancel threads.*/ + event_cancel(&vty->t_read); +@@ -2936,39 +2801,17 @@ bool vty_read_config(struct nb_config *config, const char *config_file, + int vty_config_enter(struct vty *vty, bool private_config, bool exclusive, + bool file_lock) + { +- if (exclusive && !vty_mgmt_fe_enabled() && +- nb_running_lock(NB_CLIENT_CLI, vty)) { ++ int ret = CMD_SUCCESS; ++ ++ if (vty_config_enter_mgmt_cb) ++ ret = vty_config_enter_mgmt_cb(vty, private_config, exclusive, file_lock); ++ else if (exclusive && nb_running_lock(NB_CLIENT_CLI, vty)) { + vty_out(vty, "%% Configuration is locked by other client\n"); +- return CMD_WARNING; ++ ret = CMD_WARNING; + } + +- /* +- * We only need to do a lock when reading a config file as we will be +- * sending a batch of setcfg changes followed by a single commit +- * message. For user interactive mode we are doing implicit commits +- * those will obtain the lock (or not) when they try and commit. +- */ +- if (file_lock && vty_mgmt_fe_enabled() && !private_config) { +- if (vty_mgmt_lock_candidate_inline(vty)) { +- vty_out(vty, +- "%% Can't enter config; candidate datastore locked by another session\n"); +- return CMD_WARNING_CONFIG_FAILED; +- } +- if (vty_mgmt_lock_running_inline(vty)) { +- vty_out(vty, +- "%% Can't enter config; running datastore locked by another session\n"); +- vty_mgmt_unlock_candidate_inline(vty); +- return CMD_WARNING_CONFIG_FAILED; +- } +- assert(vty->mgmt_locked_candidate_ds); +- assert(vty->mgmt_locked_running_ds); +- +- /* +- * As datastores are locked explicitly, we don't need implicit +- * commits and should allow pending changes. +- */ +- vty->pending_allowed = true; +- } ++ if (ret != CMD_SUCCESS) ++ return ret; + + vty->node = CONFIG_NODE; + vty->config = true; +@@ -3026,11 +2869,8 @@ int vty_config_node_exit(struct vty *vty) + + vty->pending_allowed = false; + +- if (vty->mgmt_locked_running_ds) +- vty_mgmt_unlock_running_inline(vty); +- +- if (vty->mgmt_locked_candidate_ds) +- vty_mgmt_unlock_candidate_inline(vty); ++ if (vty_config_node_exit_mgmt_cb) ++ vty_config_node_exit_mgmt_cb(vty); + + /* Perform any pending commits. */ + (void)nb_cli_pending_commit_check(vty); +@@ -3539,575 +3379,6 @@ void vty_init_vtysh(void) + /* currently nothing to do, but likely to have future use */ + } + +- +-/* +- * These functions allow for CLI handling to be placed inside daemons; however, +- * currently they are only used by mgmtd, with mgmtd having each daemons CLI +- * functionality linked into it. This design choice was taken for efficiency. +- */ +- +-static void vty_mgmt_server_connected(struct mgmt_fe_client *client, +- uintptr_t usr_data, bool connected) +-{ +- debug_fe_client("Got %sconnected %s MGMTD Frontend Server", +- !connected ? "dis: " : "", !connected ? "from" : "to"); +- +- /* +- * We should not have any sessions for connecting or disconnecting case. +- * The fe client library will delete all session on disconnect before +- * calling us. +- */ +- assert(mgmt_fe_client_session_count(client) == 0); +- +- mgmt_fe_connected = connected; +- +- /* Start or stop listening for vty connections */ +- if (connected) +- frr_vty_serv_start(true); +- else +- frr_vty_serv_stop(); +-} +- +-/* +- * A session has successfully been created for a vty. +- */ +-static void vty_mgmt_session_notify(struct mgmt_fe_client *client, +- uintptr_t usr_data, uint64_t client_id, +- bool create, bool success, +- uintptr_t session_id, uintptr_t session_ctx) +-{ +- struct vty *vty; +- +- vty = (struct vty *)session_ctx; +- +- if (!success) { +- zlog_err("%s session for client %" PRIu64 " failed!", +- create ? "Creating" : "Destroying", client_id); +- return; +- } +- +- debug_fe_client("%s session for client %" PRIu64 " successfully", +- create ? "Created" : "Destroyed", client_id); +- +- if (create) { +- assert(session_id != 0); +- vty->mgmt_session_id = session_id; +- } else { +- vty->mgmt_session_id = 0; +- /* We may come here by way of vty_close() and short-circuits */ +- if (vty->status != VTY_CLOSE) +- vty_close(vty); +- } +-} +- +-static void vty_mgmt_ds_lock_notified(struct mgmt_fe_client *client, uintptr_t usr_data, +- uint64_t client_id, uintptr_t session_id, +- uintptr_t session_ctx, uint64_t req_id, bool lock_ds, +- bool success, enum mgmt_ds_id ds_id, char *errmsg_if_any) +-{ +- struct vty *vty; +- bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client); +- +- vty = (struct vty *)session_ctx; +- +- assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING); +- if (!success) +- zlog_err("%socking for DS %u failed, Err: '%s' vty %p", +- lock_ds ? "L" : "Unl", ds_id, errmsg_if_any, vty); +- else { +- debug_fe_client("%socked DS %u successfully", +- lock_ds ? "L" : "Unl", ds_id); +- if (ds_id == MGMTD_DS_CANDIDATE) +- vty->mgmt_locked_candidate_ds = lock_ds; +- else +- vty->mgmt_locked_running_ds = lock_ds; +- } +- +- if (!is_short_circuit && vty->mgmt_req_pending_cmd) { +- assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ")); +- vty_mgmt_resume_response(vty, +- success ? CMD_SUCCESS : CMD_WARNING); +- } +-} +- +-static void vty_mgmt_commit_config_result_notified(struct mgmt_fe_client *client, +- uintptr_t usr_data, uint64_t client_id, +- uintptr_t session_id, uintptr_t session_ctx, +- uint64_t req_id, bool success, +- enum mgmt_ds_id src_ds_id, +- enum mgmt_ds_id dst_ds_id, bool validate_only, +- bool unlock, char *errmsg_if_any) +-{ +- struct vty *vty; +- +- vty = (struct vty *)session_ctx; +- +- if (!success) { +- zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 +- " failed, Error: '%s'", +- client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); +- vty_out(vty, "%% Configuration failed.\n\n"); +- if (errmsg_if_any) +- vty_out(vty, "%s\n", errmsg_if_any); +- } else { +- debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64 +- " req-id %" PRIu64 " was successfull%s%s", +- client_id, req_id, errmsg_if_any ? ": " : "", +- errmsg_if_any ?: ""); +- if (!unlock && errmsg_if_any) +- vty_out(vty, "MGMTD: %s\n", errmsg_if_any); +- } +- +- if (unlock) { +- /* we locked these when we sent the commit, unlock now */ +- vty_mgmt_unlock_candidate_inline(vty); +- vty_mgmt_unlock_running_inline(vty); +- } +- +- vty_mgmt_resume_response(vty, success ? CMD_SUCCESS +- : CMD_WARNING_CONFIG_FAILED); +-} +- +-static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf, +- size_t count) +-{ +- struct vty *vty = user_data; +- +- vty_out(vty, "%.*s", (int)count, (const char *)buf); +- return count; +-} +- +-static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format, +- const struct ly_err_item *ei) +-{ +-#if (LY_VERSION_MAJOR < 3) +-#define data_path path +-#else +-#define data_path data_path +-#endif +- bool have_apptag = ei->apptag && ei->apptag[0] != 0; +- bool have_path = ei->data_path && ei->data_path[0] != 0; +- bool have_msg = ei->msg && ei->msg[0] != 0; +- const char *severity = NULL; +- const char *evalid = NULL; +- const char *ecode = NULL; +-#if (LY_VERSION_MAJOR < 3) +- LY_ERR err = ei->no; +-#else +- LY_ERR err = ei->err; +-#endif +- +- if (ei->level == LY_LLERR) +- severity = "error"; +- else if (ei->level == LY_LLWRN) +- severity = "warning"; +- +- ecode = yang_ly_strerrcode(err); +- if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS) +- evalid = yang_ly_strvecode(ei->vecode); +- +- switch (format) { +- case LYD_XML: +- vty_out(vty, +- "<rpc-error xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"); +- vty_out(vty, "<error-type>application</error-type>"); +- if (severity) +- vty_out(vty, "<error-severity>%s</error-severity>", +- severity); +- if (ecode) +- vty_out(vty, "<error-code>%s</error-code>", ecode); +- if (evalid) +- vty_out(vty, "<error-validation>%s</error-validation>\n", +- evalid); +- if (have_path) +- vty_out(vty, "<error-path>%s</error-path>\n", +- ei->data_path); +- if (have_apptag) +- vty_out(vty, "<error-app-tag>%s</error-app-tag>\n", +- ei->apptag); +- if (have_msg) +- vty_out(vty, "<error-message>%s</error-message>\n", +- ei->msg); +- +- vty_out(vty, "</rpc-error>"); +- break; +- case LYD_JSON: +- vty_out(vty, "{ \"error-type\": \"application\""); +- if (severity) +- vty_out(vty, ", \"error-severity\": \"%s\"", severity); +- if (ecode) +- vty_out(vty, ", \"error-code\": \"%s\"", ecode); +- if (evalid) +- vty_out(vty, ", \"error-validation\": \"%s\"", evalid); +- if (have_path) +- vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path); +- if (have_apptag) +- vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag); +- if (have_msg) +- vty_out(vty, ", \"error-message\": \"%s\"", ei->msg); +- +- vty_out(vty, "}"); +- break; +- case LYD_UNKNOWN: +- case LYD_LYB: +- default: +- vty_out(vty, "%% error"); +- if (severity) +- vty_out(vty, " severity: %s", severity); +- if (evalid) +- vty_out(vty, " invalid: %s", evalid); +- if (have_path) +- vty_out(vty, " path: %s", ei->data_path); +- if (have_apptag) +- vty_out(vty, " app-tag: %s", ei->apptag); +- if (have_msg) +- vty_out(vty, " msg: %s", ei->msg); +- break; +- } +-#undef data_path +-} +- +-static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format) +-{ +- const struct ly_err_item *ei = ly_err_first(ly_native_ctx); +- uint count; +- +- if (!ei) +- return 0; +- +- if (format == LYD_JSON) +- vty_out(vty, "\"ietf-restconf:errors\": [ "); +- +- for (count = 0; ei; count++, ei = ei->next) { +- if (count) +- vty_out(vty, ", "); +- vty_out_yang_error(vty, format, ei); +- } +- +- if (format == LYD_JSON) +- vty_out(vty, " ]"); +- +- ly_err_clean(ly_native_ctx, NULL); +- +- return count; +-} +- +- +-static int vty_mgmt_get_tree_result_notified(struct mgmt_fe_client *client, uintptr_t user_data, +- uint64_t client_id, uint64_t session_id, +- uintptr_t session_ctx, uint64_t req_id, +- enum mgmt_ds_id ds_id, LYD_FORMAT result_type, +- void *result, size_t len, int partial_error) +-{ +- struct vty *vty; +- struct lyd_node *dnode; +- int ret = CMD_SUCCESS; +- LY_ERR err; +- +- vty = (struct vty *)session_ctx; +- +- debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64 +- " req-id %" PRIu64, +- partial_error ? "partially " : "", client_id, req_id); +- +- assert(result_type == LYD_LYB || +- result_type == vty->mgmt_req_pending_data); +- +- if (vty->mgmt_req_pending_data == LYD_XML && partial_error) +- vty_out(vty, +- "<!-- some errors occurred gathering results -->\n"); +- +- if (result_type == LYD_LYB) { +- /* +- * parse binary into tree and print in the specified format +- */ +- result_type = vty->mgmt_req_pending_data; +- +- err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0, +- &dnode); +- if (!err) +- err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode, +- result_type, LYD_PRINT_WITHSIBLINGS); +- lyd_free_all(dnode); +- +- if (vty_out_yang_errors(vty, result_type) || err) +- ret = CMD_WARNING; +- } else { +- /* +- * Print the in-format result +- */ +- assert(result_type == LYD_XML || result_type == LYD_JSON); +- vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result); +- } +- +- vty_mgmt_resume_response(vty, ret); +- +- return 0; +-} +- +-static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client, +- uintptr_t user_data, +- uint64_t client_id, uint64_t session_id, +- uintptr_t session_ctx, uint64_t req_id, +- const char *xpath) +-{ +- struct vty *vty = (struct vty *)session_ctx; +- +- debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64 +- " was successful, xpath: %s", +- client_id, req_id, xpath); +- +- vty_mgmt_resume_response(vty, CMD_SUCCESS); +- +- return 0; +-} +- +-static int vty_mgmt_rpc_result_notified(struct mgmt_fe_client *client, +- uintptr_t user_data, uint64_t client_id, +- uint64_t session_id, +- uintptr_t session_ctx, uint64_t req_id, +- const char *result) +-{ +- struct vty *vty = (struct vty *)session_ctx; +- +- debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64 +- " was successful", +- client_id, req_id); +- +- if (result) +- vty_out(vty, "%s\n", result); +- +- vty_mgmt_resume_response(vty, CMD_SUCCESS); +- +- return 0; +-} +- +-static int vty_mgmt_error_notified(struct mgmt_fe_client *client, +- uintptr_t user_data, uint64_t client_id, +- uint64_t session_id, uintptr_t session_ctx, +- uint64_t req_id, int error, +- const char *errstr) +-{ +- struct vty *vty = (struct vty *)session_ctx; +- const char *cname = mgmt_fe_client_name(client); +- +- if (!vty->mgmt_req_pending_cmd) { +- debug_fe_client("Error with no pending command: %d returned for client %s 0x%" PRIx64 +- " session-id %" PRIu64 " req-id %" PRIu64 +- "error-str %s", +- error, cname, client_id, session_id, req_id, +- errstr); +- vty_out(vty, +- "%% Error %d from MGMTD for %s with no pending command: %s\n", +- error, cname, errstr); +- return CMD_WARNING; +- } +- +- debug_fe_client("Error %d returned for client %s 0x%" PRIx64 +- " session-id %" PRIu64 " req-id %" PRIu64 "error-str %s", +- error, cname, client_id, session_id, req_id, errstr); +- +- vty_out(vty, "%% %s (for %s, client %s)\n", errstr, +- vty->mgmt_req_pending_cmd, cname); +- +- vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS); +- +- return 0; +-} +- +-static struct mgmt_fe_client_cbs mgmt_cbs = { +- .client_connect_notify = vty_mgmt_server_connected, +- .client_session_notify = vty_mgmt_session_notify, +- .lock_ds_notify = vty_mgmt_ds_lock_notified, +- .commit_config_notify = vty_mgmt_commit_config_result_notified, +- .get_tree_notify = vty_mgmt_get_tree_result_notified, +- .edit_notify = vty_mgmt_edit_result_notified, +- .rpc_notify = vty_mgmt_rpc_result_notified, +- .error_notify = vty_mgmt_error_notified, +- +-}; +- +-void vty_init_mgmt_fe(void) +-{ +- char name[40]; +- +- assert(vty_master); +- assert(!mgmt_fe_client); +- snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(), +- (long)getpid()); +- mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, vty_master); +- assert(mgmt_fe_client); +-} +- +-bool vty_mgmt_fe_enabled(void) +-{ +- return mgmt_fe_client && mgmt_fe_connected; +-} +- +-bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty) +-{ +- return vty->type != VTY_FILE && vty_mgmt_fe_enabled(); +-} +- +-int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok) +-{ +- assert(mgmt_fe_client); +- assert(vty->mgmt_session_id); +- +- vty->mgmt_req_id++; +- if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id, +- vty->mgmt_req_id, ds_id, lock, scok)) { +- zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64, +- lock ? "" : "UN", vty->mgmt_req_id); +- vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n", +- lock ? "" : "UN"); +- return -1; +- } +- +- if (!scok) +- vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ"; +- +- return 0; +-} +- +-int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, +- bool implicit_commit) +-{ +- char err_buf[BUFSIZ]; +- bool error = false; +- +- /* ADD this: candidate is locked or error */ +- +- if (implicit_commit) { +- assert(vty->mgmt_client_id && vty->mgmt_session_id); +- if (vty_mgmt_lock_candidate_inline(vty)) { +- vty_out(vty, "%% could not lock candidate DS\n"); +- return CMD_WARNING_CONFIG_FAILED; +- } else if (vty_mgmt_lock_running_inline(vty)) { +- vty_out(vty, "%% could not lock running DS\n"); +- vty_mgmt_unlock_candidate_inline(vty); +- return CMD_WARNING_CONFIG_FAILED; +- } +- } +- +- nb_candidate_edit_config_changes(vty->candidate_config, vty->cfg_changes, +- vty->num_cfg_changes, xpath_base, false, err_buf, +- sizeof(err_buf), &error); +- if (error) { +- /* +- * Failure to edit the candidate configuration should never +- * happen in practice, unless there's a bug in the code. When +- * that happens, log the error but otherwise ignore it. +- */ +- vty_out(vty, "%% Couldn't apply changes: %s", err_buf); +- +-#if 0 // or do we expect the caller to do this? +- /* the candidate is no longer valid so restore it */ +- nb_config_replace(vty->candidate_config, running_config, true); +-#endif +-error: +- if (implicit_commit) { +- vty_mgmt_unlock_running_inline(vty); +- vty_mgmt_unlock_candidate_inline(vty); +- } +- return CMD_WARNING_CONFIG_FAILED; +- } +- +- if (!implicit_commit) +- return CMD_SUCCESS; +- +- assert(vty->mgmt_client_id && vty->mgmt_session_id); +- if (vty_mgmt_send_commit_config(vty, false, false, true) < 0) +- goto error; +- +- return CMD_SUCCESS; +-} +- +-int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock) +-{ +- if (mgmt_fe_client && vty->mgmt_session_id) { +- vty->mgmt_req_id++; +- if (mgmt_fe_send_commitcfg_req(mgmt_fe_client, vty->mgmt_session_id, +- vty->mgmt_req_id, MGMTD_DS_CANDIDATE, +- MGMTD_DS_RUNNING, validate_only, abort, unlock)) { +- zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64, +- vty->mgmt_req_id); +- vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n"); +- return -1; +- } +- +- vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ"; +- vty->mgmt_num_pending_setcfg = 0; +- } +- +- return 0; +-} +- +-int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, +- LYD_FORMAT result_type, uint8_t flags, +- uint8_t defaults, 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, datastore, +- intern_format, flags, defaults, xpath)) { +- 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-DATA to MGMTD!\n"); +- return -1; +- } +- +- vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ"; +- vty->mgmt_req_pending_data = result_type; +- +- return 0; +-} +- +-int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, +- LYD_FORMAT request_type, uint8_t flags, +- uint8_t operation, const char *xpath, +- const char *data) +-{ +- vty->mgmt_req_id++; +- +- if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id, +- vty->mgmt_req_id, datastore, request_type, +- flags, operation, xpath, data)) { +- zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64 +- " req-id %" PRIu64 ".", +- vty->mgmt_session_id, vty->mgmt_req_id); +- vty_out(vty, "Failed to send EDIT to MGMTD!\n"); +- return -1; +- } +- +- vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ"; +- +- return 0; +-} +- +-int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, +- const char *xpath, const char *data) +-{ +- vty->mgmt_req_id++; +- +- if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id, +- vty->mgmt_req_id, request_type, xpath, data)) { +- zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64 +- " req-id %" PRIu64 ".", +- vty->mgmt_session_id, vty->mgmt_req_id); +- vty_out(vty, "Failed to send RPC to MGMTD!\n"); +- return -1; +- } +- +- vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ"; +- +- return 0; +-} +- + /* Install vty's own commands like `who' command. */ + void vty_init(struct event_loop *master_thread, bool do_command_logging) + { +@@ -4173,10 +3444,5 @@ void vty_terminate(void) + vtys_fini(vtysh_sessions); + vtys_init(vtysh_sessions); + +- if (mgmt_fe_client) { +- mgmt_fe_client_destroy(mgmt_fe_client); +- mgmt_fe_client = NULL; +- } +- + vty_serv_stop(); + } +diff --git a/lib/vty.h b/lib/vty.h +index 83185c7b8f..5caa412ef7 100644 +--- a/lib/vty.h ++++ b/lib/vty.h +@@ -415,30 +415,6 @@ extern void vty_stdio_suspend(void); + extern void vty_stdio_resume(void); + extern void vty_stdio_close(void); + +-extern void vty_init_mgmt_fe(void); +-extern bool vty_mgmt_fe_enabled(void); +-extern bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty); +- +-extern bool mgmt_vty_read_configs(void); +-extern int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, +- bool implicit_commit); +-extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock); +-extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, +- LYD_FORMAT result_type, uint8_t flags, +- uint8_t defaults, const char *xpath); +-extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, +- LYD_FORMAT request_type, uint8_t flags, +- uint8_t operation, const char *xpath, +- const char *data); +-extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, +- const char *xpath, const char *data); +-extern int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok); +-extern void vty_mgmt_resume_response(struct vty *vty, int ret); +- +-static inline bool vty_needs_implicit_commit(const struct vty *vty) +-{ +- return frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed; +-} + + /* Applications can check vty status */ + static inline bool vty_is_closed(const struct vty *vty) +@@ -447,6 +423,23 @@ static inline bool vty_is_closed(const struct vty *vty) + vty->wfd < 0); + } + ++/* ++ * Semi-private APIs for use in mgmtd-vty code ++ */ ++extern void vty_resume_response(struct vty *vty, int ret); ++ ++/* --------------------------------------------------- */ ++/* Callbacks for Mgmtd front-end CLI vty modifications */ ++/* --------------------------------------------------- */ ++extern void (*vty_new_mgmt_cb)(struct vty *vty); ++extern void (*vty_close_mgmt_cb)(struct vty *vty); ++extern int (*vty_config_enter_mgmt_cb)(struct vty *vty, bool private_config, bool exclusive, ++ bool file_lock); ++extern void (*vty_config_node_exit_mgmt_cb)(struct vty *vty); ++extern int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs); ++extern int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input); ++ ++ + #ifdef __cplusplus + } + #endif +diff --git a/mgmtd/mgmt.c b/mgmtd/mgmt.c +index 7f8a142171..d882fa2ed0 100644 +--- a/mgmtd/mgmt.c ++++ b/mgmtd/mgmt.c +@@ -57,7 +57,7 @@ void mgmt_init(void) + mgmt_history_init(); + + /* Initialize MGMTD Transaction module */ +- mgmt_txn_init(mm, mm->master); ++ mgmt_txn_init(); + + /* Initialize the MGMTD Frontend Adapter Module */ + mgmt_fe_adapter_init(mm->master); +@@ -66,7 +66,7 @@ void mgmt_init(void) + * Initialize the CLI frontend client -- this queues an event for the + * client to short-circuit connect to the server (ourselves). + */ +- vty_init_mgmt_fe(); ++ vty_mgmt_init(); + + /* + * MGMTD VTY commands installation -- the frr lib code will queue an +@@ -91,9 +91,9 @@ void mgmt_init(void) + void mgmt_terminate(void) + { + mgmt_be_client_destroy(mgmt_be_client); ++ mgmt_txn_destroy(); + mgmt_fe_adapter_destroy(); + mgmt_be_adapter_destroy(); +- mgmt_txn_destroy(); + mgmt_history_destroy(); + mgmt_ds_destroy(); + } +diff --git a/mgmtd/mgmt.h b/mgmtd/mgmt.h +index 665e8d8fd8..592b993ad5 100644 +--- a/mgmtd/mgmt.h ++++ b/mgmtd/mgmt.h +@@ -33,7 +33,7 @@ extern struct debug mgmt_debug_txn; + #define MGMT_DEBUG_FE_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL) + #define MGMT_DEBUG_TXN_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_tx, DEBUG_MODE_ALL) + +-struct mgmt_txn_ctx; ++struct mgmt_txn; + + /* + * MGMTD master for system wide configurations and variables. +@@ -44,16 +44,6 @@ struct mgmt_master { + /* How big should we set the socket buffer size */ + uint32_t socket_buffer; + +- /* The single instance of config transaction allowed at any time */ +- struct mgmt_txns_head txn_list; +- +- /* Map of Transactions and its ID */ +- struct hash *txn_hash; +- uint64_t next_txn_id; +- +- /* The single instance of config transaction allowed at any time */ +- struct mgmt_txn_ctx *cfg_txn; +- + /* Datastores */ + struct mgmt_ds_ctx *running_ds; + struct mgmt_ds_ctx *candidate_ds; +@@ -66,6 +56,7 @@ struct mgmt_master { + struct mgmt_cmt_infos_head cmts; /* List of last 10 commits executed. */ + }; + ++extern struct frr_daemon_info *mgmt_daemon_info; + extern struct mgmt_master *mm; + + /* Inline functions */ +@@ -101,4 +92,24 @@ extern void mgmt_master_init(struct event_loop *master, const int buffer_size); + extern void mgmt_init(void); + extern void mgmt_vty_init(void); + ++/* ++ * mgmt vty ++ */ ++extern void vty_mgmt_init(void); ++extern void vty_mgmt_terminate(void); ++ ++extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, ++ bool unlock); ++extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, LYD_FORMAT result_type, ++ uint8_t flags, uint8_t defaults, const char *xpath); ++extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, LYD_FORMAT request_type, ++ uint8_t flags, uint8_t operation, const char *xpath, ++ const char *data); ++extern void vty_mgmt_resume_response(struct vty *vty, int ret); ++ ++ ++extern int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, bool implicit_commit); ++extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, const char *xpath, ++ const char *data); ++ + #endif /* _FRR_MGMTD_H */ +diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c +index 6d0e303c9a..ff31d29051 100644 +--- a/mgmtd/mgmt_be_adapter.c ++++ b/mgmtd/mgmt_be_adapter.c +@@ -4,7 +4,7 @@ + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar <spushpasis@vmware.com> +- * Copyright (c) 2023, LabN Consulting, L.L.C. ++ * Copyright (c) 2023-2025, LabN Consulting, L.L.C. + */ + + #include <zebra.h> +@@ -22,15 +22,35 @@ + #include "mgmtd/mgmt_be_adapter.h" + + #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_be, "BE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__) ++#define _log_warn(fmt, ...) zlog_warn("BE-ADAPTER: %s: WARNING: " fmt, __func__, ##__VA_ARGS__) + #define _log_err(fmt, ...) zlog_err("BE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) + +-#define FOREACH_ADAPTER_IN_LIST(adapter) \ +- frr_each_safe (mgmt_be_adapters, &mgmt_be_adapters, (adapter)) + + /* ---------- */ +-/* Client IDs */ ++/* Prototypes */ + /* ---------- */ + ++/* Forward declarations */ ++static void be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter); ++ ++static void be_adapter_delete(struct mgmt_be_client_adapter *adapter); ++ ++static enum mgmt_be_client_id mgmt_be_client_name2id(const char *name); ++ ++ ++/* --------- */ ++/* Constants */ ++/* --------- */ ++ ++/* ++ * Client IDs ++ */ ++ ++/* ++ * NOTE: This mapping is more trouble than it's worth. Just convert to a dynamic ++ * allocation as backends subscribe/register. ++ */ ++ + const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { + [MGMTD_BE_CLIENT_ID_TESTC] = "mgmtd-testc", /* always first */ + [MGMTD_BE_CLIENT_ID_MGMTD] = "mgmtd", /* loopback */ +@@ -47,9 +67,15 @@ const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { + [MGMTD_BE_CLIENT_ID_MAX] = "Unknown/Invalid", + }; + +-/* ------------- */ +-/* XPATH MAPPING */ +-/* ------------- */ ++ ++/* ------------------------------- */ ++/* Const XPath Mappings (for init) */ ++/* ------------------------------- */ ++ ++/* ++ * NOTE: These mappings are more trouble than they are worth. Just convert to ++ * use the subscribe message exclusively. ++ */ + + /* + * Mapping of YANG XPath prefixes to their corresponding backend clients. +@@ -228,6 +254,10 @@ static const char *const *be_client_rpc_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { + [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_rpc_xpaths, + }; + ++/* ---------------- */ ++/* Global Variables */ ++/* ---------------- */ ++ + /* + * We would like to have a better ADT than one with O(n) comparisons + * +@@ -245,7 +275,7 @@ static struct mgmt_be_xpath_map *be_rpc_xpath_map; + static struct event_loop *mgmt_loop; + static struct msg_server mgmt_be_server = {.fd = -1}; + +-static struct mgmt_be_adapters_head mgmt_be_adapters; ++LIST_HEAD(be_adapter_list_head, mgmt_be_client_adapter) be_adapters; + + static struct mgmt_be_client_adapter + *mgmt_be_adapters_by_id[MGMTD_BE_CLIENT_ID_MAX]; +@@ -259,14 +289,11 @@ static const char *const mgmtd_config_xpaths[] = { + }; + + +-/* Forward declarations */ +-static void +-mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter); +- +-static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, +- enum mgmt_be_xpath_subscr_type type); ++/* ---------------- */ ++/* Lookup Functions */ ++/* ---------------- */ + +-const char *mgmt_be_client_id2name(enum mgmt_be_client_id id) ++static const char *be_adapter_id_name(enum mgmt_be_client_id id) + { + if (id > MGMTD_BE_CLIENT_ID_MAX) + return "invalid client id"; +@@ -278,43 +305,173 @@ static enum mgmt_be_client_id mgmt_be_client_name2id(const char *name) + enum mgmt_be_client_id id; + + FOREACH_MGMTD_BE_CLIENT_ID (id) { +- if (!strncmp(mgmt_be_client_names[id], name, +- MGMTD_CLIENT_NAME_MAX_LEN)) ++ if (!strncmp(mgmt_be_client_names[id], name, MGMTD_CLIENT_NAME_MAX_LEN)) + return id; + } + + return MGMTD_BE_CLIENT_ID_MAX; + } + +-static struct mgmt_be_client_adapter * +-mgmt_be_find_adapter_by_fd(int conn_fd) ++static struct mgmt_be_client_adapter *mgmt_be_find_adapter_by_fd(int conn_fd) + { + struct mgmt_be_client_adapter *adapter; + +- FOREACH_ADAPTER_IN_LIST (adapter) { ++ LIST_FOREACH (adapter, &be_adapters, link) + if (adapter->conn->fd == conn_fd) + return adapter; ++ return NULL; ++} ++ ++struct mgmt_be_client_adapter *mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id) ++{ ++ return (id < MGMTD_BE_CLIENT_ID_MAX ? mgmt_be_adapters_by_id[id] : NULL); ++} ++ ++ ++/* ======================= */ ++/* XPath Mapping Functions */ ++/* ======================= */ ++ ++/* ++ * Check if either path or xpath is a prefix of the other. Before checking the ++ * xpath is converted to a regular path string (e..g, removing key value ++ * specifiers). ++ */ ++static bool mgmt_be_xpath_prefix(const char *path, const char *xpath) ++{ ++ int xc, pc; ++ ++ while ((xc = *xpath++)) { ++ if (xc == '[') { ++ xpath = frrstr_skip_over_char(xpath, ']'); ++ if (!xpath) ++ return false; ++ continue; ++ } ++ pc = *path++; ++ if (!pc) ++ return true; ++ if (pc != xc) ++ return false; + } ++ return true; ++} + +- return NULL; ++/* ++ * Get the mask of clients interested in an xpath. ++ */ ++uint64_t mgmt_be_interested_clients(const char *xpath, enum mgmt_be_xpath_subscr_type type) ++{ ++ struct mgmt_be_xpath_map *maps = NULL, *map; ++ uint64_t clients = 0; ++ bool wild_root; ++ ++ switch (type) { ++ case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: ++ maps = be_cfg_xpath_map; ++ break; ++ case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: ++ maps = be_oper_xpath_map; ++ break; ++ case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: ++ maps = be_notif_xpath_map; ++ break; ++ case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: ++ maps = be_rpc_xpath_map; ++ break; ++ } ++ ++ /* wild_root will select all clients that advertise op-state */ ++ wild_root = !strcmp(xpath, "/") || !strcmp(xpath, "/*"); ++ darr_foreach_p (maps, map) ++ if (wild_root || mgmt_be_xpath_prefix(map->xpath_prefix, xpath)) ++ clients |= map->clients; ++ ++ _dbg("xpath: '%s' subscribed clients: 0x%Lx", xpath, clients); ++ ++ return clients; + } + +-static struct mgmt_be_client_adapter * +-mgmt_be_find_adapter_by_name(const char *name) ++/* ++ * Test for interest by mgmtd in xpath. ++ * ++ * Mgmtd handles it's own config directly vs as a backend client of ++ * itself. This function supports that. ++ */ ++bool mgmt_is_mgmtd_interested(const char *xpath) + { +- struct mgmt_be_client_adapter *adapter; ++ const char *const *match = mgmtd_config_xpaths; ++ const char *const *ematch = match + array_size(mgmtd_config_xpaths); + +- FOREACH_ADAPTER_IN_LIST (adapter) { +- if (!strncmp(adapter->name, name, sizeof(adapter->name))) +- return adapter; ++ for (; match < ematch; match++) { ++ if (mgmt_be_xpath_prefix(*match, xpath)) { ++ _dbg("mgmtd: subscribed to %s", xpath); ++ return true; ++ } ++ } ++ return false; ++} ++ ++ ++/* ++ * This function is inefficient. For each xpath it walks the global map list. ++ * We should keep a separate per-client map and use that here. ++ * ++ * NOTE: Fix this when removing the global constant maps used for bootstrapping. ++ */ ++static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, ++ enum mgmt_be_xpath_subscr_type type) ++{ ++ uint64_t clients; ++ ++ assert(id < MGMTD_BE_CLIENT_ID_MAX); ++ ++ _dbg("Checking client: %s for xpath: '%s'", be_adapter_id_name(id), xpath); ++ ++ clients = mgmt_be_interested_clients(xpath, type); ++ if (IS_IDBIT_SET(clients, id)) { ++ _dbg("client: %s: interested", be_adapter_id_name(id)); ++ return true; + } + +- return NULL; ++ _dbg("client: %s: not interested", be_adapter_id_name(id)); ++ return false; ++} ++ ++/* ++ * Get full config changes for adapter. ++ * ++ * Walk the entire running config building a set of create-changes to send to ++ * the adapter and return a set of config changes. ++ */ ++struct nb_config_cbs mgmt_be_adapter_get_config(struct mgmt_be_client_adapter *adapter) ++{ ++ struct nb_config_cbs changes = { 0 }; ++ const struct lyd_node *root, *dnode; ++ uint32_t seq = 0; ++ char *xpath; ++ ++ LY_LIST_FOR (running_config->dnode, root) { ++ LYD_TREE_DFS_BEGIN (root, dnode) { ++ if (lysc_is_key(dnode->schema)) ++ goto walk_cont; ++ ++ xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0); ++ if (be_is_client_interested(xpath, adapter->id, ++ MGMT_BE_XPATH_SUBSCR_TYPE_CFG)) ++ nb_config_diff_add_change(&changes, NB_CB_CREATE, &seq, dnode); ++ else ++ LYD_TREE_DFS_continue = 1; /* skip any subtree */ ++ free(xpath); ++walk_cont: ++ LYD_TREE_DFS_END(root, dnode); ++ } ++ } ++ return changes; + } + +-static void mgmt_register_client_xpath(enum mgmt_be_client_id id, +- const char *xpath, +- enum mgmt_be_xpath_subscr_type type) ++static void be_adapter_register_client_xpath(enum mgmt_be_client_id id, const char *xpath, ++ enum mgmt_be_xpath_subscr_type type) + { + struct mgmt_be_xpath_map **maps, *map; + +@@ -337,7 +494,7 @@ static void mgmt_register_client_xpath(enum mgmt_be_client_id id, + + darr_foreach_p (*maps, map) { + if (!strcmp(xpath, map->xpath_prefix)) { +- map->clients |= (1u << id); ++ SET_IDBIT(map->clients, id); + return; + } + } +@@ -348,9 +505,15 @@ static void mgmt_register_client_xpath(enum mgmt_be_client_id id, + } + + /* +- * initial the combined maps from per client maps ++ * Use the global per-client constants to initialize the xpath maps. ++ * ++ * NOTE: This system was used to bootstrap the mgmtd work, and has proved to add ++ * complexity to the process converting daemons to use mgmtd with no real ++ * performance requirement, the daemon can send it's xpath regsitrations in it's ++ * subscribe message so we should just use that. Please convert to do that and ++ * remove this function at first convenience. + */ +-static void mgmt_be_xpath_map_init(void) ++static void be_adapter_xpath_maps_init(void) + { + enum mgmt_be_client_id id; + const char *const *init; +@@ -361,29 +524,26 @@ static void mgmt_be_xpath_map_init(void) + /* Initialize the common config init map */ + for (init = be_client_config_xpaths[id]; init && *init; init++) { + _dbg(" - CFG XPATH: '%s'", *init); +- mgmt_register_client_xpath(id, *init, +- MGMT_BE_XPATH_SUBSCR_TYPE_CFG); ++ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + } + + /* Initialize the common oper init map */ + for (init = be_client_oper_xpaths[id]; init && *init; init++) { + _dbg(" - OPER XPATH: '%s'", *init); +- mgmt_register_client_xpath(id, *init, +- MGMT_BE_XPATH_SUBSCR_TYPE_OPER); ++ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + } + + /* Initialize the common NOTIF init map */ + for (init = be_client_notif_xpaths[id]; init && *init; init++) { + _dbg(" - NOTIF XPATH: '%s'", *init); +- mgmt_register_client_xpath(id, *init, +- MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); ++ be_adapter_register_client_xpath(id, *init, ++ MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); + } + + /* Initialize the common RPC init map */ + for (init = be_client_rpc_xpaths[id]; init && *init; init++) { + _dbg(" - RPC XPATH: '%s'", *init); +- mgmt_register_client_xpath(id, *init, +- MGMT_BE_XPATH_SUBSCR_TYPE_RPC); ++ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_RPC); + } + } + +@@ -393,7 +553,10 @@ static void mgmt_be_xpath_map_init(void) + _dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map)); + } + +-static void mgmt_be_xpath_map_cleanup(void) ++/* ++ * Cleanup the xpath maps. ++ */ ++static void be_adapter_xpath_maps_cleanup(void) + { + struct mgmt_be_xpath_map *map; + +@@ -415,138 +578,32 @@ static void mgmt_be_xpath_map_cleanup(void) + } + + ++/* ============================== */ ++/* Backend Message (API) Handling */ ++/* ============================== */ ++ + /* +- * Check if either path or xpath is a prefix of the other. Before checking the +- * xpath is converted to a regular path string (e..g, removing key value +- * specifiers). ++ * The TXN module is the primary producer/consumer of messages to/from backend ++ * clients so that is where you will find most of the backend message handling ++ * functions. + */ +-static bool mgmt_be_xpath_prefix(const char *path, const char *xpath) +-{ +- int xc, pc; +- +- while ((xc = *xpath++)) { +- if (xc == '[') { +- xpath = frrstr_skip_over_char(xpath, ']'); +- if (!xpath) +- return false; +- continue; +- } +- pc = *path++; +- if (!pc) +- return true; +- if (pc != xc) +- return false; +- } +- return true; +-} +- +-static void mgmt_be_adapter_delete(struct mgmt_be_client_adapter *adapter) +-{ +- _dbg("deleting client adapter '%s'", adapter->name); +- +- /* +- * Notify about disconnect for appropriate cleanup +- */ +- mgmt_txn_notify_be_adapter_conn(adapter, false); +- if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) { +- mgmt_be_adapters_by_id[adapter->id] = NULL; +- adapter->id = MGMTD_BE_CLIENT_ID_MAX; +- } +- +- assert(adapter->refcount == 1); +- mgmt_be_adapter_unlock(&adapter); +-} +- +-static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn) +-{ +- struct mgmt_be_client_adapter *adapter = conn->user; +- +- _dbg("notify disconnect for client adapter '%s'", adapter->name); +- +- mgmt_be_adapter_delete(adapter); +- +- return 0; +-} +- +-static void +-mgmt_be_adapter_cleanup_old_conn(struct mgmt_be_client_adapter *adapter) +-{ +- struct mgmt_be_client_adapter *old; + +- FOREACH_ADAPTER_IN_LIST (old) { +- if (old != adapter && +- !strncmp(adapter->name, old->name, sizeof(adapter->name))) { +- /* +- * We have a Zombie lingering around +- */ +- _dbg("Client '%s' (FD:%d) seems to have reconnected. Removing old connection (FD:%d)!", +- adapter->name, adapter->conn->fd, old->conn->fd); +- /* this will/should delete old */ +- msg_conn_disconnect(old->conn, false); +- } +- } +-} +- +-int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter, uint64_t txn_id, bool create) +-{ +- struct mgmt_msg_txn_req *msg; +- int ret; +- +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_req, 0, MTYPE_MSG_NATIVE_TXN_REQ); +- msg->code = MGMT_MSG_CODE_TXN_REQ; +- msg->refer_id = txn_id; +- msg->create = create; +- +- _dbg("Sending TXN_REQ to '%s' to %s txn-id: %Lu", adapter->name, +- create ? "create" : "delete", txn_id); +- +- ret = mgmt_msg_native_send_msg(adapter->conn, msg, false); +- mgmt_msg_native_free_msg(msg); +- return ret; +-} +- +-int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter, struct mgmt_msg_cfg_req *msg) ++int mgmt_be_adapter_send(struct mgmt_be_client_adapter *adapter, void *_msg) + { ++ struct mgmt_msg_header *msg = (struct mgmt_msg_header *)_msg; ++ uint64_t txn_id = msg->refer_id; + int ret; + +- _dbg("Sending CFG_REQ to '%s' txn-id: %Lu req-id: %Lu", adapter->name, msg->refer_id, +- msg->req_id); ++ _dbg("Sending %s to '%s' txn-id: %Lu", mgmt_msg_code_name(msg->code), adapter->name, ++ txn_id); + + ret = mgmt_msg_native_send_msg(adapter->conn, msg, false); + if (ret) +- _log_err("Could not send CFG_REQ txn-id: %Lu to client '%s", msg->refer_id, +- adapter->name); +- return ret; +-} +- +-int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter, +- uint64_t txn_id) +-{ +- struct mgmt_msg_cfg_apply_req *msg; +- int ret; +- +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_apply_req, 0, +- MTYPE_MSG_NATIVE_CFG_APPLY_REQ); +- msg->code = MGMT_MSG_CODE_CFG_APPLY_REQ; +- msg->refer_id = txn_id; +- +- _dbg("Sending CFG_APPLY_REQ to '%s' txn-id: %Lu", adapter->name, txn_id); +- +- ret = mgmt_msg_native_send_msg(adapter->conn, msg, false); +- mgmt_msg_native_free_msg(msg); ++ _log_err("Failed sending %s to '%s' txn-id: %Lu", mgmt_msg_code_name(msg->code), ++ adapter->name, txn_id); + return ret; + } + +-int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg) +-{ +- struct mgmt_be_client_adapter *adapter = mgmt_be_get_adapter_by_id(id); +- +- if (!adapter) +- return -1; +- +- return mgmt_msg_native_send_msg(adapter->conn, msg, false); +-} +- + /* + * Send notification to back-ends that subscribed for them. + */ +@@ -599,9 +656,13 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t + static void be_adapter_handle_subscribe(struct mgmt_msg_subscribe *msg, size_t msg_len, + struct mgmt_be_client_adapter *adapter) + { ++ struct mgmt_be_client_adapter *old; + const char **s = NULL; + uint i = 0; + ++ _dbg("SUBSCRIBE '%s' to register xpaths config: %u oper: %u notif: %u rpc: %u", ++ adapter->name, msg->nconfig, msg->noper, msg->nnotify, msg->nrpc); ++ + s = mgmt_msg_native_strings_decode(msg, msg_len, msg->strings); + if (!s) { + _log_err("Corrupt subscribe msg from '%s'", adapter->name); +@@ -621,498 +682,336 @@ static void be_adapter_handle_subscribe(struct mgmt_msg_subscribe *msg, size_t m + if (adapter->id >= MGMTD_BE_CLIENT_ID_MAX) { + _log_err("Unable to resolve adapter '%s' to a valid ID. Disconnecting!", + adapter->name); +- /* this will/should delete old */ +- msg_conn_disconnect(adapter->conn, false); ++ be_adapter_delete(adapter); + goto done; + } ++ ++ old = mgmt_be_adapters_by_id[adapter->id]; ++ if (old) { ++ _dbg("be-client: %s using fd: %d reconnected with fd: %d", old->name, ++ old->conn->fd, adapter->conn->fd); ++ be_adapter_delete(old); ++ } + mgmt_be_adapters_by_id[adapter->id] = adapter; +- mgmt_be_adapter_cleanup_old_conn(adapter); + + /* schedule INIT sequence now that it is registered */ +- mgmt_be_adapter_sched_init_event(adapter); ++ be_adapter_sched_init_event(adapter); + + for (uint j = 0; j < msg->nconfig; j++) +- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_CFG); ++ be_adapter_register_client_xpath(adapter->id, s[i++], ++ MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + + for (uint j = 0; j < msg->noper; j++) +- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_OPER); ++ be_adapter_register_client_xpath(adapter->id, s[i++], ++ MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + + for (uint j = 0; j < msg->nnotify; j++) +- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); ++ be_adapter_register_client_xpath(adapter->id, s[i++], ++ MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); + + for (uint j = 0; j < msg->nrpc; j++) +- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_RPC); ++ be_adapter_register_client_xpath(adapter->id, s[i++], ++ MGMT_BE_XPATH_SUBSCR_TYPE_RPC); + done: + darr_free_free(s); + } + +-/* +- * Handle a native encoded message +- */ +-static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter, +- struct mgmt_msg_header *msg, +- size_t msg_len) ++ ++static void be_adapter_process_msg(uint8_t version, uint8_t *data, size_t msg_len, ++ struct msg_conn *conn) + { +- struct mgmt_msg_subscribe *subr_msg; ++ struct mgmt_be_client_adapter *adapter = conn->user; ++ struct mgmt_msg_header *msg = (typeof(msg))data; + struct mgmt_msg_notify_data *notify_msg; +- struct mgmt_msg_txn_reply *txn_msg; + struct mgmt_msg_error *error_msg; + +- /* get the transaction */ ++ if (version != MGMT_MSG_VERSION_NATIVE) { ++ _log_err("Protobuf not supported for backend messages (adapter: %s)", ++ adapter->name); ++ return; ++ } ++ if (msg_len < sizeof(*msg)) { ++ _log_err("native message to adapter %s too short %zu", adapter->name, msg_len); ++ return; ++ } + +- switch (msg->code) { +- case MGMT_MSG_CODE_SUBSCRIBE: +- subr_msg = (typeof(subr_msg))msg; +- _dbg("Got SUBSCRIBE from '%s' to register xpaths config: %u oper: %u notif: %u rpc: %u", +- adapter->name, subr_msg->nconfig, subr_msg->noper, subr_msg->nnotify, +- subr_msg->nrpc); ++ /* ++ * Most messages are sent to the TXN module for processing. ++ * ++ * NOTE: Handling the config messages may lead to disconnect and ++ * deletion of the adapater. So don't do with it after calling the txn ++ * function. ++ */ + +- be_adapter_handle_subscribe(subr_msg, msg_len, adapter); +- break; ++ _dbg("Got %s from '%s' txn-id %Lu", mgmt_msg_code_name(msg->code), adapter->name, ++ msg->refer_id); + ++ switch (msg->code) { ++ case MGMT_MSG_CODE_SUBSCRIBE: ++ be_adapter_handle_subscribe((struct mgmt_msg_subscribe *)msg, msg_len, adapter); ++ return; + case MGMT_MSG_CODE_TXN_REPLY: +- txn_msg = (typeof(txn_msg))msg; +- _dbg("Got TXN_REPLY from '%s' txn-id %Lu successfully '%s'", adapter->name, +- txn_msg->refer_id, txn_msg->created ? "Created" : "Deleted"); +- /* +- * Forward the TXN_REPLY to txn module. +- */ +- mgmt_txn_notify_be_txn_reply(txn_msg->refer_id, txn_msg->created, true, adapter); +- break; ++ assert(0); ++ return; + case MGMT_MSG_CODE_CFG_REPLY: +- _dbg("Got successful CFG_REPLY from '%s' txn-id %Lu", adapter->name, msg->refer_id); +- /* +- * Forward the CGFData-create reply to txn module. +- */ +- mgmt_txn_notify_be_cfg_reply(msg->refer_id, true, NULL, adapter); +- break; ++ mgmt_txn_handle_cfg_reply(msg->refer_id, adapter); ++ return; + case MGMT_MSG_CODE_CFG_APPLY_REPLY: +- _dbg("Got successful CFG_APPLY_REPLY from '%s' txn-id %Lu", adapter->name, +- msg->refer_id); +- /* +- * Forward the CGFData-apply reply to txn module. +- */ +- mgmt_txn_notify_be_cfg_apply_reply(msg->refer_id, true, NULL, adapter); +- break; +- ++ mgmt_txn_handle_cfg_apply_reply(msg->refer_id, adapter); ++ return; + case MGMT_MSG_CODE_ERROR: + error_msg = (typeof(error_msg))msg; +- _dbg("Got ERROR from '%s' txn-id %Lu", adapter->name, msg->refer_id); +- +- /* Forward the reply to the txn module */ +- mgmt_txn_notify_error(adapter, msg->refer_id, msg->req_id, +- error_msg->error, error_msg->errstr); +- /* We may have lost our connection and adapter at this point */ +- break; ++ mgmt_txn_handle_error_reply(adapter, msg->refer_id, msg->req_id, error_msg->error, ++ error_msg->errstr); ++ return; + case MGMT_MSG_CODE_TREE_DATA: +- /* tree data from a backend client */ +- _dbg("Got TREE_DATA from '%s' txn-id %" PRIu64, adapter->name, msg->refer_id); +- +- /* Forward the reply to the txn module */ +- mgmt_txn_notify_tree_data_reply(adapter, (struct mgmt_msg_tree_data *)msg, msg_len); +- break; ++ mgmt_txn_handle_tree_data_reply(adapter, (struct mgmt_msg_tree_data *)msg, msg_len); ++ return; + case MGMT_MSG_CODE_RPC_REPLY: +- /* RPC reply from a backend client */ +- _dbg("Got RPC_REPLY from '%s' txn-id %" PRIu64, adapter->name, msg->refer_id); +- +- /* Forward the reply to the txn module */ +- mgmt_txn_notify_rpc_reply(adapter, (struct mgmt_msg_rpc_reply *)msg, msg_len); +- break; ++ mgmt_txn_handle_rpc_reply(adapter, (struct mgmt_msg_rpc_reply *)msg, msg_len); ++ return; + case MGMT_MSG_CODE_NOTIFY: + /* +- * Handle notify message from a back-end client ++ * Handle notify message from a back-end client no TXN for this. + */ + notify_msg = (typeof(notify_msg))msg; +- _dbg("Got NOTIFY from '%s'", adapter->name); + mgmt_be_adapter_send_notify(notify_msg, msg_len, adapter); + mgmt_fe_adapter_send_notify(notify_msg, msg_len); +- break; ++ return; + default: + _log_err("unknown native message txn-id %" PRIu64 " req-id %" PRIu64 + " code %u from BE client for adapter %s", + msg->refer_id, msg->req_id, msg->code, adapter->name); +- break; +- } +-} +- +- +-static void mgmt_be_adapter_process_msg(uint8_t version, uint8_t *data, +- size_t len, struct msg_conn *conn) +-{ +- struct mgmt_be_client_adapter *adapter = conn->user; +- +- if (version == MGMT_MSG_VERSION_NATIVE) { +- struct mgmt_msg_header *msg = (typeof(msg))data; +- +- if (len >= sizeof(*msg)) +- be_adapter_handle_native_msg(adapter, msg, len); +- else +- _log_err("native message to adapter %s too short %zu", adapter->name, len); + return; + } +- +- _log_err("Protobuf not supported for backend messages (adapter: %s)", adapter->name); + } + +-/* +- * Args for callback +- */ +-struct mgmt_be_get_adapter_config_params { +- struct mgmt_be_client_adapter *adapter; +- struct nb_config_cbs *cfg_chgs; +- uint32_t seq; +-}; ++/* =================== */ ++/* Backend VTY support */ ++/* =================== */ + +-/* +- * Initialize a BE client over a new connection +- */ +-static void mgmt_be_adapter_conn_init(struct event *thread) ++void mgmt_be_adapter_status_write(struct vty *vty) + { + struct mgmt_be_client_adapter *adapter; ++ uint count = 0; + +- adapter = (struct mgmt_be_client_adapter *)EVENT_ARG(thread); +- assert(adapter && adapter->conn->fd >= 0); ++ vty_out(vty, "MGMTD Backend Adapters\n"); + +- /* +- * Notify TXN module to create a CONFIG transaction and +- * download the CONFIGs identified for this new client. +- * If the TXN module fails to initiate the CONFIG transaction +- * retry a bit later. It only fails if there's an existing config +- * transaction in progress. +- */ +- if (mgmt_txn_notify_be_adapter_conn(adapter, true) != 0) { +- zlog_err("XXX txn in progress, retry init"); +- mgmt_be_adapter_sched_init_event(adapter); +- return; ++ LIST_FOREACH (adapter, &be_adapters, link) { ++ vty_out(vty, " Client: \t\t\t%s\n", adapter->name); ++ vty_out(vty, " Conn-FD: \t\t\t%d\n", adapter->conn->fd); ++ vty_out(vty, " Client-Id: \t\t\t%d\n", adapter->id); ++ vty_out(vty, " Msg-Recvd: \t\t\t%Lu\n", adapter->conn->mstate.nrxm); ++ vty_out(vty, " Bytes-Recvd: \t\t%Lu\n", adapter->conn->mstate.nrxb); ++ vty_out(vty, " Msg-Sent: \t\t\t%Lu\n", adapter->conn->mstate.ntxm); ++ vty_out(vty, " Bytes-Sent: \t\t%Lu\n", adapter->conn->mstate.ntxb); ++ count++; + } ++ vty_out(vty, " Total: %u\n", count); + } + +-/* +- * Schedule the initialization of the BE client connection. +- */ +-static void +-mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter) +-{ +- event_add_timer_msec(mgmt_loop, mgmt_be_adapter_conn_init, adapter, +- MGMTD_BE_CONN_INIT_DELAY_MSEC, +- &adapter->conn_init_ev); +-} +- +-void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter) ++static void _show_xpath_map(struct vty *vty, struct mgmt_be_xpath_map *map) + { +- adapter->refcount++; +-} ++ enum mgmt_be_client_id id; ++ const char *astr; + +-extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter) +-{ +- struct mgmt_be_client_adapter *a = *adapter; +- assert(a && a->refcount); +- +- if (!--a->refcount) { +- mgmt_be_adapters_del(&mgmt_be_adapters, a); +- event_cancel(&a->conn_init_ev); +- msg_server_conn_delete(a->conn); +- XFREE(MTYPE_MGMTD_BE_ADPATER, a); ++ vty_out(vty, " - xpath: '%s'\n", map->xpath_prefix); ++ FOREACH_BE_CLIENT_BITS (id, map->clients) { ++ astr = mgmt_be_get_adapter_by_id(id) ? "active" : "inactive"; ++ vty_out(vty, " -- %s-client: '%s'\n", astr, be_adapter_id_name(id)); + } +- +- *adapter = NULL; + } + +-/* +- * Initialize the BE adapter module +- */ +-void mgmt_be_adapter_init(struct event_loop *tm) ++void mgmt_be_xpath_register_write(struct vty *vty) + { +- char server_path[MAXPATHLEN]; ++ struct mgmt_be_xpath_map *map; + +- assert(!mgmt_loop); +- mgmt_loop = tm; ++ vty_out(vty, "MGMTD Backend CFG XPath Registry: Count: %u\n", darr_len(be_oper_xpath_map)); ++ darr_foreach_p (be_cfg_xpath_map, map) ++ _show_xpath_map(vty, map); + +- mgmt_be_adapters_init(&mgmt_be_adapters); +- mgmt_be_xpath_map_init(); ++ vty_out(vty, "\nMGMTD Backend OPER XPath Registry: Count: %u\n", ++ darr_len(be_oper_xpath_map)); ++ darr_foreach_p (be_oper_xpath_map, map) ++ _show_xpath_map(vty, map); + +- snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); ++ vty_out(vty, "\nMGMTD Backend NOTIFY XPath Registry: Count: %u\n", ++ darr_len(be_notif_xpath_map)); ++ darr_foreach_p (be_notif_xpath_map, map) ++ _show_xpath_map(vty, map); + +- if (msg_server_init(&mgmt_be_server, server_path, tm, +- mgmt_be_create_adapter, "backend", &mgmt_debug_be)) { +- zlog_err("cannot initialize backend server"); +- exit(1); +- } ++ vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n", darr_len(be_rpc_xpath_map)); ++ darr_foreach_p (be_rpc_xpath_map, map) ++ _show_xpath_map(vty, map); + } + + /* +- * Destroy the BE adapter module ++ * Should replace this with proper YANG module + */ +-void mgmt_be_adapter_destroy(void) ++void mgmt_be_adapter_show_xpath_registries(struct vty *vty, const char *xpath) + { ++ enum mgmt_be_client_id id; + struct mgmt_be_client_adapter *adapter; ++ uint64_t cclients, nclients, oclients, rclients, combined; + +- msg_server_cleanup(&mgmt_be_server); +- FOREACH_ADAPTER_IN_LIST (adapter) { +- mgmt_be_adapter_delete(adapter); +- } +- mgmt_be_xpath_map_cleanup(); +-} +- +-/* +- * The server accepted a new connection +- */ +-struct msg_conn *mgmt_be_create_adapter(int conn_fd, union sockunion *from) +-{ +- struct mgmt_be_client_adapter *adapter = NULL; +- +- assert(!mgmt_be_find_adapter_by_fd(conn_fd)); +- +- adapter = XCALLOC(MTYPE_MGMTD_BE_ADPATER, +- sizeof(struct mgmt_be_client_adapter)); +- adapter->id = MGMTD_BE_CLIENT_ID_MAX; +- snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", +- conn_fd); +- +- mgmt_be_adapter_lock(adapter); +- mgmt_be_adapters_add_tail(&mgmt_be_adapters, adapter); +- RB_INIT(nb_config_cbs, &adapter->cfg_chgs); +- +- adapter->conn = msg_server_conn_create(mgmt_loop, conn_fd, +- mgmt_be_adapter_notify_disconnect, +- mgmt_be_adapter_process_msg, +- MGMTD_BE_MAX_NUM_MSG_PROC, +- MGMTD_BE_MAX_NUM_MSG_WRITE, +- MGMTD_BE_MAX_MSG_LEN, adapter, +- "BE-adapter"); +- +- adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL); +- +- _dbg("Added new MGMTD Backend adapter '%s'", adapter->name); +- +- return adapter->conn; +-} ++ cclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); ++ oclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_OPER); ++ nclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); ++ rclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_RPC); ++ combined = cclients | nclients | oclients | rclients; + +-struct mgmt_be_client_adapter * +-mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id) +-{ +- return (id < MGMTD_BE_CLIENT_ID_MAX ? mgmt_be_adapters_by_id[id] : NULL); ++ vty_out(vty, "XPath: '%s'\n", xpath); ++ FOREACH_BE_CLIENT_BITS (id, combined) { ++ vty_out(vty, " -- Client: '%s'\tconfig:%d notify:%d oper:%d rpc:%d\n", ++ be_adapter_id_name(id), IS_IDBIT_SET(cclients, id), ++ IS_IDBIT_SET(nclients, id), IS_IDBIT_SET(oclients, id), ++ IS_IDBIT_SET(rclients, id)); ++ adapter = mgmt_be_get_adapter_by_id(id); ++ if (adapter) ++ vty_out(vty, " -- Adapter: %p\n", adapter); ++ } + } + +-struct mgmt_be_client_adapter * +-mgmt_be_get_adapter_by_name(const char *name) +-{ +- return mgmt_be_find_adapter_by_name(name); +-} ++/* ========================== */ ++/* Backend Adapter Management */ ++/* ========================== */ + + void mgmt_be_adapter_toggle_client_debug(bool set) + { + struct mgmt_be_client_adapter *adapter; + +- FOREACH_ADAPTER_IN_LIST (adapter) ++ LIST_FOREACH (adapter, &be_adapters, link) + adapter->conn->debug = set; + } + + /* +- * Get a full set of changes for all the config that an adapter is subscribed to +- * receive. ++ * Delete a BE client adapter + */ +-void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, +- struct nb_config_cbs **changes) ++static void be_adapter_delete(struct mgmt_be_client_adapter *adapter) + { +- const struct lyd_node *root, *dnode; +- uint32_t seq = 0; +- char *xpath; +- +- /* We can't be in the middle of sending other chgs when here. */ +- assert(RB_EMPTY(nb_config_cbs, &adapter->cfg_chgs)); +- +- *changes = &adapter->cfg_chgs; +- LY_LIST_FOR (running_config->dnode, root) { +- LYD_TREE_DFS_BEGIN (root, dnode) { +- if (lysc_is_key(dnode->schema)) +- goto walk_cont; ++ _dbg("deleting client adapter '%s'", adapter->name); + +- xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0); +- if (be_is_client_interested(xpath, adapter->id, +- MGMT_BE_XPATH_SUBSCR_TYPE_CFG)) +- nb_config_diff_add_change(*changes, NB_CB_CREATE, &seq, dnode); +- else +- LYD_TREE_DFS_continue = 1; /* skip any subtree */ +- free(xpath); +- walk_cont: +- LYD_TREE_DFS_END(root, dnode); +- } ++ /* ++ * Notify about disconnect for appropriate cleanup ++ */ ++ mgmt_txn_handle_be_adapter_connect(adapter, false); ++ if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) { ++ mgmt_be_adapters_by_id[adapter->id] = NULL; ++ adapter->id = MGMTD_BE_CLIENT_ID_MAX; + } ++ ++ LIST_REMOVE(adapter, link); ++ event_cancel(&adapter->conn_init_ev); ++ msg_server_conn_delete(adapter->conn); ++ XFREE(MTYPE_MGMTD_BE_ADPATER, adapter); + } + +-uint64_t mgmt_be_interested_clients(const char *xpath, +- enum mgmt_be_xpath_subscr_type type) ++static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn) + { +- struct mgmt_be_xpath_map *maps = NULL, *map; +- enum mgmt_be_client_id id; +- uint64_t clients; +- bool wild_root; ++ struct mgmt_be_client_adapter *adapter = conn->user; + +- switch (type) { +- case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: +- maps = be_cfg_xpath_map; +- break; +- case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: +- maps = be_oper_xpath_map; +- break; +- case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: +- maps = be_notif_xpath_map; +- break; +- case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: +- maps = be_rpc_xpath_map; +- break; +- } ++ _dbg("notify disconnect for client adapter '%s'", adapter->name); + +- clients = 0; ++ be_adapter_delete(adapter); + +- _dbg("XPATH: '%s'", xpath); ++ return 0; ++} + +- /* wild_root will select all clients that advertise op-state */ +- wild_root = !strcmp(xpath, "/") || !strcmp(xpath, "/*"); +- darr_foreach_p (maps, map) +- if (wild_root || mgmt_be_xpath_prefix(map->xpath_prefix, xpath)) +- clients |= map->clients; ++/* ++ * Initialize a BE client over a new connection ++ */ ++static void be_adapter_conn_init(struct event *thread) ++{ ++ struct mgmt_be_client_adapter *adapter; ++ enum mgmt_be_client_id id; + +- if (DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL)) { +- FOREACH_BE_CLIENT_BITS (id, clients) +- _dbg("Cient: %s: subscribed", mgmt_be_client_id2name(id)); ++ adapter = (struct mgmt_be_client_adapter *)EVENT_ARG(thread); ++ assert(adapter && adapter->conn->fd >= 0); ++ id = adapter->id; ++ ++ /* ++ * Notify TXN module to create a CONFIG transaction and ++ * download the CONFIGs identified for this new client. ++ * If the TXN module fails to initiate the CONFIG transaction ++ * retry a bit later. It only fails if there's an existing config ++ * transaction in progress. ++ */ ++ if (mgmt_txn_handle_be_adapter_connect(adapter, true) != 0) { ++ /* Deal with a disconnect happening */ ++ if (!mgmt_be_adapters_by_id[id]) ++ return; ++ _log_err("Couldn't send intial config to adapter: %s", adapter->name); ++ be_adapter_sched_init_event(adapter); + } +- return clients; + } + +-bool mgmt_is_mgmtd_interested(const char *xpath) ++/* ++ * Schedule the initialization of the BE client connection. ++ */ ++static void be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter) + { +- const char *const *match = mgmtd_config_xpaths; +- const char *const *ematch = match + array_size(mgmtd_config_xpaths); +- +- for (; match < ematch; match++) { +- if (mgmt_be_xpath_prefix(*match, xpath)) { +- _dbg("mgmtd: subscribed to %s", xpath); +- return true; +- } +- } +- return false; ++ event_add_timer_msec(mgmt_loop, be_adapter_conn_init, adapter, ++ MGMTD_BE_CONN_INIT_DELAY_MSEC, &adapter->conn_init_ev); + } + +-/** +- * Return true if `client_id` is interested in `xpath` for `config` +- * or oper (!`config`). +- * +- * Args: +- * xpath - the xpath to check for interest. +- * client_id - the BE client being checked for. +- * bool - check for config (vs oper) subscription. +- * +- * Returns: +- * Interested or not. ++/* ++ * The server accepted a new connection + */ +-static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, +- enum mgmt_be_xpath_subscr_type type) ++static struct msg_conn *be_adapter_create(int conn_fd, union sockunion *from) + { +- uint64_t clients; ++ struct mgmt_be_client_adapter *adapter = NULL; + +- assert(id < MGMTD_BE_CLIENT_ID_MAX); ++ assert(!mgmt_be_find_adapter_by_fd(conn_fd)); + +- _dbg("Checking client: %s for xpath: '%s'", mgmt_be_client_id2name(id), xpath); ++ adapter = XCALLOC(MTYPE_MGMTD_BE_ADPATER, sizeof(struct mgmt_be_client_adapter)); ++ adapter->id = MGMTD_BE_CLIENT_ID_MAX; ++ snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", conn_fd); + +- clients = mgmt_be_interested_clients(xpath, type); +- if (IS_IDBIT_SET(clients, id)) { +- _dbg("client: %s: interested", mgmt_be_client_id2name(id)); +- return true; +- } ++ LIST_INSERT_HEAD(&be_adapters, adapter, link); + +- _dbg("client: %s: not interested", mgmt_be_client_id2name(id)); +- return false; +-} ++ adapter->conn = msg_server_conn_create(mgmt_loop, conn_fd, ++ mgmt_be_adapter_notify_disconnect, ++ be_adapter_process_msg, MGMTD_BE_MAX_NUM_MSG_PROC, ++ MGMTD_BE_MAX_NUM_MSG_WRITE, MGMTD_BE_MAX_MSG_LEN, ++ adapter, "BE-adapter"); + +-void mgmt_be_adapter_status_write(struct vty *vty) +-{ +- struct mgmt_be_client_adapter *adapter; ++ adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL); + +- vty_out(vty, "MGMTD Backend Adapters\n"); ++ _dbg("Added new MGMTD Backend adapter '%s'", adapter->name); + +- FOREACH_ADAPTER_IN_LIST (adapter) { +- vty_out(vty, " Client: \t\t\t%s\n", adapter->name); +- vty_out(vty, " Conn-FD: \t\t\t%d\n", adapter->conn->fd); +- vty_out(vty, " Client-Id: \t\t\t%d\n", adapter->id); +- vty_out(vty, " Ref-Count: \t\t\t%u\n", adapter->refcount); +- vty_out(vty, " Msg-Recvd: \t\t\t%" PRIu64 "\n", +- adapter->conn->mstate.nrxm); +- vty_out(vty, " Bytes-Recvd: \t\t%" PRIu64 "\n", +- adapter->conn->mstate.nrxb); +- vty_out(vty, " Msg-Sent: \t\t\t%" PRIu64 "\n", +- adapter->conn->mstate.ntxm); +- vty_out(vty, " Bytes-Sent: \t\t%" PRIu64 "\n", +- adapter->conn->mstate.ntxb); +- } +- vty_out(vty, " Total: %d\n", +- (int)mgmt_be_adapters_count(&mgmt_be_adapters)); ++ return adapter->conn; + } + +-static void be_show_xpath_register(struct vty *vty, +- struct mgmt_be_xpath_map *map) +-{ +- enum mgmt_be_client_id id; +- const char *astr; +- +- vty_out(vty, " - xpath: '%s'\n", map->xpath_prefix); +- FOREACH_BE_CLIENT_BITS (id, map->clients) { +- astr = mgmt_be_get_adapter_by_id(id) ? "active" : "inactive"; +- vty_out(vty, " -- %s-client: '%s'\n", astr, +- mgmt_be_client_id2name(id)); +- } +-} +-void mgmt_be_xpath_register_write(struct vty *vty) ++/* ++ * Initialize the BE adapter module ++ */ ++void mgmt_be_adapter_init(struct event_loop *tm) + { +- struct mgmt_be_xpath_map *map; ++ char server_path[MAXPATHLEN]; + +- vty_out(vty, "MGMTD Backend CFG XPath Registry: Count: %u\n", +- darr_len(be_oper_xpath_map)); +- darr_foreach_p (be_cfg_xpath_map, map) +- be_show_xpath_register(vty, map); ++ assert(!mgmt_loop); ++ mgmt_loop = tm; + +- vty_out(vty, "\nMGMTD Backend OPER XPath Registry: Count: %u\n", +- darr_len(be_oper_xpath_map)); +- darr_foreach_p (be_oper_xpath_map, map) +- be_show_xpath_register(vty, map); ++ be_adapter_xpath_maps_init(); + +- vty_out(vty, "\nMGMTD Backend NOTIFY XPath Registry: Count: %u\n", +- darr_len(be_notif_xpath_map)); +- darr_foreach_p (be_notif_xpath_map, map) +- be_show_xpath_register(vty, map); ++ snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); + +- vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n", +- darr_len(be_rpc_xpath_map)); +- darr_foreach_p (be_rpc_xpath_map, map) +- be_show_xpath_register(vty, map); ++ if (msg_server_init(&mgmt_be_server, server_path, tm, be_adapter_create, "backend", ++ &mgmt_debug_be)) { ++ zlog_err("cannot initialize backend server"); ++ exit(1); ++ } + } + +-void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath) ++/* ++ * Destroy the BE adapter module ++ */ ++void mgmt_be_adapter_destroy(void) + { +- enum mgmt_be_client_id id; +- struct mgmt_be_client_adapter *adapter; +- uint64_t cclients, nclients, oclients, rclients, combined; +- +- cclients = mgmt_be_interested_clients(xpath, +- MGMT_BE_XPATH_SUBSCR_TYPE_CFG); +- oclients = mgmt_be_interested_clients(xpath, +- MGMT_BE_XPATH_SUBSCR_TYPE_OPER); +- nclients = mgmt_be_interested_clients(xpath, +- MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); +- rclients = mgmt_be_interested_clients(xpath, +- MGMT_BE_XPATH_SUBSCR_TYPE_RPC); +- combined = cclients | nclients | oclients | rclients; ++ struct mgmt_be_client_adapter *adapter, *next; + +- vty_out(vty, "XPath: '%s'\n", xpath); +- FOREACH_BE_CLIENT_BITS (id, combined) { +- vty_out(vty, +- " -- Client: '%s'\tconfig:%d notify:%d oper:%d rpc:%d\n", +- mgmt_be_client_id2name(id), IS_IDBIT_SET(cclients, id), +- IS_IDBIT_SET(nclients, id), IS_IDBIT_SET(oclients, id), +- IS_IDBIT_SET(rclients, id)); +- adapter = mgmt_be_get_adapter_by_id(id); +- if (adapter) +- vty_out(vty, " -- Adapter: %p\n", adapter); +- } ++ msg_server_cleanup(&mgmt_be_server); ++ LIST_FOREACH_SAFE (adapter, &be_adapters, link, next) ++ be_adapter_delete(adapter); ++ be_adapter_xpath_maps_cleanup(); + } +diff --git a/mgmtd/mgmt_be_adapter.h b/mgmtd/mgmt_be_adapter.h +index 26dd56ac62..390ae078e9 100644 +--- a/mgmtd/mgmt_be_adapter.h ++++ b/mgmtd/mgmt_be_adapter.h +@@ -14,6 +14,7 @@ + #include "mgmt_msg.h" + #include "mgmt_defines.h" + #include "mgmtd/mgmt_ds.h" ++#include "queue.h" + + #define MGMTD_BE_CONN_INIT_DELAY_MSEC 50 + +@@ -45,7 +46,6 @@ enum mgmt_be_client_id { + + + PREDECL_LIST(mgmt_be_adapters); +-PREDECL_LIST(mgmt_txn_badapters); + + struct mgmt_be_client_adapter { + struct msg_conn *conn; +@@ -53,38 +53,13 @@ struct mgmt_be_client_adapter { + struct event *conn_init_ev; + + enum mgmt_be_client_id id; +- uint32_t flags; + char name[MGMTD_CLIENT_NAME_MAX_LEN]; + +- int refcount; +- +- /* +- * List of config items that should be sent to the +- * backend during re/connect. This is temporarily +- * created and then freed-up as soon as the initial +- * config items has been applied onto the backend. +- */ +- struct nb_config_cbs cfg_chgs; + struct mgmt_commit_stats cfg_stats; +- + struct mgmt_be_adapters_item list_linkage; +-}; +- +-#define MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED (1U << 0) + +-DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); +- +-/* +- * MGMT_SUBSCR_xxx - flags for subscription types for xpaths registrations +- * +- * MGMT_SUBSCR_VALIDATE_CFG :: the client should be asked to validate config +- * MGMT_SUBSCR_NOTIFY_CFG :: the client should be notified of config changes +- * MGMT_SUBSCR_OPER_OWN :: the client owns the given oeprational state +- */ +-#define MGMT_SUBSCR_VALIDATE_CFG 0x1 +-#define MGMT_SUBSCR_NOTIFY_CFG 0x2 +-#define MGMT_SUBSCR_OPER_OWN 0x4 +-#define MGMT_SUBSCR_ALL 0x7 ++ LIST_ENTRY(mgmt_be_client_adapter) link; ++}; + + /* --------- */ + /* CLIENT-ID */ +@@ -94,8 +69,11 @@ DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); + for ((id) = MGMTD_BE_CLIENT_ID_MIN; (id) < MGMTD_BE_CLIENT_ID_MAX; \ + (id)++) + ++#define IDBIT_MASK(id) (1ull << (id)) ++#define IS_IDBIT_UNSET(v, id) (!((v)&IDBIT_MASK(id))) + #define IS_IDBIT_SET(v, id) (!IS_IDBIT_UNSET(v, id)) +-#define IS_IDBIT_UNSET(v, id) (!((v) & (1ull << (id)))) ++#define SET_IDBIT(v, id) ((v) |= IDBIT_MASK(id)) ++#define UNSET_IDBIT(v, id) ((v) &= ~IDBIT_MASK(id)) + + #define _GET_NEXT_SET(id, bits) \ + ({ \ +@@ -111,6 +89,26 @@ DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); + for ((id) = _GET_NEXT_SET(MGMTD_BE_CLIENT_ID_MIN, bits); (id) < MGMTD_BE_CLIENT_ID_MAX; \ + (id) = _GET_NEXT_SET((id) + 1, bits)) + ++/* This is required to avoid assignment in if conditional in FOREACH_BE_ADAPTER_BITS :( */ ++#define _GET_NEXT_SET_ADAPTER(id, adapter, bits) \ ++ ({ \ ++ enum mgmt_be_client_id _gnsa_id = (id); \ ++ \ ++ for (; _gnsa_id < MGMTD_BE_CLIENT_ID_MAX; _gnsa_id++) { \ ++ if (IS_IDBIT_SET(bits, _gnsa_id)) { \ ++ (adapter) = mgmt_be_get_adapter_by_id(_gnsa_id); \ ++ if ((adapter)) \ ++ break; \ ++ } \ ++ } \ ++ _gnsa_id; \ ++ }) ++ ++#define FOREACH_BE_ADAPTER_BITS(id, adapter, bits) \ ++ for ((id) = _GET_NEXT_SET_ADAPTER(MGMTD_BE_CLIENT_ID_MIN, (adapter), (bits)); \ ++ (id) < MGMTD_BE_CLIENT_ID_MAX; \ ++ (id) = _GET_NEXT_SET_ADAPTER((id) + 1, (adapter), (bits))) ++ + /* ---------- */ + /* Prototypes */ + /* ---------- */ +@@ -121,20 +119,6 @@ extern void mgmt_be_adapter_init(struct event_loop *tm); + /* Destroy the backend adapter module. */ + extern void mgmt_be_adapter_destroy(void); + +-/* Acquire lock for backend adapter. */ +-extern void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter); +- +-/* Remove lock from backend adapter. */ +-extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter); +- +-/* Create backend adapter. */ +-extern struct msg_conn *mgmt_be_create_adapter(int conn_fd, +- union sockunion *su); +- +-/* Fetch backend adapter given an adapter name. */ +-extern struct mgmt_be_client_adapter * +-mgmt_be_get_adapter_by_name(const char *name); +- + /* Fetch backend adapter given an client ID. */ + extern struct mgmt_be_client_adapter * + mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id); +@@ -146,42 +130,7 @@ extern const char *mgmt_be_client_id2name(enum mgmt_be_client_id id); + extern void mgmt_be_adapter_toggle_client_debug(bool set); + + /* Fetch backend adapter config. */ +-extern void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, +- struct nb_config_cbs **changes); +- +-/* Create/destroy a transaction. */ +-extern int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter, +- uint64_t txn_id, bool create); +- +-/* +- * Send config data create request to backend client. +- * +- * adaptr +- * Backend adapter information. +- * +- * msg +- * The already constructed message to send. +- * +- * Returns: +- * 0 on success, -1 on failure. +- */ +-extern int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter, +- struct mgmt_msg_cfg_req *msg); +- +-/* +- * Send config apply request to backend client. +- * +- * adapter +- * Backend adapter information. +- * +- * txn_id +- * Unique transaction identifier. +- * +- * Returns: +- * 0 on success, -1 on failure. +- */ +-extern int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter, +- uint64_t txn_id); ++extern struct nb_config_cbs mgmt_be_adapter_get_config(struct mgmt_be_client_adapter *adapter); + + /* + * Dump backend adapter status to vty. +@@ -204,7 +153,7 @@ extern void mgmt_be_xpath_register_write(struct vty *vty); + * Return: + * Any return value from msg_conn_send_msg(). + */ +-extern int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg); ++extern int mgmt_be_adapter_send(struct mgmt_be_client_adapter *adapter, void *msg); + + enum mgmt_be_xpath_subscr_type { + MGMT_BE_XPATH_SUBSCR_TYPE_CFG, +@@ -224,17 +173,10 @@ enum mgmt_be_xpath_subscr_type { + extern uint64_t mgmt_be_interested_clients(const char *xpath, + enum mgmt_be_xpath_subscr_type type); + +-/** +- * mgmt_fe_adapter_send_notify() - notify FE clients of a notification. +- * @msg: the notify message from the backend client. +- * @msglen: the length of the notify message. +- */ +-extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, +- size_t msglen); + /* + * Dump backend client information for a given xpath to vty. + */ +-extern void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath); ++extern void mgmt_be_adapter_show_xpath_registries(struct vty *vty, const char *xpath); + + /* + * Specials for mgmtd internally handling BE like behaviors +diff --git a/mgmtd/mgmt_ds.c b/mgmtd/mgmt_ds.c +index a0e6c83393..4dae8f5222 100644 +--- a/mgmtd/mgmt_ds.c ++++ b/mgmtd/mgmt_ds.c +@@ -16,12 +16,14 @@ + #include "libyang/libyang.h" + + #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_ds, "DS: %s: " fmt, __func__, ##__VA_ARGS__) ++#define _log_warn(fmt, ...) zlog_warn("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) + #define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) + + struct mgmt_ds_ctx { + enum mgmt_ds_id ds_id; + + bool locked; ++ uint64_t txn_locked; /* lock held by txn */ + uint64_t vty_session_id; /* Owner of the lock or 0 */ + + bool config_ds; +@@ -222,14 +224,17 @@ bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx) + return ds_ctx->config_ds; + } + +-bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id) ++bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id, uint64_t *txn_id) + { +- if (!ds_ctx || !ds_ctx->locked) ++ if (!ds_ctx) + return false; + +- if (session_id) ++ if (ds_ctx->locked && session_id) + *session_id = ds_ctx->vty_session_id; +- return true; ++ if (ds_ctx->txn_locked && txn_id) ++ *txn_id = ds_ctx->txn_locked; ++ ++ return ds_ctx->locked || ds_ctx->txn_locked; + } + + int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) +@@ -237,31 +242,93 @@ int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) + assert(ds_ctx); + + if (ds_ctx->locked) { +- _log_err("lock already taken on DS:%s by session-id %Lu", ++ _log_err("LOCK already taken on DS:%s by session-id %Lu", + mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id); +- return EBUSY; ++ return -EBUSY; + } +- +- _dbg("obtaining lock on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), +- session_id); ++ if (ds_ctx->txn_locked) { ++ _log_err("LOCK already taken on DS:%s by session-less txn-id %Lu", ++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked); ++ return -EBUSY; ++ } ++ _dbg("LOCK on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), session_id); + + ds_ctx->locked = true; + ds_ctx->vty_session_id = session_id; + return 0; + } + +-void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx) ++void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) + { + assert(ds_ctx); + if (!ds_ctx->locked) + _log_err("unlock on unlocked in DS:%s last session-id %Lu", + mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id); +- else ++ else { ++ assert(ds_ctx->vty_session_id == session_id); + _dbg("releasing lock on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), + ds_ctx->vty_session_id); ++ } + + ds_ctx->locked = 0; + ds_ctx->vty_session_id = MGMTD_SESSION_ID_NONE; ++ ++ if (ds_ctx->txn_locked) ++ _dbg("TXN-LOCK remains on DS:%s for txn-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), ++ ds_ctx->txn_locked); ++} ++ ++bool mgmt_ds_is_txn_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *txn_id) ++{ ++ if (ds_ctx && ds_ctx->txn_locked) { ++ *txn_id = ds_ctx->txn_locked; ++ return true; ++ } ++ return false; ++} ++ ++uint64_t mgmt_ds_txn_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id) ++{ ++ assert(ds_ctx); ++ ++ if (ds_ctx->txn_locked && ds_ctx->txn_locked != txn_id) { ++ _log_err("TXN-LOCK txn lock held on DS:%s by txn-id: %Lu not txn-id: %Lu", ++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked, txn_id); ++ return ds_ctx->txn_locked; ++ } ++ if (ds_ctx->txn_locked == txn_id) { ++ _log_warn("TXN-LOCK double lock on DS:%s txn-id: %Lu", ++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked); ++ return 0; ++ } ++ _dbg("TXN-LOCK on DS:%s session-id: %Lu txn-id: %Lu", mgmt_ds_id2name(ds_ctx->ds_id), ++ ds_ctx->vty_session_id, txn_id); ++ ds_ctx->txn_locked = txn_id; ++ return 0; ++} ++ ++void mgmt_ds_txn_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id) ++{ ++ assert(ds_ctx); ++ if (ds_ctx->txn_locked && ds_ctx->txn_locked != txn_id) { ++ _log_err("TXN-UNLOCK: txn lock held on DS:%s by session-id: %Lu txn-id: %Lu not txn-id: %Lu", ++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, ++ ds_ctx->txn_locked, txn_id); ++ return; ++ } ++ if (!ds_ctx->txn_locked) ++ _log_warn("TXN-UNLOCK of unlocked on DS:%s session-id: %Lu txn-id: %Lu", ++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, txn_id); ++ else ++ _dbg("TXN-UNLOCK on DS:%s session-id: %Lu txn-id: %Lu", ++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, txn_id); ++ ds_ctx->txn_locked = 0; ++} ++ ++void mgmt_ds_restore_nb_config(struct mgmt_ds_ctx *ds_ctx, struct nb_config *backup) ++{ ++ assert(ds_ctx->config_ds); ++ nb_config_replace(ds_ctx->root.cfg_root, backup, false); + } + + int mgmt_ds_copy_dss(struct mgmt_ds_ctx *dst, struct mgmt_ds_ctx *src, bool updt_cmt_rec) +@@ -514,7 +581,8 @@ void mgmt_ds_dump_tree(struct vty *vty, struct mgmt_ds_ctx *ds_ctx, + + void mgmt_ds_status_write_one(struct vty *vty, struct mgmt_ds_ctx *ds_ctx) + { +- uint64_t session_id; ++ uint64_t session_id = 0; ++ uint64_t txn_id = 0; + bool locked; + + if (!ds_ctx) { +@@ -522,13 +590,13 @@ void mgmt_ds_status_write_one(struct vty *vty, struct mgmt_ds_ctx *ds_ctx) + return; + } + +- locked = mgmt_ds_is_locked(ds_ctx, &session_id); ++ locked = mgmt_ds_is_locked(ds_ctx, &session_id, &txn_id); + vty_out(vty, " DS: %s\n", mgmt_ds_id2name(ds_ctx->ds_id)); + vty_out(vty, " DS-Hndl: \t\t\t%p\n", ds_ctx); + vty_out(vty, " Config: \t\t\t%s\n", + ds_ctx->config_ds ? "True" : "False"); +- vty_out(vty, " Locked: \t\t\t%s Session-ID: %Lu\n", locked ? "True" : "False", +- locked ? session_id : 0); ++ vty_out(vty, " Locked: \t\t\t%s Session-ID: %Lu Txn-ID: %Lu\n", ++ locked ? "True" : "False", session_id, txn_id); + } + + void mgmt_ds_status_write(struct vty *vty) +diff --git a/mgmtd/mgmt_ds.h b/mgmtd/mgmt_ds.h +index 0849674acd..a509426ef1 100644 +--- a/mgmtd/mgmt_ds.h ++++ b/mgmtd/mgmt_ds.h +@@ -34,6 +34,7 @@ + + extern struct nb_config *running_config; + ++struct mgmt_master; + struct mgmt_ds_ctx; + + /*************************************************************** +@@ -178,10 +179,10 @@ extern struct mgmt_ds_ctx *mgmt_ds_get_ctx_by_id(struct mgmt_master *mm, enum mg + extern bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx); + + /* +- * Check if a given datastore is locked by a session, if so return that session +- * ID if session_id is not NULL. ++ * Check if a given datastore is locked. If so return that session ++ * ID if session_id is not NULL. Also return txn_id if locked by a config txn. + */ +-extern bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id); ++extern bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id, uint64_t *txn_id); + + /* + * Acquire write lock to a ds given a ds_handle +@@ -190,8 +191,11 @@ extern int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id); + + /* + * Remove a lock from ds given a ds_handle ++ * ++ * session_id ++ * Session ID that holds the lock. + */ +-extern void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx); ++extern void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id); + + /* + * Copy from source to destination datastore. +@@ -215,6 +219,11 @@ extern int mgmt_ds_copy_dss(struct mgmt_ds_ctx *dst, struct mgmt_ds_ctx *src, bo + */ + extern struct nb_config *mgmt_ds_get_nb_config(struct mgmt_ds_ctx *ds_ctx); + ++/* ++ * Restore configuration for a given datastore context from a backup. ++ */ ++extern void mgmt_ds_restore_nb_config(struct mgmt_ds_ctx *ds_ctx, struct nb_config *backup); ++ + /* + * Find YANG data node given a datastore handle YANG xpath. + */ +@@ -329,4 +338,13 @@ extern void mgmt_ds_status_write(struct vty *vty); + */ + void mgmt_ds_reset_candidate(void); + ++ ++/* ++ * Private TXN-Lock functions. ++ */ ++ ++extern bool mgmt_ds_is_txn_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *txn_id); ++extern uint64_t mgmt_ds_txn_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id); ++extern void mgmt_ds_txn_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id); ++ + #endif /* _FRR_MGMTD_DS_H_ */ +diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c +index 36f37eb1f0..fcc8c61fac 100644 +--- a/mgmtd/mgmt_fe_adapter.c ++++ b/mgmtd/mgmt_fe_adapter.c +@@ -26,10 +26,6 @@ + #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_fe, "FE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__) + #define _log_err(fmt, ...) zlog_err("FE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) + +-#define FOREACH_ADAPTER_IN_LIST(adapter) \ +- frr_each_safe (mgmt_fe_adapters, &mgmt_fe_adapters, (adapter)) +- +- + enum mgmt_session_event { + MGMTD_FE_SESSION_CFG_TXN_CLNUP = 1, + MGMTD_FE_SESSION_SHOW_TXN_CLNUP, +@@ -47,13 +43,21 @@ struct mgmt_fe_session_ctx { + struct event *proc_cfg_txn_clnp; + struct event *proc_show_txn_clnp; + +- struct mgmt_fe_sessions_item list_linkage; ++ LIST_ENTRY(mgmt_fe_session_ctx) link; + }; + +-DECLARE_LIST(mgmt_fe_sessions, struct mgmt_fe_session_ctx, list_linkage); ++struct mgmt_fe_client_adapter { ++ struct msg_conn *conn; ++ char name[MGMTD_CLIENT_NAME_MAX_LEN]; + +-#define FOREACH_SESSION_IN_LIST(adapter, session) \ +- frr_each_safe (mgmt_fe_sessions, &(adapter)->fe_sessions, (session)) ++ LIST_ENTRY(mgmt_fe_client_adapter) link; ++ ++ /* List of sessions created and being maintained for this client. */ ++ LIST_HEAD(fe_session_list_head, mgmt_fe_session_ctx) sessions; ++ ++ /* NOTE: shared by all sessions, only works b/c one session configuring at a time */ ++ struct mgmt_commit_stats cmt_stats; ++}; + + /* + * A tree for storing unique notify-select strings. +@@ -67,6 +71,13 @@ struct ns_string { + static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2); + DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare); + ++/* ---------- */ ++/* Prototypes */ ++/* ---------- */ ++ ++static struct msg_conn *fe_adapter_create(int conn_fd, union sockunion *from); ++static void fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats); ++ + /* ---------------- */ + /* Global variables */ + /* ---------------- */ +@@ -74,7 +85,7 @@ DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare); + static struct event_loop *mgmt_loop; + static struct msg_server mgmt_fe_server = {.fd = -1}; + +-static struct mgmt_fe_adapters_head mgmt_fe_adapters; ++LIST_HEAD(fe_adapter_list_head, mgmt_fe_client_adapter) fe_adapters; + + static struct hash *mgmt_fe_sessions; + static uint64_t mgmt_fe_next_session_id; +@@ -164,11 +175,36 @@ char **mgmt_fe_get_all_selectors(void) + return selectors; + } + ++enum mgmt_result nb_error_to_mgmt_result(enum nb_error error) ++{ ++ switch (error) { ++ case NB_OK: ++ return MGMTD_SUCCESS; ++ case NB_ERR: ++ return MGMTD_INTERNAL_ERROR; ++ case NB_ERR_NO_CHANGES: ++ return MGMTD_NO_CFG_CHANGES; ++ case NB_ERR_NOT_FOUND: ++ return MGMTD_INVALID_PARAM; ++ case NB_ERR_EXISTS: ++ return MGMTD_INVALID_PARAM; ++ case NB_ERR_LOCKED: ++ return MGMTD_DS_LOCK_FAILED; ++ case NB_ERR_VALIDATION: ++ return MGMTD_VALIDATION_ERROR; ++ case NB_ERR_RESOURCE: ++ return MGMTD_INTERNAL_ERROR; ++ case NB_ERR_INCONSISTENCY: ++ return MGMTD_INTERNAL_ERROR; ++ case NB_YIELD: ++ return MGMTD_UNKNOWN_FAILURE; ++ } ++ return MGMTD_UNKNOWN_FAILURE; ++} + +-/* Forward declarations */ +-static void +-mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session, +- enum mgmt_session_event event); ++/* =========================== */ ++/* Frontend Session Management */ ++/* =========================== */ + + static int mgmt_fe_session_write_lock_ds(enum mgmt_ds_id ds_id, struct mgmt_ds_ctx *ds_ctx, + struct mgmt_fe_session_ctx *session) +@@ -201,80 +237,17 @@ static void mgmt_fe_session_unlock_ds(enum mgmt_ds_id ds_id, struct mgmt_ds_ctx + session->session_id, mgmt_ds_id2name(ds_id)); + + session->ds_locked[ds_id] = false; +- mgmt_ds_unlock(ds_ctx); ++ mgmt_ds_unlock(ds_ctx, session->session_id); + _dbg("Unlocked DS:%s write-locked earlier by session-id: %" PRIu64 " from %s", + mgmt_ds_id2name(ds_id), session->session_id, session->adapter->name); + } + +-static void +-mgmt_fe_session_cfg_txn_cleanup(struct mgmt_fe_session_ctx *session) +-{ +- /* +- * Destroy the actual transaction created earlier. +- */ +- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE) +- mgmt_destroy_txn(&session->cfg_txn_id); +-} +- +-static void +-mgmt_fe_session_show_txn_cleanup(struct mgmt_fe_session_ctx *session) +-{ +- /* +- * Destroy the transaction created recently. +- */ +- if (session->txn_id != MGMTD_TXN_ID_NONE) +- mgmt_destroy_txn(&session->txn_id); +-} +- +-static void +-mgmt_fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats) +-{ +- cmt_stats->last_exec_tm = +- timeval_elapsed(cmt_stats->last_end, cmt_stats->last_start); +- if (cmt_stats->last_exec_tm > cmt_stats->max_tm) { +- cmt_stats->max_tm = cmt_stats->last_exec_tm; +- cmt_stats->max_batch_cnt = cmt_stats->last_batch_cnt; +- } +- +- if (cmt_stats->last_exec_tm < cmt_stats->min_tm) { +- cmt_stats->min_tm = cmt_stats->last_exec_tm; +- cmt_stats->min_batch_cnt = cmt_stats->last_batch_cnt; +- } +-} +- +-static void mgmt_fe_cleanup_session(struct mgmt_fe_session_ctx **sessionp) +-{ +- enum mgmt_ds_id ds_id; +- struct mgmt_ds_ctx *ds_ctx; +- struct mgmt_fe_session_ctx *session = *sessionp; +- +- if (session->adapter) { +- mgmt_fe_session_cfg_txn_cleanup(session); +- mgmt_fe_session_show_txn_cleanup(session); +- for (ds_id = 0; ds_id < MGMTD_DS_MAX_ID; ds_id++) { +- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); +- if (ds_ctx && session->ds_locked[ds_id]) +- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, +- session); +- } +- mgmt_fe_sessions_del(&session->adapter->fe_sessions, session); +- assert(session->adapter->refcount > 1); +- mgmt_fe_adapter_unlock(&session->adapter); +- } +- mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session); +- darr_free_free(session->notify_xpaths); +- hash_release(mgmt_fe_sessions, session); +- XFREE(MTYPE_MGMTD_FE_SESSION, session); +- *sessionp = NULL; +-} +- + static struct mgmt_fe_session_ctx * +-mgmt_fe_find_session_by_client_id(struct mgmt_fe_client_adapter *adapter, +- uint64_t client_id) ++fe_session_lookup_by_client_id(struct mgmt_fe_client_adapter *adapter, uint64_t client_id) + { + struct mgmt_fe_session_ctx *session; + +- FOREACH_SESSION_IN_LIST (adapter, session) { ++ LIST_FOREACH (session, &adapter->sessions, link) { + if (session->client_id == client_id) { + _dbg("Found session-id %" PRIu64 " using client-id %" PRIu64, + session->session_id, client_id); +@@ -285,7 +258,7 @@ mgmt_fe_find_session_by_client_id(struct mgmt_fe_client_adapter *adapter, + return NULL; + } + +-static unsigned int mgmt_fe_session_hash_key(const void *data) ++static unsigned int fe_session_hash_key(const void *data) + { + const struct mgmt_fe_session_ctx *session = data; + +@@ -293,7 +266,7 @@ static unsigned int mgmt_fe_session_hash_key(const void *data) + sizeof(session->session_id) / sizeof(uint32_t), 0); + } + +-static bool mgmt_fe_session_hash_cmp(const void *d1, const void *d2) ++static bool fe_session_hash_cmp(const void *d1, const void *d2) + { + const struct mgmt_fe_session_ctx *session1 = d1; + const struct mgmt_fe_session_ctx *session2 = d2; +@@ -301,12 +274,14 @@ static bool mgmt_fe_session_hash_cmp(const void *d1, const void *d2) + return (session1->session_id == session2->session_id); + } + +-static inline struct mgmt_fe_session_ctx * +-mgmt_session_id2ctx(uint64_t session_id) ++static inline struct mgmt_fe_session_ctx *fe_session_lookup(uint64_t session_id) + { + struct mgmt_fe_session_ctx key = {0}; + struct mgmt_fe_session_ctx *session; + ++ if (session_id == MGMTD_SESSION_ID_NONE) ++ return NULL; ++ + if (!mgmt_fe_sessions) + return NULL; + +@@ -316,31 +291,47 @@ mgmt_session_id2ctx(uint64_t session_id) + return session; + } + +-void mgmt_fe_adapter_toggle_client_debug(bool set) ++static struct mgmt_fe_session_ctx *fe_session_by_txn_id(uint64_t txn_id) + { +- struct mgmt_fe_client_adapter *adapter; ++ uint64_t session_id = mgmt_txn_get_session_id(txn_id); + +- FOREACH_ADAPTER_IN_LIST (adapter) +- adapter->conn->debug = set; ++ if (session_id == MGMTD_SESSION_ID_NONE) ++ return NULL; ++ return fe_session_lookup(session_id); + } + +-static struct mgmt_fe_session_ctx *fe_adapter_session_by_txn_id(uint64_t txn_id) ++static void fe_session_cleanup(struct mgmt_fe_session_ctx **sessionp) + { +- uint64_t session_id = mgmt_txn_get_session_id(txn_id); ++ enum mgmt_ds_id ds_id; ++ struct mgmt_ds_ctx *ds_ctx; ++ struct mgmt_fe_session_ctx *session = *sessionp; + +- if (session_id == MGMTD_SESSION_ID_NONE) +- return NULL; +- return mgmt_session_id2ctx(session_id); ++ /* XXXchopps what about RPC txns? */ ++ mgmt_destroy_txn(&session->cfg_txn_id); ++ mgmt_destroy_txn(&session->txn_id); ++ for (ds_id = 0; ds_id < MGMTD_DS_MAX_ID; ds_id++) { ++ ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); ++ if (ds_ctx && session->ds_locked[ds_id]) ++ mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); ++ } ++ ++ LIST_REMOVE(session, link); ++ ++ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session); ++ darr_free_free(session->notify_xpaths); ++ hash_release(mgmt_fe_sessions, session); ++ XFREE(MTYPE_MGMTD_FE_SESSION, session); ++ *sessionp = NULL; + } + +-static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_adapter *adapter, +- uint8_t notify_format, uint64_t client_id) ++static struct mgmt_fe_session_ctx *fe_session_create(struct mgmt_fe_client_adapter *adapter, ++ uint8_t notify_format, uint64_t client_id) + { + struct mgmt_fe_session_ctx *session; + +- session = mgmt_fe_find_session_by_client_id(adapter, client_id); ++ session = fe_session_lookup_by_client_id(adapter, client_id); + if (session) +- mgmt_fe_cleanup_session(&session); ++ fe_session_cleanup(&session); + + session = XCALLOC(MTYPE_MGMTD_FE_SESSION, + sizeof(struct mgmt_fe_session_ctx)); +@@ -350,8 +341,7 @@ static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_ + session->notify_format = notify_format; + session->txn_id = MGMTD_TXN_ID_NONE; + session->cfg_txn_id = MGMTD_TXN_ID_NONE; +- mgmt_fe_adapter_lock(adapter); +- mgmt_fe_sessions_add_tail(&adapter->fe_sessions, session); ++ LIST_INSERT_HEAD(&adapter->sessions, session, link); + if (!mgmt_fe_next_session_id) + mgmt_fe_next_session_id++; + session->session_id = mgmt_fe_next_session_id++; +@@ -360,42 +350,49 @@ static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_ + return session; + } + +-static int fe_adapter_send_native_msg(struct mgmt_fe_client_adapter *adapter, +- void *msg, size_t len, +- bool short_circuit_ok) +-{ +- return msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, msg, +- len, NULL, short_circuit_ok); +-} + +-static int fe_adapter_conn_send_error(struct msg_conn *conn, +- uint64_t session_id, uint64_t req_id, +- bool short_circuit_ok, int16_t error, +- const char *errfmt, ...) PRINTFRR(6, 7); +-static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, +- uint64_t req_id, bool short_circuit_ok, +- int16_t error, const char *errfmt, ...) +-{ +- va_list ap; +- int ret; ++/* =============================== */ ++/* Frontend Message (API) Handling */ ++/* =============================== */ + +- va_start(ap, errfmt); +- +- ret = vmgmt_msg_native_send_error(conn, session_id, req_id, +- short_circuit_ok, error, errfmt, ap); +- va_end(ap); ++/* ++ * Code structure: 3 functions per message type: ++ * ++ * 1) send reply back to FE client ++ * 2) process result from Txn module to send back to FE client, call (1) ++ * 3) handle request from FE client, either create TXN or call (1) ++ * ++ * For message types that don't require a transaction then (2) is elided. ++ * ++ * Txn's are used by any message that requires a fan-out to multiple backends: ++ * ++ * 1) FE handler creates Txn and calls Txn module to process request ++ * 2) Txn module creates Txn request determines backends to contact and sends ++ * them each a message. ++ * 3) Each backend client responds and the BE adapter calls TXN module ++ * 4) Txn module aggregates results and calls FE reply function to process ++ * send results back to FE client. ++ * ++ * .-> (BE Daemon) -> [BE adapter/Txn Handler] ++ * (1) (2) / o (3) \\ (4) ++ * [FE handler] => [TXN] ... o [TXN] => [FE reply] ++ * session \ o // ++ * `-> (BE Daemon) -> [BE adapter/Txn Handler] ++ */ + +- return ret; ++static int fe_adapter_send_msg(struct mgmt_fe_client_adapter *adapter, void *msg, size_t len, ++ bool short_circuit_ok) ++{ ++ return msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, msg, len, NULL, ++ short_circuit_ok); + } + +-static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, +- uint64_t req_id, bool short_circuit_ok, +- int16_t error, const char *errfmt, ...) ++static int fe_session_send_error(struct mgmt_fe_session_ctx *session, uint64_t req_id, ++ bool short_circuit_ok, int16_t error, const char *errfmt, ...) + PRINTFRR(5, 6); + +-static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, +- uint64_t req_id, bool short_circuit_ok, +- int16_t error, const char *errfmt, ...) ++static int fe_session_send_error(struct mgmt_fe_session_ctx *session, uint64_t req_id, ++ bool short_circuit_ok, int16_t error, const char *errfmt, ...) + { + va_list ap; + int ret; +@@ -409,300 +406,39 @@ static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, + return ret; + } + +- +-static void mgmt_fe_session_cfg_txn_clnup(struct event *thread) +-{ +- struct mgmt_fe_session_ctx *session; +- +- session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread); +- +- mgmt_fe_session_cfg_txn_cleanup(session); +-} +- +-static void mgmt_fe_session_show_txn_clnup(struct event *thread) +-{ +- struct mgmt_fe_session_ctx *session; +- +- session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread); +- +- mgmt_fe_session_show_txn_cleanup(session); +-} +- +-static void +-mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session, +- enum mgmt_session_event event) +-{ +- struct timeval tv = {.tv_sec = 0, +- .tv_usec = MGMTD_FE_MSG_PROC_DELAY_USEC}; +- +- switch (event) { +- case MGMTD_FE_SESSION_CFG_TXN_CLNUP: +- event_add_timer_tv(mgmt_loop, mgmt_fe_session_cfg_txn_clnup, +- session, &tv, &session->proc_cfg_txn_clnp); +- break; +- case MGMTD_FE_SESSION_SHOW_TXN_CLNUP: +- event_add_timer_tv(mgmt_loop, mgmt_fe_session_show_txn_clnup, +- session, &tv, &session->proc_show_txn_clnp); +- break; +- } +-} +- +-static struct mgmt_fe_client_adapter * +-mgmt_fe_find_adapter_by_fd(int conn_fd) +-{ +- struct mgmt_fe_client_adapter *adapter; +- +- FOREACH_ADAPTER_IN_LIST (adapter) { +- if (adapter->conn->fd == conn_fd) +- return adapter; +- } +- +- return NULL; +-} +- +-static void mgmt_fe_adapter_delete(struct mgmt_fe_client_adapter *adapter) +-{ +- struct mgmt_fe_session_ctx *session; +- _dbg("deleting client adapter '%s'", adapter->name); +- +- /* TODO: notify about client disconnect for appropriate cleanup */ +- FOREACH_SESSION_IN_LIST (adapter, session) +- mgmt_fe_cleanup_session(&session); +- mgmt_fe_sessions_fini(&adapter->fe_sessions); +- +- assert(adapter->refcount == 1); +- mgmt_fe_adapter_unlock(&adapter); +-} +- +-static int mgmt_fe_adapter_notify_disconnect(struct msg_conn *conn) +-{ +- struct mgmt_fe_client_adapter *adapter = conn->user; +- +- _dbg("notify disconnect for client adapter '%s'", adapter->name); +- +- mgmt_fe_adapter_delete(adapter); +- +- return 0; +-} +- +-/** +- * Send result of get-tree request back to the FE client. +- * +- * Args: +- * session: the session. +- * req_id: the request ID. +- * short_circuit_ok: if allowed to short circuit the message. +- * result_format: LYD_FORMAT for the sent output. +- * tree: the tree to send, can be NULL which will send an empty tree. +- * partial_error: if an error occurred during gathering results. ++/* ++ * Transaction (txn) Error - send an error back to the FE client and cleanup any ++ * in-progress txn. + * +- * Return: +- * Any error that occurs -- the message is likely not sent if non-zero. ++ * XXXchopps this is used in 2 places for GET-DATA and RPC errors from TXN ++ * module just adapt those 2 normal reply fucntions to take an error result and ++ * get rid of this function. + */ +-static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, +- uint64_t req_id, bool short_circuit_ok, +- uint8_t result_type, uint32_t wd_options, +- const struct lyd_node *tree, +- int partial_error) +- ++int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, bool short_circuit_ok, ++ int16_t error, const char *errstr) + { +- struct mgmt_msg_tree_data *msg; +- uint8_t **darrp = NULL; +- int ret = 0; +- +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, +- MTYPE_MSG_NATIVE_TREE_DATA); +- msg->refer_id = session->session_id; +- msg->req_id = req_id; +- msg->code = MGMT_MSG_CODE_TREE_DATA; +- msg->partial_error = partial_error; +- msg->result_type = result_type; +- +- darrp = mgmt_msg_native_get_darrp(msg); +- ret = yang_print_tree_append(darrp, tree, result_type, +- (wd_options | LYD_PRINT_WITHSIBLINGS)); +- if (ret != LY_SUCCESS) { +- _log_err("Error building get-tree result for client %s session-id %" PRIu64 +- " req-id %" PRIu64 " scok %d result type %u", +- session->adapter->name, session->session_id, req_id, short_circuit_ok, +- result_type); +- goto done; +- } +- +- _dbg("Sending get-tree result from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 +- " scok %d result type %u len %u", +- session->adapter->name, session->session_id, req_id, short_circuit_ok, result_type, +- mgmt_msg_native_get_msg_len(msg)); +- +- ret = fe_adapter_send_native_msg(session->adapter, msg, +- mgmt_msg_native_get_msg_len(msg), +- short_circuit_ok); +-done: +- mgmt_msg_native_free_msg(msg); +- +- return ret; +-} +- +-static int fe_adapter_send_rpc_reply(struct mgmt_fe_session_ctx *session, +- uint64_t req_id, uint8_t result_type, +- const struct lyd_node *result) +-{ +- struct mgmt_msg_rpc_reply *msg; +- uint8_t **darrp = NULL; ++ struct mgmt_fe_session_ctx *session; + int ret; + +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, +- MTYPE_MSG_NATIVE_RPC_REPLY); +- msg->refer_id = session->session_id; +- msg->req_id = req_id; +- msg->code = MGMT_MSG_CODE_RPC_REPLY; +- msg->result_type = result_type; +- +- if (result) { +- darrp = mgmt_msg_native_get_darrp(msg); +- ret = yang_print_tree_append(darrp, result, result_type, 0); +- if (ret != LY_SUCCESS) { +- _log_err("Error building rpc-reply result for client %s session-id %" PRIu64 +- " req-id %" PRIu64 " result type %u", +- session->adapter->name, session->session_id, req_id, result_type); +- goto done; +- } ++ session = fe_session_by_txn_id(txn_id); ++ if (!session) { ++ _log_err("failed sending error for txn-id %" PRIu64 " session not found", txn_id); ++ return -ENOENT; + } + +- _dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 " len %u", +- session->adapter->name, session->session_id, req_id, mgmt_msg_native_get_msg_len(msg)); +- +- ret = fe_adapter_send_native_msg(session->adapter, msg, +- mgmt_msg_native_get_msg_len(msg), +- false); +-done: +- mgmt_msg_native_free_msg(msg); +- +- return ret; +-} +- +-static int fe_adapter_send_edit_reply(struct mgmt_fe_session_ctx *session, +- uint64_t req_id, bool changed, +- bool created, const char *xpath, +- const char *data) +-{ +- struct mgmt_msg_edit_reply *msg; +- int ret; ++ ret = fe_session_send_error(session, req_id, false, error, "%s", errstr); + +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0, +- MTYPE_MSG_NATIVE_EDIT_REPLY); +- msg->refer_id = session->session_id; +- msg->req_id = req_id; +- msg->changed = changed; +- msg->created = created; +- msg->code = MGMT_MSG_CODE_EDIT_REPLY; +- +- mgmt_msg_native_xpath_encode(msg, xpath); +- +- if (data) +- mgmt_msg_native_append(msg, data, strlen(data) + 1); +- +- _dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 +- " changed %u created %u len %u", +- session->adapter->name, session->session_id, req_id, changed, created, +- mgmt_msg_native_get_msg_len(msg)); +- +- ret = fe_adapter_send_native_msg(session->adapter, msg, +- mgmt_msg_native_get_msg_len(msg), +- false); +- mgmt_msg_native_free_msg(msg); +- +- return ret; +-} +- +-static int fe_adapter_native_send_session_reply(struct mgmt_fe_client_adapter *adapter, +- uint64_t session_id, uint64_t req_id, bool created) +-{ +- struct mgmt_msg_session_reply *msg; +- bool scok = adapter->conn->is_short_circuit; +- int ret; +- +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_session_reply, 0, +- MTYPE_MSG_NATIVE_SESSION_REPLY); +- msg->refer_id = session_id; +- msg->req_id = req_id; +- msg->code = MGMT_MSG_CODE_SESSION_REPLY; +- msg->created = created; +- +- _dbg("Sending session-reply from adapter %s to session-id %Lu req-id %Lu created %u scok %u", +- adapter->name, session_id, req_id, created, scok); +- +- ret = fe_adapter_send_native_msg(adapter, msg, mgmt_msg_native_get_msg_len(msg), scok); +- mgmt_msg_native_free_msg(msg); ++ mgmt_destroy_txn(&session->txn_id); + + return ret; + } + +-/** +- * fe_adapter_handle_session_req() - Handle a session-req message from a FE client. +- * @msg_raw: the message data. +- * @msg_len: the length of the message data. +- */ +-static void fe_adapter_handle_session_req(struct mgmt_fe_client_adapter *adapter, void *_msg, +- size_t msg_len) +-{ +- const struct mgmt_msg_session_req *msg = _msg; +- struct mgmt_fe_session_ctx *session; +- bool scok = adapter->conn->is_short_circuit; +- uint64_t client_id; + +- _dbg("Got session-req is create %u req-id %Lu for refer-id %Lu notify-fmt %u from '%s'", +- msg->refer_id == 0, msg->req_id, msg->refer_id, msg->notify_format, adapter->name); ++/* -------------- */ ++/* COMMIT Message */ ++/* -------------- */ + +- /* +- * It's important that any error has its refer_id set to 0 for create +- * case and set to the passed in msg->refer_id otherwise (the destroy +- * case). For non-error return pass the session_id for create or destroy. +- */ +- +- if (msg->refer_id) { +- uint64_t session_id = msg->refer_id; +- +- session = mgmt_session_id2ctx(session_id); +- if (!session) { +- fe_adapter_conn_send_error(adapter->conn, session_id, msg->req_id, scok, +- EINVAL, +- "No session to delete for session-id: %" PRIu64, +- session_id); +- return; +- } +- fe_adapter_native_send_session_reply(adapter, session_id, msg->req_id, false); +- mgmt_fe_cleanup_session(&session); +- return; +- } +- +- client_id = msg->req_id; +- +- /* Default notification format */ +- if (msg->notify_format && msg->notify_format > MGMT_MSG_FORMAT_LAST) { +- fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, +- "Unrecognized notify format: %u", msg->notify_format); +- return; +- } +- +- /* See if we have a client name to register */ +- if (msg_len > sizeof(*msg)) { +- if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { +- fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, +- "Corrupt session-req message rcvd from client-id: %" PRIu64, +- client_id); +- return; +- } +- _dbg("Set client-name to '%s'", msg->client_name); +- strlcpy(adapter->name, msg->client_name, sizeof(adapter->name)); +- } +- +- session = mgmt_fe_create_session(adapter, msg->notify_format ?: DEFAULT_NOTIFY_FORMAT, +- client_id); +- fe_adapter_native_send_session_reply(adapter, session->session_id, msg->req_id, true); +-} +- +-static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, ++static void fe_session_send_commit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, + uint8_t source, uint8_t target, uint8_t action, + bool unlock) + { +@@ -723,8 +459,7 @@ static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, ui + session->session_id, session->adapter->name, req_id, mgmt_ds_id2name(source), + mgmt_ds_id2name(target), action, unlock); + +- ret = fe_adapter_send_native_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), +- false); ++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false); + mgmt_msg_native_free_msg(msg); + if (ret) { + _log_err("Failed to send COMMIT_REPLY to session-id %Lu", session->session_id); +@@ -732,19 +467,89 @@ static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, ui + } + } + +-/** +- * fe_adapter_handle_commit() - Handle a commit message from a FE client. +- * @msg_raw: the message data. +- * @msg_len: the length of the message data. +- */ +-static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void *_msg, ++int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, enum mgmt_ds_id src_ds_id, ++ enum mgmt_ds_id dst_ds_id, uint64_t req_id, bool validate_only, ++ bool unlock, enum mgmt_result result, const char *error_if_any) ++{ ++ struct mgmt_fe_session_ctx *session; ++ uint8_t action; ++ int ret = 0; ++ ++ /* ++ * When a session is deleted (e.g., disconnects) while there's an active ++ * TXN we can get a NULL return for the session_id after the TXN ++ * completes. A commit message never implicitly locks datastores -- ++ * those locks are managed by the client (or cleaned up on session ++ * disconnect). ++ * ++ * However, for the implicit commit (legacy CLI/non-transactional) case ++ * the intention is that each change is made to the candidate and ++ * running at the same (i.e., as the user enters commands they take ++ * affect). So a failure to apply the change to running means we should ++ * back that change out of the candidate DS, and the user will be ++ * presented with an error message. ++ * ++ * So, if we have a failiure to apply an implicit commit, we ++ * should restore the candidate DS, and we can do this by copying ++ * running back over candidate. This doesn't work in the transactional ++ * case b/c the candidate may be made up from multiple changes and a ++ * failure of the latest change shouldn't invalidate all the previous ++ * valid changes which is what would happen if we copied running back ++ * over candidate. ++ */ ++ session = fe_session_lookup(session_id); ++ if (session && session->cfg_txn_id && session->cfg_txn_id != txn_id) ++ session = NULL; ++ ++ if (validate_only) ++ action = MGMT_MSG_COMMIT_VALIDATE; ++ else ++ action = MGMT_MSG_COMMIT_APPLY; ++ ++ /* ++ * Restore the source from the dest in case of error with an implicit commit. ++ * Currently we use the unlock feedback to identify an implicit commit. ++ */ ++ if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES && unlock) ++ mgmt_ds_copy_dss(mgmt_ds_get_ctx_by_id(mm, src_ds_id), ++ mgmt_ds_get_ctx_by_id(mm, dst_ds_id), false); ++ ++ if (!session) ++ return -ENOENT; ++ ++ if (mm->perf_stats_en) ++ gettimeofday(&session->adapter->cmt_stats.last_end, NULL); ++ fe_session_compute_commit_timers(&session->adapter->cmt_stats); ++ ++ if (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES) ++ fe_session_send_commit_reply(session, req_id, src_ds_id, dst_ds_id, action, unlock); ++ else { ++ ret = fe_session_send_error( ++ session, req_id, false, EINVAL /* convert result */, ++ "commit failed session-id %Lu on %s req-id %Lu source-ds: %s target-ds: %s validate-only: %u: reason: '%s'", ++ session->session_id, session->adapter->name, req_id, ++ mgmt_ds_id2name(src_ds_id), mgmt_ds_id2name(dst_ds_id), validate_only, ++ error_if_any ?: ""); ++ } ++ ++ assert(session->cfg_txn_id == txn_id); ++ mgmt_destroy_txn(&session->cfg_txn_id); ++ ++ return ret; ++} ++ ++static void fe_session_handle_commit(struct mgmt_fe_session_ctx *session, void *_msg, + size_t msg_len) + { + struct mgmt_msg_commit *msg = _msg; + struct mgmt_ds_ctx *src_ds_ctx, *dst_ds_ctx; ++ uint64_t txn_id; + +- _dbg("Got COMMIT for source-ds: %s target-ds: %s action: %u on session-id %Lu from '%s'", +- mgmt_ds_id2name(msg->source), mgmt_ds_id2name(msg->target), msg->action, ++ _dbg("Got COMMIT for source-ds: %s target-ds: %s action: %s on session-id %Lu from '%s'", ++ mgmt_ds_id2name(msg->source), mgmt_ds_id2name(msg->target), ++ msg->action == MGMT_MSG_COMMIT_VALIDATE ? "validate" ++ : msg->action == MGMT_MSG_COMMIT_ABORT ? "abort" ++ : "apply", + session->session_id, session->adapter->name); + + if (mm->perf_stats_en) +@@ -753,29 +558,52 @@ static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void * + + /* Validate source and dest DS */ + if (msg->source != MGMTD_DS_CANDIDATE || msg->target != MGMTD_DS_RUNNING) { +- fe_adapter_send_error(session, msg->req_id, false, EINVAL, ++ fe_session_send_error(session, msg->req_id, false, EINVAL, + "source/target for commit must be candidate/running"); + return; + } + +- /* User should have lock on both source and dest DS */ +- if (!session->ds_locked[msg->target] || !session->ds_locked[msg->source]) { +- fe_adapter_send_error(session, msg->req_id, false, EBUSY, +- "source/target not locked by session-id: %Lu on '%s'", ++ src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->source); ++ dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->target); ++ ++ if (mgmt_ds_is_txn_locked(src_ds_ctx, &txn_id) || ++ mgmt_ds_is_txn_locked(dst_ds_ctx, &txn_id)) { ++ fe_session_send_error(session, msg->req_id, false, EBUSY, ++ "source/target datastore is locked by another transaction txn-id: %Lu", ++ txn_id); ++ return; ++ } ++ ++ /* User must always have lock on source */ ++ if (!session->ds_locked[msg->source]) { ++ fe_session_send_error(session, msg->req_id, false, EBUSY, ++ "source not locked by session-id: %Lu on '%s'", + session->session_id, session->adapter->name); + return; + } + ++ /* For apply/abort user must also have lock on target */ ++ if (msg->action != MGMT_MSG_COMMIT_VALIDATE && !session->ds_locked[msg->target]) { ++ fe_session_send_error(session, msg->req_id, false, EBUSY, ++ "target not locked for apply/abort by session-id: %Lu on '%s'", ++ session->session_id, session->adapter->name); ++ return; ++ } ++ ++ /* ++ * We get an exisitng cfg_txn_id if a check/validate was done first then ++ * an apply/abort later. ++ */ + if (session->cfg_txn_id == MGMTD_TXN_ID_NONE) { + /* as we have the lock no-one else should have a config txn */ +- assert(!mgmt_config_txn_in_progress()); ++ assert(!mgmt_txn_config_in_progress()); + + /* + * Start a CONFIG Transaction (if not started already) + */ + session->cfg_txn_id = mgmt_create_txn(session->session_id, MGMTD_TXN_TYPE_CONFIG); + if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { +- fe_adapter_send_error(session, msg->req_id, false, ENOMEM, ++ fe_session_send_error(session, msg->req_id, false, ENOMEM, + "failed to create config transaction for session-id: %Lu on '%s'", + session->session_id, session->adapter->name); + return; +@@ -787,23 +615,19 @@ static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void * + /* + * Create COMMIT Config request under the transaction + */ +- src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->source); +- dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->target); +- assert(src_ds_ctx && dst_ds_ctx); +- if (mgmt_txn_send_commit_config_req(session->cfg_txn_id, msg->req_id, msg->source, +- src_ds_ctx, msg->target, dst_ds_ctx, +- msg->action == MGMT_MSG_COMMIT_VALIDATE, +- msg->action == MGMT_MSG_COMMIT_ABORT, +- false /* implicit */, msg->unlock, NULL) != 0) { +- fe_adapter_send_error(session, msg->req_id, false, EINVAL, +- "failed to create config request under txn-id: %Lu for session-id: %Lu on '%s'", +- session->cfg_txn_id, session->session_id, +- session->adapter->name); +- } ++ mgmt_txn_send_commit_config_req(session->cfg_txn_id, msg->req_id, msg->source, src_ds_ctx, ++ msg->target, dst_ds_ctx, ++ msg->action == MGMT_MSG_COMMIT_VALIDATE, ++ msg->action == MGMT_MSG_COMMIT_ABORT, false /* implicit */, ++ msg->unlock, NULL); + } + +-static int fe_adapter_native_send_lock_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, +- uint8_t datastore, bool lock, bool short_circuit_ok) ++/* ------------ */ ++/* LOCK Message */ ++/* ------------ */ ++ ++static int fe_session_send_lock_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, ++ uint8_t datastore, bool lock, bool short_circuit_ok) + { + struct mgmt_msg_lock_reply *msg; + int ret; +@@ -819,25 +643,21 @@ static int fe_adapter_native_send_lock_reply(struct mgmt_fe_session_ctx *session + session->adapter->name, session->session_id, req_id, datastore, lock, + short_circuit_ok); + +- ret = fe_adapter_send_native_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), +- short_circuit_ok); ++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), ++ short_circuit_ok); + mgmt_msg_native_free_msg(msg); + + return ret; + } + +-/** +- * fe_adapter_handle_lock() - Handle a session-req message from a FE client. +- * @msg_raw: the message data. +- * @msg_len: the length of the message data. +- */ +-static void fe_adapter_handle_lock(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) ++static void fe_session_handle_lock(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) + { + const struct mgmt_msg_lock *msg = _msg; + struct mgmt_ds_ctx *ds_ctx; + bool short_circuit_ok = session->adapter->conn->is_short_circuit; + uint8_t datastore = msg->datastore; + uint64_t lock_session; ++ uint64_t txn_id; + bool lock = msg->lock; + int ret; + +@@ -845,48 +665,111 @@ static void fe_adapter_handle_lock(struct mgmt_fe_session_ctx *session, void *_m + mgmt_ds_id2name(datastore), msg->refer_id, session->adapter->name); + + if (datastore != MGMTD_DS_CANDIDATE && datastore != MGMTD_DS_RUNNING) { +- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EINVAL, ++ fe_session_send_error(session, msg->req_id, short_circuit_ok, EINVAL, + "Lock/Unlock on DS other than candidate or running DS not supported"); + return; + } + +- ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore); +- assert(ds_ctx); ++ ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore); ++ assert(ds_ctx); ++ ++ if (lock && mgmt_ds_is_locked(ds_ctx, &lock_session, &txn_id) && ++ lock_session != session->session_id) { ++ fe_session_send_error(session, msg->req_id, short_circuit_ok, EBUSY, ++ "Lock already taken on datastore %s by session: %Lu txn-id: %Lu", ++ mgmt_ds_id2name(datastore), lock_session, txn_id); ++ return; ++ } else if (lock) { ++ ret = mgmt_fe_session_write_lock_ds(datastore, ds_ctx, session); ++ if (ret) { ++ fe_session_send_error(session, msg->req_id, short_circuit_ok, EBUSY, ++ "Unexpected error %d trying to lock datastore by session-id: %Lu", ++ ret, session->session_id); ++ return; ++ } ++ } else { ++ /* unlock even if one or both of the lock indicators is wrong */ ++ mgmt_fe_session_unlock_ds(datastore, ds_ctx, session); ++ } ++ ++ if (fe_session_send_lock_reply(session, msg->req_id, msg->datastore, msg->lock, ++ short_circuit_ok)) { ++ assert(!short_circuit_ok); ++ _log_err("Failed to send LOCK_REPLY to session-id %Lu", session->session_id); ++ msg_conn_disconnect(session->adapter->conn, false); ++ } ++} ++ ++/* ---------------- */ ++/* GET-DATA Message */ ++/* ---------------- */ ++ ++/* ++ * Send result of get-tree request back to the FE client. ++ */ ++static int fe_session_send_tree_data(struct mgmt_fe_session_ctx *session, uint64_t req_id, ++ bool short_circuit_ok, uint8_t result_type, ++ uint32_t wd_options, const struct lyd_node *tree, ++ int partial_error) ++ ++{ ++ struct mgmt_msg_tree_data *msg; ++ uint8_t **darrp = NULL; ++ int ret = 0; ++ ++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, MTYPE_MSG_NATIVE_TREE_DATA); ++ msg->refer_id = session->session_id; ++ msg->req_id = req_id; ++ msg->code = MGMT_MSG_CODE_TREE_DATA; ++ msg->partial_error = partial_error; ++ msg->result_type = result_type; ++ ++ darrp = mgmt_msg_native_get_darrp(msg); ++ ret = yang_print_tree_append(darrp, tree, result_type, ++ (wd_options | LYD_PRINT_WITHSIBLINGS)); ++ if (ret != LY_SUCCESS) { ++ _log_err("Error building get-tree result for client %s session-id %" PRIu64 ++ " req-id %" PRIu64 " scok %d result type %u", ++ session->adapter->name, session->session_id, req_id, short_circuit_ok, ++ result_type); ++ goto done; ++ } ++ ++ _dbg("Sending get-tree result from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 ++ " scok %d result type %u len %u", ++ session->adapter->name, session->session_id, req_id, short_circuit_ok, result_type, ++ mgmt_msg_native_get_msg_len(msg)); ++ ++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), ++ short_circuit_ok); ++done: ++ mgmt_msg_native_free_msg(msg); ++ ++ return ret; ++} + +- if (lock && mgmt_ds_is_locked(ds_ctx, &lock_session) && +- lock_session != session->session_id) { +- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EBUSY, +- "Lock already taken on datastore by session: %Lu", +- lock_session); ++void mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint64_t req_id, ++ LYD_FORMAT result_type, uint32_t wd_options, ++ const struct lyd_node *tree, int partial_error, ++ bool short_circuit_ok) ++{ ++ struct mgmt_fe_session_ctx *session; ++ ++ session = fe_session_lookup(session_id); ++ if (!session) + return; +- } else if (lock) { +- ret = mgmt_fe_session_write_lock_ds(datastore, ds_ctx, session); +- if (ret) { +- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EBUSY, +- "Unexpected error %d trying to lock datastore by session-id: %Lu", +- ret, session->session_id); +- return; +- } +- } else { +- /* unlock even if one or both of the lock indicators is wrong */ +- mgmt_fe_session_unlock_ds(datastore, ds_ctx, session); +- } + +- if (fe_adapter_native_send_lock_reply(session, msg->req_id, msg->datastore, msg->lock, +- short_circuit_ok)) { +- assert(!short_circuit_ok); +- _log_err("Failed to send LOCK_REPLY to session-id %Lu", session->session_id); +- msg_conn_disconnect(session->adapter->conn, false); +- } ++ /* XXXchopps why do we care about this? Why not allow multple? */ ++ assert(session->txn_id == txn_id); ++ ++ if (fe_session_send_tree_data(session, req_id, short_circuit_ok, result_type, wd_options, ++ tree, partial_error)) ++ fe_session_send_error(session, req_id, false, -EIO, ++ "Failed sending GET-DATA reply"); ++ mgmt_destroy_txn(&session->txn_id); + } + +-/** +- * 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_data(struct mgmt_fe_session_ctx *session, void *_msg, ++static void fe_session_handle_get_data(struct mgmt_fe_session_ctx *session, void *_msg, + size_t msg_len) + { + struct mgmt_msg_get_data *msg = _msg; +@@ -905,14 +788,14 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + session->adapter->name, session->session_id, msg->req_id); + + if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { +- fe_adapter_send_error(session, req_id, false, -EINVAL, ++ fe_session_send_error(session, req_id, false, -EINVAL, + "Invalid message rcvd from session-id: %" PRIu64, + session->session_id); + goto done; + } + + if (session->txn_id != MGMTD_TXN_ID_NONE) { +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, ++ fe_session_send_error(session, req_id, false, -EINPROGRESS, + "Transaction in progress txn-id: %" PRIu64 + " for session-id: %" PRIu64, + session->txn_id, session->session_id); +@@ -933,7 +816,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + wd_options = LYD_PRINT_WD_IMPL_TAG; + break; + default: +- fe_adapter_send_error(session, req_id, false, -EINVAL, ++ fe_session_send_error(session, req_id, false, -EINVAL, + "Invalid defaults value %u for session-id: %" PRIu64, + msg->defaults, session->session_id); + goto done; +@@ -955,13 +838,13 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + err = ly_ctx_get_yanglib_data(ly_native_ctx, &ylib, "%u", + ly_ctx_get_change_count(ly_native_ctx)); + if (err) { +- fe_adapter_send_error(session, req_id, false, err, ++ fe_session_send_error(session, req_id, false, err, + "Error getting yang-library data, session-id: %" PRIu64 + " error: %s", + session->session_id, ly_last_errmsg()); + } else if (nb_oper_is_yang_lib_query(msg->xpath)) { + yang_lyd_trim_xpath(&ylib, msg->xpath); +- (void)fe_adapter_send_tree_data(session, req_id, false, msg->result_type, ++ (void)fe_session_send_tree_data(session, req_id, false, msg->result_type, + wd_options, ylib, 0); + goto done; + } +@@ -978,7 +861,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + ds_id = MGMTD_DS_OPERATIONAL; + break; + default: +- fe_adapter_send_error(session, req_id, false, -EINVAL, ++ fe_session_send_error(session, req_id, false, -EINVAL, + "Unsupported datastore %" PRIu8 + " requested from session-id: %" PRIu64, + msg->datastore, session->session_id); +@@ -988,7 +871,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + err = yang_resolve_snode_xpath(ly_native_ctx, msg->xpath, &snodes, + &simple_xpath); + if (err) { +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, ++ fe_session_send_error(session, req_id, false, -EINPROGRESS, + "XPath doesn't resolve for session-id: %" PRIu64, + session->session_id); + goto done; +@@ -1002,8 +885,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + _dbg("No backends provide xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64, + msg->xpath, session->txn_id, session->session_id); + +- fe_adapter_send_tree_data(session, req_id, false, +- msg->result_type, wd_options, NULL, 0); ++ fe_session_send_tree_data(session, req_id, false, msg->result_type, wd_options, ++ NULL, 0); + goto done; + } + +@@ -1013,8 +896,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + /* Start a SHOW Transaction */ + session->txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_SHOW); +- if (session->txn_id == MGMTD_SESSION_ID_NONE) { +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, ++ if (session->txn_id == MGMTD_TXN_ID_NONE) { ++ fe_session_send_error(session, req_id, false, -EINPROGRESS, + "failed to create a 'show' txn"); + goto done; + } +@@ -1028,7 +911,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void + if (ret) { + /* destroy the just created txn */ + mgmt_destroy_txn(&session->txn_id); +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, ++ fe_session_send_error(session, req_id, false, -EINPROGRESS, + "failed to create a 'show' txn"); + } + done: +@@ -1037,120 +920,215 @@ done: + darr_free(snodes); + } + +-static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) ++/* ------------ */ ++/* EDIT Message */ ++/* ------------ */ ++ ++static int fe_session_send_edit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, ++ bool changed, bool created, const char *xpath, ++ const char *data) ++{ ++ struct mgmt_msg_edit_reply *msg; ++ int ret; ++ ++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0, MTYPE_MSG_NATIVE_EDIT_REPLY); ++ msg->refer_id = session->session_id; ++ msg->req_id = req_id; ++ msg->changed = changed; ++ msg->created = created; ++ msg->code = MGMT_MSG_CODE_EDIT_REPLY; ++ ++ mgmt_msg_native_xpath_encode(msg, xpath); ++ ++ if (data) ++ mgmt_msg_native_append(msg, data, strlen(data) + 1); ++ ++ _dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 ++ " changed %u created %u len %u", ++ session->adapter->name, session->session_id, req_id, changed, created, ++ mgmt_msg_native_get_msg_len(msg)); ++ ++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false); ++ mgmt_msg_native_free_msg(msg); ++ ++ return ret; ++} ++ ++ ++int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, ++ bool unlock, bool commit, struct mgmt_edit_req **edit, ++ enum mgmt_result result, const char *errstr) ++{ ++ struct mgmt_fe_session_ctx *session; ++ const enum mgmt_ds_id can_id = MGMTD_DS_CANDIDATE; ++ const enum mgmt_ds_id run_id = MGMTD_DS_RUNNING; ++ struct mgmt_ds_ctx *can_ds = mgmt_ds_get_ctx_by_id(mm, can_id); ++ struct mgmt_ds_ctx *run_ds = mgmt_ds_get_ctx_by_id(mm, run_id); ++ int ret; ++ ++ /* ++ * When a session is deleted (e.g., disconnects) while there's an ++ * active TXN we can get a NULL return here when the TXN completes. We ++ * still want to do any cleanup that the session cleanup could not ++ * accomplish b/c of the outstanding TXN. ++ */ ++ session = fe_session_lookup(session_id); ++ if (session && session->cfg_txn_id && session->cfg_txn_id != txn_id) ++ session = NULL; ++ ++ /* ++ * Deal with the backup candidate config. If the edit was successful we ++ * free the backup. If it failed we restore the datastore from the ++ * backup. ++ */ ++ if (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES) ++ nb_config_free((*edit)->nb_backup); ++ else ++ mgmt_ds_restore_nb_config(can_ds, (*edit)->nb_backup); ++ ++ if (!session) ++ return -ENOENT; ++ ++ if ((*edit)->unlock_running) ++ mgmt_fe_session_unlock_ds(run_id, run_ds, session); ++ if ((*edit)->unlock_candidate) ++ mgmt_fe_session_unlock_ds(can_id, can_ds, session); ++ ++ ++ if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES) ++ /* Fix the error */ ++ ret = fe_session_send_error(session, req_id, false, -EINVAL, "%s", errstr); ++ else ++ ret = fe_session_send_edit_reply(session, req_id, ++ result == MGMTD_SUCCESS /*changed*/, ++ (*edit)->created, (*edit)->xpath_created, errstr); ++ XFREE(MTYPE_MGMTD_TXN_REQ, *edit); ++ ++ assert(session->cfg_txn_id == txn_id); ++ mgmt_destroy_txn(&session->cfg_txn_id); ++ ++ return ret; ++} ++ ++static void fe_session_handle_edit(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) + { + struct mgmt_msg_edit *msg = _msg; +- enum mgmt_ds_id ds_id, rds_id; +- struct mgmt_ds_ctx *ds_ctx, *rds_ctx; ++ const enum mgmt_ds_id can_id = MGMTD_DS_CANDIDATE; ++ const enum mgmt_ds_id run_id = MGMTD_DS_RUNNING; ++ struct mgmt_ds_ctx *can_ds = mgmt_ds_get_ctx_by_id(mm, can_id); ++ struct mgmt_ds_ctx *run_ds = mgmt_ds_get_ctx_by_id(mm, run_id); ++ struct mgmt_edit_req *edit; ++ struct nb_config *nb_config; ++ uint64_t txn_id = MGMTD_TXN_ID_NONE; ++ bool implicit_can_lock = false; ++ bool implicit_run_lock = false; + const char *xpath, *data; +- bool lock, commit; ++ char errstr[BUFSIZ]; ++ bool commit; + int ret; + +- lock = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_LOCK); ++ /* grab the deprecated commit flag -- the lock flag isn't required at all */ + commit = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_COMMIT); + +- if (lock && commit && msg->datastore == MGMT_MSG_DATASTORE_RUNNING) +- ; +- else if (msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) { +- fe_adapter_send_error(session, msg->req_id, false, -EINVAL, ++ /* ++ * Validate input args and obtain any needed locks ++ */ ++ ++ commit = commit || msg->datastore == MGMT_MSG_DATASTORE_RUNNING; ++ if (!commit && msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) { ++ fe_session_send_error(session, msg->req_id, false, -EINVAL, + "Unsupported datastore"); + return; + } + ++ /* Decode the target xpath and config changes */ + xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); + if (!xpath) { +- fe_adapter_send_error(session, msg->req_id, false, -EINVAL, +- "Invalid message"); ++ fe_session_send_error(session, msg->req_id, false, -EINVAL, "Invalid message"); + return; + } + +- ds_id = MGMTD_DS_CANDIDATE; +- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); +- assert(ds_ctx); +- +- rds_id = MGMTD_DS_RUNNING; +- rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); +- assert(rds_ctx); ++ /* If committing, make sure no other txn has locks on the datastores */ ++ if (commit && ++ (mgmt_ds_is_txn_locked(can_ds, &txn_id) || mgmt_ds_is_txn_locked(run_ds, &txn_id))) { ++ fe_session_send_error(session, msg->req_id, false, -EBUSY, ++ "datastores are locked by another transaction txn-id: %Lu", ++ txn_id); ++ return; ++ } + +- if (lock) { +- if (mgmt_fe_session_write_lock_ds(ds_id, ds_ctx, session)) { +- fe_adapter_send_error(session, msg->req_id, false, +- -EBUSY, ++ /* We always ensure the candidate DS is locked */ ++ if (!session->ds_locked[can_id]) { ++ if (mgmt_fe_session_write_lock_ds(can_id, can_ds, session)) { ++ fe_session_send_error(session, msg->req_id, false, -EBUSY, + "Candidate DS is locked by another session"); + return; + } ++ implicit_can_lock = true; ++ } + +- if (commit) { +- if (mgmt_fe_session_write_lock_ds(rds_id, rds_ctx, +- session)) { +- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, +- session); +- fe_adapter_send_error( +- session, msg->req_id, false, -EBUSY, +- "Running DS is locked by another session"); +- return; +- } +- } +- } else { +- if (!session->ds_locked[ds_id]) { +- fe_adapter_send_error(session, msg->req_id, false, +- -EBUSY, +- "Candidate DS is not locked"); ++ /* And if modifying running, ensure it is locked too */ ++ if (commit && !session->ds_locked[run_id]) { ++ if (mgmt_fe_session_write_lock_ds(run_id, run_ds, session)) { ++ if (implicit_can_lock) ++ mgmt_fe_session_unlock_ds(can_id, can_ds, session); ++ fe_session_send_error(session, msg->req_id, false, -EBUSY, ++ "Running DS is locked by another session"); + return; + } +- +- if (commit) { +- if (!session->ds_locked[rds_id]) { +- fe_adapter_send_error(session, msg->req_id, +- false, -EBUSY, +- "Running DS is not locked"); +- return; +- } +- } ++ implicit_run_lock = true; + } + +- session->cfg_txn_id = mgmt_create_txn(session->session_id, +- MGMTD_TXN_TYPE_CONFIG); +- if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { +- if (lock) { +- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); +- if (commit) +- mgmt_fe_session_unlock_ds(rds_id, rds_ctx, +- session); ++ /* ++ * Everythign valid and setup, proceed to make the edit. ++ */ ++ ++ errstr[0] = '\0'; ++ nb_config = mgmt_ds_get_nb_config(can_ds); ++ ++ edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req)); ++ edit->unlock_candidate = implicit_can_lock; ++ edit->unlock_running = implicit_run_lock; ++ edit->nb_backup = nb_config_dup(nb_config); /* keep a backup of candidate */ ++ ++ /* Make edits to the candidate DS */ ++ ret = nb_candidate_edit_tree(nb_config, msg->operation, msg->request_type, xpath, data, ++ &edit->created, edit->xpath_created, errstr, sizeof(errstr)); ++ if (ret && ret == NB_ERR_NO_CHANGES) ++ ret = NB_OK; ++ else if (ret) ++ _dbg("Edit failed for txn-id %Lu req-id %Lu: %s: restoring candidate", txn_id, ++ msg->req_id, errstr[0] ? errstr : "unknown reason"); ++ else if (commit) { ++ /* Get a TXN for the commit */ ++ txn_id = mgmt_create_txn(session->session_id, MGMTD_TXN_TYPE_CONFIG); ++ if (txn_id == MGMTD_SESSION_ID_NONE) { ++ ret = NB_ERR_EXISTS; /* should not happen as we have the locks */ ++ goto reply; + } +- fe_adapter_send_error(session, msg->req_id, false, -EBUSY, +- "Failed to create a configuration transaction"); +- return; +- } ++ session->cfg_txn_id = txn_id; + +- _dbg("Created new config txn-id: %" PRIu64 " for session-id: %" PRIu64, session->cfg_txn_id, +- session->session_id); ++ _dbg("Created new config txn-id: %Lu for session-id: %Lu", txn_id, ++ session->session_id); + +- ret = mgmt_txn_send_edit(session->cfg_txn_id, msg->req_id, ds_id, +- ds_ctx, rds_id, rds_ctx, lock, commit, +- msg->request_type, msg->flags, msg->operation, +- xpath, data); +- if (ret) { +- /* destroy the just created txn */ +- mgmt_destroy_txn(&session->cfg_txn_id); +- if (lock) { +- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); +- if (commit) +- mgmt_fe_session_unlock_ds(rds_id, rds_ctx, +- session); +- } +- fe_adapter_send_error(session, msg->req_id, false, -EBUSY, +- "Failed to create a configuration transaction"); ++ /* And this is modifying the running */ ++ mgmt_txn_send_commit_config_req(txn_id, msg->req_id, can_id, can_ds, run_id, run_ds, ++ false, false, true /* implicit */, false, edit); ++ return; + } ++reply: ++ mgmt_fe_adapter_send_edit_reply(session->session_id, txn_id, msg->req_id, false, commit, ++ &edit, nb_error_to_mgmt_result(ret), errstr); + } + +-/** +- * fe_adapter_handle_notify_select() - Handle an Notify Select message. +- * @session: the client session. +- * @_msg: the message data. +- * @msg_len: the length of the message data. ++/* --------------------- */ ++/* NOTIFY-SELECT Message */ ++/* --------------------- */ ++ ++/* ++ * Handle an Notify Select message - there's no reply for this message. + */ +-static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, void *_msg, ++static void fe_session_handle_notify_select(struct mgmt_fe_session_ctx *session, void *_msg, + size_t msg_len) + { + struct mgmt_msg_notify_select *msg = _msg; +@@ -1162,13 +1140,12 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, + char *selstr = NULL; + uint64_t clients = 0; + uint64_t all_matched = 0, rm_clients = 0; +- uint ret = NB_OK; + + + if (msg_len >= sizeof(*msg)) { + selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors); + if (!selectors) { +- fe_adapter_send_error(session, req_id, false, -EINVAL, "Invalid message"); ++ fe_session_send_error(session, req_id, false, -EINVAL, "Invalid message"); + return; + } + } +@@ -1177,7 +1154,7 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, + darr_foreach_p (selectors, sp) { + nb_nodes = nb_nodes_find(*sp); + if (!nb_nodes) { +- fe_adapter_send_error(session, req_id, false, -EINVAL, ++ fe_session_send_error(session, req_id, false, -EINVAL, + "Selector doesn't resolve to a node: %s", *sp); + darr_free_free(selectors); + return; +@@ -1236,35 +1213,22 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, + session->session_id); + goto done; + } +- if (!(clients | rm_clients)) { ++ if (!(clients | rm_clients)) + _dbg("No backends to newly notify for selectors: '%s' session-id: %Lu", selstr, + session->session_id); +- } else { +- /* +- * First send a message to set the selectors on the changed clients. +- */ +- ret = mgmt_txn_send_notify_selectors(req_id, MGMTD_SESSION_ID_NONE, +- (clients | rm_clients), +- msg->replace ? NULL : selectors); +- if (ret) { +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, +- "Failed to create a NOTIFY_SELECT transaction"); +- } +- } ++ else ++ /* Send a message to set the selectors on the changed clients */ ++ mgmt_txn_send_notify_selectors(req_id, MGMTD_SESSION_ID_NONE, ++ (clients | rm_clients), ++ msg->replace ? NULL : selectors); + +- if (ret != NB_OK || !all_matched || !selectors) ++ if (!all_matched || !selectors) + goto done; + + _dbg("Created new push for session-id: %Lu", session->session_id); + +- /* +- * Send a second message requesting a full state dump +- */ +- ret = mgmt_txn_send_notify_selectors(req_id, session->session_id, all_matched, selectors); +- if (ret) { +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, +- "Failed to create a NOTIFY_SELECT transaction"); +- } ++ /* Send a second message requesting a full state dump for the session */ ++ mgmt_txn_send_notify_selectors(req_id, session->session_id, all_matched, selectors); + done: + if (session->notify_xpaths != selectors) + darr_free(selectors); +@@ -1272,33 +1236,82 @@ done: + XFREE(MTYPE_TMP, selstr); + } + +-/** +- * fe_adapter_handle_rpc() - Handle an RPC message from an FE client. +- * @session: the client session. +- * @_msg: the message data. +- * @msg_len: the length of the message data. +- */ +-static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) ++/* ----------- */ ++/* RPC Message */ ++/* ----------- */ ++ ++static int fe_session_send_rpc_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, ++ uint8_t result_type, const struct lyd_node *result) ++{ ++ struct mgmt_msg_rpc_reply *msg; ++ uint8_t **darrp = NULL; ++ int ret; ++ ++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, MTYPE_MSG_NATIVE_RPC_REPLY); ++ msg->refer_id = session->session_id; ++ msg->req_id = req_id; ++ msg->code = MGMT_MSG_CODE_RPC_REPLY; ++ msg->result_type = result_type; ++ ++ if (result) { ++ darrp = mgmt_msg_native_get_darrp(msg); ++ ret = yang_print_tree_append(darrp, result, result_type, 0); ++ if (ret != LY_SUCCESS) { ++ _log_err("Error building rpc-reply result for client %s session-id %" PRIu64 ++ " req-id %" PRIu64 " result type %u", ++ session->adapter->name, session->session_id, req_id, result_type); ++ goto done; ++ } ++ } ++ ++ _dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 ++ " len %u", ++ session->adapter->name, session->session_id, req_id, mgmt_msg_native_get_msg_len(msg)); ++ ++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false); ++done: ++ mgmt_msg_native_free_msg(msg); ++ ++ return ret; ++} ++ ++void mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, ++ LYD_FORMAT result_type, const struct lyd_node *result) ++{ ++ struct mgmt_fe_session_ctx *session; ++ ++ session = fe_session_lookup(session_id); ++ if (!session) ++ return; ++ ++ /* XXXchopps why do we care about this? Why not allow multple? */ ++ assert(session->txn_id == txn_id); ++ ++ if (fe_session_send_rpc_reply(session, req_id, result_type, result)) ++ fe_session_send_error(session, req_id, false, -EIO, "Failed sending RPC reply"); ++ ++ mgmt_destroy_txn(&session->txn_id); ++} ++ ++static void fe_session_handle_rpc(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) + { + struct mgmt_msg_rpc *msg = _msg; + const struct lysc_node *snode; + const char *xpath, *data; + uint64_t req_id = msg->req_id; + uint64_t clients; +- int ret; + + _dbg("Received RPC request from client %s for session-id %" PRIu64 " req-id %" PRIu64, + session->adapter->name, session->session_id, msg->req_id); + + xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); + if (!xpath) { +- fe_adapter_send_error(session, req_id, false, -EINVAL, +- "Invalid message"); ++ fe_session_send_error(session, req_id, false, -EINVAL, "Invalid message"); + return; + } + + if (session->txn_id != MGMTD_TXN_ID_NONE) { +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, ++ fe_session_send_error(session, req_id, false, -EINPROGRESS, + "Transaction in progress txn-id: %" PRIu64 + " for session-id: %" PRIu64, + session->txn_id, session->session_id); +@@ -1307,13 +1320,12 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms + + snode = lys_find_path(ly_native_ctx, NULL, xpath, 0); + if (!snode) { +- fe_adapter_send_error(session, req_id, false, -ENOENT, +- "No such path: %s", xpath); ++ fe_session_send_error(session, req_id, false, -ENOENT, "No such path: %s", xpath); + return; + } + + if (snode->nodetype != LYS_RPC && snode->nodetype != LYS_ACTION) { +- fe_adapter_send_error(session, req_id, false, -EINVAL, ++ fe_session_send_error(session, req_id, false, -EINVAL, + "Not an RPC or action path: %s", xpath); + return; + } +@@ -1324,7 +1336,7 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms + _dbg("No backends implement xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64, + xpath, session->txn_id, session->session_id); + +- fe_adapter_send_error(session, req_id, false, -ENOENT, ++ fe_session_send_error(session, req_id, false, -ENOENT, + "No backends implement xpath: %s", xpath); + return; + } +@@ -1333,7 +1345,7 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms + session->txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_RPC); + if (session->txn_id == MGMTD_SESSION_ID_NONE) { +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, ++ fe_session_send_error(session, req_id, false, -EINPROGRESS, + "Failed to create an RPC transaction"); + return; + } +@@ -1342,27 +1354,140 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms + session->session_id); + + /* Create an RPC request under the transaction */ +- ret = mgmt_txn_send_rpc(session->txn_id, req_id, clients, +- msg->request_type, xpath, data, +- mgmt_msg_native_data_len_decode(msg, msg_len)); +- if (ret) { +- /* destroy the just created txn */ +- mgmt_destroy_txn(&session->txn_id); +- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, +- "Failed to create an RPC transaction"); ++ mgmt_txn_send_rpc(session->txn_id, req_id, clients, msg->request_type, xpath, data, ++ mgmt_msg_native_data_len_decode(msg, msg_len)); ++} ++ ++/* -------------------- */ ++/* New Session Requests */ ++/* -------------------- */ ++ ++static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, uint64_t req_id, ++ bool short_circuit_ok, int16_t error, const char *errfmt, ++ ...) PRINTFRR(6, 7); ++static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, uint64_t req_id, ++ bool short_circuit_ok, int16_t error, const char *errfmt, ...) ++{ ++ va_list ap; ++ int ret; ++ ++ va_start(ap, errfmt); ++ ++ ret = vmgmt_msg_native_send_error(conn, session_id, req_id, short_circuit_ok, error, ++ errfmt, ap); ++ va_end(ap); ++ ++ return ret; ++} ++ ++static int fe_adapter_send_session_reply(struct mgmt_fe_client_adapter *adapter, ++ uint64_t session_id, uint64_t req_id, bool created) ++{ ++ struct mgmt_msg_session_reply *msg; ++ bool scok = adapter->conn->is_short_circuit; ++ int ret; ++ ++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_session_reply, 0, ++ MTYPE_MSG_NATIVE_SESSION_REPLY); ++ msg->refer_id = session_id; ++ msg->req_id = req_id; ++ msg->code = MGMT_MSG_CODE_SESSION_REPLY; ++ msg->created = created; ++ ++ _dbg("Sending session-reply from adapter %s to session-id %Lu req-id %Lu created %u scok %u", ++ adapter->name, session_id, req_id, created, scok); ++ ++ ret = fe_adapter_send_msg(adapter, msg, mgmt_msg_native_get_msg_len(msg), scok); ++ mgmt_msg_native_free_msg(msg); ++ ++ return ret; ++} ++ ++/* ++ * Handle a session-req message from a FE client. ++ */ ++static void fe_adapter_handle_session_req(struct mgmt_fe_client_adapter *adapter, void *_msg, ++ size_t msg_len) ++{ ++ const struct mgmt_msg_session_req *msg = _msg; ++ struct mgmt_fe_session_ctx *session; ++ bool scok = adapter->conn->is_short_circuit; ++ uint64_t client_id; ++ ++ _dbg("Got session-req is create %u req-id %Lu for refer-id %Lu notify-fmt %u from '%s'", ++ msg->refer_id == 0, msg->req_id, msg->refer_id, msg->notify_format, adapter->name); ++ ++ /* ++ * It's important that any error has its refer_id set to 0 for create ++ * case and set to the passed in msg->refer_id otherwise (the destroy ++ * case). For non-error return pass the session_id for create or destroy. ++ */ ++ ++ if (msg->refer_id) { ++ uint64_t session_id = msg->refer_id; ++ ++ session = fe_session_lookup(session_id); ++ if (!session) { ++ fe_adapter_conn_send_error(adapter->conn, session_id, msg->req_id, scok, ++ EINVAL, ++ "No session to delete for session-id: %" PRIu64, ++ session_id); ++ return; ++ } ++ fe_adapter_send_session_reply(adapter, session_id, msg->req_id, false); ++ fe_session_cleanup(&session); ++ return; ++ } ++ ++ client_id = msg->req_id; ++ ++ /* Default notification format */ ++ if (msg->notify_format && msg->notify_format > MGMT_MSG_FORMAT_LAST) { ++ fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, ++ "Unrecognized notify format: %u", msg->notify_format); ++ return; ++ } ++ ++ /* See if we have a client name to register */ ++ if (msg_len > sizeof(*msg)) { ++ if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { ++ fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, ++ "Corrupt session-req msg from client-id: %Lu", ++ client_id); ++ return; ++ } ++ _dbg("Set client-name to '%s'", msg->client_name); ++ strlcpy(adapter->name, msg->client_name, sizeof(adapter->name)); + } ++ ++ session = fe_session_create(adapter, msg->notify_format ?: DEFAULT_NOTIFY_FORMAT, ++ client_id); ++ fe_adapter_send_session_reply(adapter, session->session_id, msg->req_id, true); + } + +-/** +- * Handle a native encoded message from the FE client. ++/* ++ * Handle a message from the FE client. + */ +-static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, +- struct mgmt_msg_header *msg, +- size_t msg_len) ++static void fe_adapter_process_msg(uint8_t version, uint8_t *data, size_t msg_len, ++ struct msg_conn *conn) + { ++ struct mgmt_fe_client_adapter *adapter = conn->user; + struct mgmt_fe_session_ctx *session; +- size_t min_size = mgmt_msg_get_min_size(msg->code); ++ struct mgmt_msg_header *msg = (typeof(msg))data; ++ size_t min_size; ++ ++ if (version != MGMT_MSG_VERSION_NATIVE) { ++ _log_err("Protobuf not supported for frontend messages (adapter: %s)", ++ adapter->name); ++ return; ++ } ++ ++ if (msg_len < sizeof(*msg)) { ++ _log_err("native message to adapter %s too short %zu", adapter->name, msg_len); ++ return; ++ } + ++ min_size = mgmt_msg_get_min_size(msg->code); + if (msg_len < min_size) { + if (!min_size) + _log_err("adapter %s: recv msg refer-id %" PRIu64 " unknown message type %u", +@@ -1374,6 +1499,10 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, + return; + } + ++ /* ++ * Handle new session requests up front. ++ */ ++ + if (msg->code == MGMT_MSG_CODE_SESSION_REQ) { + _dbg("adapter %s: session-id %Lu received SESSION_REQ message", adapter->name, + msg->refer_id); +@@ -1381,7 +1510,11 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, + return; + } + +- session = mgmt_session_id2ctx(msg->refer_id); ++ /* ++ * Get session and handle all other message types. ++ */ ++ ++ session = fe_session_lookup(msg->refer_id); + if (!session) { + _log_err("adapter %s: recv msg unknown session-id %" PRIu64, adapter->name, + msg->refer_id); +@@ -1393,32 +1526,32 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, + case MGMT_MSG_CODE_COMMIT: + _dbg("adapter %s: session-id %Lu received COMMIT message", adapter->name, + msg->refer_id); +- fe_adapter_handle_commit(session, msg, msg_len); ++ fe_session_handle_commit(session, msg, msg_len); + break; + case MGMT_MSG_CODE_EDIT: + _dbg("adapter %s: session-id %" PRIu64 " received EDIT message", adapter->name, + msg->refer_id); +- fe_adapter_handle_edit(session, msg, msg_len); ++ fe_session_handle_edit(session, msg, msg_len); + break; + case MGMT_MSG_CODE_LOCK: + _dbg("adapter %s: session-id %Lu received LOCK message", adapter->name, + msg->refer_id); +- fe_adapter_handle_lock(session, msg, msg_len); ++ fe_session_handle_lock(session, msg, msg_len); + break; + case MGMT_MSG_CODE_NOTIFY_SELECT: + _dbg("adapter %s: session-id %" PRIu64 " received NOTIFY_SELECT message", + adapter->name, msg->refer_id); +- fe_adapter_handle_notify_select(session, msg, msg_len); ++ fe_session_handle_notify_select(session, msg, msg_len); + break; + case MGMT_MSG_CODE_GET_DATA: + _dbg("adapter %s: session-id %" PRIu64 " received GET_DATA message", adapter->name, + msg->refer_id); +- fe_adapter_handle_get_data(session, msg, msg_len); ++ fe_session_handle_get_data(session, msg, msg_len); + break; + case MGMT_MSG_CODE_RPC: + _dbg("adapter %s: session-id %" PRIu64 " received RPC message", adapter->name, + msg->refer_id); +- fe_adapter_handle_rpc(session, msg, msg_len); ++ fe_session_handle_rpc(session, msg, msg_len); + break; + default: + _log_err("unknown native message session-id %" PRIu64 " req-id %" PRIu64 +@@ -1428,25 +1561,9 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, + } + } + +- +-static void mgmt_fe_adapter_process_msg(uint8_t version, uint8_t *data, +- size_t len, struct msg_conn *conn) +-{ +- struct mgmt_fe_client_adapter *adapter = conn->user; +- +- if (version == MGMT_MSG_VERSION_NATIVE) { +- struct mgmt_msg_header *msg = (typeof(msg))data; +- +- if (len >= sizeof(*msg)) +- fe_adapter_handle_native_msg(adapter, msg, len); +- else +- _log_err("native message to adapter %s too short %zu", adapter->name, len); +- return; +- } +- +- _log_err("Protobuf not supported for frontend messages (adapter: %s)", adapter->name); +-} +- ++/* ============================= */ ++/* Async Notification Processing */ ++/* ============================= */ + + static struct mgmt_msg_notify_data *assure_notify_msg_cache(const struct mgmt_msg_notify_data *msg, + size_t msglen, struct lyd_node **tree, +@@ -1559,7 +1676,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen + * notifications it first gets a dump of all the subscribed state. + */ + if (msg->refer_id != MGMTD_SESSION_ID_NONE) { +- session = mgmt_session_id2ctx(msg->refer_id); ++ session = fe_session_lookup(msg->refer_id); + if (!session || !session->notify_xpaths) { + _dbg("No session listening for notify 'get' data: %Lu", msg->refer_id); + return; +@@ -1567,7 +1684,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen + + send_msg = assure_notify_msg_cache(msg, msglen, &tree, session->notify_format, + cache); +- (void)fe_adapter_send_native_msg(session->adapter, send_msg, msglen, false); ++ (void)fe_adapter_send_msg(session->adapter, send_msg, msglen, false); + goto done; + } + +@@ -1602,7 +1719,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen + send_msg = assure_notify_msg_cache(msg, msglen, &tree, sessions[i]->notify_format, + cache); + send_msg->refer_id = sessions[i]->session_id; +- (void)fe_adapter_send_native_msg(sessions[i]->adapter, send_msg, msglen, false); ++ (void)fe_adapter_send_msg(sessions[i]->adapter, send_msg, msglen, false); + } + darr_free(sessions); + +@@ -1612,14 +1729,14 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen + * scheme). + */ + if (CHECK_FLAG(nb_node->snode->nodetype, LYS_NOTIF)) { +- FOREACH_ADAPTER_IN_LIST (adapter) { +- FOREACH_SESSION_IN_LIST (adapter, session) { ++ LIST_FOREACH (adapter, &fe_adapters, link) { ++ LIST_FOREACH (session, &adapter->sessions, link) { + if (session->notify_xpaths) + continue; + send_msg = assure_notify_msg_cache(msg, msglen, &tree, + session->notify_format, cache); + send_msg->refer_id = session->session_id; +- (void)fe_adapter_send_native_msg(adapter, send_msg, msglen, false); ++ (void)fe_adapter_send_msg(adapter, send_msg, msglen, false); + } + } + } +@@ -1630,275 +1747,37 @@ done: + cleanup_notify_msg_cache(msg, &tree, cache); + } + +-void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter) +-{ +- adapter->refcount++; +-} +- +-void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter) +-{ +- struct mgmt_fe_client_adapter *a = *adapter; +- +- assert(a && a->refcount); +- +- if (!--a->refcount) { +- mgmt_fe_adapters_del(&mgmt_fe_adapters, a); +- msg_server_conn_delete(a->conn); +- XFREE(MTYPE_MGMTD_FE_ADPATER, a); +- } +- *adapter = NULL; +-} +- +-/* +- * Initialize the FE adapter module +- */ +-void mgmt_fe_adapter_init(struct event_loop *tm) +-{ +- char server_path[MAXPATHLEN]; +- +- assert(!mgmt_loop); +- mgmt_loop = tm; +- +- mgmt_fe_adapters_init(&mgmt_fe_adapters); +- +- assert(!mgmt_fe_sessions); +- mgmt_fe_sessions = +- hash_create(mgmt_fe_session_hash_key, mgmt_fe_session_hash_cmp, +- "MGMT Frontend Sessions"); +- +- ns_string_init(&mgmt_fe_ns_strings); +- +- snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME); +- +- if (msg_server_init(&mgmt_fe_server, server_path, tm, +- mgmt_fe_create_adapter, "frontend", &mgmt_debug_fe)) { +- zlog_err("cannot initialize frontend server"); +- exit(1); +- } +-} +- +-static FRR_NORETURN void mgmt_fe_abort_if_session(void *data) +-{ +- struct mgmt_fe_session_ctx *session = data; +- +- _log_err("found orphaned session id %" PRIu64 " client id %" PRIu64 " adapter %s", +- session->session_id, session->client_id, +- session->adapter ? session->adapter->name : "NULL"); +- abort(); +-} +- +-/* +- * Destroy the FE adapter module +- */ +-void mgmt_fe_adapter_destroy(void) +-{ +- struct mgmt_fe_client_adapter *adapter; +- +- msg_server_cleanup(&mgmt_fe_server); +- +- +- /* Deleting the adapters will delete all the sessions */ +- FOREACH_ADAPTER_IN_LIST (adapter) +- mgmt_fe_adapter_delete(adapter); +- +- mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings); +- +- hash_clean_and_free(&mgmt_fe_sessions, mgmt_fe_abort_if_session); +-} +- +-/* +- * The server accepted a new connection +- */ +-struct msg_conn *mgmt_fe_create_adapter(int conn_fd, union sockunion *from) +-{ +- struct mgmt_fe_client_adapter *adapter = NULL; +- +- adapter = mgmt_fe_find_adapter_by_fd(conn_fd); +- if (!adapter) { +- adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter)); +- snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", +- conn_fd); +- +- mgmt_fe_sessions_init(&adapter->fe_sessions); +- mgmt_fe_adapter_lock(adapter); +- mgmt_fe_adapters_add_tail(&mgmt_fe_adapters, adapter); +- +- adapter->conn = msg_server_conn_create( +- mgmt_loop, conn_fd, mgmt_fe_adapter_notify_disconnect, +- mgmt_fe_adapter_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC, +- MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN, +- adapter, "FE-adapter"); +- +- adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_fe, +- DEBUG_MODE_ALL); +- +- adapter->cmt_stats.min_tm = ULONG_MAX; +- _dbg("Added new MGMTD Frontend adapter '%s'", adapter->name); +- } +- return adapter->conn; +-} +- +-int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, enum mgmt_ds_id src_ds_id, +- enum mgmt_ds_id dst_ds_id, uint64_t req_id, bool validate_only, +- bool unlock, enum mgmt_result result, const char *error_if_any) +-{ +- struct mgmt_fe_session_ctx *session; +- uint8_t action; +- +- session = mgmt_session_id2ctx(session_id); +- if (!session || session->cfg_txn_id != txn_id) +- return -1; +- +- if (validate_only) +- action = MGMT_MSG_COMMIT_VALIDATE; +- else +- action = MGMT_MSG_COMMIT_APPLY; +- +- /* Cleanup the CONFIG transaction associated with this session. */ +- if (session->cfg_txn_id && +- ((result == MGMTD_SUCCESS && !validate_only) || (result == MGMTD_NO_CFG_CHANGES))) +- mgmt_fe_session_register_event(session, MGMTD_FE_SESSION_CFG_TXN_CLNUP); +- +- if (mm->perf_stats_en) +- gettimeofday(&session->adapter->cmt_stats.last_end, NULL); +- mgmt_fe_session_compute_commit_timers(&session->adapter->cmt_stats); +- +- if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES) +- return fe_adapter_send_error( +- session, req_id, false, EINVAL /* convert result */, +- "commit failed session-id %Lu on %s req-id %Lu source-ds: %s target-ds: %s validate-only: %u: reason: '%s'", +- session->session_id, session->adapter->name, req_id, +- mgmt_ds_id2name(src_ds_id), mgmt_ds_id2name(dst_ds_id), validate_only, +- error_if_any ?: ""); +- fe_adapter_send_commit_reply(session, req_id, src_ds_id, dst_ds_id, action, unlock); +- return 0; +-} +- +-int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, +- uint64_t req_id, LYD_FORMAT result_type, +- uint32_t wd_options, +- const struct lyd_node *tree, +- int partial_error, bool short_circuit_ok) +-{ +- struct mgmt_fe_session_ctx *session; +- int ret; +- +- session = mgmt_session_id2ctx(session_id); +- if (!session || session->txn_id != txn_id) +- return -1; +- +- ret = fe_adapter_send_tree_data(session, req_id, short_circuit_ok, +- result_type, wd_options, tree, +- partial_error); +- +- mgmt_destroy_txn(&session->txn_id); +- +- return ret; +-} +- +-int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, +- uint64_t req_id, LYD_FORMAT result_type, +- const struct lyd_node *result) +-{ +- struct mgmt_fe_session_ctx *session; +- int ret; +- +- session = mgmt_session_id2ctx(session_id); +- if (!session || session->txn_id != txn_id) +- return -1; + +- ret = fe_adapter_send_rpc_reply(session, req_id, result_type, result); ++/* =========================== */ ++/* Frontend VTY and Statistics */ ++/* =========================== */ + +- mgmt_destroy_txn(&session->txn_id); +- +- return ret; +-} +- +-int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, +- uint64_t req_id, bool unlock, bool commit, +- bool created, const char *xpath, +- int16_t error, const char *errstr) ++static void fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats) + { +- struct mgmt_fe_session_ctx *session; +- enum mgmt_ds_id ds_id, rds_id; +- struct mgmt_ds_ctx *ds_ctx, *rds_ctx; +- int ret; +- +- session = mgmt_session_id2ctx(session_id); +- if (!session || session->cfg_txn_id != txn_id) +- return -1; +- +- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && commit) +- mgmt_fe_session_register_event(session, +- MGMTD_FE_SESSION_CFG_TXN_CLNUP); +- +- if (unlock) { +- ds_id = MGMTD_DS_CANDIDATE; +- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); +- assert(ds_ctx); +- +- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); +- +- if (commit) { +- rds_id = MGMTD_DS_RUNNING; +- rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); +- assert(rds_ctx); +- +- mgmt_fe_session_unlock_ds(rds_id, rds_ctx, session); +- } ++ cmt_stats->last_exec_tm = timeval_elapsed(cmt_stats->last_end, cmt_stats->last_start); ++ if (cmt_stats->last_exec_tm > cmt_stats->max_tm) { ++ cmt_stats->max_tm = cmt_stats->last_exec_tm; ++ cmt_stats->max_batch_cnt = cmt_stats->last_batch_cnt; + } + +- if (error != 0 && error != -EALREADY) +- ret = fe_adapter_send_error(session, req_id, false, error, "%s", +- errstr); +- else +- ret = fe_adapter_send_edit_reply(session, req_id, created, +- !error, xpath, errstr); +- +- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && !commit) +- mgmt_destroy_txn(&session->cfg_txn_id); +- +- return ret; +-} +- +-/** +- * Send an error back to the FE client and cleanup any in-progress txn. +- */ +-int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, +- bool short_circuit_ok, int16_t error, +- const char *errstr) +-{ +- struct mgmt_fe_session_ctx *session; +- int ret; +- +- session = fe_adapter_session_by_txn_id(txn_id); +- if (!session) { +- _log_err("failed sending error for txn-id %" PRIu64 " session not found", txn_id); +- return -ENOENT; ++ if (cmt_stats->last_exec_tm < cmt_stats->min_tm) { ++ cmt_stats->min_tm = cmt_stats->last_exec_tm; ++ cmt_stats->min_batch_cnt = cmt_stats->last_batch_cnt; + } +- +- ret = fe_adapter_send_error(session, req_id, false, error, "%s", errstr); +- +- mgmt_destroy_txn(&session->txn_id); +- +- return ret; + } + +-struct mgmt_commit_stats * +-mgmt_fe_get_session_commit_stats(uint64_t session_id) ++struct mgmt_commit_stats *mgmt_fe_get_session_commit_stats(uint64_t session_id) + { + struct mgmt_fe_session_ctx *session; + +- session = mgmt_session_id2ctx(session_id); ++ session = fe_session_lookup(session_id); + if (!session || !session->adapter) + return NULL; + + return &session->adapter->cmt_stats; + } + +-static void +-mgmt_fe_adapter_cmt_stats_write(struct vty *vty, +- struct mgmt_fe_client_adapter *adapter) ++static void _cmt_stats_write(struct vty *vty, struct mgmt_fe_client_adapter *adapter) + { + char buf[MGMT_LONG_TIME_MAX_LEN]; + +@@ -1968,17 +1847,20 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail) + struct mgmt_fe_session_ctx *session; + enum mgmt_ds_id ds_id; + bool locked = false; ++ uint acount = 0; ++ uint scount; + + vty_out(vty, "MGMTD Frontend Adpaters\n"); + +- FOREACH_ADAPTER_IN_LIST (adapter) { ++ LIST_FOREACH (adapter, &fe_adapters, link) { + vty_out(vty, " Client: \t\t\t\t%s\n", adapter->name); + vty_out(vty, " Conn-FD: \t\t\t\t%d\n", adapter->conn->fd); + if (detail) { +- mgmt_fe_adapter_cmt_stats_write(vty, adapter); ++ _cmt_stats_write(vty, adapter); + } ++ scount = 0; + vty_out(vty, " Sessions\n"); +- FOREACH_SESSION_IN_LIST (adapter, session) { ++ LIST_FOREACH (session, &adapter->sessions, link) { + vty_out(vty, " Session: \t\t\t\t%p\n", session); + vty_out(vty, " Client-Id: \t\t\t%" PRIu64 "\n", + session->client_id); +@@ -1994,9 +1876,9 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail) + } + if (!locked) + vty_out(vty, " None\n"); ++ scount++; + } +- vty_out(vty, " Total-Sessions: \t\t\t%d\n", +- (int)mgmt_fe_sessions_count(&adapter->fe_sessions)); ++ vty_out(vty, " Total-Sessions: \t\t\t%u\n", scount); + vty_out(vty, " Msg-Recvd: \t\t\t\t%" PRIu64 "\n", + adapter->conn->mstate.nrxm); + vty_out(vty, " Bytes-Recvd: \t\t\t%" PRIu64 "\n", +@@ -2006,8 +1888,7 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail) + vty_out(vty, " Bytes-Sent: \t\t\t%" PRIu64 "\n", + adapter->conn->mstate.ntxb); + } +- vty_out(vty, " Total: %d\n", +- (int)mgmt_fe_adapters_count(&mgmt_fe_adapters)); ++ vty_out(vty, " Total: %u\n", acount); + } + + void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config) +@@ -2018,12 +1899,127 @@ void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config) + void mgmt_fe_adapter_reset_perf_stats(struct vty *vty) + { + struct mgmt_fe_client_adapter *adapter; +- struct mgmt_fe_session_ctx *session; + +- FOREACH_ADAPTER_IN_LIST (adapter) { +- FOREACH_SESSION_IN_LIST (adapter, session) { +- memset(&adapter->cmt_stats, 0, +- sizeof(adapter->cmt_stats)); +- } ++ LIST_FOREACH (adapter, &fe_adapters, link) ++ memset(&adapter->cmt_stats, 0, sizeof(adapter->cmt_stats)); ++} ++ ++/* =================== */ ++/* Frontend Management */ ++/* =================== */ ++ ++void mgmt_fe_adapter_toggle_client_debug(bool set) ++{ ++ struct mgmt_fe_client_adapter *adapter; ++ ++ LIST_FOREACH (adapter, &fe_adapters, link) ++ adapter->conn->debug = set; ++} ++ ++static struct mgmt_fe_client_adapter *fe_adapter_lookup_by_fd(int conn_fd) ++{ ++ struct mgmt_fe_client_adapter *adapter; ++ ++ LIST_FOREACH (adapter, &fe_adapters, link) ++ if (adapter->conn->fd == conn_fd) ++ return adapter; ++ return NULL; ++} ++ ++static void fe_adapter_delete(struct mgmt_fe_client_adapter *adapter) ++{ ++ struct mgmt_fe_session_ctx *session, *next; ++ ++ _dbg("Deleting client adapter '%s'", adapter->name); ++ ++ /* TODO: notify about client disconnect for appropriate cleanup */ ++ LIST_FOREACH_SAFE (session, &adapter->sessions, link, next) ++ fe_session_cleanup(&session); ++ LIST_REMOVE(adapter, link); ++ msg_server_conn_delete(adapter->conn); ++ XFREE(MTYPE_MGMTD_FE_ADPATER, adapter); ++} ++ ++static int fe_adapter_notify_disconnect(struct msg_conn *conn) ++{ ++ struct mgmt_fe_client_adapter *adapter = conn->user; ++ ++ _dbg("notify disconnect for client adapter '%s'", adapter->name); ++ ++ fe_adapter_delete(adapter); ++ ++ return 0; ++} ++ ++/* ++ * The server accepted a new connection ++ */ ++static struct msg_conn *fe_adapter_create(int conn_fd, union sockunion *from) ++{ ++ struct mgmt_fe_client_adapter *adapter = NULL; ++ ++ adapter = fe_adapter_lookup_by_fd(conn_fd); ++ if (!adapter) { ++ adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter)); ++ snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", conn_fd); ++ ++ LIST_INSERT_HEAD(&fe_adapters, adapter, link); ++ ++ adapter->conn = ++ msg_server_conn_create(mgmt_loop, conn_fd, fe_adapter_notify_disconnect, ++ fe_adapter_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC, ++ MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN, ++ adapter, "FE-adapter"); ++ ++ adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL); ++ ++ adapter->cmt_stats.min_tm = ULONG_MAX; ++ _dbg("Added new MGMTD Frontend adapter '%s'", adapter->name); ++ } ++ return adapter->conn; ++} ++ ++ ++/* ++ * Initialize the FE adapter module ++ */ ++void mgmt_fe_adapter_init(struct event_loop *tm) ++{ ++ char server_path[MAXPATHLEN]; ++ ++ assert(!mgmt_loop); ++ mgmt_loop = tm; ++ ++ assert(!mgmt_fe_sessions); ++ mgmt_fe_sessions = hash_create(fe_session_hash_key, fe_session_hash_cmp, ++ "MGMT Frontend Sessions"); ++ ++ ns_string_init(&mgmt_fe_ns_strings); ++ ++ snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME); ++ ++ if (msg_server_init(&mgmt_fe_server, server_path, tm, fe_adapter_create, "frontend", ++ &mgmt_debug_fe)) { ++ zlog_err("cannot initialize frontend server"); ++ exit(1); + } + } ++ ++/* ++ * Destroy the FE adapter module ++ */ ++void mgmt_fe_adapter_destroy(void) ++{ ++ struct mgmt_fe_client_adapter *adapter, *next; ++ ++ msg_server_cleanup(&mgmt_fe_server); ++ ++ ++ /* Deleting the adapters will delete all the sessions */ ++ LIST_FOREACH_SAFE (adapter, &fe_adapters, link, next) ++ fe_adapter_delete(adapter); ++ ++ mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings); ++ ++ hash_free(mgmt_fe_sessions); ++} +diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h +index c0ba95c5e7..bd3b4bad91 100644 +--- a/mgmtd/mgmt_fe_adapter.h ++++ b/mgmtd/mgmt_fe_adapter.h +@@ -14,9 +14,6 @@ + #include "mgmt_msg.h" + #include "mgmt_defines.h" + +-struct mgmt_fe_client_adapter; +-struct mgmt_master; +- + struct mgmt_commit_stats { + struct timeval last_start; + struct timeval prep_cfg_start; +@@ -36,44 +33,24 @@ struct mgmt_commit_stats { + unsigned long commit_cnt; + }; + +-PREDECL_LIST(mgmt_fe_sessions); +- +-PREDECL_LIST(mgmt_fe_adapters); +- +-struct mgmt_fe_client_adapter { +- struct msg_conn *conn; +- char name[MGMTD_CLIENT_NAME_MAX_LEN]; +- +- /* List of sessions created and being maintained for this client. */ +- struct mgmt_fe_sessions_head fe_sessions; +- +- int refcount; +- struct mgmt_commit_stats cmt_stats; +- +- struct mgmt_fe_adapters_item list_linkage; ++struct mgmt_edit_req { ++ char xpath_created[XPATH_MAXLEN]; ++ struct nb_config *nb_backup; ++ bool created; ++ bool unlock_candidate; ++ bool unlock_running; + }; + +-DECLARE_LIST(mgmt_fe_adapters, struct mgmt_fe_client_adapter, list_linkage); +- + /* Initialise frontend adapter module */ + extern void mgmt_fe_adapter_init(struct event_loop *tm); + + /* Destroy frontend adapter module */ + extern void mgmt_fe_adapter_destroy(void); + +-/* Acquire lock for frontend adapter */ +-extern void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter); +- +-/* Remove lock from frontend adapter */ +-extern void +-mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter); +- +-/* Create frontend adapter */ +-extern struct msg_conn *mgmt_fe_create_adapter(int conn_fd, +- union sockunion *su); +- + /* + * Send commit-config reply to the frontend client. ++ * ++ * This also cleans up and frees the transaction. + */ + extern int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, + enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dst_ds_id, +@@ -94,16 +71,11 @@ extern int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, + * tree: the results. + * partial_error: if there were errors while gather results. + * short_circuit_ok: True if OK to short-circuit the call. +- * +- * Return: +- * the return value from the underlying send function. +- * + */ +-extern int +-mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, +- uint64_t req_id, LYD_FORMAT result_type, +- uint32_t wd_options, const struct lyd_node *tree, +- int partial_error, bool short_circuit_ok); ++extern void mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint64_t req_id, ++ LYD_FORMAT result_type, uint32_t wd_options, ++ const struct lyd_node *tree, int partial_error, ++ bool short_circuit_ok); + + /** + * Send RPC reply back to client. +@@ -116,14 +88,9 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, + * req_id: the req id for the rpc message + * result_type: the format of the result data. + * result: the results. +- * +- * Return: +- * the return value from the underlying send function. + */ +-extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, +- uint64_t req_id, +- LYD_FORMAT result_type, +- const struct lyd_node *result); ++extern void mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, ++ LYD_FORMAT result_type, const struct lyd_node *result); + + /** + * Send edit reply back to client. If error is not 0, a native error is sent. +@@ -136,16 +103,20 @@ extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, + * req_id: the req id for the edit message + * unlock: implicit-lock flag was set in the request + * commit: implicit-commit flag was set in the request +- * created: true if the node was just created +- * xpath: the xpath of the data node that was created/updated +- * error: >0 LY_ERR, < 0 -errno ++ * edit: the edit request info created when processing the edit ++ * error: mgmt result code + * errstr: the error string, if error is non-zero + */ +-extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, +- uint64_t req_id, bool unlock, +- bool commit, bool created, +- const char *xpath, int16_t error, +- const char *errstr); ++extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, ++ bool unlock, bool commit, struct mgmt_edit_req **edit, ++ enum mgmt_result error, const char *errstr); ++ ++/** ++ * mgmt_fe_adapter_send_notify() - notify FE clients of a notification. ++ * @msg: the notify message from the backend client. ++ * @msglen: the length of the notify message. ++ */ ++extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen); + + /** + * Send an error back to the FE client using native messaging. +@@ -163,10 +134,8 @@ extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, + * the return value from the underlying send function. + * + */ +-extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, +- bool short_circuit_ok, int16_t error, +- const char *errstr); +- ++extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, bool short_circuit_ok, ++ int16_t error, const char *errstr); + + /** + * mgmt_fe_get_all_selectors() - Get all selectors for all frontend adapters. +diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c +index 934748b1f2..91fae105ae 100644 +--- a/mgmtd/mgmt_history.c ++++ b/mgmtd/mgmt_history.c +@@ -216,7 +216,7 @@ static int mgmt_history_rollback_to_cmt(struct vty *vty, + + ret = mgmt_ds_lock(dst_ds_ctx, vty->mgmt_session_id); + if (ret != 0) { +- mgmt_ds_unlock(src_ds_ctx); ++ mgmt_ds_unlock(src_ds_ctx, vty->mgmt_session_id); + vty_out(vty, + "Failed to lock the DS %u for rollback Reason: %s!\n", + MGMTD_DS_RUNNING, strerror(ret)); +@@ -260,16 +260,20 @@ static int mgmt_history_rollback_to_cmt(struct vty *vty, + return 0; + + failed_unlock: +- mgmt_ds_unlock(src_ds_ctx); +- mgmt_ds_unlock(dst_ds_ctx); ++ mgmt_ds_unlock(src_ds_ctx, vty->mgmt_session_id); ++ mgmt_ds_unlock(dst_ds_ctx, vty->mgmt_session_id); + return ret; + } + + void mgmt_history_rollback_complete(bool success) + { +- vty_mgmt_resume_response(rollback_vty, +- success ? CMD_SUCCESS +- : CMD_WARNING_CONFIG_FAILED); ++ struct mgmt_ds_ctx *src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE); ++ struct mgmt_ds_ctx *dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); ++ ++ mgmt_ds_unlock(src_ds_ctx, rollback_vty->mgmt_session_id); ++ mgmt_ds_unlock(dst_ds_ctx, rollback_vty->mgmt_session_id); ++ ++ vty_mgmt_resume_response(rollback_vty, success ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED); + rollback_vty = NULL; + } + +diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c +index 0858fede2a..720286c9e0 100644 +--- a/mgmtd/mgmt_main.c ++++ b/mgmtd/mgmt_main.c +@@ -114,6 +114,8 @@ static FRR_NORETURN void mgmt_exit(int status) + /* it only makes sense for this to be called on a clean exit */ + assert(status == 0); + ++ vty_mgmt_terminate(); ++ + frr_early_fini(); + + /* stop pthreads (if any) */ +diff --git a/mgmtd/mgmt_memory.c b/mgmtd/mgmt_memory.c +index 6d07212d99..0183e4b362 100644 +--- a/mgmtd/mgmt_memory.c ++++ b/mgmtd/mgmt_memory.c +@@ -26,7 +26,4 @@ DEFINE_MTYPE(MGMTD, MGMTD_FE_ADPATER, "frontend adapter"); + DEFINE_MTYPE(MGMTD, MGMTD_FE_SESSION, "frontend session"); + DEFINE_MTYPE(MGMTD, MGMTD_TXN, "txn"); + DEFINE_MTYPE(MGMTD, MGMTD_TXN_REQ, "txn request"); +-DEFINE_MTYPE(MGMTD, MGMTD_TXN_COMMCFG_REQ, "txn commit-config requests"); +-DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETTREE_REQ, "txn get-tree requests"); +-DEFINE_MTYPE(MGMTD, MGMTD_TXN_RPC_REQ, "txn rpc requests"); + DEFINE_MTYPE(MGMTD, MGMTD_CMT_INFO, "commit info"); +diff --git a/mgmtd/mgmt_memory.h b/mgmtd/mgmt_memory.h +index 7e904e03ab..36156f7eb7 100644 +--- a/mgmtd/mgmt_memory.h ++++ b/mgmtd/mgmt_memory.h +@@ -20,8 +20,5 @@ DECLARE_MTYPE(MGMTD_FE_ADPATER); + DECLARE_MTYPE(MGMTD_FE_SESSION); + DECLARE_MTYPE(MGMTD_TXN); + DECLARE_MTYPE(MGMTD_TXN_REQ); +-DECLARE_MTYPE(MGMTD_TXN_COMMCFG_REQ); +-DECLARE_MTYPE(MGMTD_TXN_GETTREE_REQ); +-DECLARE_MTYPE(MGMTD_TXN_RPC_REQ); + DECLARE_MTYPE(MGMTD_CMT_INFO); + #endif /* _FRR_MGMTD_MEMORY_H */ +diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c +index fb98c810af..386b14c837 100644 +--- a/mgmtd/mgmt_txn.c ++++ b/mgmtd/mgmt_txn.c +@@ -4,6 +4,7 @@ + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar <spushpasis@vmware.com> ++ * Copyright (c) 2025, LabN Consulting, L.L.C. + */ + + #include <zebra.h> +@@ -14,119 +15,17 @@ + #include "mgmt_msg.h" + #include "mgmt_msg_native.h" + #include "mgmtd/mgmt.h" +-#include "mgmtd/mgmt_memory.h" +-#include "mgmtd/mgmt_txn.h" +-#include "mgmtd/mgmt_be_adapter.h" ++#include "mgmtd/mgmt_txn_priv.h" + +-#define _dbg(fmt, ...) DEBUGD(&mgmt_debug_txn, "TXN: %s: " fmt, __func__, ##__VA_ARGS__) +-#define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) +- +-#define MGMTD_TXN_LOCK(txn) mgmt_txn_lock(txn, __FILE__, __LINE__) +-#define MGMTD_TXN_UNLOCK(txn, in_hash_free) mgmt_txn_unlock(txn, in_hash_free, __FILE__, __LINE__) +- +-enum mgmt_txn_req_type { +- MGMTD_TXN_PROC_COMMITCFG = 1, +- MGMTD_TXN_PROC_GETTREE, +- MGMTD_TXN_PROC_RPC, +-}; +- +-enum mgmt_txn_frr_event { +- MGMTD_TXN_EVENT_COMMITCFG = 1, +- MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT, +-}; +- +-PREDECL_LIST(mgmt_txn_reqs); +- +-enum mgmt_commit_phase { +- MGMTD_COMMIT_PHASE_PREPARE_CFG = 0, +- MGMTD_COMMIT_PHASE_TXN_CREATE, +- MGMTD_COMMIT_PHASE_APPLY_CFG, +- MGMTD_COMMIT_PHASE_TXN_DELETE, +- MGMTD_COMMIT_PHASE_MAX +-}; +- +-static inline const char *mgmt_commit_phase2str(enum mgmt_commit_phase cmt_phase) +-{ +- switch (cmt_phase) { +- case MGMTD_COMMIT_PHASE_PREPARE_CFG: +- return "PREP-CFG"; +- case MGMTD_COMMIT_PHASE_TXN_CREATE: +- return "CREATE-TXN"; +- case MGMTD_COMMIT_PHASE_APPLY_CFG: +- return "APPLY-CFG"; +- case MGMTD_COMMIT_PHASE_TXN_DELETE: +- return "DELETE-TXN"; +- case MGMTD_COMMIT_PHASE_MAX: +- return "Invalid/Unknown"; +- } +- +- return "Invalid/Unknown"; +-} +- +-struct mgmt_edit_req { +- char xpath_created[XPATH_MAXLEN]; +- bool created; +- bool unlock; +-}; +- +-struct mgmt_commit_cfg_req { +- enum mgmt_ds_id src_ds_id; +- struct mgmt_ds_ctx *src_ds_ctx; +- enum mgmt_ds_id dst_ds_id; +- struct mgmt_ds_ctx *dst_ds_ctx; +- uint32_t nb_txn_id; +- uint8_t validate_only : 1; +- uint8_t abort : 1; +- uint8_t implicit : 1; +- uint8_t rollback : 1; +- uint8_t init : 1; +- uint8_t unlock : 1; +- +- /* Track commit phases */ +- enum mgmt_commit_phase phase; +- +- enum mgmt_commit_phase be_phase[MGMTD_BE_CLIENT_ID_MAX]; +- +- /* +- * Additional information when the commit is triggered by native edit +- * request. +- */ +- struct mgmt_edit_req *edit; +- +- /* +- * Set of config changes to commit. This is used only +- * when changes are NOT to be determined by comparing +- * candidate and running DSs. This is typically used +- * for downloading all relevant configs for a new backend +- * client that has recently come up and connected with +- * MGMTD. +- */ +- struct nb_config_cbs *cfg_chgs; +- +- /* Used for holding changes mgmtd itself is interested in */ +- struct nb_transaction *mgmtd_nb_txn; +- +- /* +- * Details on all the Backend Clients associated with +- * this commit. +- */ +- uint64_t clients; +- +- /* +- * Config messages to send to the backends, and their action strings to +- * add to the message just prior to sending. +- */ +- struct mgmt_msg_cfg_req *cfg_msgs[MGMTD_BE_CLIENT_ID_MAX]; +- char *cfg_actions[MGMTD_BE_CLIENT_ID_MAX]; +- uint64_t num_cfg_data[MGMTD_BE_CLIENT_ID_MAX]; +- +- struct mgmt_commit_stats *cmt_stats; +-}; ++/* ================= */ ++/* TXN Request Types */ ++/* ================= */ + + struct txn_req_get_tree { ++ struct txn_req req; + char *xpath; /* xpath of tree to get */ +- uint64_t sent_clients; /* Bitmask of clients sent req to */ +- uint64_t recv_clients; /* Bitmask of clients recv reply from */ ++ uint64_t clients; /* Bitmask of clients sent req to */ ++ uint64_t clients_wait; /* Bitmask of clients waiting for reply from */ + int32_t partial_error; /* an error while gather results */ + uint8_t result_type; /* LYD_FORMAT for results */ + uint8_t wd_options; /* LYD_PRINT_WD_* flags for results */ +@@ -134,807 +33,147 @@ struct txn_req_get_tree { + uint8_t simple_xpath; /* if xpath is simple */ + struct lyd_node *client_results; /* result tree from clients */ + }; ++#define as_get_tree(txn_req) \ ++ ({ \ ++ assert((txn_req)->req_type == TXN_REQ_TYPE_GETTREE); \ ++ (struct txn_req_get_tree *)(txn_req); \ ++ }) + + struct txn_req_rpc { ++ struct txn_req req; + char *xpath; /* xpath of rpc/action to invoke */ +- uint64_t sent_clients; /* Bitmask of clients sent req to */ +- uint64_t recv_clients; /* Bitmask of clients recv reply from */ ++ uint64_t clients; /* Bitmask of clients sent req to */ ++ uint64_t clients_wait; /* Bitmask of clients waiting for reply from */ + uint8_t result_type; /* LYD_FORMAT for results */ +- char *errstr; /* error string */ + struct lyd_node *client_results; /* result tree from clients */ + }; +- +-struct mgmt_txn_req { +- struct mgmt_txn_ctx *txn; +- enum mgmt_txn_req_type req_type; +- uint64_t req_id; +- union { +- /* +- * XXX Make the sub-structure variants either allocated or +- * embedded -- not both; embedding only the commit_cfg variant +- * makes little sense since it is the largest of the variants +- * though! +- */ +- struct txn_req_get_tree *get_tree; +- struct txn_req_rpc *rpc; +- struct mgmt_commit_cfg_req commit_cfg; +- } req; +- +- struct mgmt_txn_reqs_item list_linkage; ++#define as_rpc(txn_req) \ ++ ({ \ ++ assert((txn_req)->req_type == TXN_REQ_TYPE_RPC); \ ++ (struct txn_req_rpc *)(txn_req); \ ++ }) ++ ++const char *txn_req_names[] = { ++ [TXN_REQ_TYPE_COMMIT] = "COMMIT", ++ [TXN_REQ_TYPE_GETTREE] = "GET-TREE", ++ [TXN_REQ_TYPE_RPC] = "RPC", + }; + +-DECLARE_LIST(mgmt_txn_reqs, struct mgmt_txn_req, list_linkage); +- +-#define FOREACH_TXN_REQ_IN_LIST(list, req) \ +- frr_each_safe (mgmt_txn_reqs, list, req) +- +-struct mgmt_txn_ctx { +- uint64_t session_id; /* One transaction per client session */ +- uint64_t txn_id; +- enum mgmt_txn_type type; +- +- /* struct mgmt_master *mm; */ +- +- struct event *proc_comm_cfg; +- struct event *proc_get_tree; +- struct event *comm_cfg_timeout; +- struct event *get_tree_timeout; +- struct event *rpc_timeout; +- struct event *clnup; +- +- /* List of backend adapters involved in this transaction */ +- /* XXX reap this */ +- struct mgmt_txn_badapters_head be_adapters; +- +- int refcount; +- +- struct mgmt_txns_item list_linkage; +- +- /* TODO: why do we need unique lists for each type of transaction since +- * a transaction is of only 1 type? +- */ +- +- /* +- * List of pending get-tree requests. +- */ +- struct mgmt_txn_reqs_head get_tree_reqs; +- /* +- * List of pending rpc requests. +- */ +- struct mgmt_txn_reqs_head rpc_reqs; +- /* +- * There will always be one commit-config allowed for a given +- * transaction/session. No need to maintain lists for it. +- */ +- struct mgmt_txn_req *commit_cfg_req; +-}; ++/* Global list of all transactions */ ++TAILQ_HEAD(mgmt_txn_head, mgmt_txn) txn_txns = TAILQ_HEAD_INITIALIZER(txn_txns); + +-DECLARE_LIST(mgmt_txns, struct mgmt_txn_ctx, list_linkage); ++/* The single instance of config transaction allowed at any time */ ++struct mgmt_txn *txn_config_txn; + +-#define FOREACH_TXN_IN_LIST(mm, txn) \ +- frr_each_safe (mgmt_txns, &(mm)->txn_list, (txn)) ++/* Map of Transactions and its ID */ ++struct hash *txn_id_tab; ++uint64_t txn_next_id; + +-static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, +- enum mgmt_result result, +- const char *error_if_any); ++static void txn_incref(struct mgmt_txn *txn, const char *file, int line); + +-static inline const char *mgmt_txn_commit_phase_str(struct mgmt_txn_ctx *txn) ++/* ====================== */ ++/* Txn Request Management */ ++/* ====================== */ ++ ++static inline struct txn_req *txn_txn_req(struct mgmt_txn *txn, uint64_t req_id) + { +- if (!txn->commit_cfg_req) +- return "None"; ++ struct txn_req *txn_req; + +- return mgmt_commit_phase2str(txn->commit_cfg_req->req.commit_cfg.phase); ++ TAILQ_FOREACH (txn_req, &txn->reqs, link) ++ if (txn_req->req_id == req_id) ++ return txn_req; ++ return NULL; + } + +-static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line); +-static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, bool in_hash_free, const char *file, +- int line); +-static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, +- struct mgmt_be_client_adapter *adapter); +- +-static struct event_loop *mgmt_txn_tm; +-static struct mgmt_master *mgmt_txn_mm; +- +-static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, enum mgmt_txn_frr_event event); +- +-static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn); +- +-static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn, uint64_t req_id, +- enum mgmt_txn_req_type req_type) ++struct txn_req *txn_req_alloc(struct mgmt_txn *txn, uint64_t req_id, enum txn_req_type req_type, ++ size_t size) + { +- struct mgmt_txn_req *txn_req; ++ struct txn_req *txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, size); + +- txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_txn_req)); +- assert(txn_req); +- txn_req->txn = txn; +- txn_req->req_id = req_id; + txn_req->req_type = req_type; ++ txn_req->req_id = req_id; + +- switch (txn_req->req_type) { +- case MGMTD_TXN_PROC_COMMITCFG: +- txn->commit_cfg_req = txn_req; +- _dbg("Added a new COMMITCFG req-id: %" PRIu64 " txn-id: %" PRIu64 +- " session-id: %" PRIu64, +- txn_req->req_id, txn->txn_id, txn->session_id); +- +- txn_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_PREPARE_CFG; +- break; +- case MGMTD_TXN_PROC_GETTREE: +- txn_req->req.get_tree = XCALLOC(MTYPE_MGMTD_TXN_GETTREE_REQ, +- sizeof(struct txn_req_get_tree)); +- mgmt_txn_reqs_add_tail(&txn->get_tree_reqs, txn_req); +- _dbg("Added a new GETTREE req-id: %" PRIu64 " txn-id: %" PRIu64 +- " session-id: %" PRIu64, +- txn_req->req_id, txn->txn_id, txn->session_id); +- break; +- case MGMTD_TXN_PROC_RPC: +- txn_req->req.rpc = XCALLOC(MTYPE_MGMTD_TXN_RPC_REQ, +- sizeof(struct txn_req_rpc)); +- assert(txn_req->req.rpc); +- mgmt_txn_reqs_add_tail(&txn->rpc_reqs, txn_req); +- _dbg("Added a new RPC req-id: %" PRIu64 " txn-id: %" PRIu64 " session-id: %" PRIu64, +- txn_req->req_id, txn->txn_id, txn->session_id); +- break; +- } ++ txn_req->txn = txn; ++ TAILQ_INSERT_TAIL(&txn->reqs, txn_req, link); ++ TXN_INCREF(txn); + +- MGMTD_TXN_LOCK(txn); ++ _dbg("Added %s txn-req req-id: %Lu txn-id: %Lu session-id: %Lu", txn_req_names[req_type], ++ req_id, txn->txn_id, txn->session_id); + + return txn_req; + } + +-static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req) ++static struct txn_req_get_tree *txn_req_get_tree_alloc(struct mgmt_txn *txn, uint64_t req_id) + { +- struct mgmt_txn_reqs_head *req_list = NULL; +- enum mgmt_be_client_id id; +- struct mgmt_be_client_adapter *adapter; +- struct mgmt_commit_cfg_req *ccreq; +- bool cleanup; +- +- switch ((*txn_req)->req_type) { +- case MGMTD_TXN_PROC_COMMITCFG: +- _dbg("Deleting COMMITCFG req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id, +- (*txn_req)->txn->txn_id); +- +- ccreq = &(*txn_req)->req.commit_cfg; +- cleanup = (ccreq->phase >= MGMTD_COMMIT_PHASE_TXN_CREATE && +- ccreq->phase < MGMTD_COMMIT_PHASE_TXN_DELETE); +- +- XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit); +- +- if (ccreq->mgmtd_nb_txn) { +- nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0); +- ccreq->mgmtd_nb_txn = NULL; +- } +- +- FOREACH_MGMTD_BE_CLIENT_ID (id) { +- /* +- * Send TXN_DELETE to cleanup state for this +- * transaction on backend +- */ +- +- /* +- * Delete any config messages and action data +- */ +- mgmt_msg_native_free_msg(ccreq->cfg_msgs[id]); +- darr_free(ccreq->cfg_actions[id]); +- +- /* +- * If we were in the middle of the state machine then +- * send a txn delete message +- */ +- adapter = mgmt_be_get_adapter_by_id(id); +- if (adapter && cleanup && IS_IDBIT_SET(ccreq->clients, id)) +- mgmt_txn_send_be_txn_delete((*txn_req)->txn, +- adapter); +- } +- break; +- case MGMTD_TXN_PROC_GETTREE: +- _dbg("Deleting GETTREE req-id: %" PRIu64 " of txn-id: %" PRIu64, (*txn_req)->req_id, +- (*txn_req)->txn->txn_id); +- req_list = &(*txn_req)->txn->get_tree_reqs; +- lyd_free_all((*txn_req)->req.get_tree->client_results); +- XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.get_tree->xpath); +- XFREE(MTYPE_MGMTD_TXN_GETTREE_REQ, (*txn_req)->req.get_tree); +- break; +- case MGMTD_TXN_PROC_RPC: +- _dbg("Deleting RPC req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id, +- (*txn_req)->txn->txn_id); +- req_list = &(*txn_req)->txn->rpc_reqs; +- lyd_free_all((*txn_req)->req.rpc->client_results); +- XFREE(MTYPE_MGMTD_ERR, (*txn_req)->req.rpc->errstr); +- XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.rpc->xpath); +- XFREE(MTYPE_MGMTD_TXN_RPC_REQ, (*txn_req)->req.rpc); +- break; +- } +- +- if (req_list) { +- mgmt_txn_reqs_del(req_list, *txn_req); +- _dbg("Removed req-id: %" PRIu64 " from request-list (left:%zu)", (*txn_req)->req_id, +- mgmt_txn_reqs_count(req_list)); +- } +- +- MGMTD_TXN_UNLOCK(&(*txn_req)->txn, false); +- XFREE(MTYPE_MGMTD_TXN_REQ, (*txn_req)); +- *txn_req = NULL; +-} +- +-static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, +- enum mgmt_result result, +- const char *error_if_any) +-{ +- bool success, create_cmt_info_rec; +- +- if (!txn->commit_cfg_req) +- return -1; +- +- success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES); +- +- /* TODO: these replies should not be send if it's a rollback +- * b/c right now that is special cased.. that special casing should be +- * removed; however... +- */ +- if (!txn->commit_cfg_req->req.commit_cfg.edit && +- !txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && +- !txn->commit_cfg_req->req.commit_cfg.rollback && +- mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id, +- txn->commit_cfg_req->req.commit_cfg.src_ds_id, +- txn->commit_cfg_req->req.commit_cfg.dst_ds_id, +- txn->commit_cfg_req->req_id, +- txn->commit_cfg_req->req.commit_cfg.validate_only, +- txn->commit_cfg_req->req.commit_cfg.unlock, result, +- error_if_any) != 0) { +- _log_err("Failed to send COMMIT-CONFIG-REPLY txn-id: %" PRIu64 +- " session-id: %" PRIu64, +- txn->txn_id, txn->session_id); +- } +- +- if (txn->commit_cfg_req->req.commit_cfg.edit && +- mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, +- txn->commit_cfg_req->req_id, +- txn->commit_cfg_req->req.commit_cfg +- .edit->unlock, +- true, +- txn->commit_cfg_req->req.commit_cfg +- .edit->created, +- txn->commit_cfg_req->req.commit_cfg +- .edit->xpath_created, +- success ? 0 : -1, +- error_if_any) != 0) { +- _log_err("Failed to send EDIT-REPLY txn-id: %" PRIu64 " session-id: %" PRIu64, +- txn->txn_id, txn->session_id); +- } +- +- if (success) { +- /* Stop the commit-timeout timer */ +- /* XXX why only on success? */ +- event_cancel(&txn->comm_cfg_timeout); +- +- create_cmt_info_rec = +- (result != MGMTD_NO_CFG_CHANGES && +- !txn->commit_cfg_req->req.commit_cfg.rollback); +- +- /* +- * Successful commit: Copy Src DS to Dst DS if and only if +- * this was not a validate-only or abort request. +- */ +- if ((txn->session_id && +- !txn->commit_cfg_req->req.commit_cfg.validate_only && +- !txn->commit_cfg_req->req.commit_cfg.abort) || +- txn->commit_cfg_req->req.commit_cfg.rollback) { +- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, +- txn->commit_cfg_req->req.commit_cfg.src_ds_ctx, +- create_cmt_info_rec); +- } +- +- /* +- * Restore Src DS back to Dest DS only through a commit abort +- * request. +- */ +- if (txn->session_id && txn->commit_cfg_req->req.commit_cfg.abort) +- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx, +- txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, false); +- } else { +- /* +- * The commit has failied. For implicit commit requests restore +- * back the contents of the candidate DS. For non-implicit +- * commit we want to allow the user to re-commit on the changes +- * (whether further modified or not). +- */ +- if (txn->commit_cfg_req->req.commit_cfg.implicit || +- txn->commit_cfg_req->req.commit_cfg.unlock) +- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx, +- txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, false); +- } +- +- if (txn->commit_cfg_req->req.commit_cfg.rollback) { +- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx); +- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx); +- /* +- * Resume processing the rollback command. +- * +- * TODO: there's no good reason to special case rollback, the +- * rollback boolean should be passed back to the FE client and it +- * can do the right thing. +- */ +- mgmt_history_rollback_complete(success); +- } +- +- if (txn->commit_cfg_req->req.commit_cfg.init) { +- /* +- * This is the backend init request. +- * We need to unlock the running datastore. +- */ +- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx); +- } +- +- txn->commit_cfg_req->req.commit_cfg.cmt_stats = NULL; +- mgmt_txn_req_free(&txn->commit_cfg_req); +- +- /* +- * The CONFIG Transaction should be destroyed from Frontend-adapter. +- * But in case the transaction is not triggered from a front-end session +- * we need to cleanup by itself. +- */ +- if (!txn->session_id) +- mgmt_txn_cleanup_txn(&txn); +- +- return 0; +-} +- +-static int +-mgmt_try_move_commit_to_next_phase(struct mgmt_txn_ctx *txn, +- struct mgmt_commit_cfg_req *cmtcfg_req) +-{ +- enum mgmt_be_client_id id; +- +- _dbg("txn-id: %" PRIu64 ", Phase '%s'", txn->txn_id, mgmt_txn_commit_phase_str(txn)); +- +- /* +- * Check if all clients has moved to next phase or not. +- */ +- FOREACH_MGMTD_BE_CLIENT_ID (id) { +- if (IS_IDBIT_SET(cmtcfg_req->clients, id) && +- cmtcfg_req->be_phase[id] == cmtcfg_req->phase) { +- /* +- * There's atleast once client who hasn't moved to +- * next phase. +- * +- * TODO: Need to re-think this design for the case +- * set of validators for a given YANG data item is +- * different from the set of notifiers for the same. +- */ +- return -1; +- } +- } +- +- /* +- * If we are here, it means all the clients has moved to next phase. +- * So we can move the whole commit to next phase. +- */ +- cmtcfg_req->phase++; +- +- _dbg("Move entire txn-id: %" PRIu64 " to phase '%s'", txn->txn_id, +- mgmt_txn_commit_phase_str(txn)); +- +- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); +- +- return 0; +-} +- +-/* +- * This is the real workhorse +- */ +-static int mgmt_txn_create_config_msgs(struct mgmt_txn_req *txn_req, struct nb_config_cbs *changes) +-{ +- struct nb_config_cb *cb, *nxt; +- struct nb_config_change *chg; +- struct nb_config_cbs mgmtd_changes = { 0 }; +- char *xpath = NULL, *value = NULL; +- enum mgmt_be_client_id id; +- struct mgmt_be_client_adapter *adapter; +- struct mgmt_commit_cfg_req *cmtcfg_req; +- int num_chgs = 0; +- uint64_t clients, chg_clients; +- char op; +- +- cmtcfg_req = &txn_req->req.commit_cfg; +- RB_INIT(nb_config_cbs, &mgmtd_changes); +- +- RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) { +- chg = (struct nb_config_change *)cb; +- +- /* +- * Could have directly pointed to xpath in nb_node. +- * But dont want to mess with it now. +- * xpath = chg->cb.nb_node->xpath; +- */ +- xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0); +- if (!xpath) { +- (void)mgmt_txn_send_commit_cfg_reply( +- txn_req->txn, MGMTD_INTERNAL_ERROR, +- "Internal error! Could not get Xpath from Ds node!"); +- nb_config_diff_del_changes(&mgmtd_changes); +- return -1; +- } +- +- value = (char *)lyd_get_value(chg->cb.dnode); +- if (!value) +- value = (char *)MGMTD_BE_CONTAINER_NODE_VAL; +- +- _dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL"); +- +- /* Collect changes for mgmtd itself */ +- if (mgmt_is_mgmtd_interested(xpath) && +- /* We send tree changes to BEs that we don't need callbacks for */ +- nb_cb_operation_is_valid(cb->operation, cb->dnode->schema)) { +- uint32_t seq = cb->seq; +- +- nb_config_diff_add_change(&mgmtd_changes, cb->operation, &seq, cb->dnode); +- num_chgs++; +- } +- +- clients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); +- if (!clients) { +- _dbg("No backends interested in xpath: %s", xpath); +- continue; +- } +- +- chg_clients = 0; +- FOREACH_BE_CLIENT_BITS (id, clients) { +- adapter = mgmt_be_get_adapter_by_id(id); +- if (!adapter) +- continue; +- +- chg_clients |= (1ull << id); +- +- if (!cmtcfg_req->cfg_msgs[id]) { +- /* Allocate a new config message */ +- struct mgmt_msg_cfg_req *msg; +- +- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_req, 0, +- MTYPE_MSG_NATIVE_CFG_REQ); +- msg->code = MGMT_MSG_CODE_CFG_REQ; +- msg->refer_id = txn_req->txn->txn_id; +- msg->req_id = txn_req->req_id; +- cmtcfg_req->cfg_msgs[id] = msg; +- } +- +- /* +- * On the backend, we don't really care if it's CREATE +- * or MODIFY, because the existence was already checked +- * on the frontend. Therefore we use SET for both. +- */ +- op = chg->cb.operation == NB_CB_DESTROY ? 'd' : 'm'; +- darr_push(cmtcfg_req->cfg_actions[id], op); +- +- mgmt_msg_native_add_str(cmtcfg_req->cfg_msgs[id], xpath); +- if (op == 'm') +- mgmt_msg_native_add_str(cmtcfg_req->cfg_msgs[id], value); +- cmtcfg_req->num_cfg_data[id]++; +- +- _dbg(" -- %s, batch item: %Lu", adapter->name, cmtcfg_req->num_cfg_data[id]); +- +- num_chgs++; +- } +- +- if (!chg_clients) +- _dbg("Daemons interested in XPATH are not currently connected: %s", xpath); +- +- cmtcfg_req->clients |= chg_clients; +- +- free(xpath); +- } +- +- cmtcfg_req->cmt_stats->last_batch_cnt = num_chgs; +- if (!num_chgs) { +- assert(RB_EMPTY(nb_config_cbs, &mgmtd_changes)); +- (void)mgmt_txn_send_commit_cfg_reply(txn_req->txn, MGMTD_NO_CFG_CHANGES, +- "No connected daemons interested in changes"); +- return -1; +- } +- +- if (!RB_EMPTY(nb_config_cbs, &mgmtd_changes)) { +- /* Create a transaction for local mgmtd config changes */ +- char errmsg[BUFSIZ] = { 0 }; +- size_t errmsg_len = sizeof(errmsg); +- struct nb_context nb_ctx = { 0 }; +- +- _dbg("Processing mgmtd bound changes"); +- +- assert(!cmtcfg_req->mgmtd_nb_txn); +- nb_ctx.client = NB_CLIENT_MGMTD_SERVER; +- if (nb_changes_commit_prepare(nb_ctx, mgmtd_changes, "mgmtd-changes", NULL, +- &cmtcfg_req->mgmtd_nb_txn, errmsg, errmsg_len)) { +- _log_err("Failed to prepare local config for mgmtd: %s", errmsg); +- if (cmtcfg_req->mgmtd_nb_txn) { +- nb_candidate_commit_abort(cmtcfg_req->mgmtd_nb_txn, NULL, 0); +- cmtcfg_req->mgmtd_nb_txn = NULL; +- } +- (void)mgmt_txn_send_commit_cfg_reply(txn_req->txn, MGMTD_INTERNAL_ERROR, +- "Failed to prepare local config for mgmtd"); +- return -1; +- } +- assert(cmtcfg_req->mgmtd_nb_txn); +- } +- +- /* Move all BE clients to create phase -- XXX go straight to cfg_req */ +- FOREACH_MGMTD_BE_CLIENT_ID(id) { +- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) +- cmtcfg_req->be_phase[id] = +- MGMTD_COMMIT_PHASE_TXN_CREATE; +- } +- +- return 0; +-} +- +-static int mgmt_txn_prepare_config(struct mgmt_txn_ctx *txn) +-{ +- struct nb_context nb_ctx; +- struct nb_config *nb_config; +- struct nb_config_cbs changes; +- struct nb_config_cbs *cfg_chgs = NULL; +- int ret; +- bool del_cfg_chgs = false; +- +- ret = 0; +- memset(&nb_ctx, 0, sizeof(nb_ctx)); +- memset(&changes, 0, sizeof(changes)); +- if (txn->commit_cfg_req->req.commit_cfg.cfg_chgs) { +- cfg_chgs = txn->commit_cfg_req->req.commit_cfg.cfg_chgs; +- del_cfg_chgs = true; +- goto mgmt_txn_prep_config_validation_done; +- } +- +- if (txn->commit_cfg_req->req.commit_cfg.src_ds_id != MGMTD_DS_CANDIDATE) { +- (void)mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INVALID_PARAM, +- "Source DS cannot be any other than CANDIDATE!"); +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +- if (txn->commit_cfg_req->req.commit_cfg.dst_ds_id != MGMTD_DS_RUNNING) { +- (void)mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INVALID_PARAM, +- "Destination DS cannot be any other than RUNNING!"); +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +- if (!txn->commit_cfg_req->req.commit_cfg.src_ds_ctx) { +- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, +- "No such source datastore!"); +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +- if (!txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx) { +- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, +- "No such destination datastore!"); +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +- if (txn->commit_cfg_req->req.commit_cfg.abort) { +- /* +- * This is a commit abort request. Return back success. +- * That should trigger a restore of Candidate datastore to +- * Running. +- */ +- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); +- goto mgmt_txn_prepare_config_done; +- } +- +- nb_config = mgmt_ds_get_nb_config( +- txn->commit_cfg_req->req.commit_cfg.src_ds_ctx); +- if (!nb_config) { +- (void)mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INTERNAL_ERROR, +- "Unable to retrieve Commit DS Config Tree!"); +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +- /* +- * Validate YANG contents of the source DS and get the diff +- * between source and destination DS contents. +- */ +- char err_buf[BUFSIZ] = { 0 }; +- +- ret = nb_candidate_validate_yang(nb_config, true, err_buf, +- sizeof(err_buf) - 1); +- if (ret != NB_OK) { +- if (strncmp(err_buf, " ", strlen(err_buf)) == 0) +- strlcpy(err_buf, "Validation failed", sizeof(err_buf)); +- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, +- err_buf); +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +- nb_config_diff(mgmt_ds_get_nb_config(txn->commit_cfg_req->req.commit_cfg +- .dst_ds_ctx), +- nb_config, &changes); +- cfg_chgs = &changes; +- del_cfg_chgs = true; +- +- if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { +- /* +- * This means there's no changes to commit whatsoever +- * is the source of the changes in config. +- */ +- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_NO_CFG_CHANGES, +- "No changes found to be committed!"); +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +-mgmt_txn_prep_config_validation_done: +- +- if (mm->perf_stats_en) +- gettimeofday(&txn->commit_cfg_req->req.commit_cfg.cmt_stats +- ->prep_cfg_start, +- NULL); +- +- /* +- * Iterate over the diffs and create config messages to send to backends. +- */ +- ret = mgmt_txn_create_config_msgs(txn->commit_cfg_req, cfg_chgs); +- if (ret != 0) { +- ret = -1; +- goto mgmt_txn_prepare_config_done; +- } +- +- /* +- * If we have BE clients then move to the Transaction Create Phase +- * otherwise move to the Apply Config Phase to apply mgmtd changes. +- */ +- if (txn->commit_cfg_req->req.commit_cfg.clients) +- txn->commit_cfg_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_TXN_CREATE; +- else +- txn->commit_cfg_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_APPLY_CFG; +- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); +- +- /* +- * Start the COMMIT Timeout Timer to abort Txn if things get stuck at +- * backend. +- */ +- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT); +-mgmt_txn_prepare_config_done: +- +- if (cfg_chgs && del_cfg_chgs) +- nb_config_diff_del_changes(cfg_chgs); +- +- return ret; ++ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_GETTREE, ++ sizeof(struct txn_req_get_tree)); ++ assert(txn->type == MGMTD_TXN_TYPE_SHOW); ++ return as_get_tree(txn_req); + } + +-static int mgmt_txn_send_be_txn_create(struct mgmt_txn_ctx *txn) ++static struct txn_req_rpc *txn_req_rpc_alloc(struct mgmt_txn *txn, uint64_t req_id) + { +- enum mgmt_be_client_id id; +- struct mgmt_be_client_adapter *adapter; +- struct mgmt_commit_cfg_req *cmtcfg_req; +- +- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); +- +- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; +- FOREACH_MGMTD_BE_CLIENT_ID (id) { +- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) { +- adapter = mgmt_be_get_adapter_by_id(id); +- if (mgmt_be_send_txn_req(adapter, txn->txn_id, true)) { +- (void)mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INTERNAL_ERROR, +- "Could not send TXN_CREATE to backend adapter"); +- return -1; +- } +- } +- } +- +- /* +- * Dont move the commit to next phase yet. Wait for the TXN_REPLY to +- * come back. +- */ +- +- _dbg("txn-id: %" PRIu64 " session-id: %" PRIu64 " Phase '%s'", txn->txn_id, txn->session_id, +- mgmt_txn_commit_phase_str(txn)); +- +- return 0; ++ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_RPC, ++ sizeof(struct txn_req_rpc)); ++ assert(txn->type == MGMTD_TXN_TYPE_RPC); ++ return as_rpc(txn_req); + } + +-static int mgmt_txn_send_be_cfg_data(struct mgmt_txn_ctx *txn, +- struct mgmt_be_client_adapter *adapter) ++void txn_req_free(struct txn_req *txn_req) + { +- enum mgmt_be_client_id id = adapter->id; +- struct mgmt_commit_cfg_req *ccreq; +- int ret; +- +- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); +- +- ccreq = &txn->commit_cfg_req->req.commit_cfg; +- assert(IS_IDBIT_SET(ccreq->clients, id)); +- +- /* NUL terminate the actions string */ +- darr_push(ccreq->cfg_actions[id], 0); +- +- _dbg("Finished CFG_REQ for '%s' txn-id: %Lu with actions: %s", adapter->name, txn->txn_id, +- ccreq->cfg_actions[id]); ++ struct mgmt_txn *txn = txn_req->txn; ++ struct txn_req_rpc *rpc; ++ struct txn_req_get_tree *get_tree; ++ uint64_t txn_id = txn->txn_id; + +- /* Add the final action string to the message */ +- mgmt_msg_native_add_str(ccreq->cfg_msgs[id], ccreq->cfg_actions[id]); +- darr_free(ccreq->cfg_actions[id]); ++ /* prevent recursion */ ++ _dbg("Deleting %s txn-req req-id: %Lu txn-id: %Lu", txn_req_names[txn_req->req_type], ++ txn_req->req_id, txn_id); + +- ret = mgmt_be_send_cfgdata_req(adapter, ccreq->cfg_msgs[id]); +- mgmt_msg_native_free_msg(ccreq->cfg_msgs[id]); +- if (ret) { +- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INTERNAL_ERROR, +- "Internal Error! Could not send config data to backend!"); +- return -1; ++ switch (txn_req->req_type) { ++ case TXN_REQ_TYPE_COMMIT: ++ txn_cfg_cleanup(txn_req); ++ break; ++ case TXN_REQ_TYPE_GETTREE: ++ get_tree = as_get_tree(txn_req); ++ lyd_free_all(get_tree->client_results); ++ XFREE(MTYPE_MGMTD_XPATH, get_tree->xpath); ++ break; ++ case TXN_REQ_TYPE_RPC: ++ rpc = as_rpc(txn_req); ++ lyd_free_all(rpc->client_results); ++ XFREE(MTYPE_MGMTD_XPATH, rpc->xpath); ++ break; + } +- ccreq->cmt_stats->last_num_cfgdata_reqs++; +- +- /* +- * We don't advance the phase here, instead that is driven by the +- * cfg_reply. +- */ +- +- return 0; +-} +- +-static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, +- struct mgmt_be_client_adapter *adapter) +-{ +- struct mgmt_commit_cfg_req *cmtcfg_req = +- &txn->commit_cfg_req->req.commit_cfg; +- +- assert(txn->type == MGMTD_TXN_TYPE_CONFIG); +- +- if (IS_IDBIT_UNSET(cmtcfg_req->clients, adapter->id)) +- return 0; +- +- return mgmt_be_send_txn_req(adapter, txn->txn_id, false); +-} + +-static void mgmt_txn_cfg_commit_timedout(struct event *thread) +-{ +- struct mgmt_txn_ctx *txn; +- +- txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); +- assert(txn); +- +- assert(txn->type == MGMTD_TXN_TYPE_CONFIG); +- +- if (!txn->commit_cfg_req) +- return; +- +- _log_err("Backend timeout txn-id: %" PRIu64 " aborting commit", txn->txn_id); ++ TAILQ_REMOVE(&txn->reqs, txn_req, link); ++ _dbg("Removed req-id: %Lu from request-list", txn_req->req_id); + +- /* +- * Send a COMMIT_CONFIG_REPLY with failure. +- * NOTE: The transaction cleanup will be triggered from Front-end +- * adapter. +- */ +- mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INTERNAL_ERROR, +- "Operation on the backend timed-out. Aborting commit!"); ++ darr_free(txn_req->err_info); ++ TXN_DECREF(txn_req->txn); ++ XFREE(MTYPE_MGMTD_TXN_REQ, txn_req); + } + ++/* =========================== */ ++/* GET TREE (data) BACKEND TXN */ ++/* =========================== */ + +-static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, +- struct mgmt_txn_req *txn_req) ++static void txn_get_tree_data_done(struct txn_req_get_tree *get_tree) + { +- struct txn_req_get_tree *get_tree = txn_req->req.get_tree; ++ struct txn_req *txn_req = as_txn_req(get_tree); ++ struct mgmt_txn *txn = txn_req->txn; + uint64_t req_id = txn_req->req_id; + struct lyd_node *result; + int ret = NB_OK; + + /* cancel timer and send reply onward */ +- event_cancel(&txn->get_tree_timeout); ++ event_cancel(&txn_req->timeout); + + if (!get_tree->simple_xpath && get_tree->client_results) { + /* + * We have a complex query so Filter results by the xpath query. + */ +- if (yang_lyd_trim_xpath(&get_tree->client_results, +- txn_req->req.get_tree->xpath)) ++ if (yang_lyd_trim_xpath(&get_tree->client_results, get_tree->xpath)) + ret = NB_ERR; + } + +@@ -944,727 +183,110 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, + 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->wd_options, +- result, +- get_tree->partial_error, +- false); +- +- /* we're done with the request */ +- mgmt_txn_req_free(&txn_req); +- +- if (ret) { +- _log_err("Error sending the results of GETTREE for txn-id %" PRIu64 +- " req_id %" PRIu64 " to requested type %u", ++ mgmt_fe_adapter_send_tree_data(txn->session_id, txn->txn_id, txn_req->req_id, ++ get_tree->result_type, get_tree->wd_options, result, ++ get_tree->partial_error, false); ++ else { ++ _log_err("Error sending the results of GET-TREE for txn-id %Lu req_id %Lu to requested type %u", + txn->txn_id, req_id, get_tree->result_type); + + (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, + errno_from_nb_error(ret), +- "Error converting results of GETTREE"); +- } +- +- return ret; +-} +- +-static int txn_rpc_done(struct mgmt_txn_ctx *txn, struct mgmt_txn_req *txn_req) +-{ +- struct txn_req_rpc *rpc = txn_req->req.rpc; +- uint64_t req_id = txn_req->req_id; +- +- /* cancel timer and send reply onward */ +- event_cancel(&txn->rpc_timeout); +- +- if (rpc->errstr) +- mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -EINVAL, +- rpc->errstr); +- else if (mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id, +- req_id, rpc->result_type, +- rpc->client_results)) { +- _log_err("Error sending the results of RPC for txn-id %" PRIu64 " req_id %" PRIu64 +- " to requested type %u", +- txn->txn_id, req_id, rpc->result_type); +- +- (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, +- -EINVAL, +- "Error converting results of RPC"); ++ "Error converting results of GET-TREE"); + } + + /* we're done with the request */ +- mgmt_txn_req_free(&txn_req); +- +- return 0; ++ txn_req_free(txn_req); + } + + static void txn_get_tree_timeout(struct event *thread) + { +- struct mgmt_txn_ctx *txn; +- struct mgmt_txn_req *txn_req; +- +- txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread); +- txn = txn_req->txn; +- +- assert(txn); +- assert(txn->type == MGMTD_TXN_TYPE_SHOW); +- ++ struct txn_req_get_tree *get_tree = EVENT_ARG(thread); ++ struct mgmt_txn *txn = get_tree->req.txn; + + _log_err("Backend timeout txn-id: %" PRIu64 " ending get-tree", txn->txn_id); + +- /* +- * Send a get-tree data reply. +- * +- * NOTE: The transaction cleanup will be triggered from Front-end +- * adapter. +- */ +- +- txn_req->req.get_tree->partial_error = -ETIMEDOUT; +- txn_get_tree_data_done(txn, txn_req); ++ get_tree->partial_error = -ETIMEDOUT; ++ get_tree->req.err_info = darr_strdup("Get data on the backend timed-out"); ++ txn_get_tree_data_done(get_tree); + } + +-static void txn_rpc_timeout(struct event *thread) ++static void txn_get_tree_handle_error_reply(struct txn_req_get_tree *get_tree, ++ struct mgmt_be_client_adapter *adapter, int error, ++ const char *errstr) + { +- struct mgmt_txn_ctx *txn; +- struct mgmt_txn_req *txn_req; +- +- txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread); +- txn = txn_req->txn; +- +- assert(txn); +- assert(txn->type == MGMTD_TXN_TYPE_RPC); +- +- _log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id); ++ UNSET_IDBIT(get_tree->clients_wait, adapter->id); ++ get_tree->partial_error = error; + +- /* +- * Send a get-tree data reply. +- * +- * NOTE: The transaction cleanup will be triggered from Front-end +- * adapter. +- */ +- +- txn_req->req.rpc->errstr = +- XSTRDUP(MTYPE_MGMTD_ERR, "Operation on the backend timed-out"); +- txn_rpc_done(txn, txn_req); ++ /* check if done yet */ ++ if (!get_tree->clients_wait) ++ txn_get_tree_data_done(get_tree); + } + + /* +- * Send CFG_APPLY_REQs to all the backend client. +- * +- * NOTE: This is always dispatched when all CFGDATA_CREATE_REQs +- * for all backend clients has been generated. Please see +- * mgmt_txn_register_event() and mgmt_txn_process_commit_cfg() +- * for details. ++ * Get-tree data from the backend client. + */ +-static int mgmt_txn_send_be_cfg_apply(struct mgmt_txn_ctx *txn) +-{ +- enum mgmt_be_client_id id; +- struct mgmt_be_client_adapter *adapter; +- struct mgmt_commit_cfg_req *cmtcfg_req; +- +- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); +- +- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; +- if (cmtcfg_req->validate_only) { +- /* +- * If this was a validate-only COMMIT request return success. +- */ +- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); +- return 0; +- } +- +- /* +- * Handle mgmtd special case +- */ +- if (cmtcfg_req->mgmtd_nb_txn) { +- char errmsg[BUFSIZ] = { 0 }; +- +- _dbg("Applying mgmtd local bound changes"); +- +- nb_candidate_commit_apply(cmtcfg_req->mgmtd_nb_txn, false, NULL, errmsg, +- sizeof(errmsg)); +- cmtcfg_req->mgmtd_nb_txn = NULL; +- } +- +- FOREACH_MGMTD_BE_CLIENT_ID (id) { +- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) { +- adapter = mgmt_be_get_adapter_by_id(id); +- if (!adapter) +- return -1; +- +- if (mgmt_be_send_cfgapply_req(adapter, txn->txn_id)) { +- (void)mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INTERNAL_ERROR, +- "Could not send CFG_APPLY_REQ to backend adapter"); +- return -1; +- } +- cmtcfg_req->cmt_stats->last_num_apply_reqs++; +- +- UNSET_FLAG(adapter->flags, +- MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); +- } +- } +- +- /* +- * Dont move the commit to next phase yet. Wait for all VALIDATE_REPLIES +- * to come back. +- */ +- +- return 0; +-} +- +-static void mgmt_txn_process_commit_cfg(struct event *thread) ++void mgmt_txn_handle_tree_data_reply(struct mgmt_be_client_adapter *adapter, ++ struct mgmt_msg_tree_data *data_msg, size_t msg_len) + { +- struct mgmt_txn_ctx *txn; +- struct mgmt_commit_cfg_req *cmtcfg_req; +- +- txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); +- assert(txn); +- +- _dbg("Processing COMMIT_CONFIG for txn-id: %" PRIu64 " session-id: %" PRIu64 " Phase '%s'", +- txn->txn_id, txn->session_id, mgmt_txn_commit_phase_str(txn)); +- +- assert(txn->commit_cfg_req); +- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; +- switch (cmtcfg_req->phase) { +- case MGMTD_COMMIT_PHASE_PREPARE_CFG: +- mgmt_txn_prepare_config(txn); +- break; +- case MGMTD_COMMIT_PHASE_TXN_CREATE: +- if (mm->perf_stats_en) +- gettimeofday(&cmtcfg_req->cmt_stats->txn_create_start, +- NULL); +- /* +- * Send TXN_CREATE_REQ to all Backend now. +- */ +- mgmt_txn_send_be_txn_create(txn); +- break; +- case MGMTD_COMMIT_PHASE_APPLY_CFG: +- if (mm->perf_stats_en) +- gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_start, +- NULL); +- /* +- * We should have received successful CFG_VALIDATE_REPLY from +- * all concerned Backend Clients by now. Send out the +- * CFG_APPLY_REQs now. +- */ +- mgmt_txn_send_be_cfg_apply(txn); +- break; +- case MGMTD_COMMIT_PHASE_TXN_DELETE: +- if (mm->perf_stats_en) +- gettimeofday(&cmtcfg_req->cmt_stats->txn_del_start, +- NULL); +- /* +- * We would have sent TXN_DELETE_REQ to all backend by now. +- * Send a successful CONFIG_COMMIT_REPLY back to front-end. +- * NOTE: This should also trigger DS merge/unlock and Txn +- * cleanup. Please see mgmt_fe_send_commit_cfg_reply() for +- * more details. +- */ +- event_cancel(&txn->comm_cfg_timeout); +- mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); +- break; +- case MGMTD_COMMIT_PHASE_MAX: +- break; +- } +-} +- +-static struct mgmt_txn_ctx * +-mgmt_fe_find_txn_by_session_id(struct mgmt_master *cm, uint64_t session_id, +- enum mgmt_txn_type type) +-{ +- struct mgmt_txn_ctx *txn; +- +- FOREACH_TXN_IN_LIST (cm, txn) { +- if (txn->session_id == session_id && txn->type == type) +- return txn; +- } +- +- return NULL; +-} +- +-static struct mgmt_txn_ctx *mgmt_txn_create_new(uint64_t session_id, +- enum mgmt_txn_type type) +-{ +- struct mgmt_txn_ctx *txn = NULL; ++ uint64_t txn_id = data_msg->refer_id; ++ uint64_t req_id = data_msg->req_id; + +- /* Do not allow multiple config transactions */ +- if (type == MGMTD_TXN_TYPE_CONFIG && mgmt_config_txn_in_progress()) +- return NULL; ++ enum mgmt_be_client_id id = adapter->id; ++ struct mgmt_txn *txn = txn_lookup(txn_id); ++ struct txn_req *txn_req; ++ struct txn_req_get_tree *get_tree; ++ struct lyd_node *tree = NULL; ++ LY_ERR err; + +- txn = mgmt_fe_find_txn_by_session_id(mgmt_txn_mm, session_id, type); + if (!txn) { +- txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn_ctx)); +- assert(txn); +- +- txn->session_id = session_id; +- txn->type = type; +- mgmt_txns_add_tail(&mgmt_txn_mm->txn_list, txn); +- /* TODO: why do we need N lists for one transaction */ +- mgmt_txn_reqs_init(&txn->get_tree_reqs); +- mgmt_txn_reqs_init(&txn->rpc_reqs); +- txn->commit_cfg_req = NULL; +- txn->refcount = 0; +- if (!mgmt_txn_mm->next_txn_id) +- mgmt_txn_mm->next_txn_id++; +- txn->txn_id = mgmt_txn_mm->next_txn_id++; +- hash_get(mgmt_txn_mm->txn_hash, txn, hash_alloc_intern); +- +- _dbg("Added new '%s' txn-id: %" PRIu64, mgmt_txn_type2str(type), txn->txn_id); +- +- if (type == MGMTD_TXN_TYPE_CONFIG) +- mgmt_txn_mm->cfg_txn = txn; +- +- MGMTD_TXN_LOCK(txn); +- } +- +- return txn; +-} +- +-static void mgmt_txn_delete(struct mgmt_txn_ctx **txn, bool in_hash_free) +-{ +- MGMTD_TXN_UNLOCK(txn, in_hash_free); +-} +- +-static unsigned int mgmt_txn_hash_key(const void *data) +-{ +- const struct mgmt_txn_ctx *txn = data; +- +- return jhash2((uint32_t *)&txn->txn_id, +- sizeof(txn->txn_id) / sizeof(uint32_t), 0); +-} +- +-static bool mgmt_txn_hash_cmp(const void *d1, const void *d2) +-{ +- const struct mgmt_txn_ctx *txn1 = d1; +- const struct mgmt_txn_ctx *txn2 = d2; +- +- return (txn1->txn_id == txn2->txn_id); +-} +- +-static void mgmt_txn_hash_free(void *data) +-{ +- struct mgmt_txn_ctx *txn = data; +- +- mgmt_txn_delete(&txn, true); +-} +- +-static void mgmt_txn_hash_init(void) +-{ +- if (!mgmt_txn_mm || mgmt_txn_mm->txn_hash) +- return; +- +- mgmt_txn_mm->txn_hash = hash_create(mgmt_txn_hash_key, mgmt_txn_hash_cmp, +- "MGMT Transactions"); +-} +- +-static void mgmt_txn_hash_destroy(void) +-{ +- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) +- return; +- +- hash_clean(mgmt_txn_mm->txn_hash, mgmt_txn_hash_free); +- hash_free(mgmt_txn_mm->txn_hash); +- mgmt_txn_mm->txn_hash = NULL; +-} +- +-static inline struct mgmt_txn_ctx *mgmt_txn_id2ctx(uint64_t txn_id) +-{ +- struct mgmt_txn_ctx key = { 0 }; +- struct mgmt_txn_ctx *txn; +- +- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) +- return NULL; +- +- key.txn_id = txn_id; +- txn = hash_lookup(mgmt_txn_mm->txn_hash, &key); +- +- return txn; +-} +- +-uint64_t mgmt_txn_get_session_id(uint64_t txn_id) +-{ +- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); +- +- return txn ? txn->session_id : MGMTD_SESSION_ID_NONE; +-} +- +-static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line) +-{ +- txn->refcount++; +- _dbg("%s:%d --> Lock %s txn-id: %" PRIu64 " refcnt: %d", file, line, +- mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount); +-} +- +-static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, bool in_hash_free, const char *file, int line) +-{ +- assert(*txn && (*txn)->refcount); +- +- (*txn)->refcount--; +- _dbg("%s:%d --> Unlock %s txn-id: %" PRIu64 " refcnt: %d", file, line, +- mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, (*txn)->refcount); +- if (!(*txn)->refcount) { +- if ((*txn)->type == MGMTD_TXN_TYPE_CONFIG) +- if (mgmt_txn_mm->cfg_txn == *txn) +- mgmt_txn_mm->cfg_txn = NULL; +- event_cancel(&(*txn)->proc_comm_cfg); +- event_cancel(&(*txn)->comm_cfg_timeout); +- event_cancel(&(*txn)->get_tree_timeout); +- if (!in_hash_free) +- hash_release(mgmt_txn_mm->txn_hash, *txn); +- +- mgmt_txns_del(&mgmt_txn_mm->txn_list, *txn); +- +- _dbg("Deleted %s txn-id: %" PRIu64 " session-id: %" PRIu64, +- mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, (*txn)->session_id); +- +- XFREE(MTYPE_MGMTD_TXN, *txn); +- } +- +- *txn = NULL; +-} +- +-static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn) +-{ +- /* TODO: Any other cleanup applicable */ +- +- mgmt_txn_delete(txn, false); +-} +- +-static void mgmt_txn_cleanup_all_txns(void) +-{ +- struct mgmt_txn_ctx *txn; +- +- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) ++ _log_err("GETTREE reply from %s for a missing txn-id %" PRIu64, adapter->name, ++ txn_id); + return; +- +- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) +- mgmt_txn_cleanup_txn(&txn); +-} +- +-static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, enum mgmt_txn_frr_event event) +-{ +- struct timeval tv = { .tv_sec = 0, +- .tv_usec = MGMTD_TXN_PROC_DELAY_USEC }; +- +- assert(mgmt_txn_mm && mgmt_txn_tm); +- +- switch (event) { +- case MGMTD_TXN_EVENT_COMMITCFG: +- event_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_commit_cfg, +- txn, &tv, &txn->proc_comm_cfg); +- break; +- case MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT: +- event_add_timer(mgmt_txn_tm, mgmt_txn_cfg_commit_timedout, txn, +- MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC, +- &txn->comm_cfg_timeout); +- break; + } +-} +- +-int mgmt_txn_init(struct mgmt_master *m, struct event_loop *loop) +-{ +- if (mgmt_txn_mm || mgmt_txn_tm) +- assert(!"MGMTD TXN: Call txn_init() only once"); +- +- mgmt_txn_mm = m; +- mgmt_txn_tm = loop; +- mgmt_txns_init(&m->txn_list); +- mgmt_txn_hash_init(); +- assert(!m->cfg_txn); +- m->cfg_txn = NULL; +- +- return 0; +-} +- +-void mgmt_txn_destroy(void) +-{ +- mgmt_txn_cleanup_all_txns(); +- mgmt_txn_hash_destroy(); +-} +- +-bool mgmt_config_txn_in_progress(void) +-{ +- if (mgmt_txn_mm && mgmt_txn_mm->cfg_txn) +- return true; +- +- return false; +-} +- +-uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type) +-{ +- struct mgmt_txn_ctx *txn; +- +- txn = mgmt_txn_create_new(session_id, type); +- return txn ? txn->txn_id : MGMTD_TXN_ID_NONE; +-} +- +-void mgmt_destroy_txn(uint64_t *txn_id) +-{ +- struct mgmt_txn_ctx *txn; + +- txn = mgmt_txn_id2ctx(*txn_id); +- if (!txn) ++ /* Find the request. */ ++ txn_req = txn_txn_req(txn, req_id); ++ if (!txn_req || txn_req->req_type != TXN_REQ_TYPE_GETTREE) { ++ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64, ++ adapter->name, txn_id, req_id); + return; +- +- mgmt_txn_delete(&txn, false); +- *txn_id = MGMTD_TXN_ID_NONE; +-} +- +-int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id, +- struct mgmt_ds_ctx *src_ds_ctx, enum mgmt_ds_id dst_ds_id, +- struct mgmt_ds_ctx *dst_ds_ctx, bool validate_only, bool abort, +- bool implicit, bool unlock, struct mgmt_edit_req *edit) +-{ +- struct mgmt_txn_ctx *txn; +- struct mgmt_txn_req *txn_req; +- +- txn = mgmt_txn_id2ctx(txn_id); +- if (!txn) +- return -1; +- +- if (txn->commit_cfg_req) { +- _log_err("Commit already in-progress txn-id: %" PRIu64 " session-id: %" PRIu64 +- ". Cannot start another", +- txn->txn_id, txn->session_id); +- return -1; +- } +- +- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_COMMITCFG); +- txn_req->req.commit_cfg.src_ds_id = src_ds_id; +- txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx; +- txn_req->req.commit_cfg.dst_ds_id = dst_ds_id; +- txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx; +- txn_req->req.commit_cfg.validate_only = validate_only; +- txn_req->req.commit_cfg.abort = abort; +- txn_req->req.commit_cfg.implicit = implicit; +- txn_req->req.commit_cfg.unlock = unlock; +- txn_req->req.commit_cfg.edit = edit; +- txn_req->req.commit_cfg.cmt_stats = +- mgmt_fe_get_session_commit_stats(txn->session_id); +- +- /* +- * Trigger a COMMIT-CONFIG process. +- */ +- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); +- return 0; +-} +- +-int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, +- bool connect) +-{ +- struct mgmt_txn_ctx *txn; +- struct mgmt_txn_req *txn_req; +- struct mgmt_commit_cfg_req *cmtcfg_req; +- struct nb_config_cbs *adapter_cfgs = NULL; +- struct mgmt_ds_ctx *ds_ctx; +- +- if (connect) { +- ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); +- assert(ds_ctx); +- +- /* +- * Lock the running datastore to prevent any changes while we +- * are initializing the backend. +- */ +- if (mgmt_ds_lock(ds_ctx, 0) != 0) { +- _dbg("Failed to lock DS:%s for init of BE adapter '%s'", +- mgmt_ds_id2name(MGMTD_DS_RUNNING), adapter->name); +- return -1; +- } +- +- /* Get config for this single backend client */ +- mgmt_be_get_adapter_config(adapter, &adapter_cfgs); +- if (!adapter_cfgs || RB_EMPTY(nb_config_cbs, adapter_cfgs)) { +- SET_FLAG(adapter->flags, +- MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); +- mgmt_ds_unlock(ds_ctx); +- return 0; +- } +- +- /* +- * Create a CONFIG transaction to push the config changes +- * provided to the backend client. +- */ +- txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG); +- if (!txn) { +- _log_err("Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'", +- adapter->name); +- mgmt_ds_unlock(ds_ctx); +- nb_config_diff_del_changes(adapter_cfgs); +- return -1; +- } +- +- _dbg("Created initial txn-id: %" PRIu64 " for BE client '%s'", txn->txn_id, +- adapter->name); +- /* +- * Set the changeset for transaction to commit and trigger the +- * commit request. +- */ +- memset(&adapter->cfg_stats, 0, sizeof(adapter->cfg_stats)); +- txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG); +- txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_NONE; +- txn_req->req.commit_cfg.src_ds_ctx = 0; +- txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING; +- txn_req->req.commit_cfg.dst_ds_ctx = ds_ctx; +- txn_req->req.commit_cfg.validate_only = false; +- txn_req->req.commit_cfg.abort = false; +- txn_req->req.commit_cfg.init = true; +- txn_req->req.commit_cfg.cmt_stats = &adapter->cfg_stats; +- txn_req->req.commit_cfg.cfg_chgs = adapter_cfgs; +- +- /* +- * Trigger a COMMIT-CONFIG process. +- */ +- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); +- +- } else { +- /* +- * Check if any transaction is currently on-going that +- * involves this backend client. If so, report the transaction +- * has failed. +- */ +- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) { +- /* TODO: update with operational state when that is +- * completed */ +- if (txn->type == MGMTD_TXN_TYPE_CONFIG) { +- cmtcfg_req = txn->commit_cfg_req +- ? &txn->commit_cfg_req->req +- .commit_cfg +- : NULL; +- if (cmtcfg_req && IS_IDBIT_SET(cmtcfg_req->clients, +- adapter->id)) { +- mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INTERNAL_ERROR, +- "Backend daemon disconnected while processing commit!"); +- } +- } +- } +- } +- +- return 0; +-} +- +-int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, +- struct mgmt_be_client_adapter *adapter) +-{ +- struct mgmt_txn_ctx *txn; +- struct mgmt_commit_cfg_req *cmtcfg_req = NULL; +- +- txn = mgmt_txn_id2ctx(txn_id); +- if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG) +- return -1; +- +- if (!create && !txn->commit_cfg_req) +- return 0; +- +- assert(txn->commit_cfg_req); +- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; +- if (create) { +- if (success) { +- /* +- * Done with TXN_CREATE. Move the backend client to +- * next phase. +- */ +- assert(cmtcfg_req->phase == +- MGMTD_COMMIT_PHASE_TXN_CREATE); +- +- /* +- * Send CFGDATA_CREATE-REQs to the backend immediately. +- */ +- mgmt_txn_send_be_cfg_data(txn, adapter); +- } else { +- mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INTERNAL_ERROR, +- "Internal error! Failed to initiate transaction at backend!"); +- } +- } +- +- return 0; +-} +- +-int mgmt_txn_notify_be_cfg_reply(uint64_t txn_id, bool success, const char *error_if_any, +- struct mgmt_be_client_adapter *adapter) +-{ +- struct mgmt_txn_ctx *txn; +- struct mgmt_commit_cfg_req *cmtcfg_req; +- +- txn = mgmt_txn_id2ctx(txn_id); +- if (!txn) { +- _log_err("CFG_REPLY from '%s' failed no TXN for txn-id: %Lu", adapter->name, txn_id); +- return -1; +- } +- if (txn->type != MGMTD_TXN_TYPE_CONFIG) { +- _log_err("CFG_REPLY from '%s' failed wrong txn TYPE %u for txn-id: %Lu", +- adapter->name, txn->type, txn_id); +- return -1; +- } +- +- if (!txn->commit_cfg_req) { +- _log_err("CFG_REPLY from '%s' failed no COMMITCFG_REQ for txn-id: %Lu", +- adapter->name, txn_id); +- return -1; + } +- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; +- +- if (!success) { +- _log_err("CFG_REQ sent to '%s' failed txn-id: %" PRIu64 " err: %s", adapter->name, +- txn->txn_id, error_if_any ? error_if_any : "None"); +- mgmt_txn_send_commit_cfg_reply(txn, MGMTD_VALIDATION_ERROR, +- error_if_any +- ? error_if_any +- : "config validation failed by backend daemon"); +- return 0; +- } +- +- _dbg("CFG_REQ sent to '%s' was successful txn-id: %" PRIu64 " err: %s", adapter->name, +- txn->txn_id, error_if_any ? error_if_any : "None"); +- +- cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_APPLY_CFG; ++ get_tree = as_get_tree(txn_req); + +- mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req); +- +- return 0; +-} +- +-int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, +- char *error_if_any, +- struct mgmt_be_client_adapter *adapter) +-{ +- struct mgmt_txn_ctx *txn; +- struct mgmt_commit_cfg_req *cmtcfg_req = NULL; +- +- txn = mgmt_txn_id2ctx(txn_id); +- if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG || !txn->commit_cfg_req) +- return -1; +- +- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; +- +- if (!success) { +- _log_err("CFGDATA_APPLY_REQ sent to '%s' failed txn-id: %" PRIu64 " err: %s", +- adapter->name, txn->txn_id, error_if_any ? error_if_any : "None"); +- mgmt_txn_send_commit_cfg_reply( +- txn, MGMTD_INTERNAL_ERROR, +- error_if_any +- ? error_if_any +- : "Internal error! Failed to apply config data on backend!"); +- return 0; ++ /* store the result */ ++ err = lyd_parse_data_mem(ly_native_ctx, (const char *)data_msg->result, ++ data_msg->result_type, LYD_PARSE_STRICT | LYD_PARSE_ONLY, ++ 0 /*LYD_VALIDATE_OPERATIONAL*/, &tree); ++ if (err) { ++ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 ++ " error parsing result of type %u", ++ adapter->name, txn_id, req_id, data_msg->result_type); + } ++ if (!err) { ++ /* TODO: we could merge ly_errs here if it's not binary */ + +- cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_TXN_DELETE; +- +- /* +- * All configuration for the specific backend has been applied. +- * Send TXN-DELETE to wrap up the transaction for this backend. +- */ +- SET_FLAG(adapter->flags, MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); +- mgmt_txn_send_be_txn_delete(txn, adapter); ++ if (!get_tree->client_results) ++ get_tree->client_results = tree; ++ else ++ err = lyd_merge_siblings(&get_tree->client_results, tree, ++ LYD_MERGE_DESTRUCT); ++ if (err) { ++ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 ++ " error merging result", ++ adapter->name, txn_id, req_id); ++ } ++ } ++ if (!get_tree->partial_error) ++ get_tree->partial_error = (data_msg->partial_error ? data_msg->partial_error ++ : (int)err); + +- mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req); +- if (mm->perf_stats_en) +- gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_end, NULL); ++ if (!data_msg->more) ++ UNSET_IDBIT(get_tree->clients_wait, id); + +- return 0; ++ /* check if done */ ++ if (!get_tree->clients_wait) ++ txn_get_tree_data_done(get_tree); + } + + /** +@@ -1676,21 +298,19 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, + uint32_t wd_options, bool simple_xpath, struct lyd_node **ylib, + const char *xpath) + { ++ struct mgmt_be_client_adapter *adapter; + struct mgmt_msg_get_tree *msg; +- struct mgmt_txn_ctx *txn; +- struct mgmt_txn_req *txn_req; + struct txn_req_get_tree *get_tree; ++ struct mgmt_txn *txn; + enum mgmt_be_client_id id; + ssize_t slen = strlen(xpath); + int ret; + +- txn = mgmt_txn_id2ctx(txn_id); +- if (!txn) +- return -1; ++ txn = txn_lookup(txn_id); ++ assert(txn && txn->type == MGMTD_TXN_TYPE_SHOW); + + /* If error in this function below here, be sure to free the req */ +- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE); +- get_tree = txn_req->req.get_tree; ++ get_tree = txn_req_get_tree_alloc(txn, req_id); + get_tree->result_type = result_type; + get_tree->wd_options = wd_options; + get_tree->exact = CHECK_FLAG(flags, GET_DATA_FLAG_EXACT); +@@ -1702,10 +322,9 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, + * If the requested datastore is operational, get the config + * from running. + */ +- struct mgmt_ds_ctx *ds = +- mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL +- ? MGMTD_DS_RUNNING +- : ds_id); ++ struct mgmt_ds_ctx *ds = mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL ++ ? MGMTD_DS_RUNNING ++ : ds_id); + struct nb_config *config = mgmt_ds_get_nb_config(ds); + + if (config) { +@@ -1726,18 +345,16 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, + */ + if (set->count == 1) { + err = lyd_dup_single(set->dnodes[0], NULL, +- LYD_DUP_WITH_PARENTS | +- LYD_DUP_WITH_FLAGS | ++ 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); ++ 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, ++ LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS, + &get_tree->client_results); + if (!err) + get_tree->simple_xpath = false; +@@ -1762,9 +379,10 @@ state: + } + + /* If we are only getting config, we are done */ +- if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || +- ds_id != MGMTD_DS_OPERATIONAL || !clients) +- return txn_get_tree_data_done(txn, txn_req); ++ if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || ds_id != MGMTD_DS_OPERATIONAL || !clients) { ++ txn_get_tree_data_done(get_tree); ++ return 0; ++ } + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1, + MTYPE_MSG_NATIVE_GET_TREE); +@@ -1776,99 +394,146 @@ state: + strlcpy(msg->xpath, xpath, slen + 1); + + assert(clients); +- FOREACH_BE_CLIENT_BITS (id, clients) { +- ret = mgmt_be_send_native(id, msg); ++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { ++ ret = mgmt_be_adapter_send(adapter, msg); + if (ret) { +- _log_err("Could not send get-tree message to backend client %s", +- mgmt_be_client_id2name(id)); ++ darr_in_sprintf(get_tree->req.err_info, "Failed to send GET-DATA to %s", ++ adapter->name); + continue; + } +- +- _dbg("Sent get-tree req to backend client %s", mgmt_be_client_id2name(id)); +- +- /* record that we sent the request to the client */ +- get_tree->sent_clients |= (1u << id); ++ SET_IDBIT(get_tree->clients, id); + } + + mgmt_msg_native_free_msg(msg); + +- /* Return if we didn't send any messages to backends */ +- if (!get_tree->sent_clients) +- return txn_get_tree_data_done(txn, txn_req); +- +- /* Start timeout timer - pulled out of register event code so we can +- * pass a different arg +- */ +- event_add_timer(mgmt_txn_tm, txn_get_tree_timeout, txn_req, +- MGMTD_TXN_GET_TREE_MAX_DELAY_SEC, +- &txn->get_tree_timeout); ++ get_tree->clients_wait = get_tree->clients; ++ if (!get_tree->clients_wait) ++ txn_get_tree_data_done(get_tree); ++ else ++ event_add_timer(mm->master, txn_get_tree_timeout, get_tree, ++ MGMTD_TXN_GET_TREE_MAX_DELAY_SEC, &get_tree->req.timeout); + return 0; + } + +-int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id ds_id, +- struct mgmt_ds_ctx *ds_ctx, enum mgmt_ds_id commit_ds_id, +- struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit, +- LYD_FORMAT request_type, uint8_t flags, uint8_t operation, +- const char *xpath, const char *data) ++/* =============== */ ++/* RPC BACKEND TXN */ ++/* =============== */ ++ ++static void txn_rpc_done(struct txn_req_rpc *rpc) + { +- struct mgmt_txn_ctx *txn; +- struct mgmt_edit_req *edit; +- struct nb_config *nb_config; +- char errstr[BUFSIZ]; +- int ret; ++ struct txn_req *txn_req = as_txn_req(rpc); ++ struct mgmt_txn *txn = txn_req->txn; ++ uint64_t req_id = rpc->req.req_id; + +- txn = mgmt_txn_id2ctx(txn_id); +- if (!txn) +- return -1; ++ /* cancel timer and send reply onward */ ++ event_cancel(&rpc->req.timeout); ++ ++ if (txn_req->err_info) ++ mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, txn_req->error ?: -EINVAL, ++ txn_req->err_info); ++ else ++ mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id, req_id, ++ rpc->result_type, rpc->client_results); + +- edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req)); ++ /* we're done with the request */ ++ txn_req_free(txn_req); ++} ++ ++static void txn_rpc_timeout(struct event *event) ++{ ++ struct txn_req_rpc *rpc = EVENT_ARG(event); ++ struct mgmt_txn *txn = rpc->req.txn; + +- nb_config = mgmt_ds_get_nb_config(ds_ctx); +- assert(nb_config); ++ _log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id); + +- /* XXX Should we do locking here? */ ++ rpc->req.error = -ETIMEDOUT; ++ rpc->req.err_info = darr_strdup("RPC on the backend timed-out"); ++ txn_rpc_done(rpc); ++} + +- ret = nb_candidate_edit_tree(nb_config, operation, request_type, xpath, +- data, &edit->created, edit->xpath_created, +- errstr, sizeof(errstr)); +- if (ret) +- goto reply; ++static void txn_rpc_handle_error_reply(struct txn_req_rpc *rpc, ++ struct mgmt_be_client_adapter *adapter, int error, ++ const char *errstr) ++{ ++ UNSET_IDBIT(rpc->clients_wait, adapter->id); ++ if (errstr) ++ darr_in_strdup(rpc->req.err_info, errstr); ++ /* check if done */ ++ if (!rpc->clients_wait) ++ txn_rpc_done(rpc); ++} + +- if (commit) { +- edit->unlock = unlock; ++void mgmt_txn_handle_rpc_reply(struct mgmt_be_client_adapter *adapter, ++ struct mgmt_msg_rpc_reply *reply_msg, size_t msg_len) ++{ ++ uint64_t txn_id = reply_msg->refer_id; ++ uint64_t req_id = reply_msg->req_id; ++ enum mgmt_be_client_id id = adapter->id; ++ struct mgmt_txn *txn = txn_lookup(txn_id); ++ struct txn_req *txn_req; ++ struct txn_req_rpc *rpc; ++ struct lyd_node *tree; ++ size_t data_len = msg_len - sizeof(*reply_msg); ++ LY_ERR err = LY_SUCCESS; + +- mgmt_txn_send_commit_config_req(txn_id, req_id, ds_id, ds_ctx, commit_ds_id, +- commit_ds_ctx, false, false, true, false, edit); +- return 0; ++ if (!txn) { ++ _log_err("RPC reply from %s for a missing txn-id %Lu", adapter->name, txn_id); ++ return; + } +-reply: +- mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, req_id, +- unlock, commit, edit->created, +- edit->xpath_created, +- errno_from_nb_error(ret), errstr); + +- XFREE(MTYPE_MGMTD_TXN_REQ, edit); ++ /* Find the request. */ ++ txn_req = txn_txn_req(txn, req_id); ++ if (!txn_req || txn_req->req_type != TXN_REQ_TYPE_RPC) { ++ _log_err("RPC reply from %s for txn-id %Lu missing req_id %Lu", adapter->name, ++ txn_id, req_id); ++ return; ++ } ++ rpc = as_rpc(txn_req); + +- return 0; ++ tree = NULL; ++ if (data_len) ++ err = yang_parse_rpc(rpc->xpath, reply_msg->result_type, reply_msg->data, true, ++ &tree); ++ if (err) { ++ _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 ++ " error parsing result of type %u: %s", ++ adapter->name, txn_id, req_id, reply_msg->result_type, ly_strerrcode(err)); ++ } ++ if (!err && tree) { ++ if (!rpc->client_results) ++ rpc->client_results = tree; ++ else ++ err = lyd_merge_siblings(&rpc->client_results, tree, LYD_MERGE_DESTRUCT); ++ if (err) { ++ _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 ++ " error merging result: %s", ++ adapter->name, txn_id, req_id, ly_strerrcode(err)); ++ } ++ } ++ if (err) ++ darr_in_strdup(rpc->req.err_info, "Cannot parse result from the backend"); ++ ++ UNSET_IDBIT(rpc->clients_wait, id); ++ ++ /* check if done yet */ ++ if (!rpc->clients_wait) ++ txn_rpc_done(rpc); + } + +-int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, +- LYD_FORMAT result_type, const char *xpath, +- const char *data, size_t data_len) ++void mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, LYD_FORMAT result_type, ++ const char *xpath, const char *data, size_t data_len) + { +- struct mgmt_txn_ctx *txn; +- struct mgmt_txn_req *txn_req; ++ struct mgmt_be_client_adapter *adapter; ++ struct mgmt_txn *txn; + struct mgmt_msg_rpc *msg; + struct txn_req_rpc *rpc; +- uint64_t id; ++ enum mgmt_be_client_id id; + int ret; + +- txn = mgmt_txn_id2ctx(txn_id); +- if (!txn) +- return -1; ++ txn = txn_lookup(txn_id); ++ assert(txn && txn->type == MGMTD_TXN_TYPE_RPC); + +- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_RPC); +- rpc = txn_req->req.rpc; ++ rpc = txn_req_rpc_alloc(txn, req_id); + rpc->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); + rpc->result_type = result_type; + +@@ -1884,38 +549,37 @@ int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, + mgmt_msg_native_append(msg, data, data_len); + + assert(clients); +- FOREACH_BE_CLIENT_BITS (id, clients) { +- ret = mgmt_be_send_native(id, msg); ++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { ++ ret = mgmt_be_adapter_send(adapter, msg); + if (ret) { +- _log_err("Could not send rpc message to backend client %s", +- mgmt_be_client_id2name(id)); ++ darr_in_sprintf(rpc->req.err_info, "Failed to send RPC to %s", ++ adapter->name); + continue; + } +- +- _dbg("Sent rpc req to backend client %s", mgmt_be_client_id2name(id)); +- +- /* record that we sent the request to the client */ +- rpc->sent_clients |= (1u << id); ++ SET_IDBIT(rpc->clients, id); + } + + mgmt_msg_native_free_msg(msg); + +- if (!rpc->sent_clients) +- return txn_rpc_done(txn, txn_req); +- +- event_add_timer(mgmt_txn_tm, txn_rpc_timeout, txn_req, +- MGMTD_TXN_RPC_MAX_DELAY_SEC, &txn->rpc_timeout); +- +- return 0; ++ rpc->clients_wait = rpc->clients; ++ if (!rpc->clients_wait) ++ txn_rpc_done(rpc); ++ else ++ event_add_timer(mm->master, txn_rpc_timeout, rpc, MGMTD_TXN_RPC_MAX_DELAY_SEC, ++ &rpc->req.timeout); + } + +-int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, +- const char **selectors) ++/* =========================== */ ++/* NOTIFY SELECTOR BACKEND TXN */ ++/* =========================== */ ++ ++void mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, ++ const char **selectors) + { ++ struct mgmt_be_client_adapter *adapter; + struct mgmt_msg_notify_select *msg; ++ enum mgmt_be_client_id id; + char **all_selectors = NULL; +- uint64_t id; +- int ret; + uint i; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_select, 0, +@@ -1936,352 +600,255 @@ int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_ + mgmt_msg_native_add_str(msg, selectors[i]); + + assert(clients); +- FOREACH_BE_CLIENT_BITS (id, clients) { +- /* make sure the backend is running/connected */ +- if (!mgmt_be_get_adapter_by_id(id)) +- continue; +- ret = mgmt_be_send_native(id, msg); +- if (ret) { +- _log_err("Could not send notify-select message to backend client %s", +- mgmt_be_client_id2name(id)); ++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { ++ if (mgmt_be_adapter_send(adapter, msg)) { ++ /* ++ * Failed to send to this backend since we require ++ * reliable notifications from the backends we need to ++ * disconnect here. ++ */ ++ msg_conn_disconnect(adapter->conn, false); + continue; + } +- +- _dbg("Sent notify-select req to backend client %s", mgmt_be_client_id2name(id)); + } + mgmt_msg_native_free_msg(msg); + + if (all_selectors) + darr_free_free(all_selectors); +- +- return 0; + } + ++/* =============== */ ++/* TXN ERROR REPLY */ ++/* =============== */ ++ + /* + * Error reply from the backend client. ++ * ++ * NOTE: can disconnect (and delete) the backend. + */ +-int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, +- uint64_t txn_id, uint64_t req_id, int error, +- const char *errstr) ++void mgmt_txn_handle_error_reply(struct mgmt_be_client_adapter *adapter, uint64_t txn_id, ++ uint64_t req_id, int error, const char *errstr) + { +- enum mgmt_be_client_id id = adapter->id; +- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); +- struct txn_req_get_tree *get_tree; +- struct txn_req_rpc *rpc; +- struct mgmt_txn_req *txn_req; ++ struct txn_req *txn_req; ++ struct mgmt_txn *txn; + ++ txn = txn_lookup(txn_id); + if (!txn) { +- _log_err("Error reply from %s cannot find txn-id %" PRIu64, adapter->name, txn_id); +- return -1; ++ _log_err("Error reply from %s cannot find txn-id %Lu", adapter->name, txn_id); ++ return; + } + +- if (txn->type == MGMTD_TXN_TYPE_CONFIG) { +- /* +- * Handle an error during a configuration transaction. +- */ +- txn_req = txn->commit_cfg_req; +- if (!txn_req) { +- _log_err("Error reply from %s for txn-id %Lu req_id %Lu but no commit config request", +- adapter->name, txn_id, req_id); +- return -1; +- } +- /* +- * Most of the C client code doesn't set req_id so handle this. +- * If we ever wanted to support multiple active CFG_REQ for under +- * txn (unlikely), we would need to fix this. +- */ +- if (req_id != 0 && txn_req->req_id != req_id) { +- _log_err("Error reply from %s for txn-id %Lu req_id %Lu does not match current config req_id %Lu", +- adapter->name, txn_id, req_id, txn_req->req_id); +- return -1; +- } +- switch (txn_req->req.commit_cfg.phase) { +- case MGMTD_COMMIT_PHASE_TXN_CREATE: +- /* This is a validation error.*/ +- return mgmt_txn_notify_be_cfg_reply(txn_id, false, errstr, adapter); +- case MGMTD_COMMIT_PHASE_PREPARE_CFG: +- case MGMTD_COMMIT_PHASE_APPLY_CFG: +- case MGMTD_COMMIT_PHASE_TXN_DELETE: +- /* Drop the connection these errors should never happen */ +- msg_conn_disconnect(adapter->conn, false); ++ TAILQ_FOREACH (txn_req, &txn->reqs, link) { ++ /* Clients do not set req_id for commit errors yet there's only ++ * one so this works for now. */ ++ if (req_id == 0 && txn_req->req_type == TXN_REQ_TYPE_COMMIT) + break; +- case MGMTD_COMMIT_PHASE_MAX: +- assert(!"should never have this phase"); +- } +- return 0; +- } +- +- /* +- * Find the non-config request. +- */ +- +- FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req) +- if (txn_req->req_id == req_id) ++ else if (txn_req->req_id == req_id) + break; +- if (!txn_req) +- FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) +- if (txn_req->req_id == req_id) +- break; +- ++ } + if (!txn_req) { +- _log_err("Error reply from %s for txn-id %" PRIu64 " cannot find req_id %" PRIu64, ++ _log_err("Error reply from %s for txn-id %Lu cannot find req_id %Lu", + adapter->name, txn_id, req_id); +- return -1; ++ return; + } + +- _log_err("Error reply from %s for txn-id %" PRIu64 " req_id %" PRIu64, adapter->name, +- txn_id, req_id); ++ _log_err("Error reply from %s for txn-id %Lu req_id %Lu", adapter->name, txn_id, req_id); + + switch (txn_req->req_type) { +- case MGMTD_TXN_PROC_GETTREE: +- get_tree = txn_req->req.get_tree; +- get_tree->recv_clients |= (1u << id); +- get_tree->partial_error = error; +- +- /* check if done yet */ +- if (get_tree->recv_clients != get_tree->sent_clients) +- return 0; +- return txn_get_tree_data_done(txn, txn_req); +- case MGMTD_TXN_PROC_RPC: +- rpc = txn_req->req.rpc; +- rpc->recv_clients |= (1u << id); +- if (errstr) { +- XFREE(MTYPE_MGMTD_ERR, rpc->errstr); +- rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, errstr); +- } +- /* check if done yet */ +- if (rpc->recv_clients != rpc->sent_clients) +- return 0; +- return txn_rpc_done(txn, txn_req); +- ++ case TXN_REQ_TYPE_COMMIT: ++ txn_cfg_handle_error(txn_req, adapter, error, errstr); ++ return; ++ case TXN_REQ_TYPE_GETTREE: ++ txn_get_tree_handle_error_reply(as_get_tree(txn_req), adapter, error, errstr); ++ return; ++ case TXN_REQ_TYPE_RPC: ++ txn_rpc_handle_error_reply(as_rpc(txn_req), adapter, error, errstr); ++ return; + /* non-native message events */ +- case MGMTD_TXN_PROC_COMMITCFG: + default: + assert(!"non-native req type in native error path"); +- return -1; + } + } + +-/* +- * Get-tree data from the backend client. +- */ +-int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter, +- struct mgmt_msg_tree_data *data_msg, +- size_t msg_len) ++/* ============== */ ++/* Txn Management */ ++/* ============== */ ++ ++static unsigned int txn_txn_id_hash(const struct mgmt_txn *txn) + { +- uint64_t txn_id = data_msg->refer_id; +- uint64_t req_id = data_msg->req_id; ++ uint64_t key = txn->txn_id; + +- enum mgmt_be_client_id id = adapter->id; +- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); +- struct mgmt_txn_req *txn_req; +- struct txn_req_get_tree *get_tree; +- struct lyd_node *tree = NULL; +- LY_ERR err; ++ return (uint32_t)key ^ (uint32_t)(key >> 32); ++} + +- if (!txn) { +- _log_err("GETTREE reply from %s for a missing txn-id %" PRIu64, adapter->name, +- txn_id); +- return -1; +- } ++static bool txn_txn_id_cmp(const struct mgmt_txn *a, const struct mgmt_txn *b) ++{ ++ return (a->txn_id == b->txn_id); ++} + +- /* Find the request. */ +- FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req) +- if (txn_req->req_id == req_id) +- break; +- if (!txn_req) { +- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64, +- adapter->name, txn_id, req_id); +- return -1; +- } ++struct mgmt_txn *txn_lookup(uint64_t txn_id) ++{ ++ struct mgmt_txn key = { 0 }; + +- get_tree = txn_req->req.get_tree; ++ if (!txn_id_tab) ++ return NULL; ++ key.txn_id = txn_id; ++ return hash_lookup(txn_id_tab, &key); ++} + +- /* store the result */ +- err = lyd_parse_data_mem(ly_native_ctx, (const char *)data_msg->result, +- data_msg->result_type, +- LYD_PARSE_STRICT | LYD_PARSE_ONLY, +- 0 /*LYD_VALIDATE_OPERATIONAL*/, &tree); +- if (err) { +- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 +- " error parsing result of type %u", +- adapter->name, txn_id, req_id, data_msg->result_type); +- } +- if (!err) { +- /* TODO: we could merge ly_errs here if it's not binary */ ++uint64_t mgmt_txn_get_session_id(uint64_t txn_id) ++{ ++ struct mgmt_txn *txn = txn_lookup(txn_id); + +- if (!get_tree->client_results) +- get_tree->client_results = tree; +- else +- err = lyd_merge_siblings(&get_tree->client_results, +- tree, LYD_MERGE_DESTRUCT); +- if (err) { +- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 +- " error merging result", +- adapter->name, txn_id, req_id); +- } ++ return txn ? txn->session_id : MGMTD_SESSION_ID_NONE; ++} ++ ++static void txn_incref(struct mgmt_txn *txn, const char *file, int line) ++{ ++ txn->refcount++; ++ _dbg("TXN-INCREF %s txn-id: %Lu refcnt: %d file: %s line: %d)", ++ mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount, file, line); ++} ++ ++void txn_decref(struct mgmt_txn *txn, const char *file, int line) ++{ ++ assert(txn && txn->refcount); ++ ++ txn->refcount--; ++ _dbg("TXN-DECREF %s txn-id: %Lu refcnt: %d file: %s line: %d", ++ mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount, file, line); ++ if (!txn->refcount) { ++ if (txn->type == MGMTD_TXN_TYPE_CONFIG) ++ if (txn_config_txn == txn) ++ txn_config_txn = NULL; ++ hash_release(txn_id_tab, txn); ++ TAILQ_REMOVE(&txn_txns, txn, link); ++ ++ _dbg("Deleted %s txn-id: %Lu session-id: %Lu", mgmt_txn_type2str(txn->type), ++ txn->txn_id, txn->session_id); ++ ++ XFREE(MTYPE_MGMTD_TXN, txn); + } +- if (!get_tree->partial_error) +- get_tree->partial_error = (data_msg->partial_error +- ? data_msg->partial_error +- : (int)err); ++} + +- if (!data_msg->more) +- get_tree->recv_clients |= (1u << id); ++struct mgmt_txn *txn_create(enum mgmt_txn_type type) ++{ ++ struct mgmt_txn *txn; + +- /* check if done yet */ +- if (get_tree->recv_clients != get_tree->sent_clients) +- return 0; ++ txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn)); ++ txn->type = type; ++ TAILQ_INSERT_TAIL(&txn_txns, txn, link); ++ TAILQ_INIT(&txn->reqs); ++ txn->txn_id = txn_next_id++; ++ hash_get(txn_id_tab, txn, hash_alloc_intern); ++ ++ _dbg("Added new '%s' txn-id: %" PRIu64, mgmt_txn_type2str(type), txn->txn_id); + +- return txn_get_tree_data_done(txn, txn_req); ++ TXN_INCREF(txn); ++ return txn; + } + +-int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, +- struct mgmt_msg_rpc_reply *reply_msg, +- size_t msg_len) ++uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type) + { +- uint64_t txn_id = reply_msg->refer_id; +- uint64_t req_id = reply_msg->req_id; +- enum mgmt_be_client_id id = adapter->id; +- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); +- struct mgmt_txn_req *txn_req; +- struct txn_req_rpc *rpc; +- struct lyd_node *tree; +- size_t data_len = msg_len - sizeof(*reply_msg); +- LY_ERR err = LY_SUCCESS; ++ struct mgmt_txn *txn; + +- if (!txn) { +- _log_err("RPC reply from %s for a missing txn-id %" PRIu64, adapter->name, txn_id); +- return -1; +- } ++ /* Do not allow multiple (external) config transactions */ ++ if (type == MGMTD_TXN_TYPE_CONFIG && txn_config_txn) ++ return MGMTD_TXN_ID_NONE; + +- /* Find the request. */ +- FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) +- if (txn_req->req_id == req_id) +- break; +- if (!txn_req) { +- _log_err("RPC reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64, +- adapter->name, txn_id, req_id); +- return -1; +- } ++ /* Find existing txn for this session and type */ ++ TAILQ_FOREACH (txn, &txn_txns, link) ++ if (txn->session_id == session_id && txn->type == type) ++ return txn->txn_id; + +- rpc = txn_req->req.rpc; ++ txn = txn_create(type); ++ txn->session_id = session_id; ++ if (type == MGMTD_TXN_TYPE_CONFIG) ++ txn_config_txn = txn; ++ return txn->txn_id; ++} + +- tree = NULL; +- if (data_len) +- err = yang_parse_rpc(rpc->xpath, reply_msg->result_type, +- reply_msg->data, true, &tree); +- if (err) { +- _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 +- " error parsing result of type %u: %s", +- adapter->name, txn_id, req_id, reply_msg->result_type, ly_strerrcode(err)); +- } +- if (!err && tree) { +- if (!rpc->client_results) +- rpc->client_results = tree; +- else +- err = lyd_merge_siblings(&rpc->client_results, tree, +- LYD_MERGE_DESTRUCT); +- if (err) { +- _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 +- " error merging result: %s", +- adapter->name, txn_id, req_id, ly_strerrcode(err)); +- } +- } +- if (err) { +- XFREE(MTYPE_MGMTD_ERR, rpc->errstr); +- rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, +- "Cannot parse result from the backend"); +- } ++void mgmt_destroy_txn(uint64_t *txn_id) ++{ ++ struct mgmt_txn *txn; + +- rpc->recv_clients |= (1u << id); ++ if (*txn_id == MGMTD_TXN_ID_NONE) ++ return; + +- /* check if done yet */ +- if (rpc->recv_clients != rpc->sent_clients) +- return 0; ++ txn = txn_lookup(*txn_id); ++ if (!txn) ++ return; ++ ++ TXN_DECREF(txn); ++ *txn_id = MGMTD_TXN_ID_NONE; ++} ++ ++bool mgmt_txn_config_in_progress(void) ++{ ++ return txn_config_txn != NULL; ++} ++ ++int mgmt_txn_handle_be_adapter_connect(struct mgmt_be_client_adapter *adapter, bool connect) ++{ ++ struct mgmt_txn *txn, *next; + +- return txn_rpc_done(txn, txn_req); ++ if (connect) ++ txn_cfg_be_client_connect(adapter); ++ else { ++ /* ++ * Check if any transaction is currently on-going that ++ * involves this backend client. If so check if we can now ++ * advance that configuration. ++ */ ++ TAILQ_FOREACH_SAFE (txn, &txn_txns, link, next) { ++ /* XXX update to handle get-tree and RPC too! */ ++ if (txn->type == MGMTD_TXN_TYPE_CONFIG) ++ txn_cfg_txn_be_client_disconnect(txn, adapter); ++ } ++ } ++ ++ return 0; + } + + void mgmt_txn_status_write(struct vty *vty) + { +- struct mgmt_txn_ctx *txn; ++ struct mgmt_txn *txn; ++ uint count = 0; + + vty_out(vty, "MGMTD Transactions\n"); + +- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) { ++ TAILQ_FOREACH (txn, &txn_txns, link) { + vty_out(vty, " Txn: \t\t\t0x%p\n", txn); + vty_out(vty, " Txn-Id: \t\t\t%" PRIu64 "\n", txn->txn_id); +- vty_out(vty, " Session-Id: \t\t%" PRIu64 "\n", +- txn->session_id); +- vty_out(vty, " Type: \t\t\t%s\n", +- mgmt_txn_type2str(txn->type)); ++ vty_out(vty, " Session-Id: \t\t%" PRIu64 "\n", txn->session_id); ++ vty_out(vty, " Type: \t\t\t%s\n", mgmt_txn_type2str(txn->type)); + vty_out(vty, " Ref-Count: \t\t\t%d\n", txn->refcount); ++ count++; + } +- vty_out(vty, " Total: %d\n", +- (int)mgmt_txns_count(&mgmt_txn_mm->txn_list)); ++ vty_out(vty, " Total: %u\n", count); + } + +-int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, +- struct mgmt_ds_ctx *dst_ds_ctx) ++void mgmt_txn_init(void) + { +- static struct nb_config_cbs changes; +- static struct mgmt_commit_stats dummy_stats; +- +- struct nb_config_cbs *cfg_chgs = NULL; +- struct mgmt_txn_ctx *txn; +- struct mgmt_txn_req *txn_req; +- +- memset(&changes, 0, sizeof(changes)); +- memset(&dummy_stats, 0, sizeof(dummy_stats)); +- /* +- * This could be the case when the config is directly +- * loaded onto the candidate DS from a file. Get the +- * diff from a full comparison of the candidate and +- * running DSs. +- */ +- nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx), +- mgmt_ds_get_nb_config(src_ds_ctx), &changes); +- cfg_chgs = &changes; +- +- if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { +- /* +- * This means there's no changes to commit whatsoever +- * is the source of the changes in config. +- */ +- return -1; +- } ++ assert(txn_next_id == 0); ++ txn_next_id = 1; ++ txn_id_tab = hash_create((unsigned int (*)(const void *))txn_txn_id_hash, ++ (bool (*)(const void *, const void *))txn_txn_id_cmp, ++ "Mgmt Txns by txn-id"); ++} + +- /* +- * Create a CONFIG transaction to push the config changes +- * provided to the backend client. +- */ +- txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG); +- if (!txn) { +- _log_err("Failed to create CONFIG Transaction for downloading CONFIGs"); +- return -1; ++void mgmt_txn_destroy(void) ++{ ++ struct mgmt_txn *txn, *next; ++ ++ TAILQ_FOREACH_SAFE (txn, &txn_txns, link, next) { ++ /* Free all txn_reqs associated with this txn */ ++ while (!TAILQ_EMPTY(&txn->reqs)) ++ txn_req_free(TAILQ_FIRST(&txn->reqs)); ++ assert(txn->refcount == 1); ++ TXN_DECREF(txn); + } + +- _dbg("Created rollback txn-id: %" PRIu64, txn->txn_id); +- +- /* +- * Set the changeset for transaction to commit and trigger the commit +- * request. +- */ +- txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG); +- txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_CANDIDATE; +- txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx; +- txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING; +- txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx; +- txn_req->req.commit_cfg.validate_only = false; +- txn_req->req.commit_cfg.abort = false; +- txn_req->req.commit_cfg.rollback = true; +- txn_req->req.commit_cfg.cmt_stats = &dummy_stats; +- txn_req->req.commit_cfg.cfg_chgs = cfg_chgs; +- +- /* +- * Trigger a COMMIT-CONFIG process. +- */ +- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); +- return 0; ++ if (txn_id_tab) ++ hash_free(txn_id_tab); + } +diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h +index f472d4f839..54ebbfd803 100644 +--- a/mgmtd/mgmt_txn.h ++++ b/mgmtd/mgmt_txn.h +@@ -4,6 +4,7 @@ + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar <spushpasis@vmware.com> ++ * Copyright (c) 2025, LabN Consulting, L.L.C. + */ + + #ifndef _FRR_MGMTD_TXN_H_ +@@ -27,10 +28,7 @@ + + #define MGMTD_TXN_ID_NONE 0 + +-PREDECL_LIST(mgmt_txns); +- + struct mgmt_master; +-struct mgmt_edit_req; + + enum mgmt_txn_type { + MGMTD_TXN_TYPE_NONE = 0, +@@ -82,105 +80,59 @@ static inline int16_t errno_from_nb_error(enum nb_error ret) + } + } + ++/* --------------------- */ ++/* Gernal Txn Functions. */ ++/* --------------------- */ + +-/* Initialise transaction module. */ +-extern int mgmt_txn_init(struct mgmt_master *cm, struct event_loop *tm); +- +-/* Destroy the transaction module. */ ++extern void mgmt_txn_init(void); + extern void mgmt_txn_destroy(void); + +-/* +- * Check if configuration transaction is in progress. +- * +- * Returns: +- * true if in-progress, false otherwise. +- */ +-extern bool mgmt_config_txn_in_progress(void); ++extern enum mgmt_result nb_error_to_mgmt_result(enum nb_error error); + +-/** +- * Get the session ID associated with the given ``txn-id``. +- * +- */ +-extern uint64_t mgmt_txn_get_session_id(uint64_t txn_id); ++/* Dump transaction status to vty */ ++extern void mgmt_txn_status_write(struct vty *vty); + +-/* +- * Create transaction. +- * +- * session_id +- * Session ID. +- * +- * type +- * Transaction type (CONFIG/SHOW/NONE) +- * +- * Returns: +- * transaction ID. +- */ +-extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type); ++/* ---------------------------------------- */ ++/* Txn API for Backend messages and events. */ ++/* ---------------------------------------- */ + +-/* +- * Destroy transaction. +- * +- * txn_id +- * Unique transaction identifier. +- */ ++extern bool mgmt_txn_config_in_progress(void); ++extern uint64_t mgmt_txn_get_session_id(uint64_t txn_id); ++extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type); + extern void mgmt_destroy_txn(uint64_t *txn_id); + +-/* +- * Send commit-config request to be processed later in transaction. +- * +- * txn_id +- * Unique transaction identifier. +- * +- * req_id +- * Unique transaction request identifier. +- * +- * src_ds_id +- * Source datastore ID. +- * +- * src_ds_hndl +- * Source Datastore handle. +- * +- * validate_only +- * TRUE if commit request needs to be validated only, FALSE otherwise. +- * +- * abort +- * TRUE if need to restore Src DS back to Dest DS, FALSE otherwise. +- * +- * implicit +- * TRUE if the commit is implicit, FALSE otherwise. +- * +- * unlock +- * pass back in the commit config reply +- * +- * edit +- * Additional info when triggered from native edit request. +- * +- * Returns: +- * 0 on success, -1 on failures. +- */ +-extern int ++/** ++ * mgmt_txn_send_commit_config_req() - Send commit-config to apply the config changes. ++ * @txn_id: Unique transaction identifier. ++ * @req_id: Unique transaction request identifier. ++ * @src_ds_id: Source datastore ID. ++ * @src_ds_hndl: Source Datastore handle. ++ * @validate_only: TRUE if commit request needs to be validated only, FALSE otherwise. ++ * @abort: TRUE if need to restore Src DS back to Dest DS, FALSE otherwise. ++ * @implicit: TRUE if the commit is implicit, FALSE otherwise. ++ * @unlock: pass back in the commit config reply ++ * @edit: Additional info when triggered from native edit request. ++ */ ++extern void + mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id, + struct mgmt_ds_ctx *dst_ds_ctx, enum mgmt_ds_id dst_ds_id, + struct mgmt_ds_ctx *src_ds_ctx, bool validate_only, bool abort, + bool implicit, bool unlock, struct mgmt_edit_req *edit); + + /** +- * Send get-tree to the backend `clients`. +- * +- * Args: +- * txn_id: Transaction identifier. +- * req_id: FE client request identifier. +- * clients: Bitmask of clients to send get-tree to. +- * ds_id: datastore ID. +- * result_type: LYD_FORMAT result format. +- * flags: option flags for the request. +- * wd_options: LYD_PRINT_WD_* flags for the result. +- * simple_xpath: true if xpath is simple (only key predicates). +- * ylib: libyang tree for yang-library module to be merged. +- * xpath: The xpath to get the tree from. ++ * mgmt_txn_send_get_tree() - Send get-tree to the backend `clients`. ++ * @txn_id: Transaction identifier. ++ * @req_id: FE client request identifier. ++ * @clients: Bitmask of clients to send get-tree to. ++ * @ds_id: datastore ID. ++ * @result_type: LYD_FORMAT result format. ++ * @flags: option flags for the request. ++ * @wd_options: LYD_PRINT_WD_* flags for the result. ++ * @simple_xpath: true if xpath is simple (only key predicates). ++ * @ylib: libyang tree for yang-library module to be merged. ++ * @xpath: The xpath to get the tree from. + * +- * Return: +- * 0 on success. ++ * Return 0 on success. + */ + extern int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, + enum mgmt_ds_id ds_id, LYD_FORMAT result_type, uint8_t flags, +@@ -188,47 +140,18 @@ extern int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t cli + const char *xpath); + + /** +- * Send edit request. +- * +- * Args: +- * txn_id: Transaction identifier. +- * req_id: FE client request identifier. +- * ds_id: Datastore ID. +- * ds_ctx: Datastore context. +- * commit_ds_id: Commit datastore ID. +- * commit_ds_ctx: Commit datastore context. +- * unlock: Unlock datastores after the edit. +- * commit: Commit the candidate datastore after the edit. +- * request_type: LYD_FORMAT request type. +- * flags: option flags for the request. +- * operation: The operation to perform. +- * xpath: The xpath of data node to edit. +- * data: The data tree. +- */ +-extern int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id ds_id, +- struct mgmt_ds_ctx *ds_ctx, enum mgmt_ds_id commit_ds_id, +- struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit, +- LYD_FORMAT request_type, uint8_t flags, uint8_t operation, +- const char *xpath, const char *data); +- +-/** +- * Send RPC request. +- * +- * Args: +- * txn_id: Transaction identifier. +- * req_id: FE client request identifier. +- * clients: Bitmask of clients to send RPC to. +- * result_type: LYD_FORMAT result format. +- * xpath: The xpath of the RPC. +- * data: The input parameters data tree. +- * data_len: The length of the input parameters data. +- * +- * Return: +- * 0 on success. ++ * mgmt_txn_send_rpc() - Send RPC request. ++ * @txn_id: Transaction identifier. ++ * @req_id: FE client request identifier. ++ * @clients: Bitmask of clients to send RPC to. ++ * @result_type: LYD_FORMAT result format. ++ * @xpath: The xpath of the RPC. ++ * @data: The input parameters data tree. ++ * @data_len: The length of the input parameters data. + */ +-extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, +- LYD_FORMAT result_type, const char *xpath, +- const char *data, size_t data_len); ++extern void mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, ++ LYD_FORMAT result_type, const char *xpath, const char *data, ++ size_t data_len); + + /** + * mgmt_txn_send_notify_selectors() - Send NOTIFY SELECT request. +@@ -238,99 +161,32 @@ extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, + * back to the given session. + * @clients: Bitmask of backend clients to send message to. + * @selectors: Array of selectors or NULL to resend all selectors to BE clients. +- * +- * Returns 0 on success. + */ +-extern int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, +- const char **selectors); ++extern void mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, ++ const char **selectors); + +-/* +- * Notifiy backend adapter on connection. +- */ +-extern int +-mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, +- bool connect); +- +-/* +- * Reply to backend adapter about transaction create/delete. +- */ +-extern int +-mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, +- struct mgmt_be_client_adapter *adapter); + + /* +- * Reply to backend adapater with config data create request. +- */ +-extern int mgmt_txn_notify_be_cfg_reply(uint64_t txn_id, bool success, const char *error_if_any, +- struct mgmt_be_client_adapter *adapter); +- +-/* +- * Reply to backend adapater with config data validate request. +- */ +-extern int mgmt_txn_notify_be_cfg_validate_reply( +- uint64_t txn_id, bool success, uint64_t batch_ids[], +- size_t num_batch_ids, char *error_if_any, +- struct mgmt_be_client_adapter *adapter); +- +-/* +- * Reply to backend adapater with config data apply request. +- */ +-extern int +-mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, +- char *error_if_any, +- struct mgmt_be_client_adapter *adapter); +- +- +-/** +- * Process a reply from a backend client to our get-tree request +- * +- * Args: +- * adapter: The adapter that received the result. +- * txn_id: The transaction for this get-tree request. +- * req_id: The request ID for this transaction. +- * error: the integer error value (negative) +- * errstr: the string description of the error. +- */ +-int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, +- uint64_t txn_id, uint64_t req_id, int error, +- const char *errstr); +- +-/** +- * Process a reply from a backend client to our get-tree request ++ * Trigger rollback config apply. + * +- * Args: +- * adapter: The adapter that received the result. +- * data_msg: The message from the backend. +- * msg_len: Total length of the message. ++ * Creates a new transaction and commit request for rollback. + */ ++extern int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, ++ struct mgmt_ds_ctx *dst_ds_ctx); + +-extern int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter, +- struct mgmt_msg_tree_data *data_msg, +- size_t msg_len); ++/* ---------------------------------------- */ ++/* Txn API for Backend messages and events. */ ++/* ---------------------------------------- */ + +-/** +- * Process a reply from a backend client to our RPC request +- * +- * Args: +- * adapter: The adapter that received the result. +- * reply_msg: The message from the backend. +- * msg_len: Total length of the message. +- */ +-extern int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, +- struct mgmt_msg_rpc_reply *reply_msg, +- size_t msg_len); ++extern void mgmt_txn_handle_cfg_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter); ++extern void mgmt_txn_handle_cfg_apply_reply(uint64_t txn_id, ++ struct mgmt_be_client_adapter *adapter); ++extern void mgmt_txn_handle_error_reply(struct mgmt_be_client_adapter *adapter, uint64_t txn_id, ++ uint64_t req_id, int error, const char *errstr); ++extern int mgmt_txn_handle_be_adapter_connect(struct mgmt_be_client_adapter *adapter, bool connect); ++extern void mgmt_txn_handle_rpc_reply(struct mgmt_be_client_adapter *adapter, ++ struct mgmt_msg_rpc_reply *reply_msg, size_t msg_len); ++extern void mgmt_txn_handle_tree_data_reply(struct mgmt_be_client_adapter *adapter, ++ struct mgmt_msg_tree_data *data_msg, size_t msg_len); + +-/* +- * Dump transaction status to vty. +- */ +-extern void mgmt_txn_status_write(struct vty *vty); +- +-/* +- * Trigger rollback config apply. +- * +- * Creates a new transaction and commit request for rollback. +- */ +-extern int +-mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, +- struct mgmt_ds_ctx *dst_ds_ctx); + #endif /* _FRR_MGMTD_TXN_H_ */ +diff --git a/mgmtd/mgmt_txn_cfg.c b/mgmtd/mgmt_txn_cfg.c +new file mode 100644 +index 0000000000..742c03cc4c +--- /dev/null ++++ b/mgmtd/mgmt_txn_cfg.c +@@ -0,0 +1,1096 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * November 18 2025, Christian Hopps <chopps@labn.net> ++ * ++ * Copyright (c) 2025, LabN Consulting, L.L.C. ++ * ++ */ ++ ++#include <zebra.h> ++#include "mgmtd/mgmt_txn_priv.h" ++ ++/* ================================ */ ++/* Config Request Types and Globals */ ++/* ================================ */ ++ ++enum mgmt_commit_phase { ++ MGMTD_COMMIT_PHASE_SEND_CFG = 0, ++ MGMTD_COMMIT_PHASE_APPLY_CFG, ++ MGMTD_COMMIT_PHASE_FINISH, ++}; ++ ++static const char *const mgmt_commit_phase_name[] = { ++ [MGMTD_COMMIT_PHASE_SEND_CFG] = "SEND-CFG", ++ [MGMTD_COMMIT_PHASE_APPLY_CFG] = "APPLY-CFG", ++ [MGMTD_COMMIT_PHASE_FINISH] = "FINISH", ++}; ++ ++struct txn_req_commit { ++ struct txn_req req; ++ enum mgmt_ds_id src_ds_id; ++ struct mgmt_ds_ctx *src_ds_ctx; ++ enum mgmt_ds_id dst_ds_id; ++ struct mgmt_ds_ctx *dst_ds_ctx; ++ ++ uint32_t nb_txn_id; ++ ++ uint8_t validate_only : 1; ++ uint8_t abort : 1; ++ uint8_t implicit : 1; ++ uint8_t rollback : 1; ++ uint8_t init : 1; ++ uint8_t unlock_info : 1; ++ uint8_t txn_lock : 1; ++ ++ /* Track commit phases */ ++ enum mgmt_commit_phase phase; ++ ++ /* ++ * Additional information when the commit is triggered by native edit ++ * request. ++ */ ++ struct mgmt_edit_req *edit; ++ ++ ++ /* Used for holding changes mgmtd itself is interested in */ ++ struct nb_transaction *mgmtd_nb_txn; ++ ++ /* ++ * Details on all the Backend Clients associated with ++ * this commit. ++ */ ++ uint64_t clients; /* interested clients */ ++ uint64_t clients_wait; /* set when cfg_req sent */ ++ ++ struct mgmt_commit_stats *cmt_stats; ++}; ++#define as_commit(txn_req) \ ++ ({ \ ++ assert((txn_req)->req_type == TXN_REQ_TYPE_COMMIT); \ ++ (struct txn_req_commit *)(txn_req); \ ++ }) ++ ++/* The number of init txn's in progress, we lock on 0->1 and unlock on 1->0 */ ++uint txn_init_readers; ++ ++static void txn_cfg_timeout(struct event *event); ++static void txn_cfg_adapter_acked(struct txn_req_commit *ccreq, ++ struct mgmt_be_client_adapter *adapter); ++static int txn_cfg_send_txn_delete(struct mgmt_be_client_adapter *adapter, uint64_t txn_id); ++ ++/* ------------------------------------- */ ++/* Utility Functions for Commit Requests */ ++/* ------------------------------------- */ ++ ++static inline struct txn_req_commit *txn_txn_req_commit(struct mgmt_txn *txn) ++{ ++ struct txn_req *first_req = TAILQ_FIRST(&txn->reqs); ++ ++ if (first_req && first_req->req_type == TXN_REQ_TYPE_COMMIT) ++ return as_commit(first_req); ++ return NULL; ++} ++ ++static int txn_set_config_error(struct txn_req *txn_req, enum mgmt_result error, ++ const char *error_info) ++{ ++ txn_req->error = error; ++ darr_in_strdup(txn_req->err_info, error_info); ++ return -1; ++} ++ ++/* ------------------------------------ */ ++/* Allocate and Cleanup Commit Requests */ ++/* ------------------------------------ */ ++ ++static struct txn_req_commit *txn_req_commit_alloc(struct mgmt_txn *txn, uint64_t req_id) ++{ ++ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_COMMIT, ++ sizeof(struct txn_req_commit)); ++ assert(txn->type == MGMTD_TXN_TYPE_CONFIG); ++ return as_commit(txn_req); ++} ++ ++void txn_cfg_cleanup(struct txn_req *txn_req) ++{ ++ struct txn_req_commit *ccreq = as_commit(txn_req); ++ struct mgmt_be_client_adapter *adapter; ++ enum mgmt_commit_phase phase; ++ enum mgmt_be_client_id id; ++ uint64_t txn_id = txn_req->txn->txn_id; ++ uint64_t clients; ++ ++ _dbg("Deleting COMMITCFG req-id: %Lu txn-id: %Lu", txn_req->req_id, txn_id); ++ ++ XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit); ++ ++ /* If we (still) had an internal nb transaction, abort it */ ++ if (ccreq->mgmtd_nb_txn) { ++ nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0); ++ ccreq->mgmtd_nb_txn = NULL; ++ } ++ ++ /* ++ * What state are we in: ++ * ++ * FINISH: Everythign went OK. If this was NOT validate-only then all ++ * clients have replied to the CFG_APPLY and deleted their txn state. We ++ * have nothing to do. Otherwise, this is validate-only and all the ++ * clients still have config txn state with the changes. We need to ++ * clean that txn state up. Treat the phase as SEND-CFG so we send ++ * config abort (TXN-delete) to have the clients delete their txn state. ++ * ++ * APPLY: Any clients that have not CFG_APPLY_REPLY'd yet (clients_wait) ++ * need to be disconnected so they will resync config state on reconnect. ++ * ++ * SEND-CFG: Some clients may have cfg txn state. We send all clients ++ * config abort (TXN-delete) to cleanup any transaction state. Even if ++ * clients have rejected the config and deleted txn state already they ++ * are expected to handle receving TXN-delete gracefully (e.g., their ++ * reply may still be in flight when we got here). ++ */ ++ phase = ccreq->phase; ++ if (phase == MGMTD_COMMIT_PHASE_FINISH && ccreq->validate_only) ++ phase = MGMTD_COMMIT_PHASE_SEND_CFG; ++ ++ switch (phase) { ++ case MGMTD_COMMIT_PHASE_FINISH: ++ break; ++ case MGMTD_COMMIT_PHASE_APPLY_CFG: ++ /* cache and clear the bits as they are checked on client disconnect */ ++ clients = ccreq->clients_wait; ++ ccreq->clients_wait = 0; ++ ccreq->clients = 0; ++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { ++ _dbg("Disconnect client: %s for txn-id: %Lu resync required", ++ adapter->name, txn_id); ++ msg_conn_disconnect(adapter->conn, false); ++ } ++ break; ++ case MGMTD_COMMIT_PHASE_SEND_CFG: ++ /* cache and clear the bits as they are checked on client disconnect */ ++ clients = ccreq->clients; ++ ccreq->clients_wait = 0; ++ ccreq->clients = 0; ++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { ++ if (!txn_cfg_send_txn_delete(adapter, txn_id)) ++ continue; ++ _dbg("Disconnect client: %s for txn-id: %Lu failed to send CFG_ABORT", ++ adapter->name, txn_id); ++ msg_conn_disconnect(adapter->conn, false); ++ } ++ break; ++ } ++ ++ /* ++ * Now release the TXN locks if held. ++ */ ++ if (ccreq->txn_lock) { ++ mgmt_ds_txn_unlock(ccreq->src_ds_ctx, txn_id); ++ mgmt_ds_txn_unlock(ccreq->dst_ds_ctx, txn_id); ++ } ++} ++ ++/* =============================================== */ ++/* Config Phases For Syncing with Backend Clients */ ++/* =============================================== */ ++ ++/* --------- */ ++/* Any Phase */ ++/* --------- */ ++ ++/* ++ * Abort and Cleanup Config Changes. ++ * ++ * This will cleanup the Config Txn on the backend client. Backends that have ++ * replied to SEND_CFG with an error (validation/prepare) have already cleaned ++ * up. Likewise Backends that have replied to APPLY_CFG with an success ++ * have also already done this. In any case Backends must handle receiving this ++ * message even if they've already cleaned up. ++ */ ++static int txn_cfg_send_txn_delete(struct mgmt_be_client_adapter *adapter, uint64_t txn_id) ++{ ++ struct mgmt_msg_txn_req *msg; ++ int ret; ++ ++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_req, 0, MTYPE_MSG_NATIVE_TXN_REQ); ++ msg->code = MGMT_MSG_CODE_TXN_REQ; ++ msg->refer_id = txn_id; ++ msg->create = false; ++ ++ ret = mgmt_be_adapter_send(adapter, msg); ++ mgmt_msg_native_free_msg(msg); ++ return ret; ++} ++ ++ ++/* -------------- */ ++/* SEND_CFG phase */ ++/* -------------- */ ++ ++/* ++ * This is the real workhorse. Take the list of changes and check each change ++ * against our backend clients to see who is interested. For each interested ++ * client we create a config message -- we also track which changes mgmtd itself ++ * is interested in. If a client is interested we add the change to it's config ++ * message and track the type of chagne in another string array. ++ * ++ * When done with the changes we first handle mgmtd's own changes, validating, ++ * and preparing them into the mgmtd candidate (using normal lib/northbound ++ * routines). Then for each backend client we complete it's config message by ++ * append the action string and then we send it to the client. ++ * ++ * Return: 0 on success, the caller should arrange to receive REPLYS from the ++ * clients before proceeding further, if no clients were interested the caller ++ * should proceed to apply the mgmtd local changes (if any) to complete the txn. ++ * ++ * On failure, if we were called from a front-end client (the TXN has a ++ * session_id) we have it reply with the error and cleanup the txn. Otherwise ++ * this is an internal txn and we just cleanup the txn in that case. In either ++ * case we return -1 and the caller should not proceed further. ++ * ++ * Can disconnect backends, but this is not called from backends, so it's safe. ++ */ ++ ++static int txn_cfg_make_and_send_cfg_req(struct txn_req_commit *ccreq, ++ const struct nb_config_cbs *changes, ++ uint64_t init_client_mask) ++{ ++ struct nb_config_cb *cb, *nxt; ++ struct nb_config_change *chg; ++ struct nb_config_cbs mgmtd_changes = { 0 }; ++ char *xpath = NULL, *value = NULL; ++ enum mgmt_be_client_id id; ++ struct mgmt_be_client_adapter *adapter; ++ struct txn_req *txn_req = &ccreq->req; ++ struct mgmt_msg_cfg_req **cfg_msgs = NULL; ++ char **cfg_actions = NULL; ++ uint64_t *num_cfg_data = NULL; ++ bool mgmtd_interest; ++ uint batch_items = 0; ++ uint num_chgs = 0; ++ uint64_t clients, chg_clients; ++ char op; ++ int ret = -1; ++ ++ RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) { ++ chg = (struct nb_config_change *)cb; ++ ++ xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0); ++ assert(xpath); ++ ++ value = (char *)lyd_get_value(chg->cb.dnode); ++ if (!value) ++ value = (char *)MGMTD_BE_CONTAINER_NODE_VAL; ++ ++ _dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL"); ++ ++ /* Collect changes for mgmtd itself */ ++ mgmtd_interest = false; ++ if (!init_client_mask && mgmt_is_mgmtd_interested(xpath) && ++ /* We send tree changes to BEs that we don't need callbacks for */ ++ nb_cb_operation_is_valid(cb->operation, cb->dnode->schema)) { ++ uint32_t seq = cb->seq; ++ ++ nb_config_diff_add_change(&mgmtd_changes, cb->operation, &seq, cb->dnode); ++ mgmtd_interest = true; ++ } ++ if (init_client_mask) ++ clients = init_client_mask; ++ else ++ clients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); ++ if (!clients) ++ _dbg("No backends interested in xpath: %s", xpath); ++ ++ if (mgmtd_interest || clients) ++ num_chgs++; ++ ++ chg_clients = 0; ++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { ++ SET_IDBIT(chg_clients, id); ++ ++ darr_ensure_i(cfg_msgs, id); ++ darr_ensure_i(cfg_actions, id); ++ if (!cfg_msgs[id]) { ++ /* Allocate a new config message */ ++ struct mgmt_msg_cfg_req *msg; ++ ++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_req, 0, ++ MTYPE_MSG_NATIVE_CFG_REQ); ++ msg->code = MGMT_MSG_CODE_CFG_REQ; ++ msg->refer_id = txn_req->txn->txn_id; ++ msg->req_id = txn_req->req_id; ++ cfg_msgs[id] = msg; ++ } ++ ++ /* ++ * On the backend, we don't really care if it's CREATE ++ * or MODIFY, because the existence was already checked ++ * on the frontend. Therefore we use SET for both. ++ */ ++ op = chg->cb.operation == NB_CB_DESTROY ? 'd' : 'm'; ++ darr_push(cfg_actions[id], op); ++ ++ mgmt_msg_native_add_str(cfg_msgs[id], xpath); ++ if (op == 'm') ++ mgmt_msg_native_add_str(cfg_msgs[id], value); ++ if (DEBUG_MODE_CHECK(&mgmt_debug_txn, DEBUG_MODE_ALL)) { ++ darr_ensure_i(num_cfg_data, id); ++ num_cfg_data[id]++; ++ _dbg(" -- %s item: %Lu", adapter->name, num_cfg_data[id]); ++ } ++ ++ batch_items++; ++ } ++ ++ if (clients && clients != chg_clients) ++ _dbg("Some deamons interested in XPATH are not currently connected"); ++ ++ ccreq->clients |= chg_clients; ++ ++ free(xpath); ++ } ++ _dbg("Total xpath changes processed: %u sent", num_chgs); ++ ++ ccreq->cmt_stats->last_batch_cnt = batch_items; ++ ++ if (!RB_EMPTY(nb_config_cbs, &mgmtd_changes)) { ++ /* Create a northbound transaction for local mgmtd config changes */ ++ char errmsg[BUFSIZ] = { 0 }; ++ size_t errmsg_len = sizeof(errmsg); ++ struct nb_context nb_ctx = { 0 }; ++ ++ _dbg("Processing mgmtd bound changes"); ++ ++ assert(!ccreq->mgmtd_nb_txn); ++ nb_ctx.client = NB_CLIENT_MGMTD_SERVER; ++ ++ /* Prepare the mgmtd local config changes */ ++ /* ++ * This isn't calling the VALIDATE callback, it's just ++ * running PREPARE. See #19948 ++ */ ++ if (nb_changes_commit_prepare(nb_ctx, mgmtd_changes, "mgmtd-changes", NULL, ++ &ccreq->mgmtd_nb_txn, errmsg, errmsg_len)) { ++ _log_err("Failed to prepare local config for mgmtd: %s", errmsg); ++ if (ccreq->mgmtd_nb_txn) { ++ nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0); ++ ccreq->mgmtd_nb_txn = NULL; ++ } ++ (void)txn_set_config_error(txn_req, MGMTD_INTERNAL_ERROR, ++ "Failed to prepare local config for mgmtd"); ++ goto done; ++ } ++ assert(ccreq->mgmtd_nb_txn); ++ } ++ ++ /* Record txn create start time */ ++ if (mm->perf_stats_en) ++ gettimeofday(&ccreq->cmt_stats->txn_create_start, NULL); ++ ++ /* Send the messages to the backends */ ++ FOREACH_BE_ADAPTER_BITS (id, adapter, ccreq->clients) { ++ /* NUL terminate actions string and add to tail of message */ ++ darr_ensure_i(cfg_actions, id); /* required for clang SA NULL ptr check */ ++ darr_push(cfg_actions[id], 0); ++ mgmt_msg_native_add_str(cfg_msgs[id], cfg_actions[id]); ++ _dbg("Finished CFG_REQ for '%s' txn-id: %Lu with actions: %s", adapter->name, ++ txn_req->txn->txn_id, cfg_actions[id]); ++ ++ if (mgmt_be_adapter_send(adapter, cfg_msgs[id])) { ++ /* remove this client and reset the connection */ ++ UNSET_IDBIT(ccreq->clients, id); ++ msg_conn_disconnect(adapter->conn, false); ++ } ++ ccreq->cmt_stats->last_num_cfgdata_reqs++; ++ } ++ /* Record who we are waiting for */ ++ ccreq->clients_wait = ccreq->clients; ++ ++ if (ccreq->clients) { ++ /* set a timeout for hearing back from the backend clients */ ++ event_add_timer(mm->master, txn_cfg_timeout, txn_req, ++ MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC, &txn_req->timeout); ++ } else { ++ /* We have no connected interested clients */ ++ if (ccreq->mgmtd_nb_txn) ++ _dbg("No connected and interested backend clients, proceed with mgmtd local changes"); ++ else ++ _dbg("No connected and interested backend clients, proceed to apply changes"); ++ txn_cfg_adapter_acked(ccreq, NULL); ++ } ++ ++ ret = 0; ++done: ++ darr_free(num_cfg_data); ++ darr_free_func(cfg_msgs, mgmt_msg_native_free_msg); ++ darr_free_free(cfg_actions); ++ return ret; ++} ++ ++/* --------------- */ ++/* APPLY_CFG phase */ ++/* --------------- */ ++ ++/* ++ * Send CFG_APPLY_REQs to all the backend client. ++ */ ++static void txn_cfg_send_cfg_apply(struct txn_req_commit *ccreq) ++{ ++ enum mgmt_be_client_id id; ++ struct mgmt_be_client_adapter *adapter; ++ struct mgmt_msg_cfg_apply_req *msg; ++ uint64_t txn_id = ccreq->req.txn->txn_id; ++ ++ assert(ccreq->req.txn->type == MGMTD_TXN_TYPE_CONFIG); ++ ++ assert(!ccreq->validate_only); ++ ++ if (mm->perf_stats_en) ++ gettimeofday(&ccreq->cmt_stats->apply_cfg_start, NULL); ++ /* ++ * Handle mgmtd internal special case ++ */ ++ if (ccreq->mgmtd_nb_txn) { ++ char errmsg[BUFSIZ] = { 0 }; ++ ++ _dbg("Applying mgmtd local bound changes"); ++ ++ (void)nb_candidate_commit_apply(ccreq->mgmtd_nb_txn, false, NULL, errmsg, ++ sizeof(errmsg)); ++ ccreq->mgmtd_nb_txn = NULL; ++ } ++ ++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_apply_req, 0, ++ MTYPE_MSG_NATIVE_CFG_APPLY_REQ); ++ msg->code = MGMT_MSG_CODE_CFG_APPLY_REQ; ++ msg->refer_id = txn_id; ++ FOREACH_BE_ADAPTER_BITS (id, adapter, ccreq->clients) { ++ if (mgmt_be_adapter_send(adapter, msg)) { ++ msg_conn_disconnect(adapter->conn, false); ++ continue; ++ } ++ SET_IDBIT(ccreq->clients_wait, id); ++ ccreq->cmt_stats->last_num_apply_reqs++; ++ } ++ mgmt_msg_native_free_msg(msg); ++ ++ if (!ccreq->clients_wait) { ++ _dbg("No backends to wait for on CFG_APPLY for txn-id: %Lu", txn_id); ++ txn_cfg_adapter_acked(ccreq, NULL); ++ } ++} ++ ++ ++/* ------------ */ ++/* FINISH phase */ ++/* ------------ */ ++ ++/* ++ * Finish processing a commit-config request. ++ * ++ * NOTE: can disconnect (and delete) backends. ++ */ ++static void txn_finish_commit(struct txn_req_commit *ccreq, enum mgmt_result result, ++ const char *error_if_any) ++{ ++ bool success, apply_op, accept_changes, discard_changes; ++ struct txn_req *txn_req = as_txn_req(ccreq); ++ struct mgmt_txn *txn = txn_req->txn; ++ int ret = 0; ++ ++ success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES); ++ ++ /* ++ * Stop the commit-timeout timer. ++ */ ++ event_cancel(&txn_req->timeout); ++ ++ /* ++ * Decide what our datastores should now look like ++ * ++ * Accept changes into running (candidate->running): ++ * ++ * If this is a commit (apply or rollback) and we've at least started ++ * telling backend clients to apply, we need to accept the changes ++ * into running. Any clients who have not acked the apply yet will be ++ * disconnected in the txn cleanup so they sync to running on ++ * reconnect. ++ * ++ * Discard candidate changes (running->candidate): ++ * ++ * If this is a successful abort ++ */ ++ apply_op = !ccreq->validate_only && !ccreq->abort && !ccreq->init; ++ accept_changes = ccreq->phase >= MGMTD_COMMIT_PHASE_APPLY_CFG && apply_op; ++ discard_changes = (result == MGMTD_SUCCESS && ccreq->abort); ++ if (accept_changes) { ++ bool create_cmt_info_rec = (result != MGMTD_NO_CFG_CHANGES && !ccreq->rollback); ++ ++ mgmt_ds_copy_dss(ccreq->dst_ds_ctx, ccreq->src_ds_ctx, create_cmt_info_rec); ++ } ++ if (discard_changes) ++ mgmt_ds_copy_dss(ccreq->src_ds_ctx, ccreq->dst_ds_ctx, false); ++ ++ /* ++ * For internal txns do lock cleanup, for front-end session send replies. ++ */ ++ if (ccreq->init) { ++ /* ++ * This is the backend init request. Unlock the running ++ * datastore if we are the last reader. ++ */ ++ if (!--txn_init_readers) ++ mgmt_ds_unlock(ccreq->dst_ds_ctx, 0); ++ TXN_DECREF(txn); ++ } else if (ccreq->rollback) { ++ /* ++ * Resume processing the rollback command. ++ * ++ * TODO: there's no good reason to special case rollback, the ++ * rollback boolean should be passed back to the FE client and it ++ * can do the right thing. ++ */ ++ mgmt_history_rollback_complete(success); ++ TXN_DECREF(txn); ++ } else if (!ccreq->edit) { ++ /* ++ * This means we are in the mgmtd CLI vty code -- session code ++ * will release it's reference on the TXN ++ */ ++ ret = mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id, ccreq->src_ds_id, ++ ccreq->dst_ds_id, txn_req->req_id, ++ ccreq->validate_only, ccreq->unlock_info, ++ result, error_if_any); ++ } else { ++ /* Session code will release it's reference on the TXN */ ++ ret = mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, ++ txn_req->req_id, ccreq->unlock_info, ++ true /* commit */, &ccreq->edit, ++ success ? 0 : MGMTD_INTERNAL_ERROR, ++ error_if_any); ++ } ++ if (ret) ++ _log_err("Failed sending config reply for txn-id: %Lu session-id: %Lu", ++ txn->txn_id, txn->session_id); ++ ++ ccreq->cmt_stats = NULL; ++ txn_req_free(txn_req); ++} ++ ++/* ---------------------- */ ++/* Config Phase Machinery */ ++/* ---------------------- */ ++ ++static void txn_cfg_next_phase(struct txn_req_commit *ccreq) ++{ ++ struct txn_req *txn_req = &ccreq->req; ++ ++ switch (ccreq->phase) { ++ case MGMTD_COMMIT_PHASE_SEND_CFG: ++ if (ccreq->validate_only) ++ ccreq->phase = MGMTD_COMMIT_PHASE_FINISH; ++ else ++ ccreq->phase = MGMTD_COMMIT_PHASE_APPLY_CFG; ++ break; ++ case MGMTD_COMMIT_PHASE_APPLY_CFG: ++ if (mm->perf_stats_en) ++ gettimeofday(&ccreq->cmt_stats->apply_cfg_end, NULL); ++ ccreq->phase = MGMTD_COMMIT_PHASE_FINISH; ++ break; ++ case MGMTD_COMMIT_PHASE_FINISH: ++ default: ++ assert(!"Invalid commit phase transition from FINISH"); ++ break; ++ } ++ ++ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu transition to state: %s", txn_req->txn->txn_id, ++ mgmt_commit_phase_name[ccreq->phase]); ++ ++ switch (ccreq->phase) { ++ case MGMTD_COMMIT_PHASE_APPLY_CFG: ++ txn_cfg_send_cfg_apply(ccreq); ++ break; ++ case MGMTD_COMMIT_PHASE_FINISH: ++ if (mm->perf_stats_en) ++ gettimeofday(&ccreq->cmt_stats->txn_del_start, NULL); ++ txn_finish_commit(ccreq, MGMTD_SUCCESS, NULL); ++ return; ++ case MGMTD_COMMIT_PHASE_SEND_CFG: ++ default: ++ assert(!"Invalid commit phase transition to SEND_CFG"); ++ } ++} ++ ++static void txn_cfg_adapter_acked(struct txn_req_commit *ccreq, ++ struct mgmt_be_client_adapter *adapter) ++{ ++ struct txn_req *txn_req = &ccreq->req; ++ ++ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu in state: %s", txn_req->txn->txn_id, ++ mgmt_commit_phase_name[ccreq->phase]); ++ ++ if (adapter) { ++ enum mgmt_be_client_id id = adapter->id; ++ ++ if (IS_IDBIT_SET(ccreq->clients_wait, id)) ++ UNSET_IDBIT(ccreq->clients_wait, id); ++ else ++ _dbg("Wasn't waiting on client: %s", adapter->name); ++ } ++ ++ if (ccreq->clients_wait) { ++ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu still waiting on clients: 0x%Lx", ++ txn_req->txn->txn_id, ccreq->clients_wait); ++ return; ++ } ++ ++ txn_cfg_next_phase(ccreq); ++} ++ ++static void txn_cfg_timeout(struct event *event) ++{ ++ struct txn_req *txn_req; ++ struct txn_req_commit *ccreq; ++ ++ txn_req = EVENT_ARG(event); ++ ccreq = as_commit(txn_req); ++ ++ /* ++ * If we are applying changes we need to return SUCCESS as there is no ++ * way to abort those. Slow backends that haven't replied yet will be ++ * disconnected. If we are still sending config for validate/prepare ++ * phase we return an error and abort the commit. ++ */ ++ if (ccreq->phase < MGMTD_COMMIT_PHASE_APPLY_CFG) { ++ _log_err("Backend timeout validating txn-id: %Lu waiting: 0x%Lx aborting commit", ++ txn_req->txn->txn_id, ccreq->clients_wait); ++ txn_finish_commit(ccreq, MGMTD_INTERNAL_ERROR, ++ "Some backend clients taking too long to validate the changes."); ++ } else { ++ _log_warn("Backend timeout applying txn-id: %Lu waiting: 0x%Lx, applying commit", ++ txn_req->txn->txn_id, ccreq->clients_wait); ++ txn_finish_commit(ccreq, MGMTD_SUCCESS, ++ "Some backend clients taking too long to apply the changes."); ++ } ++} ++ ++/* ++ * Given a list of config changes, make backend client messages and send them. Aslo ++ * apply any mgmtd specific config as well. ++ */ ++static int txn_cfg_send_config_changes(struct txn_req_commit *ccreq, ++ struct nb_config_cbs *cfg_chgs, uint64_t init_client_mask) ++{ ++ int ret; ++ ++ if (mm->perf_stats_en) ++ gettimeofday(&ccreq->cmt_stats->prep_cfg_start, NULL); ++ ++ ret = txn_cfg_make_and_send_cfg_req(ccreq, cfg_chgs, init_client_mask); ++ nb_config_diff_del_changes(cfg_chgs); ++ return ret; ++} ++ ++/* ================================== */ ++/* APIs for Backend Clients (daemons) */ ++/* ================================== */ ++ ++/* ------------------------------------ */ ++/* Handle Messages From Backend Clients */ ++/* ------------------------------------ */ ++ ++static struct txn_req_commit * ++txn_cfg_ensure_msg(uint64_t txn_id, struct mgmt_be_client_adapter *adapter, const char *tag) ++{ ++ struct mgmt_txn *txn = txn_lookup(txn_id); ++ struct txn_req_commit *ccreq; ++ ++ if (!txn) { ++ _log_err("%s reply from '%s' for txn-id: %Lu no TXN, resetting connection", tag, ++ adapter->name, txn_id); ++ return NULL; ++ } ++ if (txn->type != MGMTD_TXN_TYPE_CONFIG) { ++ _log_err("%s reply from '%s' for txn-id: %Lu failed wrong txn TYPE %u, resetting connection", ++ tag, adapter->name, txn_id, txn->type); ++ return NULL; ++ } ++ ccreq = txn_txn_req_commit(txn); ++ if (!ccreq) { ++ _log_err("%s reply from '%s' for txn-id: %Lu failed no COMMITCFG_REQ, resetting connection", ++ tag, adapter->name, txn_id); ++ return NULL; ++ } ++ /* make sure we are part of this config commit */ ++ if (!IS_IDBIT_SET(ccreq->clients, adapter->id)) { ++ _log_err("%s reply from '%s' for txn-id %Lu not participating, resetting connection", ++ tag, adapter->name, txn_id); ++ return NULL; ++ } ++ return ccreq; ++} ++ ++/* ++ * Handle CFG_REQ reply from backend client adapter. This is the only point of ++ * failure that is expected in the config commit process; it is where the ++ * backend client can report validation or prepare errors. ++ * ++ * NOTE: can disconnect (and delete) the backend. ++ */ ++static void txn_cfg_handle_cfg_reply(struct txn_req_commit *ccreq, bool success, ++ const char *error_if_any, ++ struct mgmt_be_client_adapter *adapter) ++{ ++ struct txn_req *txn_req = as_txn_req(ccreq); ++ ++ if (!IS_IDBIT_SET(ccreq->clients_wait, adapter->id)) { ++ _log_warn("CFG_REPLY from '%s' but not waiting for it txn-id: %Lu, resetting conn", ++ adapter->name, txn_req->txn->txn_id); ++ msg_conn_disconnect(adapter->conn, false); ++ return; ++ } ++ ++ if (success) ++ _dbg("CFG_REPLY from '%s'", adapter->name); ++ else { ++ _log_err("CFG_REQ to '%s' failed err: %s", adapter->name, error_if_any ?: "None"); ++ txn_finish_commit(ccreq, MGMTD_VALIDATION_ERROR, ++ error_if_any ?: "config validation failed by backend daemon"); ++ return; ++ } ++ ++ txn_cfg_adapter_acked(ccreq, adapter); ++} ++ ++/* ++ * NOTE: can disconnect (and delete) the backend. ++ */ ++void mgmt_txn_handle_cfg_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter) ++{ ++ struct txn_req_commit *ccreq = txn_cfg_ensure_msg(txn_id, adapter, "CFG_REQ"); ++ ++ if (!ccreq) ++ msg_conn_disconnect(adapter->conn, false); ++ else ++ txn_cfg_handle_cfg_reply(ccreq, true, NULL, adapter); ++} ++ ++/* ++ * NOTE: can disconnect (and delete) the backend. ++ */ ++void mgmt_txn_handle_cfg_apply_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter) ++{ ++ struct txn_req_commit *ccreq = txn_cfg_ensure_msg(txn_id, adapter, "CFG_APPLY"); ++ ++ if (!ccreq) ++ msg_conn_disconnect(adapter->conn, false); ++ else ++ txn_cfg_adapter_acked(ccreq, adapter); ++} ++ ++/* ++ * NOTE: can disconnect (and delete) the backend. ++ */ ++void txn_cfg_handle_error(struct txn_req *txn_req, struct mgmt_be_client_adapter *adapter, ++ int error, const char *errstr) ++{ ++ struct txn_req_commit *ccreq; ++ ++ /* ++ * Handle an error during a configuration transaction. ++ */ ++ ccreq = txn_cfg_ensure_msg(txn_req->txn->txn_id, adapter, "ERROR"); ++ if (!ccreq || as_txn_req(ccreq) != txn_req) { ++ msg_conn_disconnect(adapter->conn, false); ++ return; ++ } ++ /* ++ * We only handle errors in reply to our CFG_REQ messages (due ++ * to validation/preparation). Otherwise, this is an error ++ * during some other phase of the commit process and we ++ * disconnect the client and start over. ++ */ ++ if (ccreq->phase == MGMTD_COMMIT_PHASE_SEND_CFG) ++ txn_cfg_handle_cfg_reply(ccreq, false, errstr, adapter); ++ else ++ /* Drop the connection these errors should never happen */ ++ msg_conn_disconnect(adapter->conn, false); ++} ++ ++/* ----------------------------------- */ ++/* Backend Client Connect / Disconnect */ ++/* ----------------------------------- */ ++ ++int txn_cfg_be_client_connect(struct mgmt_be_client_adapter *adapter) ++{ ++ struct nb_config_cbs adapter_cfgs; ++ struct mgmt_txn *txn; ++ struct txn_req_commit *ccreq; ++ struct mgmt_ds_ctx *ds_ctx; ++ ++ ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); ++ assert(ds_ctx); ++ ++ /* We don't send configs to ourselves we already have them */ ++ if (adapter->id == MGMTD_BE_CLIENT_ID_MGMTD) { ++ _dbg("Not sending initial config to myself"); ++ return 0; ++ } ++ ++ /* ++ * If this is the first set of changes we are sending to a ++ * backend, obtain a lock on running. We will release the lock ++ * when the last init cfg request completes. This allows for ++ * initializing the backends in parallel. ++ */ ++ ++ if (!txn_init_readers++ && mgmt_ds_lock(ds_ctx, 0) != 0) { ++ _dbg("Failed to lock DS:%s for init of BE adapter '%s'", ++ mgmt_ds_id2name(MGMTD_DS_RUNNING), adapter->name); ++ --txn_init_readers; ++ return -1; ++ } ++ ++ /* Get config for this single backend client */ ++ adapter_cfgs = mgmt_be_adapter_get_config(adapter); ++ if (RB_EMPTY(nb_config_cbs, &adapter_cfgs)) { ++ if (!--txn_init_readers) ++ mgmt_ds_unlock(ds_ctx, 0); ++ return 0; ++ } ++ ++ /* ++ * Create a CONFIG transaction to push the config changes ++ * provided to the backend client. ++ */ ++ txn = txn_create(MGMTD_TXN_TYPE_CONFIG); ++ if (!txn) { ++ _log_err("Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'", ++ adapter->name); ++ if (!--txn_init_readers) ++ mgmt_ds_unlock(ds_ctx, 0); ++ nb_config_diff_del_changes(&adapter_cfgs); ++ return -1; ++ } ++ ++ _dbg("Created initial txn-id: %" PRIu64 " for BE client '%s'", txn->txn_id, ++ adapter->name); ++ ++ /* ++ * Set the changeset for transaction to commit and trigger the ++ * commit request. ++ */ ++ memset(&adapter->cfg_stats, 0, sizeof(adapter->cfg_stats)); ++ ccreq = txn_req_commit_alloc(txn, 0); ++ ccreq->src_ds_id = MGMTD_DS_NONE; ++ ccreq->src_ds_ctx = 0; ++ ccreq->dst_ds_id = MGMTD_DS_RUNNING; ++ ccreq->dst_ds_ctx = ds_ctx; ++ ccreq->validate_only = false; ++ ccreq->abort = false; ++ ccreq->init = true; ++ ccreq->cmt_stats = &adapter->cfg_stats; ++ ++ /* ++ * Apply the initial changes. ++ */ ++ return txn_cfg_send_config_changes(ccreq, &adapter_cfgs, IDBIT_MASK(adapter->id)); ++} ++ ++/* static */ void txn_cfg_txn_be_client_disconnect(struct mgmt_txn *txn, ++ struct mgmt_be_client_adapter *adapter) ++{ ++ struct txn_req_commit *ccreq = txn_txn_req_commit(txn); ++ ++ if (!ccreq) ++ return; ++ ++ UNSET_IDBIT(ccreq->clients, adapter->id); ++ if (IS_IDBIT_SET(ccreq->clients_wait, adapter->id)) ++ txn_cfg_adapter_acked(ccreq, adapter); ++} ++ ++ ++/* ================================================ */ ++/* APIs for Frontend Clients (vtysh, restconf, ...) */ ++/* ================================================ */ ++ ++/* ++ * Prepare and send config changes by comparing the source and destination ++ * datastores. ++ */ ++static int txn_get_config_changes(struct txn_req_commit *ccreq, struct nb_config_cbs *cfg_chgs) ++{ ++ struct nb_config *nb_config; ++ struct txn_req *txn_req = &ccreq->req; ++ int ret = 0; ++ ++ if (ccreq->src_ds_id != MGMTD_DS_CANDIDATE) { ++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, ++ "Source DS cannot be any other than CANDIDATE!"); ++ } ++ ++ if (ccreq->dst_ds_id != MGMTD_DS_RUNNING) { ++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, ++ "Destination DS cannot be any other than RUNNING!"); ++ } ++ ++ if (!ccreq->src_ds_ctx) { ++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, ++ "No such source datastore!"); ++ } ++ ++ if (!ccreq->dst_ds_ctx) { ++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, ++ "No such destination datastore!"); ++ } ++ ++ if (ccreq->abort) { ++ /* ++ * This is a commit abort request. Return back success. ++ * The reply routing special cases abort, this isn't pretty, ++ * fix in later cleanup. ++ */ ++ return txn_set_config_error(txn_req, MGMTD_SUCCESS, "commit abort"); ++ } ++ ++ nb_config = mgmt_ds_get_nb_config(ccreq->src_ds_ctx); ++ if (!nb_config) { ++ return txn_set_config_error(txn_req, MGMTD_INTERNAL_ERROR, ++ "Unable to retrieve Commit DS Config Tree!"); ++ } ++ ++ /* ++ * Validate YANG contents of the source DS and get the diff ++ * between source and destination DS contents. ++ */ ++ char err_buf[BUFSIZ] = { 0 }; ++ ++ ret = nb_candidate_validate_yang(nb_config, true, err_buf, sizeof(err_buf) - 1); ++ if (ret != NB_OK) { ++ if (strncmp(err_buf, " ", strlen(err_buf)) == 0) ++ strlcpy(err_buf, "Validation failed", sizeof(err_buf)); ++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, err_buf); ++ } ++ ++ nb_config_diff(mgmt_ds_get_nb_config(ccreq->dst_ds_ctx), nb_config, cfg_chgs); ++ if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { ++ return txn_set_config_error(txn_req, MGMTD_NO_CFG_CHANGES, ++ "No changes found to be committed!"); ++ } ++ return 0; ++} ++ ++/** ++ * mgmt_txn_send_commit_config_req() - Send a commit config request ++ * @txn_id - A config TXN which must exist. ++ * ++ * Return: -1 on setup failure -- should immediately handle error, Otherwise 0 ++ * and will reply to session with any downstream error. ++ */ ++void mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id, ++ struct mgmt_ds_ctx *src_ds_ctx, enum mgmt_ds_id dst_ds_id, ++ struct mgmt_ds_ctx *dst_ds_ctx, bool validate_only, bool abort, ++ bool implicit, bool unlock, struct mgmt_edit_req *edit) ++{ ++ struct mgmt_txn *txn; ++ struct txn_req_commit *ccreq; ++ struct nb_config_cbs changes = { 0 }; ++ int ret; ++ ++ txn = txn_lookup(txn_id); ++ assert(txn && txn->type == MGMTD_TXN_TYPE_CONFIG); ++ ++ /* Only one outstanding commit request per txn */ ++ assert(txn_txn_req_commit(txn) == NULL); ++ ++ ccreq = txn_req_commit_alloc(txn, req_id); ++ ++ /* Lock the datastores for this transaction */ ++ assert(!mgmt_ds_txn_lock(src_ds_ctx, txn->txn_id)); ++ assert(!mgmt_ds_txn_lock(dst_ds_ctx, txn->txn_id)); ++ ccreq->txn_lock = true; ++ ++ ccreq->src_ds_id = src_ds_id; ++ ccreq->src_ds_ctx = src_ds_ctx; ++ ccreq->dst_ds_id = dst_ds_id; ++ ccreq->dst_ds_ctx = dst_ds_ctx; ++ ccreq->validate_only = validate_only; ++ ccreq->abort = abort; ++ ccreq->implicit = implicit; /* this is only true iff edit */ ++ ccreq->unlock_info = unlock; /* this is true for implicit commit in front-end */ ++ ccreq->edit = edit; ++ ccreq->cmt_stats = mgmt_fe_get_session_commit_stats(txn->session_id); ++ ++ ret = txn_get_config_changes(ccreq, &changes); ++ if (ret == 0) ++ ret = txn_cfg_send_config_changes(ccreq, &changes, 0); ++ if (ret) ++ txn_finish_commit(ccreq, ccreq->req.error, ccreq->req.err_info); ++} ++ ++/* ++ * TODO: this needs to be fixed to be like a normal commit config request and ++ * use a VTY session not the special 0 session ID. We should only use 0 for the ++ * adapter init case. ++ */ ++int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, ++ struct mgmt_ds_ctx *dst_ds_ctx) ++{ ++ static struct mgmt_commit_stats dummy_stats = { 0 }; ++ ++ struct nb_config_cbs changes = { 0 }; ++ struct mgmt_txn *txn; ++ struct txn_req *txn_req; ++ struct txn_req_commit *ccreq; ++ int ret; ++ ++ /* ++ * This could be the case when the config is directly ++ * loaded onto the candidate DS from a file. Get the ++ * diff from a full comparison of the candidate and ++ * running DSs. ++ */ ++ nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx), ++ mgmt_ds_get_nb_config(src_ds_ctx), &changes); ++ ++ if (RB_EMPTY(nb_config_cbs, &changes)) ++ return -1; ++ ++ /* ++ * Create a CONFIG transaction to push the config changes ++ * provided to the backend client. ++ */ ++ txn = txn_create(MGMTD_TXN_TYPE_CONFIG); ++ if (!txn) { ++ _log_err("Failed to create CONFIG Transaction for downloading CONFIGs"); ++ nb_config_diff_del_changes(&changes); ++ return -1; ++ } ++ ++ _dbg("Created rollback txn-id: %" PRIu64, txn->txn_id); ++ ++ /* ++ * Set the changeset for transaction to commit and trigger the commit ++ * request. ++ */ ++ ccreq = txn_req_commit_alloc(txn, 0); ++ txn_req = as_txn_req(ccreq); ++ ccreq->src_ds_id = MGMTD_DS_CANDIDATE; ++ ccreq->src_ds_ctx = src_ds_ctx; ++ ccreq->dst_ds_id = MGMTD_DS_RUNNING; ++ ccreq->dst_ds_ctx = dst_ds_ctx; ++ ccreq->validate_only = false; ++ ccreq->abort = false; ++ ccreq->rollback = true; ++ ccreq->cmt_stats = &dummy_stats; ++ ++ /* ++ * Send the changes. ++ */ ++ ret = txn_cfg_send_config_changes(ccreq, &changes, 0); ++ if (ret) ++ txn_finish_commit(ccreq, txn_req->error, txn_req->err_info); ++ return ret; ++} +diff --git a/mgmtd/mgmt_txn_priv.h b/mgmtd/mgmt_txn_priv.h +new file mode 100644 +index 0000000000..25707773b9 +--- /dev/null ++++ b/mgmtd/mgmt_txn_priv.h +@@ -0,0 +1,80 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * November 17 2025, Christian Hopps <chopps@labn.net> ++ * ++ * Copyright (c) 2025, LabN Consulting, L.L.C. ++ * ++ */ ++#include <zebra.h> ++#include "mgmtd/mgmt.h" ++#include "mgmtd/mgmt_txn.h" ++#include "queue.h" ++ ++#define _dbg(fmt, ...) DEBUGD(&mgmt_debug_txn, "TXN: %s: " fmt, __func__, ##__VA_ARGS__) ++#define _log_warn(fmt, ...) zlog_warn("%s: WARNING: " fmt, __func__, ##__VA_ARGS__) ++#define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) ++ ++#define TXN_INCREF(txn) txn_incref(txn, __FILE__, __LINE__) ++#define TXN_DECREF(txn) txn_decref(txn, __FILE__, __LINE__) ++ ++/* ----------------- */ ++/* Txn Private Types */ ++/* ----------------- */ ++ ++enum txn_req_type { ++ TXN_REQ_TYPE_COMMIT = 1, ++ TXN_REQ_TYPE_GETTREE, ++ TXN_REQ_TYPE_RPC, ++}; ++ ++struct txn_req { ++ TAILQ_ENTRY(txn_req) link; ++ enum txn_req_type req_type; ++ uint64_t req_id; ++ struct mgmt_txn *txn; ++ int16_t error; /* cfg: MGMTD return code, else errno */ ++ char *err_info; /* darr str */ ++ struct event *timeout; /* for timing out the request */ ++}; ++#define as_txn_req(xreq) (&(xreq)->req) /* Macro to coerce specific req back into txn_req */ ++ ++struct mgmt_txn { ++ TAILQ_ENTRY(mgmt_txn) link; ++ enum mgmt_txn_type type; ++ uint64_t txn_id; ++ int refcount; ++ uint64_t session_id; /* One transaction per client session */ ++ /* List of pending requests */ ++ TAILQ_HEAD(txn_req_head, txn_req) reqs; ++}; ++ ++/* ---------------------------- */ ++/* TXN Private Config Functions */ ++/* ---------------------------- */ ++ ++extern void txn_cfg_cleanup(struct txn_req *txn_req); ++extern void txn_cfg_handle_error(struct txn_req *txn_req, struct mgmt_be_client_adapter *adapter, ++ int error, const char *errstr); ++extern int txn_cfg_be_client_connect(struct mgmt_be_client_adapter *adapter); ++extern void txn_cfg_txn_be_client_disconnect(struct mgmt_txn *txn, ++ struct mgmt_be_client_adapter *adapter); ++/* ---------------------------- */ ++/* TXN Private Shared Functions */ ++/* ---------------------------- */ ++ ++extern struct txn_req *txn_req_alloc(struct mgmt_txn *txn, uint64_t req_id, ++ enum txn_req_type req_type, size_t size); ++extern void txn_req_free(struct txn_req *txn_req); ++extern struct mgmt_txn *txn_create(enum mgmt_txn_type type); ++extern void mgmt_txn_cleanup_txn(struct mgmt_txn **txn); ++extern struct mgmt_txn *txn_lookup(uint64_t txn_id); ++extern void txn_decref(struct mgmt_txn *txn, const char *file, int line); ++ ++extern struct event_loop *mgmt_txn_tm; ++ ++/* ------------------- */ ++/* TXN Private Globals */ ++/* ------------------- */ ++ ++extern struct event_loop *mgmt_txn_tm; ++extern struct mgmt_master *mgmt_txn_mm; +diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c +index a8f7827f28..a9d2ce13ea 100644 +--- a/mgmtd/mgmt_vty.c ++++ b/mgmtd/mgmt_vty.c +@@ -29,8 +29,6 @@ + #include "staticd/static_vty.h" + #include "zebra/zebra_cli.h" + +-extern struct frr_daemon_info *mgmt_daemon_info; +- + DEFPY(show_mgmt_be_adapter, + show_mgmt_be_adapter_cmd, + "show mgmt backend-adapter all", +@@ -147,6 +145,11 @@ DEFPY(mgmt_commit, + bool validate_only = type[0] == 'c'; + bool abort = type[1] == 'b'; + ++ if (!vty->mgmt_locked_candidate_ds) ++ vty_out(vty, "Warning: candidate datastore is not locked.\n"); ++ if (!validate_only && !vty->mgmt_locked_running_ds) ++ vty_out(vty, "Warning: running datastore is not locked.\n"); ++ + if (vty_mgmt_send_commit_config(vty, validate_only, abort, false) != 0) + return CMD_WARNING_CONFIG_FAILED; + return CMD_SUCCESS; +@@ -159,12 +162,14 @@ DEFPY(mgmt_create_config_data, mgmt_create_config_data_cmd, + "XPath expression specifying the YANG data path\n" + "Value of the data to create\n") + { +- strlcpy(vty->cfg_changes[0].xpath, path, +- sizeof(vty->cfg_changes[0].xpath)); ++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = value; + vty->cfg_changes[0].operation = NB_OP_CREATE_EXCL; + vty->num_cfg_changes = 1; + ++ if (!vty->mgmt_locked_candidate_ds) ++ vty_out(vty, "Warning: candidate datastore is not locked.\n"); ++ + return vty_mgmt_send_config_data(vty, NULL, false); + } + +@@ -175,12 +180,14 @@ DEFPY(mgmt_set_config_data, mgmt_set_config_data_cmd, + "XPath expression specifying the YANG data path\n" + "Value of the data to set\n") + { +- strlcpy(vty->cfg_changes[0].xpath, path, +- sizeof(vty->cfg_changes[0].xpath)); ++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = value; + vty->cfg_changes[0].operation = NB_OP_MODIFY; + vty->num_cfg_changes = 1; + ++ if (!vty->mgmt_locked_candidate_ds) ++ vty_out(vty, "Warning: candidate datastore is not locked.\n"); ++ + return vty_mgmt_send_config_data(vty, NULL, false); + } + +@@ -190,13 +197,14 @@ DEFPY(mgmt_delete_config_data, mgmt_delete_config_data_cmd, + "Delete configuration data\n" + "XPath expression specifying the YANG data path\n") + { +- +- strlcpy(vty->cfg_changes[0].xpath, path, +- sizeof(vty->cfg_changes[0].xpath)); ++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = NULL; + vty->cfg_changes[0].operation = NB_OP_DELETE; + vty->num_cfg_changes = 1; + ++ if (!vty->mgmt_locked_candidate_ds) ++ vty_out(vty, "Warning: candidate datastore is not locked.\n"); ++ + return vty_mgmt_send_config_data(vty, NULL, false); + } + +@@ -206,13 +214,14 @@ DEFPY(mgmt_remove_config_data, mgmt_remove_config_data_cmd, + "Remove configuration data\n" + "XPath expression specifying the YANG data path\n") + { +- +- strlcpy(vty->cfg_changes[0].xpath, path, +- sizeof(vty->cfg_changes[0].xpath)); ++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = NULL; + vty->cfg_changes[0].operation = NB_OP_DESTROY; + vty->num_cfg_changes = 1; + ++ if (!vty->mgmt_locked_candidate_ds) ++ vty_out(vty, "Warning: candidate datastore is not locked.\n"); ++ + return vty_mgmt_send_config_data(vty, NULL, false); + } + +@@ -230,6 +239,9 @@ DEFPY(mgmt_replace_config_data, mgmt_replace_config_data_cmd, + vty->cfg_changes[0].operation = NB_OP_REPLACE; + vty->num_cfg_changes = 1; + ++ if (!vty->mgmt_locked_candidate_ds) ++ vty_out(vty, "Warning: candidate datastore is not locked.\n"); ++ + return vty_mgmt_send_config_data(vty, NULL, false); + } + +@@ -251,7 +263,7 @@ DEFPY(mgmt_edit, mgmt_edit_cmd, + { + LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; + uint8_t operation; +- uint8_t flags = 0; ++ uint8_t ds_id; + + switch (op[2]) { + case 'e': +@@ -280,14 +292,8 @@ DEFPY(mgmt_edit, mgmt_edit_cmd, + return CMD_WARNING_CONFIG_FAILED; + } + +- if (lock) +- flags |= EDIT_FLAG_IMPLICIT_LOCK; +- +- if (commit) +- flags |= EDIT_FLAG_IMPLICIT_COMMIT; +- +- return vty_mgmt_send_edit_req(vty, MGMT_MSG_DATASTORE_CANDIDATE, format, flags, operation, +- xpath, data); ++ ds_id = commit ? MGMT_MSG_DATASTORE_RUNNING : MGMT_MSG_DATASTORE_CANDIDATE; ++ return vty_mgmt_send_edit_req(vty, ds_id, format, 0, operation, xpath, data); + } + + DEFPY(mgmt_rpc, mgmt_rpc_cmd, +@@ -426,7 +432,7 @@ DEFPY(show_mgmt_map_xpath, + "Get YANG Backend Subscription\n" + "XPath expression specifying the YANG data path\n") + { +- mgmt_be_show_xpath_registries(vty, path); ++ mgmt_be_adapter_show_xpath_registries(vty, path); + return CMD_SUCCESS; + } + +@@ -568,18 +574,6 @@ DEFPY(debug_mgmt, debug_mgmt_cmd, + return CMD_SUCCESS; + } + +-static void mgmt_config_read_in(struct event *event) +-{ +- if (vty_mgmt_fe_enabled()) +- mgmt_vty_read_configs(); +- else { +- zlog_warn("%s: no connection to front-end server, retry in 1s", +- __func__); +- event_add_timer(mm->master, mgmt_config_read_in, NULL, 1, +- &mgmt_daemon_info->read_in); +- } +-} +- + static int mgmtd_config_write(struct vty *vty) + { + struct lyd_node *root; +@@ -624,10 +618,6 @@ void mgmt_vty_init(void) + #ifdef HAVE_STATICD + static_vty_init(); + #endif +- +- event_add_event(mm->master, mgmt_config_read_in, NULL, 0, +- &mgmt_daemon_info->read_in); +- + install_node(&mgmtd_node); + + install_element(VIEW_NODE, &show_mgmt_be_adapter_cmd); +diff --git a/mgmtd/mgmt_vty_frontend.c b/mgmtd/mgmt_vty_frontend.c +new file mode 100644 +index 0000000000..a4f7cc0f7d +--- /dev/null ++++ b/mgmtd/mgmt_vty_frontend.c +@@ -0,0 +1,898 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * November 15 2025, Christian Hopps <chopps@labn.net> ++ * ++ * Copyright (c) 2025, LabN Consulting, L.L.C. ++ * ++ */ ++ ++#include <zebra.h> ++#include <libyang/libyang.h> ++#include <libyang/version.h> ++#include "lib/debug.h" ++#include "lib/command.h" ++#include "lib/lib_vty.h" ++#include "lib/northbound_cli.h" ++#include "lib/vty.h" ++#include "mgmtd/mgmt.h" ++ ++#define _dbg debug_fe_client ++#define _log_err log_err_fe_client ++#define _log_warn(fmt, ...) zlog_warn("FE-CLIENT: %s: WARNING: " fmt, __func__, ##__VA_ARGS__) ++ ++/* static */ struct mgmt_fe_client *mgmt_fe_client; ++static bool mgmt_fe_connected; ++ ++static uint64_t mgmt_client_id_next; ++ ++char const *const mgmt_daemons[] = { ++ "zebra", ++#ifdef HAVE_RIPD ++ "ripd", ++#endif ++#ifdef HAVE_RIPNGD ++ "ripngd", ++#endif ++#ifdef HAVE_STATICD ++ "staticd", ++#endif ++}; ++uint mgmt_daemons_count = array_size(mgmt_daemons); ++ ++ ++/* ================= */ ++/* Utility Functions */ ++/* ================= */ ++ ++static bool vty_mgmt_fe_enabled(void) ++{ ++ return mgmt_fe_client && mgmt_fe_connected; ++} ++ ++static void fe_client_set_vty_callbacks(bool connected); ++static int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok); ++ ++static int vty_mgmt_lock_candidate_inline(struct vty *vty) ++{ ++ assert(!vty->mgmt_locked_candidate_ds); ++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true); ++ return vty->mgmt_locked_candidate_ds ? 0 : -1; ++} ++ ++static int vty_mgmt_unlock_candidate_inline(struct vty *vty) ++{ ++ assert(vty->mgmt_locked_candidate_ds); ++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true); ++ return vty->mgmt_locked_candidate_ds ? -1 : 0; ++} ++ ++static int vty_mgmt_lock_running_inline(struct vty *vty) ++{ ++ assert(!vty->mgmt_locked_running_ds); ++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true); ++ return vty->mgmt_locked_running_ds ? 0 : -1; ++} ++ ++static int vty_mgmt_unlock_running_inline(struct vty *vty) ++{ ++ assert(vty->mgmt_locked_running_ds); ++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true); ++ return vty->mgmt_locked_running_ds ? -1 : 0; ++} ++ ++void vty_mgmt_resume_response(struct vty *vty, int ret) ++{ ++ if (!vty->mgmt_req_pending_cmd) { ++ zlog_err("vty resume response called without mgmt_req_pending_cmd"); ++ return; ++ } ++ ++ debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64 " with '%s'", ++ vty->mgmt_req_pending_cmd, vty->mgmt_session_id, ++ ret == CMD_SUCCESS ? "success" : "failed"); ++ ++ vty->mgmt_req_pending_cmd = NULL; ++ ++ vty_resume_response(vty, ret); ++} ++ ++/* ======================================================= */ ++/* Startup Read Config Files for all mgmt-enabled daemons. */ ++/* ======================================================= */ ++ ++static bool mgmt_vty_read_configs(void) ++{ ++ char path[PATH_MAX]; ++ struct vty *vty; ++ FILE *confp; ++ uint line_num = 0; ++ uint count = 0; ++ uint index; ++ ++ vty = vty_new(); ++ vty->wfd = STDERR_FILENO; ++ vty->type = VTY_FILE; /* We don't send these changes to backends */ ++ vty->node = CONFIG_NODE; ++ vty->config = true; ++ vty->pending_allowed = true; ++ ++ vty->candidate_config = vty_shared_candidate_config; ++ ++ vty_mgmt_lock_candidate_inline(vty); ++ vty_mgmt_lock_running_inline(vty); ++ ++ for (index = 0; index < array_size(mgmt_daemons); index++) { ++ snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir, mgmt_daemons[index]); ++ ++ confp = vty_open_config(path, config_default); ++ if (!confp) ++ continue; ++ ++ zlog_info("mgmtd: reading config file: %s", path); ++ ++ /* Execute configuration file */ ++ line_num = 0; ++ (void)config_from_file(vty, confp, &line_num); ++ count++; ++ ++ fclose(confp); ++ } ++ ++ snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir); ++ confp = vty_open_config(path, config_default); ++ if (confp) { ++ zlog_info("mgmtd: reading config file: %s", path); ++ ++ line_num = 0; ++ (void)config_from_file(vty, confp, &line_num); ++ count++; ++ ++ fclose(confp); ++ } ++ ++ /* Conditionally unlock as the config file may have "exit"d early which ++ * would then have unlocked things. ++ */ ++ if (vty->mgmt_locked_running_ds) ++ vty_mgmt_unlock_running_inline(vty); ++ if (vty->mgmt_locked_candidate_ds) ++ vty_mgmt_unlock_candidate_inline(vty); ++ ++ vty->pending_allowed = false; ++ ++ if (!count) ++ vty_close(vty); ++ else ++ vty_read_file_finish(vty, NULL); ++ ++ zlog_info("mgmtd: finished reading config files"); ++ ++ return true; ++} ++ ++ ++/* ++ * This is analogous to frr_config_read_in() in libfrr.c, but customized for ++ * mgmtd. It reads in all the mgmt-enabled daemon config files, this method is ++ * now deprecrated in favor of integrated config via vtysh. ++ */ ++static void mgmt_config_read_in(struct event *event) ++{ ++ if (vty_mgmt_fe_enabled()) ++ mgmt_vty_read_configs(); ++ else { ++ zlog_warn("%s: no connection to front-end server, retry in 1s", __func__); ++ event_add_timer(mm->master, mgmt_config_read_in, NULL, 1, ++ &mgmt_daemon_info->read_in); ++ } ++} ++ ++static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf, size_t count) ++{ ++ struct vty *vty = user_data; ++ ++ vty_out(vty, "%.*s", (int)count, (const char *)buf); ++ return count; ++} ++ ++static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format, const struct ly_err_item *ei) ++{ ++#if (LY_VERSION_MAJOR < 3) ++#define data_path path ++#else ++#define data_path data_path ++#endif ++ bool have_apptag = ei->apptag && ei->apptag[0] != 0; ++ bool have_path = ei->data_path && ei->data_path[0] != 0; ++ bool have_msg = ei->msg && ei->msg[0] != 0; ++ const char *severity = NULL; ++ const char *evalid = NULL; ++ const char *ecode = NULL; ++#if (LY_VERSION_MAJOR < 3) ++ LY_ERR err = ei->no; ++#else ++ LY_ERR err = ei->err; ++#endif ++ ++ if (ei->level == LY_LLERR) ++ severity = "error"; ++ else if (ei->level == LY_LLWRN) ++ severity = "warning"; ++ ++ ecode = yang_ly_strerrcode(err); ++ if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS) ++ evalid = yang_ly_strvecode(ei->vecode); ++ ++ switch (format) { ++ case LYD_XML: ++ vty_out(vty, "<rpc-error xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"); ++ vty_out(vty, "<error-type>application</error-type>"); ++ if (severity) ++ vty_out(vty, "<error-severity>%s</error-severity>", severity); ++ if (ecode) ++ vty_out(vty, "<error-code>%s</error-code>", ecode); ++ if (evalid) ++ vty_out(vty, "<error-validation>%s</error-validation>\n", evalid); ++ if (have_path) ++ vty_out(vty, "<error-path>%s</error-path>\n", ei->data_path); ++ if (have_apptag) ++ vty_out(vty, "<error-app-tag>%s</error-app-tag>\n", ei->apptag); ++ if (have_msg) ++ vty_out(vty, "<error-message>%s</error-message>\n", ei->msg); ++ ++ vty_out(vty, "</rpc-error>"); ++ break; ++ case LYD_JSON: ++ vty_out(vty, "{ \"error-type\": \"application\""); ++ if (severity) ++ vty_out(vty, ", \"error-severity\": \"%s\"", severity); ++ if (ecode) ++ vty_out(vty, ", \"error-code\": \"%s\"", ecode); ++ if (evalid) ++ vty_out(vty, ", \"error-validation\": \"%s\"", evalid); ++ if (have_path) ++ vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path); ++ if (have_apptag) ++ vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag); ++ if (have_msg) ++ vty_out(vty, ", \"error-message\": \"%s\"", ei->msg); ++ ++ vty_out(vty, "}"); ++ break; ++ case LYD_UNKNOWN: ++ case LYD_LYB: ++ default: ++ vty_out(vty, "%% error"); ++ if (severity) ++ vty_out(vty, " severity: %s", severity); ++ if (evalid) ++ vty_out(vty, " invalid: %s", evalid); ++ if (have_path) ++ vty_out(vty, " path: %s", ei->data_path); ++ if (have_apptag) ++ vty_out(vty, " app-tag: %s", ei->apptag); ++ if (have_msg) ++ vty_out(vty, " msg: %s", ei->msg); ++ break; ++ } ++#undef data_path ++} ++ ++static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format) ++{ ++ const struct ly_err_item *ei = ly_err_first(ly_native_ctx); ++ uint count; ++ ++ if (!ei) ++ return 0; ++ ++ if (format == LYD_JSON) ++ vty_out(vty, "\"ietf-restconf:errors\": [ "); ++ ++ for (count = 0; ei; count++, ei = ei->next) { ++ if (count) ++ vty_out(vty, ", "); ++ vty_out_yang_error(vty, format, ei); ++ } ++ ++ if (format == LYD_JSON) ++ vty_out(vty, " ]"); ++ ++ ly_err_clean(ly_native_ctx, NULL); ++ ++ return count; ++} ++ ++ ++static int vty_mgmt_handle_error_reply(struct mgmt_fe_client *client, uintptr_t user_data, ++ uint64_t client_id, uint64_t session_id, uintptr_t session_ctx, ++ uint64_t req_id, int error, const char *errstr) ++{ ++ struct vty *vty = (struct vty *)session_ctx; ++ const char *cname = mgmt_fe_client_name(client); ++ ++ if (!vty->mgmt_req_pending_cmd) { ++ debug_fe_client("Error with no pending command: %d returned for client %s 0x%Lx session-id %Lu req-id %Lu error-str %s", ++ error, cname, client_id, session_id, req_id, errstr); ++ vty_out(vty, "%% Error %d from MGMTD for %s with no pending command: %s\n", error, ++ cname, errstr); ++ return CMD_WARNING; ++ } ++ ++ debug_fe_client("Error %d returned for client %s 0x%" PRIx64 " session-id %" PRIu64 ++ " req-id %" PRIu64 "error-str %s", ++ error, cname, client_id, session_id, req_id, errstr); ++ ++ vty_out(vty, "%% %s (for %s, client %s)\n", errstr, vty->mgmt_req_pending_cmd, cname); ++ ++ vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS); ++ ++ return 0; ++} ++ ++/* =================================== */ ++/* Mgmtd Frontend Client Functionality */ ++/* =================================== */ ++ ++/* ------- */ ++/* Locking */ ++/* ------- */ ++ ++static int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok) ++{ ++ assert(mgmt_fe_client); ++ assert(vty->mgmt_session_id); ++ ++ vty->mgmt_req_id++; ++ if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, ds_id, ++ lock, scok)) { ++ zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64, lock ? "" : "UN", ++ vty->mgmt_req_id); ++ vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n", lock ? "" : "UN"); ++ return -1; ++ } ++ ++ if (!scok) ++ vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ"; ++ ++ return 0; ++} ++ ++static void vty_mgmt_handle_lock_ds_reply(struct mgmt_fe_client *client, uintptr_t usr_data, ++ uint64_t client_id, uintptr_t session_id, ++ uintptr_t session_ctx, uint64_t req_id, bool lock_ds, ++ bool success, enum mgmt_ds_id ds_id, char *errmsg_if_any) ++{ ++ struct vty *vty; ++ bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client); ++ ++ vty = (struct vty *)session_ctx; ++ ++ assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING); ++ if (!success) ++ zlog_err("%socking for DS %u failed, Err: '%s' vty %p", lock_ds ? "L" : "Unl", ++ ds_id, errmsg_if_any, vty); ++ else { ++ debug_fe_client("%socked DS %u successfully", lock_ds ? "L" : "Unl", ds_id); ++ if (ds_id == MGMTD_DS_CANDIDATE) ++ vty->mgmt_locked_candidate_ds = lock_ds; ++ else ++ vty->mgmt_locked_running_ds = lock_ds; ++ } ++ ++ if (!is_short_circuit && vty->mgmt_req_pending_cmd) { ++ assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ")); ++ vty_mgmt_resume_response(vty, success ? CMD_SUCCESS : CMD_WARNING); ++ } ++} ++ ++/* ------------------------------------------------ */ ++/* "Send" Config Data -- actually just edits inline */ ++/* ------------------------------------------------ */ ++ ++int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, bool implicit_commit) ++{ ++ char err_buf[BUFSIZ]; ++ bool error = false; ++ ++ if (implicit_commit) { ++ assert(vty->mgmt_client_id && vty->mgmt_session_id); ++ if (vty_mgmt_lock_candidate_inline(vty)) { ++ vty_out(vty, "%% could not lock candidate DS\n"); ++ return CMD_WARNING_CONFIG_FAILED; ++ } else if (vty_mgmt_lock_running_inline(vty)) { ++ vty_out(vty, "%% could not lock running DS\n"); ++ vty_mgmt_unlock_candidate_inline(vty); ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ } ++ ++ if (!vty->mgmt_locked_candidate_ds) ++ vty_out(vty, "%% WARNING: changing candidate datastore without lock.\n"); ++ ++ nb_candidate_edit_config_changes(vty->candidate_config, vty->cfg_changes, ++ vty->num_cfg_changes, xpath_base, false, err_buf, ++ sizeof(err_buf), &error); ++ if (error) { ++ /* ++ * Failure to edit the candidate configuration should never ++ * happen in practice, unless there's a bug in the code. When ++ * that happens, log the error but otherwise ignore it. ++ */ ++ vty_out(vty, "%% Couldn't apply changes: %s", err_buf); ++error: ++ if (implicit_commit) { ++ vty_mgmt_unlock_running_inline(vty); ++ vty_mgmt_unlock_candidate_inline(vty); ++ } ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ ++ if (!implicit_commit) ++ return CMD_SUCCESS; ++ ++ assert(vty->mgmt_client_id && vty->mgmt_session_id); ++ if (vty_mgmt_send_commit_config(vty, false, false, true) < 0) ++ goto error; ++ ++ return CMD_SUCCESS; ++} ++ ++/* ------------- */ ++/* Commit Config */ ++/* ------------- */ ++ ++int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock) ++{ ++ if (mgmt_fe_client && vty->mgmt_session_id) { ++ vty->mgmt_req_id++; ++ if (mgmt_fe_send_commit_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, ++ MGMTD_DS_CANDIDATE, MGMTD_DS_RUNNING, validate_only, ++ abort, unlock)) { ++ zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64, vty->mgmt_req_id); ++ vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n"); ++ return -1; ++ } ++ ++ vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ"; ++ vty->mgmt_num_pending_setcfg = 0; ++ } ++ ++ return 0; ++} ++ ++static void vty_mgmt_handle_commit_config_reply(struct mgmt_fe_client *client, uintptr_t usr_data, ++ uint64_t client_id, uintptr_t session_id, ++ uintptr_t session_ctx, uint64_t req_id, ++ bool success, enum mgmt_ds_id src_ds_id, ++ enum mgmt_ds_id dst_ds_id, bool validate_only, ++ bool unlock, char *errmsg_if_any) ++{ ++ struct vty *vty; ++ ++ vty = (struct vty *)session_ctx; ++ ++ if (!success) { ++ zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 " failed, Error: '%s'", ++ client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); ++ vty_out(vty, "%% Configuration failed.\n\n"); ++ if (errmsg_if_any) ++ vty_out(vty, "%s\n", errmsg_if_any); ++ } else { ++ debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64 " req-id %" PRIu64 ++ " was successfull%s%s", ++ client_id, req_id, errmsg_if_any ? ": " : "", errmsg_if_any ?: ""); ++ if (!unlock && errmsg_if_any) ++ vty_out(vty, "MGMTD: %s\n", errmsg_if_any); ++ } ++ ++ if (unlock) { ++ /* we locked these when we sent the commit, unlock now */ ++ vty_mgmt_unlock_candidate_inline(vty); ++ vty_mgmt_unlock_running_inline(vty); ++ } ++ ++ vty_mgmt_resume_response(vty, success ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED); ++} ++ ++/* -------- */ ++/* Get Data */ ++/* -------- */ ++ ++int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, LYD_FORMAT result_type, ++ uint8_t flags, uint8_t defaults, 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, ++ datastore, intern_format, flags, defaults, xpath)) { ++ 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-DATA to MGMTD!\n"); ++ return -1; ++ } ++ ++ vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ"; ++ vty->mgmt_req_pending_data = result_type; ++ ++ return 0; ++} ++ ++static int vty_mgmt_handle_get_tree_reply(struct mgmt_fe_client *client, uintptr_t user_data, ++ uint64_t client_id, uint64_t session_id, ++ uintptr_t session_ctx, uint64_t req_id, ++ enum mgmt_ds_id ds_id, LYD_FORMAT result_type, ++ void *result, size_t len, int partial_error) ++{ ++ struct vty *vty; ++ struct lyd_node *dnode; ++ int ret = CMD_SUCCESS; ++ LY_ERR err; ++ ++ vty = (struct vty *)session_ctx; ++ ++ debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64 " req-id %" PRIu64, ++ partial_error ? "partially " : "", client_id, req_id); ++ ++ assert(result_type == LYD_LYB || result_type == vty->mgmt_req_pending_data); ++ ++ if (vty->mgmt_req_pending_data == LYD_XML && partial_error) ++ vty_out(vty, "<!-- some errors occurred gathering results -->\n"); ++ ++ if (result_type == LYD_LYB) { ++ /* ++ * parse binary into tree and print in the specified format ++ */ ++ result_type = vty->mgmt_req_pending_data; ++ ++ err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0, &dnode); ++ if (!err) ++ err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode, result_type, ++ LYD_PRINT_WITHSIBLINGS); ++ lyd_free_all(dnode); ++ ++ if (vty_out_yang_errors(vty, result_type) || err) ++ ret = CMD_WARNING; ++ } else { ++ /* ++ * Print the in-format result ++ */ ++ assert(result_type == LYD_XML || result_type == LYD_JSON); ++ vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result); ++ } ++ ++ vty_mgmt_resume_response(vty, ret); ++ ++ return 0; ++} ++ ++ ++/* ----------- */ ++/* Edit Config */ ++/* ----------- */ ++ ++int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, LYD_FORMAT request_type, ++ uint8_t flags, uint8_t operation, const char *xpath, const char *data) ++{ ++ vty->mgmt_req_id++; ++ ++ if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, ++ datastore, request_type, flags, operation, xpath, data)) { ++ zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".", ++ vty->mgmt_session_id, vty->mgmt_req_id); ++ vty_out(vty, "Failed to send EDIT to MGMTD!\n"); ++ return -1; ++ } ++ ++ vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ"; ++ ++ return 0; ++} ++ ++static int vty_mgmt_handle_edit_reply(struct mgmt_fe_client *client, uintptr_t user_data, ++ uint64_t client_id, uint64_t session_id, ++ uintptr_t session_ctx, uint64_t req_id, const char *xpath) ++{ ++ struct vty *vty = (struct vty *)session_ctx; ++ ++ debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64 ++ " was successful, xpath: %s", ++ client_id, req_id, xpath); ++ ++ vty_mgmt_resume_response(vty, CMD_SUCCESS); ++ ++ return 0; ++} ++ ++ ++/* =========== */ ++/* Execute RPC */ ++/* =========== */ ++ ++int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, const char *xpath, ++ const char *data) ++{ ++ vty->mgmt_req_id++; ++ ++ if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, ++ request_type, xpath, data)) { ++ zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".", ++ vty->mgmt_session_id, vty->mgmt_req_id); ++ vty_out(vty, "Failed to send RPC to MGMTD!\n"); ++ return -1; ++ } ++ ++ vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ"; ++ ++ return 0; ++} ++ ++static int vty_mgmt_handle_rpc_reply(struct mgmt_fe_client *client, uintptr_t user_data, ++ uint64_t client_id, uint64_t session_id, ++ uintptr_t session_ctx, uint64_t req_id, const char *result) ++{ ++ struct vty *vty = (struct vty *)session_ctx; ++ ++ debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64 " was successful", ++ client_id, req_id); ++ ++ if (result) ++ vty_out(vty, "%s\n", result); ++ ++ vty_mgmt_resume_response(vty, CMD_SUCCESS); ++ ++ return 0; ++} ++ ++ ++/* ==================================== */ ++/* VTY Augmenting/Hooking Functionality */ ++/* ==================================== */ ++ ++static void vty_new_mgmt(struct vty *new) ++{ ++ if (!mgmt_fe_client) ++ return; ++ if (!mgmt_client_id_next) ++ mgmt_client_id_next++; ++ new->mgmt_client_id = mgmt_client_id_next++; ++ new->mgmt_session_id = 0; ++ mgmt_fe_create_client_session(mgmt_fe_client, new->mgmt_client_id, (uintptr_t)new); ++ ++ /* we short-circuit create the session so it must be set now */ ++ assertf(new->mgmt_session_id != 0, "Failed to create client session for VTY"); ++} ++ ++static void vty_close_mgmt(struct vty *vty) ++{ ++ if (!mgmt_fe_client || !vty->mgmt_client_id) ++ return; ++ ++ debug_fe_client("closing vty session"); ++ mgmt_fe_destroy_client_session(mgmt_fe_client, vty->mgmt_client_id); ++ vty->mgmt_session_id = 0; ++} ++ ++static int vty_config_enter_mgmt(struct vty *vty, bool private_config, bool exclusive, ++ bool file_lock) ++{ ++ /* if no file lock requested, nothing to do */ ++ if (!file_lock) ++ return CMD_SUCCESS; ++ ++ /* Working on a private config is outside normal mgmtd actions */ ++ if (private_config) ++ return CMD_SUCCESS; ++ ++ /* Exclude is actually what we are doing with file-lock so it's no-op */ ++ ++ /* ++ * We only need to do a lock when reading a config file as we will be ++ * sending a batch of setcfg changes followed by a single commit ++ * message. For user interactive mode we are doing implicit commits ++ * those will obtain the lock (or not) when they try and commit. ++ */ ++ if (vty_mgmt_lock_candidate_inline(vty)) { ++ vty_out(vty, ++ "%% Can't enter config; candidate datastore locked by another session\n"); ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ if (vty_mgmt_lock_running_inline(vty)) { ++ vty_out(vty, ++ "%% Can't enter config; running datastore locked by another session\n"); ++ vty_mgmt_unlock_candidate_inline(vty); ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ assert(vty->mgmt_locked_candidate_ds); ++ assert(vty->mgmt_locked_running_ds); ++ ++ /* ++ * As datastores are locked explicitly, we don't need implicit commits ++ * and should allow pending changes. ++ */ ++ vty->pending_allowed = true; ++ ++ return CMD_SUCCESS; ++} ++ ++static void vty_config_node_exit_mgmt(struct vty *vty) ++{ ++ if (vty->mgmt_locked_running_ds) ++ vty_mgmt_unlock_running_inline(vty); ++ ++ if (vty->mgmt_locked_candidate_ds) ++ vty_mgmt_unlock_candidate_inline(vty); ++} ++ ++static void vty_end_config_mgmt(struct vty *vty) ++{ ++ /* ++ * If we have made changes with vty_mgmt_send_config_data(), but without ++ * implicit commit then we need to do a commit now to apply all those ++ * pending changes. ++ */ ++ if (vty->mgmt_num_pending_setcfg) ++ vty_mgmt_send_commit_config(vty, false, false, false); ++} ++ ++static int nb_cli_apply_changes_mgmt(struct vty *vty, const char *xpath_base_abs) ++{ ++ bool implicit_commit; ++ ++ VTY_CHECK_XPATH; ++ ++ assert(vty->type != VTY_FILE && vty_mgmt_fe_enabled()); ++ /* ++ * The legacy user wanted to clear pending (i.e., perform a ++ * commit immediately) due to some non-yang compatible ++ * functionality. This new mgmtd code however, continues to send ++ * changes putting off the commit until XFRR_end is received ++ * (i.e., end-of-config-file). This should be fine b/c all ++ * conversions to mgmtd require full proper implementations. ++ */ ++ if (!vty->num_cfg_changes) ++ return CMD_SUCCESS; ++ ++ implicit_commit = frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed; ++ if (vty_mgmt_send_config_data(vty, xpath_base_abs, implicit_commit) < 0) { ++ vty_out(vty, "%% Failed to apply configuration data.\n"); ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ if (!implicit_commit) ++ ++vty->mgmt_num_pending_setcfg; ++ return CMD_SUCCESS; ++} ++ ++static int nb_cli_rpc_mgmt(struct vty *vty, const char *xpath, const struct lyd_node *input) ++{ ++ char *data = NULL; ++ LY_ERR err; ++ int ret; ++ ++ err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK); ++ assert(err == LY_SUCCESS); ++ ++ ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data); ++ ++ free(data); ++ if (ret < 0) ++ return CMD_WARNING; ++ return CMD_SUCCESS; ++} ++ ++static void fe_client_set_vty_callbacks(bool connected) ++{ ++ if (connected) { ++ /* only call when connected */ ++ vty_config_enter_mgmt_cb = vty_config_enter_mgmt; ++ vty_config_node_exit_mgmt_cb = vty_config_node_exit_mgmt; ++ cmd_init_config_callbacks(NULL, vty_end_config_mgmt); ++ nb_cli_apply_changes_mgmt_cb = nb_cli_apply_changes_mgmt; ++ nb_cli_rpc_mgmt_cb = nb_cli_rpc_mgmt; ++ } else { ++ vty_config_enter_mgmt_cb = NULL; ++ vty_config_node_exit_mgmt_cb = NULL; ++ cmd_init_config_callbacks(NULL, NULL); ++ nb_cli_apply_changes_mgmt_cb = NULL; ++ nb_cli_rpc_mgmt_cb = NULL; ++ } ++} ++ ++/* ====================== */ ++/* Initialize and Cleanup */ ++/* ====================== */ ++ ++static void vty_mgmt_client_connect_notified(struct mgmt_fe_client *client, uintptr_t usr_data, ++ bool connected) ++{ ++ debug_fe_client("Got %sconnected %s MGMTD Frontend Server", !connected ? "dis: " : "", ++ !connected ? "from" : "to"); ++ ++ /* ++ * We should not have any sessions for connecting or disconnecting case. ++ * The fe client library will delete all session on disconnect before ++ * calling us. ++ */ ++ assert(mgmt_fe_client_session_count(client) == 0); ++ ++ mgmt_fe_connected = connected; ++ fe_client_set_vty_callbacks(connected); ++ ++ /* Start or stop listening for vty connections */ ++ if (connected) ++ frr_vty_serv_start(true); ++ else ++ frr_vty_serv_stop(); ++} ++ ++/* ++ * A session has successfully been created for a vty. ++ */ ++static void vty_mgmt_client_session_notified(struct mgmt_fe_client *client, uintptr_t usr_data, ++ uint64_t client_id, bool create, bool success, ++ uintptr_t session_id, uintptr_t session_ctx) ++{ ++ struct vty *vty; ++ ++ vty = (struct vty *)session_ctx; ++ ++ if (!success) { ++ zlog_err("%s session for client %" PRIu64 " failed!", ++ create ? "Creating" : "Destroying", client_id); ++ return; ++ } ++ ++ debug_fe_client("%s session for client %" PRIu64 " successfully", ++ create ? "Created" : "Destroyed", client_id); ++ ++ if (create) { ++ assert(session_id != 0); ++ vty->mgmt_session_id = session_id; ++ } else { ++ vty->mgmt_session_id = 0; ++ /* We may come here by way of vty_close() and short-circuits */ ++ if (vty->status != VTY_CLOSE) ++ vty_close(vty); ++ } ++} ++ ++ ++static struct mgmt_fe_client_cbs mgmt_cbs = { ++ .client_connect_notify = vty_mgmt_client_connect_notified, ++ .client_session_notify = vty_mgmt_client_session_notified, ++ .lock_ds_notify = vty_mgmt_handle_lock_ds_reply, ++ .commit_config_notify = vty_mgmt_handle_commit_config_reply, ++ .get_tree_notify = vty_mgmt_handle_get_tree_reply, ++ .edit_notify = vty_mgmt_handle_edit_reply, ++ .rpc_notify = vty_mgmt_handle_rpc_reply, ++ .error_notify = vty_mgmt_handle_error_reply, ++ ++}; ++ ++void vty_mgmt_init(void) ++{ ++ char name[40]; ++ ++ assert(mm->master); ++ assert(!mgmt_fe_client); ++ snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(), (long)getpid()); ++ mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, mm->master); ++ vty_new_mgmt_cb = vty_new_mgmt; ++ vty_close_mgmt_cb = vty_close_mgmt; ++ assert(mgmt_fe_client); ++ ++ event_add_event(mm->master, mgmt_config_read_in, NULL, 0, &mgmt_daemon_info->read_in); ++} ++ ++void vty_mgmt_terminate(void) ++{ ++ if (mgmt_fe_client) { ++ mgmt_fe_client_destroy(mgmt_fe_client); ++ mgmt_fe_client = NULL; ++ vty_new_mgmt_cb = NULL; ++ vty_close_mgmt_cb = NULL; ++ } ++} +diff --git a/mgmtd/subdir.am b/mgmtd/subdir.am +index 14544c4f05..bbe272d84e 100644 +--- a/mgmtd/subdir.am ++++ b/mgmtd/subdir.am +@@ -34,7 +34,9 @@ mgmtd_libmgmtd_a_SOURCES = \ + mgmtd/mgmt_history.c \ + mgmtd/mgmt_memory.c \ + mgmtd/mgmt_txn.c \ ++ mgmtd/mgmt_txn_cfg.c \ + mgmtd/mgmt_vty.c \ ++ mgmtd/mgmt_vty_frontend.c \ + # end + + noinst_HEADERS += \ +@@ -45,6 +47,7 @@ noinst_HEADERS += \ + mgmtd/mgmt_history.h \ + mgmtd/mgmt_memory.h \ + mgmtd/mgmt_txn.h \ ++ mgmtd/mgmt_txn_priv.h \ + zebra/zebra_cli.h \ + # end + +diff --git a/ospf6d/ospf6_main.c b/ospf6d/ospf6_main.c +index 4aa5771a79..5d7c7ee245 100644 +--- a/ospf6d/ospf6_main.c ++++ b/ospf6d/ospf6_main.c +@@ -210,7 +210,7 @@ static void ospf6_config_finish(struct event *t) + OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS); + } + +-static void ospf6_config_start(void) ++static void ospf6_config_start(struct vty *vty) + { + if (IS_OSPF6_DEBUG_EVENT) + zlog_debug("ospf6d config start received"); +@@ -219,7 +219,7 @@ static void ospf6_config_start(void) + OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf6_cfg); + } + +-static void ospf6_config_end(void) ++static void ospf6_config_end(struct vty *vty) + { + if (IS_OSPF6_DEBUG_EVENT) + zlog_debug("ospf6d config end received"); +diff --git a/ospfd/ospf_main.c b/ospfd/ospf_main.c +index 17c6f3f196..7107af9637 100644 +--- a/ospfd/ospf_main.c ++++ b/ospfd/ospf_main.c +@@ -180,7 +180,7 @@ static void ospf_config_finish(struct event *t) + OSPF_PRE_CONFIG_MAX_WAIT_SECONDS); + } + +-static void ospf_config_start(void) ++static void ospf_config_start(struct vty *vty) + { + event_cancel(&t_ospf_cfg); + if (IS_DEBUG_OSPF_EVENT) +@@ -189,7 +189,7 @@ static void ospf_config_start(void) + OSPF_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf_cfg); + } + +-static void ospf_config_end(void) ++static void ospf_config_end(struct vty *vty) + { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("ospfd config end callback received."); +diff --git a/sharpd/sharp_main.c b/sharpd/sharp_main.c +index 2374070102..76aa923e09 100644 +--- a/sharpd/sharp_main.c ++++ b/sharpd/sharp_main.c +@@ -151,12 +151,12 @@ FRR_DAEMON_INFO(sharpd, SHARP, + ); + /* clang-format on */ + +-static void sharp_start_configuration(void) ++static void sharp_start_configuration(struct vty *vty) + { + zlog_debug("Configuration has started to be read"); + } + +-static void sharp_end_configuration(void) ++static void sharp_end_configuration(struct vty *vty) + { + zlog_debug("Configuration has finished being read"); + } +diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl +index 63c1f60b4f..7060334c4c 100755 +--- a/tools/checkpatch.pl ++++ b/tools/checkpatch.pl +@@ -563,7 +563,7 @@ our $Iterators = qr{ + SUBGRP_FOREACH_PEER|SUBGRP_FOREACH_PEER_SAFE| + SUBGRP_FOREACH_ADJ|SUBGRP_FOREACH_ADJ_SAFE| + AF_FOREACH|FOREACH_AFI_SAFI|FOREACH_SAFI| +- FOREACH_BE_CLIENT_BITS|FOREACH_MGMTD_BE_CLIENT_ID| ++ FOREACH_BE_CLIENT_BITS|FOREACH_MGMTD_BE_CLIENT_ID|FOREACH_BE_ADAPTER_BITS| + FOREACH_SESSION_IN_LIST| + LSDB_LOOP + }x; +diff --git a/watchfrr/watchfrr.c b/watchfrr/watchfrr.c +index 62c6a1f42c..6bfbd47950 100644 +--- a/watchfrr/watchfrr.c ++++ b/watchfrr/watchfrr.c +@@ -1275,12 +1275,12 @@ static FRR_NORETURN void netns_setup(const char *nsname) + } + #endif + +-static void watchfrr_start_config(void) ++static void watchfrr_start_config(struct vty *vty) + { + gs.reading_configuration = true; + } + +-static void watchfrr_end_config(void) ++static void watchfrr_end_config(struct vty *vty) + { + gs.reading_configuration = false; + } +-- +2.50.1 + diff --git a/scripts/package-build/frr/patches/frr/0015-zebra-fix-crash-on-inactive-VRF-and-import-table.patch b/scripts/package-build/frr/patches/frr/0015-zebra-fix-crash-on-inactive-VRF-and-import-table.patch new file mode 100644 index 00000000..aadddd45 --- /dev/null +++ b/scripts/package-build/frr/patches/frr/0015-zebra-fix-crash-on-inactive-VRF-and-import-table.patch @@ -0,0 +1,60 @@ +From 5962cfb671440dd5609c43f50cd78375aa068821 Mon Sep 17 00:00:00 2001 +From: Kyrylo Yatsenko <hedrok@gmail.com> +Date: Mon, 19 Jan 2026 20:45:02 +0200 +Subject: [PATCH] zebra: fix crash on inactive VRF and import table + +When configuration has inactive VRF and import-table zebra crashes +`zebra_router_get_table` because NULL is passed as `zvrf`. + +Fix this in two places: + +* In case `vrf_info_lookup` in `zebra_vrf_get_table_with_table_id` + returns NULL don't call `zebra_router_get_table` - just return NULL. +* As optimization just ignore inactive VRFs in `zebra_import_table_rm_update`. +--- + zebra/redistribute.c | 7 +++++-- + zebra/zebra_vrf.c | 3 +++ + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/zebra/redistribute.c b/zebra/redistribute.c +index 5dcf578c4e..dee61ceed8 100644 +--- a/zebra/redistribute.c ++++ b/zebra/redistribute.c +@@ -917,8 +917,8 @@ static void zebra_import_table_rm_update_vrf_afi(struct zebra_vrf *zvrf, afi_t a + table = zebra_vrf_get_table_with_table_id(afi, safi, zvrf->vrf->vrf_id, table_id); + if (!table) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) +- zlog_debug("%s: Table id=%d not found", __func__, +- table_id); ++ zlog_debug("%s: Table id=%d not found for vrf %u", __func__, table_id, ++ zvrf->vrf->vrf_id); + return; + } + +@@ -975,6 +975,9 @@ void zebra_import_table_rm_update(const char *rmap) + if (!zvrf) + continue; + ++ if (!CHECK_FLAG(vrf->status, VRF_ACTIVE)) ++ continue; ++ + zebra_import_table_rm_update_vrf(zvrf, rmap); + } + } +diff --git a/zebra/zebra_vrf.c b/zebra/zebra_vrf.c +index 374a460bbd..00b9a5f519 100644 +--- a/zebra/zebra_vrf.c ++++ b/zebra/zebra_vrf.c +@@ -389,6 +389,9 @@ struct route_table *zebra_vrf_get_table_with_table_id(afi_t afi, safi_t safi, + struct other_route_table *otable; + struct route_table *table; + ++ if (!zvrf) ++ return NULL; ++ + table = zebra_vrf_lookup_table_with_table_id(afi, safi, vrf_id, + table_id); + +-- +2.50.1 + |
