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 event_loop
*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 event
*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
, false, false, errmsg
,
274 if (ret
!= NB_OK
&& ret
!= NB_ERR_NO_CHANGES
)
277 "%s: failed to prepare configuration transaction: %s (%s)",
278 __func__
, nb_err_name(ret
), errmsg
);
281 nb_config_free(candidate
);
283 /* Map northbound return code to sysrepo return code. */
287 case NB_ERR_NO_CHANGES
:
290 return SR_ERR_LOCKED
;
291 case NB_ERR_RESOURCE
:
292 return SR_ERR_NO_MEMORY
;
294 return SR_ERR_VALIDATION_FAILED
;
298 static int frr_sr_config_change_cb_apply(sr_session_ctx_t
*session
,
299 const char *module_name
)
301 /* Apply the transaction. */
303 struct nb_config
*candidate
= transaction
->config
;
304 char errmsg
[BUFSIZ
] = {0};
306 nb_candidate_commit_apply(transaction
, true, NULL
, errmsg
,
308 nb_config_free(candidate
);
314 static int frr_sr_config_change_cb_abort(sr_session_ctx_t
*session
,
315 const char *module_name
)
317 /* Abort the transaction. */
319 struct nb_config
*candidate
= transaction
->config
;
320 char errmsg
[BUFSIZ
] = {0};
322 nb_candidate_commit_abort(transaction
, errmsg
, sizeof(errmsg
));
323 nb_config_free(candidate
);
329 /* Callback for changes in the running configuration. */
330 static int frr_sr_config_change_cb(sr_session_ctx_t
*session
, uint32_t sub_id
,
331 const char *module_name
, const char *xpath
,
332 sr_event_t sr_ev
, uint32_t request_id
,
338 return frr_sr_config_change_cb_prepare(session
, module_name
);
340 return frr_sr_config_change_cb_apply(session
, module_name
);
342 return frr_sr_config_change_cb_abort(session
, module_name
);
344 flog_err(EC_LIB_LIBSYSREPO
, "%s: unexpected sysrepo event: %u",
346 return SR_ERR_INTERNAL
;
350 static int frr_sr_state_data_iter_cb(const struct lysc_node
*snode
,
351 struct yang_translator
*translator
,
352 struct yang_data
*data
, void *arg
)
354 struct lyd_node
*dnode
= arg
;
358 ly_errno
= lyd_new_path(NULL
, ly_native_ctx
, data
->xpath
, data
->value
,
360 if (!dnode
&& ly_errno
) {
361 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
363 yang_data_free(data
);
367 yang_data_free(data
);
371 /* Callback for state retrieval. */
372 static int frr_sr_state_cb(sr_session_ctx_t
*session
, uint32_t sub_id
,
373 const char *module_name
, const char *xpath
,
374 const char *request_xpath
, uint32_t request_id
,
375 struct lyd_node
**parent
, void *private_ctx
)
377 struct lyd_node
*dnode
;
380 if (nb_oper_data_iterate(request_xpath
, NULL
, 0,
381 frr_sr_state_data_iter_cb
, dnode
)
383 flog_warn(EC_LIB_NB_OPERATIONAL_DATA
,
384 "%s: failed to obtain operational data [xpath %s]",
386 return SR_ERR_INTERNAL
;
393 static int frr_sr_config_rpc_cb(sr_session_ctx_t
*session
, uint32_t sub_id
,
394 const char *xpath
, const sr_val_t
*sr_input
,
395 const size_t input_cnt
, sr_event_t sr_ev
,
396 uint32_t request_id
, sr_val_t
**sr_output
,
397 size_t *sr_output_cnt
, void *private_ctx
)
399 struct nb_node
*nb_node
;
402 struct yang_data
*data
;
403 size_t cb_output_cnt
;
405 char errmsg
[BUFSIZ
] = {0};
407 nb_node
= nb_node_find(xpath
);
409 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
410 "%s: unknown data path: %s", __func__
, xpath
);
411 return SR_ERR_INTERNAL
;
414 input
= yang_data_list_new();
415 output
= yang_data_list_new();
418 for (size_t i
= 0; i
< input_cnt
; i
++) {
419 char value_str
[YANG_VALUE_MAXLEN
];
421 sr_val_to_buff(&sr_input
[i
], value_str
, sizeof(value_str
));
423 data
= yang_data_new(xpath
, value_str
);
424 listnode_add(input
, data
);
427 /* Execute callback registered for this XPath. */
428 if (nb_callback_rpc(nb_node
, xpath
, input
, output
, errmsg
,
431 flog_warn(EC_LIB_NB_CB_RPC
, "%s: rpc callback failed: %s",
433 ret
= SR_ERR_OPERATION_FAILED
;
437 /* Process output. */
438 if (listcount(output
) > 0) {
439 sr_val_t
*values
= NULL
;
440 struct listnode
*node
;
443 cb_output_cnt
= listcount(output
);
444 ret
= sr_new_values(cb_output_cnt
, &values
);
445 if (ret
!= SR_ERR_OK
) {
446 flog_err(EC_LIB_LIBSYSREPO
, "%s: sr_new_values(): %s",
447 __func__
, sr_strerror(ret
));
451 for (ALL_LIST_ELEMENTS_RO(output
, node
, data
)) {
452 if (yang_data_frr2sr(data
, &values
[i
++]) != 0) {
454 EC_LIB_SYSREPO_DATA_CONVERT
,
455 "%s: failed to convert data to Sysrepo format",
457 ret
= SR_ERR_INTERNAL
;
458 sr_free_values(values
, cb_output_cnt
);
464 *sr_output_cnt
= cb_output_cnt
;
468 /* Release memory. */
470 list_delete(&output
);
475 static int frr_sr_notification_send(const char *xpath
, struct list
*arguments
)
477 sr_val_t
*values
= NULL
;
478 size_t values_cnt
= 0;
481 if (arguments
&& listcount(arguments
) > 0) {
482 struct yang_data
*data
;
483 struct listnode
*node
;
486 values_cnt
= listcount(arguments
);
487 ret
= sr_new_values(values_cnt
, &values
);
488 if (ret
!= SR_ERR_OK
) {
489 flog_err(EC_LIB_LIBSYSREPO
, "%s: sr_new_values(): %s",
490 __func__
, sr_strerror(ret
));
494 for (ALL_LIST_ELEMENTS_RO(arguments
, node
, data
)) {
495 if (yang_data_frr2sr(data
, &values
[i
++]) != 0) {
497 EC_LIB_SYSREPO_DATA_CONVERT
,
498 "%s: failed to convert data to sysrepo format",
500 sr_free_values(values
, values_cnt
);
506 ret
= sr_notif_send(session
, xpath
, values
, values_cnt
, 0, 0);
507 if (ret
!= SR_ERR_OK
) {
508 flog_err(EC_LIB_LIBSYSREPO
,
509 "%s: sr_event_notif_send() failed for xpath %s",
517 static void frr_sr_read_cb(struct event
*thread
)
519 struct yang_module
*module
= EVENT_ARG(thread
);
520 int fd
= EVENT_FD(thread
);
523 ret
= sr_subscription_process_events(module
->sr_subscription
, session
,
525 if (ret
!= SR_ERR_OK
) {
526 flog_err(EC_LIB_LIBSYSREPO
, "%s: sr_fd_event_process(): %s",
527 __func__
, sr_strerror(ret
));
531 event_add_read(master
, frr_sr_read_cb
, module
, fd
, &module
->sr_thread
);
534 static void frr_sr_subscribe_config(struct yang_module
*module
)
538 DEBUGD(&nb_dbg_client_sysrepo
,
539 "sysrepo: subscribing for configuration changes made in the '%s' module",
542 ret
= sr_module_change_subscribe(
543 session
, module
->name
, NULL
, frr_sr_config_change_cb
, NULL
, 0,
544 SR_SUBSCR_DEFAULT
| SR_SUBSCR_ENABLED
| SR_SUBSCR_NO_THREAD
,
545 &module
->sr_subscription
);
546 if (ret
!= SR_ERR_OK
)
547 flog_err(EC_LIB_LIBSYSREPO
, "sr_module_change_subscribe(): %s",
551 static int frr_sr_subscribe_state(const struct lysc_node
*snode
, void *arg
)
553 struct yang_module
*module
= arg
;
554 struct nb_node
*nb_node
;
557 if (!CHECK_FLAG(snode
->flags
, LYS_CONFIG_R
))
558 return YANG_ITER_CONTINUE
;
559 /* We only need to subscribe to the root of the state subtrees. */
560 if (snode
->parent
&& CHECK_FLAG(snode
->parent
->flags
, LYS_CONFIG_R
))
561 return YANG_ITER_CONTINUE
;
563 nb_node
= snode
->priv
;
565 return YANG_ITER_CONTINUE
;
567 DEBUGD(&nb_dbg_client_sysrepo
, "sysrepo: providing data to '%s'",
570 ret
= sr_oper_get_subscribe(session
, snode
->module
->name
,
571 nb_node
->xpath
, frr_sr_state_cb
, NULL
, 0,
572 &module
->sr_subscription
);
573 if (ret
!= SR_ERR_OK
)
574 flog_err(EC_LIB_LIBSYSREPO
, "sr_oper_get_items_subscribe(): %s",
577 return YANG_ITER_CONTINUE
;
580 static int frr_sr_subscribe_rpc(const struct lysc_node
*snode
, void *arg
)
582 struct yang_module
*module
= arg
;
583 struct nb_node
*nb_node
;
586 if (snode
->nodetype
!= LYS_RPC
)
587 return YANG_ITER_CONTINUE
;
589 nb_node
= snode
->priv
;
591 return YANG_ITER_CONTINUE
;
593 DEBUGD(&nb_dbg_client_sysrepo
, "sysrepo: providing RPC to '%s'",
596 ret
= sr_rpc_subscribe(session
, nb_node
->xpath
, frr_sr_config_rpc_cb
,
597 NULL
, 0, 0, &module
->sr_subscription
);
598 if (ret
!= SR_ERR_OK
)
599 flog_err(EC_LIB_LIBSYSREPO
, "sr_rpc_subscribe(): %s",
602 return YANG_ITER_CONTINUE
;
608 "[no] debug northbound client sysrepo",
611 "Northbound debugging\n"
612 "Northbound client\n"
615 uint32_t mode
= DEBUG_NODE2MODE(vty
->node
);
616 bool no
= strmatch(argv
[0]->text
, "no");
618 DEBUG_MODE_SET(&nb_dbg_client_sysrepo
, mode
, !no
);
623 static int frr_sr_debug_config_write(struct vty
*vty
)
625 if (DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo
, DEBUG_MODE_CONF
))
626 vty_out(vty
, "debug northbound client sysrepo\n");
631 static int frr_sr_debug_set_all(uint32_t flags
, bool set
)
633 DEBUG_FLAGS_SET(&nb_dbg_client_sysrepo
, flags
, set
);
635 /* If all modes have been turned off, don't preserve options. */
636 if (!DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo
, DEBUG_MODE_ALL
))
637 DEBUG_CLEAR(&nb_dbg_client_sysrepo
);
642 static void frr_sr_cli_init(void)
644 hook_register(nb_client_debug_config_write
, frr_sr_debug_config_write
);
645 hook_register(nb_client_debug_set_all
, frr_sr_debug_set_all
);
647 install_element(ENABLE_NODE
, &debug_nb_sr_cmd
);
648 install_element(CONFIG_NODE
, &debug_nb_sr_cmd
);
651 /* FRR's Sysrepo initialization. */
652 static int frr_sr_init(void)
654 struct yang_module
*module
;
657 /* Connect to Sysrepo. */
658 ret
= sr_connect(SR_CONN_DEFAULT
, &connection
);
659 if (ret
!= SR_ERR_OK
) {
660 flog_err(EC_LIB_SYSREPO_INIT
, "%s: sr_connect(): %s", __func__
,
666 ret
= sr_session_start(connection
, SR_DS_RUNNING
, &session
);
667 if (ret
!= SR_ERR_OK
) {
668 flog_err(EC_LIB_SYSREPO_INIT
, "%s: sr_session_start(): %s",
669 __func__
, sr_strerror(ret
));
673 /* Perform subscriptions. */
674 RB_FOREACH (module
, yang_modules
, &yang_modules
) {
677 frr_sr_subscribe_config(module
);
678 yang_snodes_iterate(module
->info
, frr_sr_subscribe_state
, 0,
680 yang_snodes_iterate(module
->info
, frr_sr_subscribe_rpc
, 0,
683 /* Watch subscriptions. */
684 ret
= sr_get_event_pipe(module
->sr_subscription
, &event_pipe
);
685 if (ret
!= SR_ERR_OK
) {
686 flog_err(EC_LIB_SYSREPO_INIT
,
687 "%s: sr_get_event_pipe(): %s", __func__
,
691 event_add_read(master
, frr_sr_read_cb
, module
, event_pipe
,
695 hook_register(nb_notification_send
, frr_sr_notification_send
);
705 static int frr_sr_finish(void)
707 struct yang_module
*module
;
709 RB_FOREACH (module
, yang_modules
, &yang_modules
) {
710 if (!module
->sr_subscription
)
712 sr_unsubscribe(module
->sr_subscription
);
713 EVENT_OFF(module
->sr_thread
);
717 sr_session_stop(session
);
719 sr_disconnect(connection
);
724 static int frr_sr_module_config_loaded(struct event_loop
*tm
)
728 if (frr_sr_init() < 0) {
729 flog_err(EC_LIB_SYSREPO_INIT
,
730 "failed to initialize the Sysrepo module");
734 hook_register(frr_fini
, frr_sr_finish
);
739 static int frr_sr_module_late_init(struct event_loop
*tm
)
746 static int frr_sr_module_init(void)
748 hook_register(frr_late_init
, frr_sr_module_late_init
);
749 hook_register(frr_config_post
, frr_sr_module_config_loaded
);
754 FRR_MODULE_SETUP(.name
= "frr_sysrepo", .version
= FRR_VERSION
,
755 .description
= "FRR sysrepo integration module",
756 .init
= frr_sr_module_init
,