1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2018 NetDEF, Inc.
10 #include "lib_errors.h"
15 #include "lib/version.h"
16 #include "northbound.h"
19 #include <sysrepo/values.h>
20 #include <sysrepo/xpath.h>
22 DEFINE_MTYPE_STATIC(LIB
, SYSREPO
, "Sysrepo module");
24 static struct debug nb_dbg_client_sysrepo
= {0, "Northbound client: Sysrepo"};
26 static struct thread_master
*master
;
27 static sr_session_ctx_t
*session
;
28 static sr_conn_ctx_t
*connection
;
29 static struct nb_transaction
*transaction
;
31 static void frr_sr_read_cb(struct thread
*thread
);
32 static int frr_sr_finish(void);
34 /* Convert FRR YANG data value to sysrepo YANG data value. */
35 static int yang_data_frr2sr(struct yang_data
*frr_data
, sr_val_t
*sr_data
)
37 struct nb_node
*nb_node
;
38 const struct lysc_node
*snode
;
39 struct lysc_node_container
*scontainer
;
40 struct lysc_node_leaf
*sleaf
;
41 struct lysc_node_leaflist
*sleaflist
;
44 sr_val_set_xpath(sr_data
, frr_data
->xpath
);
46 nb_node
= nb_node_find(frr_data
->xpath
);
48 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
49 "%s: unknown data path: %s", __func__
,
54 snode
= nb_node
->snode
;
55 switch (snode
->nodetype
) {
57 scontainer
= (struct lysc_node_container
*)snode
;
58 if (!CHECK_FLAG(scontainer
->flags
, LYS_PRESENCE
))
60 sr_data
->type
= SR_CONTAINER_PRESENCE_T
;
63 sr_data
->type
= SR_LIST_T
;
66 sleaf
= (struct lysc_node_leaf
*)snode
;
67 type
= sleaf
->type
->basetype
;
70 sleaflist
= (struct lysc_node_leaflist
*)snode
;
71 type
= sleaflist
->type
->basetype
;
79 sr_val_set_str_data(sr_data
, SR_BINARY_T
, frr_data
->value
);
82 sr_val_set_str_data(sr_data
, SR_BITS_T
, frr_data
->value
);
85 sr_data
->type
= SR_BOOL_T
;
86 sr_data
->data
.bool_val
= yang_str2bool(frr_data
->value
);
89 sr_data
->type
= SR_DECIMAL64_T
;
90 sr_data
->data
.decimal64_val
=
91 yang_str2dec64(frr_data
->xpath
, frr_data
->value
);
94 sr_data
->type
= SR_LEAF_EMPTY_T
;
97 sr_val_set_str_data(sr_data
, SR_ENUM_T
, frr_data
->value
);
100 sr_val_set_str_data(sr_data
, SR_IDENTITYREF_T
, frr_data
->value
);
103 sr_val_set_str_data(sr_data
, SR_INSTANCEID_T
, frr_data
->value
);
106 sr_data
->type
= SR_INT8_T
;
107 sr_data
->data
.int8_val
= yang_str2int8(frr_data
->value
);
110 sr_data
->type
= SR_INT16_T
;
111 sr_data
->data
.int16_val
= yang_str2int16(frr_data
->value
);
114 sr_data
->type
= SR_INT32_T
;
115 sr_data
->data
.int32_val
= yang_str2int32(frr_data
->value
);
118 sr_data
->type
= SR_INT64_T
;
119 sr_data
->data
.int64_val
= yang_str2int64(frr_data
->value
);
122 sr_val_set_str_data(sr_data
, SR_STRING_T
, frr_data
->value
);
125 sr_data
->type
= SR_UINT8_T
;
126 sr_data
->data
.uint8_val
= yang_str2uint8(frr_data
->value
);
129 sr_data
->type
= SR_UINT16_T
;
130 sr_data
->data
.uint16_val
= yang_str2uint16(frr_data
->value
);
133 sr_data
->type
= SR_UINT32_T
;
134 sr_data
->data
.uint32_val
= yang_str2uint32(frr_data
->value
);
137 sr_data
->type
= SR_UINT64_T
;
138 sr_data
->data
.uint64_val
= yang_str2uint64(frr_data
->value
);
147 static int frr_sr_process_change(struct nb_config
*candidate
,
148 sr_change_oper_t sr_op
, sr_val_t
*sr_old_val
,
149 sr_val_t
*sr_new_val
)
151 struct nb_node
*nb_node
;
152 enum nb_operation nb_op
;
155 char value_str
[YANG_VALUE_MAXLEN
];
156 struct yang_data
*data
;
159 sr_data
= sr_new_val
? sr_new_val
: sr_old_val
;
162 xpath
= sr_data
->xpath
;
164 DEBUGD(&nb_dbg_client_sysrepo
, "sysrepo: processing change [xpath %s]",
167 /* Non-presence container - nothing to do. */
168 if (sr_data
->type
== SR_CONTAINER_T
)
171 nb_node
= nb_node_find(xpath
);
173 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
174 "%s: unknown data path: %s", __func__
, xpath
);
178 /* Map operation values. */
182 if (nb_operation_is_valid(NB_OP_CREATE
, nb_node
->snode
))
183 nb_op
= NB_OP_CREATE
;
184 else if (nb_operation_is_valid(NB_OP_MODIFY
, nb_node
->snode
)) {
185 nb_op
= NB_OP_MODIFY
;
187 /* Ignore list keys modifications. */
192 * When a list is deleted or one of its keys is changed, we are
193 * notified about the removal of all of its leafs, even the ones
194 * that are non-optional. We need to ignore these notifications.
196 if (!nb_operation_is_valid(NB_OP_DESTROY
, nb_node
->snode
))
199 nb_op
= NB_OP_DESTROY
;
205 flog_err(EC_LIB_DEVELOPMENT
,
206 "%s: unexpected operation %u [xpath %s]", __func__
,
211 sr_val_to_buff(sr_data
, value_str
, sizeof(value_str
));
212 data
= yang_data_new(xpath
, value_str
);
214 ret
= nb_candidate_edit(candidate
, nb_node
, nb_op
, xpath
, NULL
, data
);
215 yang_data_free(data
);
216 if (ret
!= NB_OK
&& ret
!= NB_ERR_NOT_FOUND
) {
218 EC_LIB_NB_CANDIDATE_EDIT_ERROR
,
219 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
220 __func__
, nb_operation_name(nb_op
), xpath
);
227 static int frr_sr_config_change_cb_prepare(sr_session_ctx_t
*session
,
228 const char *module_name
)
230 sr_change_iter_t
*it
;
232 sr_change_oper_t sr_op
;
233 sr_val_t
*sr_old_val
, *sr_new_val
;
234 struct nb_context context
= {};
235 struct nb_config
*candidate
;
236 char errmsg
[BUFSIZ
] = {0};
238 ret
= sr_get_changes_iter(session
, "//*", &it
);
239 if (ret
!= SR_ERR_OK
) {
240 flog_err(EC_LIB_LIBSYSREPO
,
241 "%s: sr_get_changes_iter() failed for \"%s\"",
242 __func__
, module_name
);
246 candidate
= nb_config_dup(running_config
);
248 while ((ret
= sr_get_change_next(session
, it
, &sr_op
, &sr_old_val
,
251 ret
= frr_sr_process_change(candidate
, sr_op
, sr_old_val
,
253 sr_free_val(sr_old_val
);
254 sr_free_val(sr_new_val
);
259 sr_free_change_iter(it
);
260 if (ret
!= NB_OK
&& ret
!= SR_ERR_NOT_FOUND
) {
261 nb_config_free(candidate
);
262 return SR_ERR_INTERNAL
;
266 context
.client
= NB_CLIENT_SYSREPO
;
268 * Validate the configuration changes and allocate all resources
269 * required to apply them.
271 ret
= nb_candidate_commit_prepare(&context
, candidate
, NULL
,
272 &transaction
, errmsg
, sizeof(errmsg
));
273 if (ret
!= NB_OK
&& ret
!= NB_ERR_NO_CHANGES
)
276 "%s: failed to prepare configuration transaction: %s (%s)",
277 __func__
, nb_err_name(ret
), errmsg
);
280 nb_config_free(candidate
);
282 /* Map northbound return code to sysrepo return code. */
286 case NB_ERR_NO_CHANGES
:
289 return SR_ERR_LOCKED
;
290 case NB_ERR_RESOURCE
:
291 return SR_ERR_NO_MEMORY
;
293 return SR_ERR_VALIDATION_FAILED
;
297 static int frr_sr_config_change_cb_apply(sr_session_ctx_t
*session
,
298 const char *module_name
)
300 /* Apply the transaction. */
302 struct nb_config
*candidate
= transaction
->config
;
303 char errmsg
[BUFSIZ
] = {0};
305 nb_candidate_commit_apply(transaction
, true, NULL
, errmsg
,
307 nb_config_free(candidate
);
313 static int frr_sr_config_change_cb_abort(sr_session_ctx_t
*session
,
314 const char *module_name
)
316 /* Abort the transaction. */
318 struct nb_config
*candidate
= transaction
->config
;
319 char errmsg
[BUFSIZ
] = {0};
321 nb_candidate_commit_abort(transaction
, errmsg
, sizeof(errmsg
));
322 nb_config_free(candidate
);
328 /* Callback for changes in the running configuration. */
329 static int frr_sr_config_change_cb(sr_session_ctx_t
*session
, uint32_t sub_id
,
330 const char *module_name
, const char *xpath
,
331 sr_event_t sr_ev
, uint32_t request_id
,
337 return frr_sr_config_change_cb_prepare(session
, module_name
);
339 return frr_sr_config_change_cb_apply(session
, module_name
);
341 return frr_sr_config_change_cb_abort(session
, module_name
);
343 flog_err(EC_LIB_LIBSYSREPO
, "%s: unexpected sysrepo event: %u",
345 return SR_ERR_INTERNAL
;
349 static int frr_sr_state_data_iter_cb(const struct lysc_node
*snode
,
350 struct yang_translator
*translator
,
351 struct yang_data
*data
, void *arg
)
353 struct lyd_node
*dnode
= arg
;
357 ly_errno
= lyd_new_path(NULL
, ly_native_ctx
, data
->xpath
, data
->value
,
359 if (!dnode
&& ly_errno
) {
360 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
362 yang_data_free(data
);
366 yang_data_free(data
);
370 /* Callback for state retrieval. */
371 static int frr_sr_state_cb(sr_session_ctx_t
*session
, uint32_t sub_id
,
372 const char *module_name
, const char *xpath
,
373 const char *request_xpath
, uint32_t request_id
,
374 struct lyd_node
**parent
, void *private_ctx
)
376 struct lyd_node
*dnode
;
379 if (nb_oper_data_iterate(request_xpath
, NULL
, 0,
380 frr_sr_state_data_iter_cb
, dnode
)
382 flog_warn(EC_LIB_NB_OPERATIONAL_DATA
,
383 "%s: failed to obtain operational data [xpath %s]",
385 return SR_ERR_INTERNAL
;
392 static int frr_sr_config_rpc_cb(sr_session_ctx_t
*session
, uint32_t sub_id
,
393 const char *xpath
, const sr_val_t
*sr_input
,
394 const size_t input_cnt
, sr_event_t sr_ev
,
395 uint32_t request_id
, sr_val_t
**sr_output
,
396 size_t *sr_output_cnt
, void *private_ctx
)
398 struct nb_node
*nb_node
;
401 struct yang_data
*data
;
402 size_t cb_output_cnt
;
404 char errmsg
[BUFSIZ
] = {0};
406 nb_node
= nb_node_find(xpath
);
408 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
409 "%s: unknown data path: %s", __func__
, xpath
);
410 return SR_ERR_INTERNAL
;
413 input
= yang_data_list_new();
414 output
= yang_data_list_new();
417 for (size_t i
= 0; i
< input_cnt
; i
++) {
418 char value_str
[YANG_VALUE_MAXLEN
];
420 sr_val_to_buff(&sr_input
[i
], value_str
, sizeof(value_str
));
422 data
= yang_data_new(xpath
, value_str
);
423 listnode_add(input
, data
);
426 /* Execute callback registered for this XPath. */
427 if (nb_callback_rpc(nb_node
, xpath
, input
, output
, errmsg
,
430 flog_warn(EC_LIB_NB_CB_RPC
, "%s: rpc callback failed: %s",
432 ret
= SR_ERR_OPERATION_FAILED
;
436 /* Process output. */
437 if (listcount(output
) > 0) {
438 sr_val_t
*values
= NULL
;
439 struct listnode
*node
;
442 cb_output_cnt
= listcount(output
);
443 ret
= sr_new_values(cb_output_cnt
, &values
);
444 if (ret
!= SR_ERR_OK
) {
445 flog_err(EC_LIB_LIBSYSREPO
, "%s: sr_new_values(): %s",
446 __func__
, sr_strerror(ret
));
450 for (ALL_LIST_ELEMENTS_RO(output
, node
, data
)) {
451 if (yang_data_frr2sr(data
, &values
[i
++]) != 0) {
453 EC_LIB_SYSREPO_DATA_CONVERT
,
454 "%s: failed to convert data to Sysrepo format",
456 ret
= SR_ERR_INTERNAL
;
457 sr_free_values(values
, cb_output_cnt
);
463 *sr_output_cnt
= cb_output_cnt
;
467 /* Release memory. */
469 list_delete(&output
);
474 static int frr_sr_notification_send(const char *xpath
, struct list
*arguments
)
476 sr_val_t
*values
= NULL
;
477 size_t values_cnt
= 0;
480 if (arguments
&& listcount(arguments
) > 0) {
481 struct yang_data
*data
;
482 struct listnode
*node
;
485 values_cnt
= listcount(arguments
);
486 ret
= sr_new_values(values_cnt
, &values
);
487 if (ret
!= SR_ERR_OK
) {
488 flog_err(EC_LIB_LIBSYSREPO
, "%s: sr_new_values(): %s",
489 __func__
, sr_strerror(ret
));
493 for (ALL_LIST_ELEMENTS_RO(arguments
, node
, data
)) {
494 if (yang_data_frr2sr(data
, &values
[i
++]) != 0) {
496 EC_LIB_SYSREPO_DATA_CONVERT
,
497 "%s: failed to convert data to sysrepo format",
499 sr_free_values(values
, values_cnt
);
505 ret
= sr_notif_send(session
, xpath
, values
, values_cnt
, 0, 0);
506 if (ret
!= SR_ERR_OK
) {
507 flog_err(EC_LIB_LIBSYSREPO
,
508 "%s: sr_event_notif_send() failed for xpath %s",
516 static void frr_sr_read_cb(struct thread
*thread
)
518 struct yang_module
*module
= THREAD_ARG(thread
);
519 int fd
= THREAD_FD(thread
);
522 ret
= sr_subscription_process_events(module
->sr_subscription
, session
,
524 if (ret
!= SR_ERR_OK
) {
525 flog_err(EC_LIB_LIBSYSREPO
, "%s: sr_fd_event_process(): %s",
526 __func__
, sr_strerror(ret
));
530 thread_add_read(master
, frr_sr_read_cb
, module
, fd
, &module
->sr_thread
);
533 static void frr_sr_subscribe_config(struct yang_module
*module
)
537 DEBUGD(&nb_dbg_client_sysrepo
,
538 "sysrepo: subscribing for configuration changes made in the '%s' module",
541 ret
= sr_module_change_subscribe(
542 session
, module
->name
, NULL
, frr_sr_config_change_cb
, NULL
, 0,
543 SR_SUBSCR_DEFAULT
| SR_SUBSCR_ENABLED
| SR_SUBSCR_NO_THREAD
,
544 &module
->sr_subscription
);
545 if (ret
!= SR_ERR_OK
)
546 flog_err(EC_LIB_LIBSYSREPO
, "sr_module_change_subscribe(): %s",
550 static int frr_sr_subscribe_state(const struct lysc_node
*snode
, void *arg
)
552 struct yang_module
*module
= arg
;
553 struct nb_node
*nb_node
;
556 if (!CHECK_FLAG(snode
->flags
, LYS_CONFIG_R
))
557 return YANG_ITER_CONTINUE
;
558 /* We only need to subscribe to the root of the state subtrees. */
559 if (snode
->parent
&& CHECK_FLAG(snode
->parent
->flags
, LYS_CONFIG_R
))
560 return YANG_ITER_CONTINUE
;
562 nb_node
= snode
->priv
;
564 return YANG_ITER_CONTINUE
;
566 DEBUGD(&nb_dbg_client_sysrepo
, "sysrepo: providing data to '%s'",
569 ret
= sr_oper_get_subscribe(session
, snode
->module
->name
,
570 nb_node
->xpath
, frr_sr_state_cb
, NULL
, 0,
571 &module
->sr_subscription
);
572 if (ret
!= SR_ERR_OK
)
573 flog_err(EC_LIB_LIBSYSREPO
, "sr_oper_get_items_subscribe(): %s",
576 return YANG_ITER_CONTINUE
;
579 static int frr_sr_subscribe_rpc(const struct lysc_node
*snode
, void *arg
)
581 struct yang_module
*module
= arg
;
582 struct nb_node
*nb_node
;
585 if (snode
->nodetype
!= LYS_RPC
)
586 return YANG_ITER_CONTINUE
;
588 nb_node
= snode
->priv
;
590 return YANG_ITER_CONTINUE
;
592 DEBUGD(&nb_dbg_client_sysrepo
, "sysrepo: providing RPC to '%s'",
595 ret
= sr_rpc_subscribe(session
, nb_node
->xpath
, frr_sr_config_rpc_cb
,
596 NULL
, 0, 0, &module
->sr_subscription
);
597 if (ret
!= SR_ERR_OK
)
598 flog_err(EC_LIB_LIBSYSREPO
, "sr_rpc_subscribe(): %s",
601 return YANG_ITER_CONTINUE
;
607 "[no] debug northbound client sysrepo",
610 "Northbound debugging\n"
611 "Northbound client\n"
614 uint32_t mode
= DEBUG_NODE2MODE(vty
->node
);
615 bool no
= strmatch(argv
[0]->text
, "no");
617 DEBUG_MODE_SET(&nb_dbg_client_sysrepo
, mode
, !no
);
622 static int frr_sr_debug_config_write(struct vty
*vty
)
624 if (DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo
, DEBUG_MODE_CONF
))
625 vty_out(vty
, "debug northbound client sysrepo\n");
630 static int frr_sr_debug_set_all(uint32_t flags
, bool set
)
632 DEBUG_FLAGS_SET(&nb_dbg_client_sysrepo
, flags
, set
);
634 /* If all modes have been turned off, don't preserve options. */
635 if (!DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo
, DEBUG_MODE_ALL
))
636 DEBUG_CLEAR(&nb_dbg_client_sysrepo
);
641 static void frr_sr_cli_init(void)
643 hook_register(nb_client_debug_config_write
, frr_sr_debug_config_write
);
644 hook_register(nb_client_debug_set_all
, frr_sr_debug_set_all
);
646 install_element(ENABLE_NODE
, &debug_nb_sr_cmd
);
647 install_element(CONFIG_NODE
, &debug_nb_sr_cmd
);
650 /* FRR's Sysrepo initialization. */
651 static int frr_sr_init(void)
653 struct yang_module
*module
;
656 /* Connect to Sysrepo. */
657 ret
= sr_connect(SR_CONN_DEFAULT
, &connection
);
658 if (ret
!= SR_ERR_OK
) {
659 flog_err(EC_LIB_SYSREPO_INIT
, "%s: sr_connect(): %s", __func__
,
665 ret
= sr_session_start(connection
, SR_DS_RUNNING
, &session
);
666 if (ret
!= SR_ERR_OK
) {
667 flog_err(EC_LIB_SYSREPO_INIT
, "%s: sr_session_start(): %s",
668 __func__
, sr_strerror(ret
));
672 /* Perform subscriptions. */
673 RB_FOREACH (module
, yang_modules
, &yang_modules
) {
676 frr_sr_subscribe_config(module
);
677 yang_snodes_iterate(module
->info
, frr_sr_subscribe_state
, 0,
679 yang_snodes_iterate(module
->info
, frr_sr_subscribe_rpc
, 0,
682 /* Watch subscriptions. */
683 ret
= sr_get_event_pipe(module
->sr_subscription
, &event_pipe
);
684 if (ret
!= SR_ERR_OK
) {
685 flog_err(EC_LIB_SYSREPO_INIT
,
686 "%s: sr_get_event_pipe(): %s", __func__
,
690 thread_add_read(master
, frr_sr_read_cb
, module
,
691 event_pipe
, &module
->sr_thread
);
694 hook_register(nb_notification_send
, frr_sr_notification_send
);
704 static int frr_sr_finish(void)
706 struct yang_module
*module
;
708 RB_FOREACH (module
, yang_modules
, &yang_modules
) {
709 if (!module
->sr_subscription
)
711 sr_unsubscribe(module
->sr_subscription
);
712 THREAD_OFF(module
->sr_thread
);
716 sr_session_stop(session
);
718 sr_disconnect(connection
);
723 static int frr_sr_module_config_loaded(struct thread_master
*tm
)
727 if (frr_sr_init() < 0) {
728 flog_err(EC_LIB_SYSREPO_INIT
,
729 "failed to initialize the Sysrepo module");
733 hook_register(frr_fini
, frr_sr_finish
);
738 static int frr_sr_module_late_init(struct thread_master
*tm
)
745 static int frr_sr_module_init(void)
747 hook_register(frr_late_init
, frr_sr_module_late_init
);
748 hook_register(frr_config_post
, frr_sr_module_config_loaded
);
753 FRR_MODULE_SETUP(.name
= "frr_sysrepo", .version
= FRR_VERSION
,
754 .description
= "FRR sysrepo integration module",
755 .init
= frr_sr_module_init
,