2 * Copyright (C) 2018 NetDEF, Inc.
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 #include "lib_errors.h"
28 #include "termtable.h"
31 #include "yang_translator.h"
32 #include "northbound.h"
33 #include "northbound_cli.h"
34 #include "northbound_db.h"
35 #ifndef VTYSH_EXTRACT_PL
36 #include "lib/northbound_cli_clippy.c"
39 struct debug nb_dbg_cbs_config
= {0, "Northbound callbacks: configuration"};
40 struct debug nb_dbg_cbs_state
= {0, "Northbound callbacks: state"};
41 struct debug nb_dbg_cbs_rpc
= {0, "Northbound callbacks: RPCs"};
42 struct debug nb_dbg_notif
= {0, "Northbound notifications"};
43 struct debug nb_dbg_events
= {0, "Northbound events"};
44 struct debug nb_dbg_libyang
= {0, "libyang debugging"};
46 struct nb_config
*vty_shared_candidate_config
;
47 static struct thread_master
*master
;
49 static void vty_show_libyang_errors(struct vty
*vty
, struct ly_ctx
*ly_ctx
)
51 struct ly_err_item
*ei
;
54 ei
= ly_err_first(ly_ctx
);
58 for (; ei
; ei
= ei
->next
)
59 vty_out(vty
, "%s\n", ei
->msg
);
61 path
= ly_errpath(ly_ctx
);
63 vty_out(vty
, "YANG path: %s\n", path
);
65 ly_err_clean(ly_ctx
, NULL
);
68 void nb_cli_enqueue_change(struct vty
*vty
, const char *xpath
,
69 enum nb_operation operation
, const char *value
)
71 struct vty_cfg_change
*change
;
73 if (vty
->num_cfg_changes
== VTY_MAXCFGCHANGES
) {
74 /* Not expected to happen. */
76 "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
81 change
= &vty
->cfg_changes
[vty
->num_cfg_changes
++];
82 strlcpy(change
->xpath
, xpath
, sizeof(change
->xpath
));
83 change
->operation
= operation
;
84 change
->value
= value
;
87 int nb_cli_apply_changes(struct vty
*vty
, const char *xpath_base_fmt
, ...)
89 char xpath_base
[XPATH_MAXLEN
] = {};
95 /* Parse the base XPath format string. */
99 va_start(ap
, xpath_base_fmt
);
100 vsnprintf(xpath_base
, sizeof(xpath_base
), xpath_base_fmt
, ap
);
104 /* Edit candidate configuration. */
105 for (size_t i
= 0; i
< vty
->num_cfg_changes
; i
++) {
106 struct vty_cfg_change
*change
= &vty
->cfg_changes
[i
];
107 struct nb_node
*nb_node
;
108 char xpath
[XPATH_MAXLEN
];
109 struct yang_data
*data
;
111 /* Handle relative XPaths. */
112 memset(xpath
, 0, sizeof(xpath
));
113 if (vty
->xpath_index
> 0
114 && ((xpath_base_fmt
&& xpath_base
[0] == '.')
115 || change
->xpath
[0] == '.'))
116 strlcpy(xpath
, VTY_CURR_XPATH
, sizeof(xpath
));
117 if (xpath_base_fmt
) {
118 if (xpath_base
[0] == '.')
119 strlcat(xpath
, xpath_base
+ 1, sizeof(xpath
));
121 strlcat(xpath
, xpath_base
, sizeof(xpath
));
123 if (change
->xpath
[0] == '.')
124 strlcat(xpath
, change
->xpath
+ 1, sizeof(xpath
));
126 strlcpy(xpath
, change
->xpath
, sizeof(xpath
));
128 /* Find the northbound node associated to the data path. */
129 nb_node
= nb_node_find(xpath
);
131 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
132 "%s: unknown data path: %s", __func__
, xpath
);
137 /* If the value is not set, get the default if it exists. */
138 if (change
->value
== NULL
)
139 change
->value
= yang_snode_get_default(nb_node
->snode
);
140 data
= yang_data_new(xpath
, change
->value
);
143 * Ignore "not found" errors when editing the candidate
146 ret
= nb_candidate_edit(vty
->candidate_config
, nb_node
,
147 change
->operation
, xpath
, NULL
, data
);
148 yang_data_free(data
);
149 if (ret
!= NB_OK
&& ret
!= NB_ERR_NOT_FOUND
) {
151 EC_LIB_NB_CANDIDATE_EDIT_ERROR
,
152 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
153 __func__
, nb_operation_name(change
->operation
),
162 * Failure to edit the candidate configuration should never
163 * happen in practice, unless there's a bug in the code. When
164 * that happens, log the error but otherwise ignore it.
166 vty_out(vty
, "%% Failed to edit configuration.\n\n");
167 vty_show_libyang_errors(vty
, ly_native_ctx
);
170 /* Do an implicit "commit" when using the classic CLI mode. */
171 if (frr_get_cli_mode() == FRR_CLI_CLASSIC
) {
172 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
,
173 vty
, false, NULL
, NULL
);
174 if (ret
!= NB_OK
&& ret
!= NB_ERR_NO_CHANGES
) {
175 vty_out(vty
, "%% Configuration failed: %s.\n\n",
178 "Please check the logs for more details.\n");
180 /* Regenerate candidate for consistency. */
181 nb_config_replace(vty
->candidate_config
, running_config
,
183 return CMD_WARNING_CONFIG_FAILED
;
190 int nb_cli_rpc(const char *xpath
, struct list
*input
, struct list
*output
)
192 struct nb_node
*nb_node
;
195 nb_node
= nb_node_find(xpath
);
197 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
198 "%s: unknown data path: %s", __func__
, xpath
);
202 ret
= nb_callback_rpc(nb_node
, xpath
, input
, output
);
211 void nb_cli_confirmed_commit_clean(struct vty
*vty
)
213 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
214 nb_config_free(vty
->confirmed_commit_rollback
);
215 vty
->confirmed_commit_rollback
= NULL
;
218 int nb_cli_confirmed_commit_rollback(struct vty
*vty
)
220 uint32_t transaction_id
;
223 /* Perform the rollback. */
224 ret
= nb_candidate_commit(
225 vty
->confirmed_commit_rollback
, NB_CLIENT_CLI
, vty
, true,
226 "Rollback to previous configuration - confirmed commit has timed out",
230 "Rollback performed successfully (Transaction ID #%u).\n",
233 vty_out(vty
, "Failed to rollback to previous configuration.\n");
238 static int nb_cli_confirmed_commit_timeout(struct thread
*thread
)
240 struct vty
*vty
= THREAD_ARG(thread
);
242 /* XXX: broadcast this message to all logged-in users? */
244 "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
246 nb_cli_confirmed_commit_rollback(vty
);
247 nb_cli_confirmed_commit_clean(vty
);
252 static int nb_cli_commit(struct vty
*vty
, bool force
,
253 unsigned int confirmed_timeout
, char *comment
)
255 uint32_t transaction_id
= 0;
258 /* Check if there's a pending confirmed commit. */
259 if (vty
->t_confirmed_commit_timeout
) {
260 if (confirmed_timeout
) {
261 /* Reset timeout if "commit confirmed" is used again. */
263 "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
266 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
267 thread_add_timer(master
,
268 nb_cli_confirmed_commit_timeout
, vty
,
269 confirmed_timeout
* 60,
270 &vty
->t_confirmed_commit_timeout
);
272 /* Accept commit confirmation. */
273 vty_out(vty
, "%% Commit complete.\n\n");
274 nb_cli_confirmed_commit_clean(vty
);
279 /* "force" parameter. */
280 if (!force
&& nb_candidate_needs_update(vty
->candidate_config
)) {
282 "%% Candidate configuration needs to be updated before commit.\n\n");
284 "Use the \"update\" command or \"commit force\".\n");
288 /* "confirm" parameter. */
289 if (confirmed_timeout
) {
290 vty
->confirmed_commit_rollback
= nb_config_dup(running_config
);
292 vty
->t_confirmed_commit_timeout
= NULL
;
293 thread_add_timer(master
, nb_cli_confirmed_commit_timeout
, vty
,
294 confirmed_timeout
* 60,
295 &vty
->t_confirmed_commit_timeout
);
298 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
, vty
,
299 true, comment
, &transaction_id
);
301 /* Map northbound return code to CLI return code. */
304 nb_config_replace(vty
->candidate_config_base
, running_config
,
307 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
310 case NB_ERR_NO_CHANGES
:
311 vty_out(vty
, "%% No configuration changes to commit.\n\n");
315 "%% Failed to commit candidate configuration: %s.\n\n",
317 vty_out(vty
, "Please check the logs for more details.\n");
322 static int nb_cli_candidate_load_file(struct vty
*vty
,
323 enum nb_cfg_format format
,
324 struct yang_translator
*translator
,
325 const char *path
, bool replace
)
327 struct nb_config
*loaded_config
= NULL
;
328 struct lyd_node
*dnode
;
329 struct ly_ctx
*ly_ctx
;
333 case NB_CFG_FMT_CMDS
:
334 loaded_config
= nb_config_new(NULL
);
335 if (!vty_read_config(loaded_config
, path
, config_default
)) {
336 vty_out(vty
, "%% Failed to load configuration.\n\n");
338 "Please check the logs for more details.\n");
339 nb_config_free(loaded_config
);
343 case NB_CFG_FMT_JSON
:
345 ly_format
= (format
== NB_CFG_FMT_JSON
) ? LYD_JSON
: LYD_XML
;
347 ly_ctx
= translator
? translator
->ly_ctx
: ly_native_ctx
;
348 dnode
= lyd_parse_path(ly_ctx
, path
, ly_format
, LYD_OPT_EDIT
);
350 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_parse_path() failed",
352 vty_out(vty
, "%% Failed to load configuration:\n\n");
353 vty_show_libyang_errors(vty
, ly_ctx
);
357 && yang_translate_dnode(translator
,
358 YANG_TRANSLATE_TO_NATIVE
, &dnode
)
359 != YANG_TRANSLATE_SUCCESS
) {
360 vty_out(vty
, "%% Failed to translate configuration\n");
361 yang_dnode_free(dnode
);
364 loaded_config
= nb_config_new(dnode
);
369 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
370 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
373 "%% Failed to merge the loaded configuration:\n\n");
374 vty_show_libyang_errors(vty
, ly_native_ctx
);
381 static int nb_cli_candidate_load_transaction(struct vty
*vty
,
382 uint32_t transaction_id
,
385 struct nb_config
*loaded_config
;
387 loaded_config
= nb_db_transaction_load(transaction_id
);
388 if (!loaded_config
) {
389 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
395 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
396 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
399 "%% Failed to merge the loaded configuration:\n\n");
400 vty_show_libyang_errors(vty
, ly_native_ctx
);
408 * ly_iter_next_is_up: detects when iterating up on the yang model.
410 * This function detects whether next node in the iteration is upwards,
411 * then return the node otherwise return NULL.
413 static struct lyd_node
*ly_iter_next_up(const struct lyd_node
*elem
)
415 /* Are we going downwards? Is this still not a leaf? */
416 if (!(elem
->schema
->nodetype
& (LYS_LEAF
| LYS_LEAFLIST
| LYS_ANYDATA
)))
419 /* Are there still leaves in this branch? */
420 if (elem
->next
!= NULL
)
426 /* Prepare the configuration for display. */
427 void nb_cli_show_config_prepare(struct nb_config
*config
, bool with_defaults
)
429 /* Nothing to do for daemons that don't implement any YANG module. */
430 if (config
->dnode
== NULL
)
433 lyd_schema_sort(config
->dnode
, 1);
436 * Call lyd_validate() only to create default child nodes, ignoring
437 * any possible validation error. This doesn't need to be done when
438 * displaying the running configuration since it's always fully
441 if (config
!= running_config
)
442 (void)lyd_validate(&config
->dnode
,
443 LYD_OPT_CONFIG
| LYD_OPT_WHENAUTODEL
,
447 void nb_cli_show_dnode_cmds(struct vty
*vty
, struct lyd_node
*root
,
450 struct lyd_node
*next
, *child
, *parent
;
452 LY_TREE_DFS_BEGIN (root
, next
, child
) {
453 struct nb_node
*nb_node
;
455 nb_node
= child
->schema
->priv
;
456 if (!nb_node
->cbs
.cli_show
)
459 /* Skip default values. */
460 if (!with_defaults
&& yang_dnode_is_default_recursive(child
))
463 (*nb_node
->cbs
.cli_show
)(vty
, child
, with_defaults
);
466 * When transiting upwards in the yang model we should
467 * give the previous container/list node a chance to
468 * print its close vty output (e.g. "!" or "end-family"
471 parent
= ly_iter_next_up(child
);
472 if (parent
!= NULL
) {
473 nb_node
= parent
->schema
->priv
;
474 if (nb_node
->cbs
.cli_show_end
)
475 (*nb_node
->cbs
.cli_show_end
)(vty
, parent
);
478 LY_TREE_DFS_END(root
, next
, child
);
482 static void nb_cli_show_config_cmds(struct vty
*vty
, struct nb_config
*config
,
485 struct lyd_node
*root
;
487 vty_out(vty
, "Configuration:\n");
489 vty_out(vty
, "frr version %s\n", FRR_VER_SHORT
);
490 vty_out(vty
, "frr defaults %s\n", frr_defaults_profile());
492 LY_TREE_FOR (config
->dnode
, root
)
493 nb_cli_show_dnode_cmds(vty
, root
, with_defaults
);
496 vty_out(vty
, "end\n");
499 static int nb_cli_show_config_libyang(struct vty
*vty
, LYD_FORMAT format
,
500 struct nb_config
*config
,
501 struct yang_translator
*translator
,
504 struct lyd_node
*dnode
;
508 dnode
= yang_dnode_dup(config
->dnode
);
510 && yang_translate_dnode(translator
, YANG_TRANSLATE_FROM_NATIVE
,
512 != YANG_TRANSLATE_SUCCESS
) {
513 vty_out(vty
, "%% Failed to translate configuration\n");
514 yang_dnode_free(dnode
);
518 SET_FLAG(options
, LYP_FORMAT
| LYP_WITHSIBLINGS
);
520 SET_FLAG(options
, LYP_WD_ALL
);
522 SET_FLAG(options
, LYP_WD_TRIM
);
524 if (lyd_print_mem(&strp
, dnode
, format
, options
) == 0 && strp
) {
525 vty_out(vty
, "%s", strp
);
529 yang_dnode_free(dnode
);
534 static int nb_cli_show_config(struct vty
*vty
, struct nb_config
*config
,
535 enum nb_cfg_format format
,
536 struct yang_translator
*translator
,
539 nb_cli_show_config_prepare(config
, with_defaults
);
542 case NB_CFG_FMT_CMDS
:
543 nb_cli_show_config_cmds(vty
, config
, with_defaults
);
545 case NB_CFG_FMT_JSON
:
546 return nb_cli_show_config_libyang(vty
, LYD_JSON
, config
,
547 translator
, with_defaults
);
549 return nb_cli_show_config_libyang(vty
, LYD_XML
, config
,
550 translator
, with_defaults
);
556 static int nb_write_config(struct nb_config
*config
, enum nb_cfg_format format
,
557 struct yang_translator
*translator
, char *path
,
561 struct vty
*file_vty
;
564 snprintf(path
, pathlen
, "/tmp/frr.tmp.XXXXXXXX");
567 flog_warn(EC_LIB_SYSTEM_CALL
, "%s: mkstemp() failed: %s",
568 __func__
, safe_strerror(errno
));
572 /* Make vty for configuration file. */
573 file_vty
= vty_new();
575 file_vty
->type
= VTY_FILE
;
577 ret
= nb_cli_show_config(file_vty
, config
, format
, translator
,
584 static int nb_cli_show_config_compare(struct vty
*vty
,
585 struct nb_config
*config1
,
586 struct nb_config
*config2
,
587 enum nb_cfg_format format
,
588 struct yang_translator
*translator
)
590 char config1_path
[256];
591 char config2_path
[256];
592 char command
[BUFSIZ
];
597 if (nb_write_config(config1
, format
, translator
, config1_path
,
598 sizeof(config1_path
))
600 vty_out(vty
, "%% Failed to process configurations.\n\n");
603 if (nb_write_config(config2
, format
, translator
, config2_path
,
604 sizeof(config2_path
))
606 vty_out(vty
, "%% Failed to process configurations.\n\n");
607 unlink(config1_path
);
611 snprintf(command
, sizeof(command
), "diff -u %s %s", config1_path
,
613 fp
= popen(command
, "r");
615 vty_out(vty
, "%% Failed to generate configuration diff.\n\n");
616 unlink(config1_path
);
617 unlink(config2_path
);
620 /* Print diff line by line. */
621 while (fgets(line
, sizeof(line
), fp
) != NULL
) {
624 vty_out(vty
, "%s", line
);
628 unlink(config1_path
);
629 unlink(config2_path
);
634 /* Configure exclusively from this terminal. */
635 DEFUN (config_exclusive
,
636 config_exclusive_cmd
,
637 "configure exclusive",
638 "Configuration from vty interface\n"
639 "Configure exclusively from this terminal\n")
641 return vty_config_enter(vty
, true, true);
644 /* Configure using a private candidate configuration. */
645 DEFUN (config_private
,
648 "Configuration from vty interface\n"
649 "Configure using a private candidate configuration\n")
651 return vty_config_enter(vty
, true, false);
654 DEFPY (config_commit
,
656 "commit [{force$force|confirmed (1-60)}]",
657 "Commit changes into the running configuration\n"
658 "Force commit even if the candidate is outdated\n"
659 "Rollback this commit unless there is a confirming commit\n"
660 "Timeout in minutes for the commit to be confirmed\n")
662 return nb_cli_commit(vty
, !!force
, confirmed
, NULL
);
665 DEFPY (config_commit_comment
,
666 config_commit_comment_cmd
,
667 "commit [{force$force|confirmed (1-60)}] comment LINE...",
668 "Commit changes into the running configuration\n"
669 "Force commit even if the candidate is outdated\n"
670 "Rollback this commit unless there is a confirming commit\n"
671 "Timeout in minutes for the commit to be confirmed\n"
672 "Assign a comment to this commit\n"
673 "Comment for this commit (Max 80 characters)\n")
679 argv_find(argv
, argc
, "LINE", &idx
);
680 comment
= argv_concat(argv
, argc
, idx
);
681 ret
= nb_cli_commit(vty
, !!force
, confirmed
, comment
);
682 XFREE(MTYPE_TMP
, comment
);
687 DEFPY (config_commit_check
,
688 config_commit_check_cmd
,
690 "Commit changes into the running configuration\n"
691 "Check if the configuration changes are valid\n")
695 ret
= nb_candidate_validate(vty
->candidate_config
);
698 "%% Failed to validate candidate configuration.\n\n");
699 vty_show_libyang_errors(vty
, ly_native_ctx
);
703 vty_out(vty
, "%% Candidate configuration validated successfully.\n\n");
708 DEFPY (config_update
,
711 "Update candidate configuration\n")
713 if (!nb_candidate_needs_update(vty
->candidate_config
)) {
714 vty_out(vty
, "%% Update is not necessary.\n\n");
718 if (nb_candidate_update(vty
->candidate_config
) != NB_OK
) {
720 "%% Failed to update the candidate configuration.\n\n");
721 vty_out(vty
, "Please check the logs for more details.\n");
725 nb_config_replace(vty
->candidate_config_base
, running_config
, true);
727 vty_out(vty
, "%% Candidate configuration updated successfully.\n\n");
732 DEFPY (config_discard
,
735 "Discard changes in the candidate configuration\n")
737 nb_config_replace(vty
->candidate_config
, vty
->candidate_config_base
,
747 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
748 |transaction (1-4294967295)$tid\
751 "Configuration related settings\n"
752 "Load configuration into candidate\n"
753 "Load configuration file into candidate\n"
754 "Load configuration file in JSON format\n"
755 "Load configuration file in XML format\n"
756 "Translate configuration file\n"
757 "YANG module translator\n"
758 "Configuration file name (full path)\n"
759 "Load configuration from transaction into candidate\n"
761 "Replace instead of merge\n")
764 enum nb_cfg_format format
;
765 struct yang_translator
*translator
= NULL
;
768 format
= NB_CFG_FMT_JSON
;
770 format
= NB_CFG_FMT_XML
;
772 format
= NB_CFG_FMT_CMDS
;
774 if (translator_family
) {
775 translator
= yang_translator_find(translator_family
);
778 "%% Module translator \"%s\" not found\n",
784 return nb_cli_candidate_load_file(vty
, format
, translator
,
785 filename
, !!replace
);
788 return nb_cli_candidate_load_transaction(vty
, tid
, !!replace
);
791 DEFPY (show_config_running
,
792 show_config_running_cmd
,
793 "show configuration running\
794 [<json$json|xml$xml> [translate WORD$translator_family]]\
795 [with-defaults$with_defaults]",
797 "Configuration information\n"
798 "Running configuration\n"
799 "Change output format to JSON\n"
800 "Change output format to XML\n"
802 "YANG module translator\n"
803 "Show default values\n")
806 enum nb_cfg_format format
;
807 struct yang_translator
*translator
= NULL
;
810 format
= NB_CFG_FMT_JSON
;
812 format
= NB_CFG_FMT_XML
;
814 format
= NB_CFG_FMT_CMDS
;
816 if (translator_family
) {
817 translator
= yang_translator_find(translator_family
);
819 vty_out(vty
, "%% Module translator \"%s\" not found\n",
825 nb_cli_show_config(vty
, running_config
, format
, translator
,
831 DEFPY (show_config_candidate
,
832 show_config_candidate_cmd
,
833 "show configuration candidate\
834 [<json$json|xml$xml> [translate WORD$translator_family]]\
836 with-defaults$with_defaults\
840 "Configuration information\n"
841 "Candidate configuration\n"
842 "Change output format to JSON\n"
843 "Change output format to XML\n"
845 "YANG module translator\n"
846 "Show default values\n"
847 "Show changes applied in the candidate configuration\n")
850 enum nb_cfg_format format
;
851 struct yang_translator
*translator
= NULL
;
854 format
= NB_CFG_FMT_JSON
;
856 format
= NB_CFG_FMT_XML
;
858 format
= NB_CFG_FMT_CMDS
;
860 if (translator_family
) {
861 translator
= yang_translator_find(translator_family
);
863 vty_out(vty
, "%% Module translator \"%s\" not found\n",
870 return nb_cli_show_config_compare(
871 vty
, vty
->candidate_config_base
, vty
->candidate_config
,
874 nb_cli_show_config(vty
, vty
->candidate_config
, format
, translator
,
880 DEFPY (show_config_candidate_section
,
881 show_config_candidate_section_cmd
,
885 struct lyd_node
*dnode
;
887 /* Top-level configuration node, display everything. */
888 if (vty
->xpath_index
== 0)
889 return nb_cli_show_config(vty
, vty
->candidate_config
,
890 NB_CFG_FMT_CMDS
, NULL
, false);
892 /* Display only the current section of the candidate configuration. */
893 dnode
= yang_dnode_get(vty
->candidate_config
->dnode
, VTY_CURR_XPATH
);
895 /* Shouldn't happen. */
898 nb_cli_show_dnode_cmds(vty
, dnode
, 0);
904 DEFPY (show_config_compare
,
905 show_config_compare_cmd
,
906 "show configuration compare\
908 candidate$c1_candidate\
910 |transaction (1-4294967295)$c1_tid\
913 candidate$c2_candidate\
915 |transaction (1-4294967295)$c2_tid\
917 [<json$json|xml$xml> [translate WORD$translator_family]]",
919 "Configuration information\n"
920 "Compare two different configurations\n"
921 "Candidate configuration\n"
922 "Running configuration\n"
923 "Configuration transaction\n"
925 "Candidate configuration\n"
926 "Running configuration\n"
927 "Configuration transaction\n"
929 "Change output format to JSON\n"
930 "Change output format to XML\n"
932 "YANG module translator\n")
934 enum nb_cfg_format format
;
935 struct yang_translator
*translator
= NULL
;
936 struct nb_config
*config1
, *config_transaction1
= NULL
;
937 struct nb_config
*config2
, *config_transaction2
= NULL
;
938 int ret
= CMD_WARNING
;
941 config1
= vty
->candidate_config
;
943 config1
= running_config
;
945 config_transaction1
= nb_db_transaction_load(c1_tid
);
946 if (!config_transaction1
) {
947 vty_out(vty
, "%% Transaction %u does not exist\n\n",
948 (unsigned int)c1_tid
);
951 config1
= config_transaction1
;
955 config2
= vty
->candidate_config
;
957 config2
= running_config
;
959 config_transaction2
= nb_db_transaction_load(c2_tid
);
960 if (!config_transaction2
) {
961 vty_out(vty
, "%% Transaction %u does not exist\n\n",
962 (unsigned int)c2_tid
);
965 config2
= config_transaction2
;
969 format
= NB_CFG_FMT_JSON
;
971 format
= NB_CFG_FMT_XML
;
973 format
= NB_CFG_FMT_CMDS
;
975 if (translator_family
) {
976 translator
= yang_translator_find(translator_family
);
978 vty_out(vty
, "%% Module translator \"%s\" not found\n",
984 ret
= nb_cli_show_config_compare(vty
, config1
, config2
, format
,
987 if (config_transaction1
)
988 nb_config_free(config_transaction1
);
989 if (config_transaction2
)
990 nb_config_free(config_transaction2
);
996 * Stripped down version of the "show configuration compare" command.
997 * The "candidate" option is not present so the command can be installed in
1000 ALIAS (show_config_compare
,
1001 show_config_compare_without_candidate_cmd
,
1002 "show configuration compare\
1005 |transaction (1-4294967295)$c1_tid\
1009 |transaction (1-4294967295)$c2_tid\
1011 [<json$json|xml$xml> [translate WORD$translator_family]]",
1013 "Configuration information\n"
1014 "Compare two different configurations\n"
1015 "Running configuration\n"
1016 "Configuration transaction\n"
1018 "Running configuration\n"
1019 "Configuration transaction\n"
1021 "Change output format to JSON\n"
1022 "Change output format to XML\n"
1023 "Translate output\n"
1024 "YANG module translator\n")
1026 DEFPY (clear_config_transactions
,
1027 clear_config_transactions_cmd
,
1028 "clear configuration transactions oldest (1-100)$n",
1030 "Configuration activity\n"
1031 "Delete transactions from the transactions log\n"
1032 "Delete oldest <n> transactions\n"
1033 "Number of transactions to delete\n")
1035 #ifdef HAVE_CONFIG_ROLLBACKS
1036 if (nb_db_clear_transactions(n
) != NB_OK
) {
1037 vty_out(vty
, "%% Failed to delete transactions.\n\n");
1042 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1043 #endif /* HAVE_CONFIG_ROLLBACKS */
1048 DEFPY (config_database_max_transactions
,
1049 config_database_max_transactions_cmd
,
1050 "configuration database max-transactions (1-100)$max",
1051 "Configuration related settings\n"
1052 "Configuration database\n"
1053 "Set the maximum number of transactions to store\n"
1054 "Number of transactions\n")
1056 #ifdef HAVE_CONFIG_ROLLBACKS
1057 if (nb_db_set_max_transactions(max
) != NB_OK
) {
1059 "%% Failed to update the maximum number of transactions.\n\n");
1063 "%% Maximum number of transactions updated successfully.\n\n");
1066 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1067 #endif /* HAVE_CONFIG_ROLLBACKS */
1072 DEFPY (yang_module_translator_load
,
1073 yang_module_translator_load_cmd
,
1074 "yang module-translator load FILENAME$filename",
1075 "YANG related settings\n"
1076 "YANG module translator\n"
1077 "Load YANG module translator\n"
1078 "File name (full path)\n")
1080 struct yang_translator
*translator
;
1082 translator
= yang_translator_load(filename
);
1084 vty_out(vty
, "%% Failed to load \"%s\"\n\n", filename
);
1085 vty_out(vty
, "Please check the logs for more details.\n");
1089 vty_out(vty
, "%% Module translator \"%s\" loaded successfully.\n\n",
1090 translator
->family
);
1095 DEFPY (yang_module_translator_unload_family
,
1096 yang_module_translator_unload_cmd
,
1097 "yang module-translator unload WORD$translator_family",
1098 "YANG related settings\n"
1099 "YANG module translator\n"
1100 "Unload YANG module translator\n"
1101 "Name of the module translator\n")
1103 struct yang_translator
*translator
;
1105 translator
= yang_translator_find(translator_family
);
1107 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1112 yang_translator_unload(translator
);
1117 #ifdef HAVE_CONFIG_ROLLBACKS
1118 static void nb_cli_show_transactions_cb(void *arg
, int transaction_id
,
1119 const char *client_name
,
1120 const char *date
, const char *comment
)
1122 struct ttable
*tt
= arg
;
1124 ttable_add_row(tt
, "%d|%s|%s|%s", transaction_id
, client_name
, date
,
1128 static int nb_cli_show_transactions(struct vty
*vty
)
1132 /* Prepare table. */
1133 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1134 ttable_add_row(tt
, "Transaction ID|Client|Date|Comment");
1135 tt
->style
.cell
.rpad
= 2;
1136 tt
->style
.corner
= '+';
1138 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1140 /* Fetch transactions from the northbound database. */
1141 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb
, tt
)
1144 "%% Failed to fetch configuration transactions.\n");
1148 /* Dump the generated table. */
1149 if (tt
->nrows
> 1) {
1152 table
= ttable_dump(tt
, "\n");
1153 vty_out(vty
, "%s\n", table
);
1154 XFREE(MTYPE_TMP
, table
);
1156 vty_out(vty
, "No configuration transactions to display.\n\n");
1162 #endif /* HAVE_CONFIG_ROLLBACKS */
1164 DEFPY (show_config_transaction
,
1165 show_config_transaction_cmd
,
1166 "show configuration transaction\
1168 (1-4294967295)$transaction_id\
1169 [<json$json|xml$xml> [translate WORD$translator_family]]\
1171 with-defaults$with_defaults\
1176 "Configuration information\n"
1177 "Configuration transaction\n"
1179 "Change output format to JSON\n"
1180 "Change output format to XML\n"
1181 "Translate output\n"
1182 "YANG module translator\n"
1183 "Show default values\n"
1184 "Show changes compared to the previous transaction\n")
1186 #ifdef HAVE_CONFIG_ROLLBACKS
1187 if (transaction_id
) {
1188 struct nb_config
*config
;
1189 enum nb_cfg_format format
;
1190 struct yang_translator
*translator
= NULL
;
1193 format
= NB_CFG_FMT_JSON
;
1195 format
= NB_CFG_FMT_XML
;
1197 format
= NB_CFG_FMT_CMDS
;
1199 if (translator_family
) {
1200 translator
= yang_translator_find(translator_family
);
1203 "%% Module translator \"%s\" not found\n",
1209 config
= nb_db_transaction_load(transaction_id
);
1211 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1212 (unsigned int)transaction_id
);
1217 struct nb_config
*prev_config
;
1220 /* NOTE: this can be NULL. */
1222 nb_db_transaction_load(transaction_id
- 1);
1224 ret
= nb_cli_show_config_compare(
1225 vty
, prev_config
, config
, format
, translator
);
1227 nb_config_free(prev_config
);
1228 nb_config_free(config
);
1233 nb_cli_show_config(vty
, config
, format
, translator
,
1235 nb_config_free(config
);
1240 return nb_cli_show_transactions(vty
);
1243 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1245 #endif /* HAVE_CONFIG_ROLLBACKS */
1248 static int nb_cli_oper_data_cb(const struct lys_node
*snode
,
1249 struct yang_translator
*translator
,
1250 struct yang_data
*data
, void *arg
)
1252 struct lyd_node
*dnode
= arg
;
1253 struct ly_ctx
*ly_ctx
;
1258 ret
= yang_translate_xpath(translator
,
1259 YANG_TRANSLATE_FROM_NATIVE
,
1260 data
->xpath
, sizeof(data
->xpath
));
1262 case YANG_TRANSLATE_SUCCESS
:
1264 case YANG_TRANSLATE_NOTFOUND
:
1266 case YANG_TRANSLATE_FAILURE
:
1270 ly_ctx
= translator
->ly_ctx
;
1272 ly_ctx
= ly_native_ctx
;
1275 dnode
= lyd_new_path(dnode
, ly_ctx
, data
->xpath
, (void *)data
->value
, 0,
1276 LYD_PATH_OPT_UPDATE
);
1277 if (!dnode
&& ly_errno
) {
1278 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
1284 yang_data_free(data
);
1288 yang_data_free(data
);
1292 DEFPY (show_yang_operational_data
,
1293 show_yang_operational_data_cmd
,
1294 "show yang operational-data XPATH$xpath\
1296 format <json$json|xml$xml>\
1297 |translate WORD$translator_family\
1300 "YANG information\n"
1301 "Show YANG operational data\n"
1302 "XPath expression specifying the YANG data path\n"
1303 "Set the output format\n"
1304 "JavaScript Object Notation\n"
1305 "Extensible Markup Language\n"
1306 "Translate operational data\n"
1307 "YANG module translator\n")
1310 struct yang_translator
*translator
= NULL
;
1311 struct ly_ctx
*ly_ctx
;
1312 struct lyd_node
*dnode
;
1320 if (translator_family
) {
1321 translator
= yang_translator_find(translator_family
);
1323 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1328 ly_ctx
= translator
->ly_ctx
;
1330 ly_ctx
= ly_native_ctx
;
1333 dnode
= yang_dnode_new(ly_ctx
, false);
1334 if (nb_oper_data_iterate(xpath
, translator
, 0, nb_cli_oper_data_cb
,
1337 vty_out(vty
, "%% Failed to fetch operational data.\n");
1338 yang_dnode_free(dnode
);
1341 lyd_validate(&dnode
, LYD_OPT_GET
, ly_ctx
);
1343 /* Display the data. */
1344 if (lyd_print_mem(&strp
, dnode
, format
,
1345 LYP_FORMAT
| LYP_WITHSIBLINGS
| LYP_WD_ALL
)
1348 vty_out(vty
, "%% Failed to display operational data.\n");
1349 yang_dnode_free(dnode
);
1352 vty_out(vty
, "%s", strp
);
1354 yang_dnode_free(dnode
);
1359 DEFPY (show_yang_module
,
1360 show_yang_module_cmd
,
1361 "show yang module [module-translator WORD$translator_family]",
1363 "YANG information\n"
1364 "Show loaded modules\n"
1365 "YANG module translator\n"
1366 "YANG module translator\n")
1368 struct ly_ctx
*ly_ctx
;
1369 struct yang_translator
*translator
= NULL
;
1370 const struct lys_module
*module
;
1374 if (translator_family
) {
1375 translator
= yang_translator_find(translator_family
);
1377 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1381 ly_ctx
= translator
->ly_ctx
;
1383 ly_ctx
= ly_native_ctx
;
1385 /* Prepare table. */
1386 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1387 ttable_add_row(tt
, "Module|Version|Revision|Flags|Namespace");
1388 tt
->style
.cell
.rpad
= 2;
1389 tt
->style
.corner
= '+';
1391 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1393 while ((module
= ly_ctx_get_module_iter(ly_ctx
, &idx
))) {
1396 snprintf(flags
, sizeof(flags
), "%c%c",
1397 module
->implemented
? 'I' : ' ',
1398 (module
->deviated
== 1) ? 'D' : ' ');
1400 ttable_add_row(tt
, "%s|%s|%s|%s|%s", module
->name
,
1401 (module
->version
== 2) ? "1.1" : "1.0",
1402 (module
->rev_size
> 0) ? module
->rev
[0].date
1407 /* Dump the generated table. */
1408 if (tt
->nrows
> 1) {
1411 vty_out(vty
, " Flags: I - Implemented, D - Deviated\n\n");
1413 table
= ttable_dump(tt
, "\n");
1414 vty_out(vty
, "%s\n", table
);
1415 XFREE(MTYPE_TMP
, table
);
1417 vty_out(vty
, "No YANG modules to display.\n\n");
1424 DEFPY (show_yang_module_detail
,
1425 show_yang_module_detail_cmd
,
1427 [module-translator WORD$translator_family]\
1428 WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1430 "YANG information\n"
1431 "Show loaded modules\n"
1432 "YANG module translator\n"
1433 "YANG module translator\n"
1435 "Display summary information about the module\n"
1436 "Display module in the tree (RFC 8340) format\n"
1437 "Display module in the YANG format\n"
1438 "Display module in the YIN format\n")
1440 struct ly_ctx
*ly_ctx
;
1441 struct yang_translator
*translator
= NULL
;
1442 const struct lys_module
*module
;
1443 LYS_OUTFORMAT format
;
1446 if (translator_family
) {
1447 translator
= yang_translator_find(translator_family
);
1449 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1453 ly_ctx
= translator
->ly_ctx
;
1455 ly_ctx
= ly_native_ctx
;
1457 module
= ly_ctx_get_module(ly_ctx
, module_name
, NULL
, 0);
1459 vty_out(vty
, "%% Module \"%s\" not found\n", module_name
);
1464 format
= LYS_OUT_YANG
;
1466 format
= LYS_OUT_YIN
;
1468 format
= LYS_OUT_TREE
;
1470 format
= LYS_OUT_INFO
;
1472 if (lys_print_mem(&strp
, module
, format
, NULL
, 0, 0) == 0) {
1473 vty_out(vty
, "%s\n", strp
);
1477 vty_out(vty
, "%% Error generating module information\n");
1484 DEFPY (show_yang_module_translator
,
1485 show_yang_module_translator_cmd
,
1486 "show yang module-translator",
1488 "YANG information\n"
1489 "Show loaded YANG module translators\n")
1491 struct yang_translator
*translator
;
1494 /* Prepare table. */
1495 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1496 ttable_add_row(tt
, "Family|Module|Deviations|Coverage (%%)");
1497 tt
->style
.cell
.rpad
= 2;
1498 tt
->style
.corner
= '+';
1500 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1502 RB_FOREACH (translator
, yang_translators
, &yang_translators
) {
1503 struct yang_tmodule
*tmodule
;
1504 struct listnode
*ln
;
1506 for (ALL_LIST_ELEMENTS_RO(translator
->modules
, ln
, tmodule
)) {
1507 ttable_add_row(tt
, "%s|%s|%s|%.2f", translator
->family
,
1508 tmodule
->module
->name
,
1509 tmodule
->deviations
->name
,
1514 /* Dump the generated table. */
1515 if (tt
->nrows
> 1) {
1518 table
= ttable_dump(tt
, "\n");
1519 vty_out(vty
, "%s\n", table
);
1520 XFREE(MTYPE_TMP
, table
);
1522 vty_out(vty
, "No YANG module translators to display.\n\n");
1529 #ifdef HAVE_CONFIG_ROLLBACKS
1530 static int nb_cli_rollback_configuration(struct vty
*vty
,
1531 uint32_t transaction_id
)
1533 struct nb_config
*candidate
;
1537 candidate
= nb_db_transaction_load(transaction_id
);
1539 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1544 snprintf(comment
, sizeof(comment
), "Rollback to transaction %u",
1547 ret
= nb_candidate_commit(candidate
, NB_CLIENT_CLI
, vty
, true, comment
,
1549 nb_config_free(candidate
);
1553 "%% Configuration was successfully rolled back.\n\n");
1555 case NB_ERR_NO_CHANGES
:
1557 "%% Aborting - no configuration changes detected.\n\n");
1560 vty_out(vty
, "%% Rollback failed.\n\n");
1561 vty_out(vty
, "Please check the logs for more details.\n");
1565 #endif /* HAVE_CONFIG_ROLLBACKS */
1567 DEFPY (rollback_config
,
1568 rollback_config_cmd
,
1569 "rollback configuration (1-4294967295)$transaction_id",
1570 "Rollback to a previous state\n"
1571 "Running configuration\n"
1574 #ifdef HAVE_CONFIG_ROLLBACKS
1575 return nb_cli_rollback_configuration(vty
, transaction_id
);
1578 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1580 #endif /* HAVE_CONFIG_ROLLBACKS */
1583 /* Debug CLI commands. */
1584 static struct debug
*nb_debugs
[] = {
1585 &nb_dbg_cbs_config
, &nb_dbg_cbs_state
, &nb_dbg_cbs_rpc
,
1586 &nb_dbg_notif
, &nb_dbg_events
, &nb_dbg_libyang
,
1589 static const char *const nb_debugs_conflines
[] = {
1590 "debug northbound callbacks configuration",
1591 "debug northbound callbacks state",
1592 "debug northbound callbacks rpc",
1593 "debug northbound notifications",
1594 "debug northbound events",
1595 "debug northbound libyang",
1598 DEFINE_HOOK(nb_client_debug_set_all
, (uint32_t flags
, bool set
), (flags
, set
));
1600 static void nb_debug_set_all(uint32_t flags
, bool set
)
1602 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++) {
1603 DEBUG_FLAGS_SET(nb_debugs
[i
], flags
, set
);
1605 /* If all modes have been turned off, don't preserve options. */
1606 if (!DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_ALL
))
1607 DEBUG_CLEAR(nb_debugs
[i
]);
1610 hook_call(nb_client_debug_set_all
, flags
, set
);
1615 "[no] debug northbound\
1617 callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1618 |notifications$notifications\
1624 "Northbound debugging\n"
1631 "libyang debugging\n")
1633 uint32_t mode
= DEBUG_NODE2MODE(vty
->node
);
1636 bool none
= (!cbs_cfg
&& !cbs_state
&& !cbs_rpc
);
1638 if (none
|| cbs_cfg
)
1639 DEBUG_MODE_SET(&nb_dbg_cbs_config
, mode
, !no
);
1640 if (none
|| cbs_state
)
1641 DEBUG_MODE_SET(&nb_dbg_cbs_state
, mode
, !no
);
1642 if (none
|| cbs_rpc
)
1643 DEBUG_MODE_SET(&nb_dbg_cbs_rpc
, mode
, !no
);
1646 DEBUG_MODE_SET(&nb_dbg_notif
, mode
, !no
);
1648 DEBUG_MODE_SET(&nb_dbg_events
, mode
, !no
);
1650 DEBUG_MODE_SET(&nb_dbg_libyang
, mode
, !no
);
1651 yang_debugging_set(!no
);
1654 /* no specific debug --> act on all of them */
1655 if (strmatch(argv
[argc
- 1]->text
, "northbound")) {
1656 nb_debug_set_all(mode
, !no
);
1657 yang_debugging_set(!no
);
1663 DEFINE_HOOK(nb_client_debug_config_write
, (struct vty
*vty
), (vty
));
1665 static int nb_debug_config_write(struct vty
*vty
)
1667 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++)
1668 if (DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_CONF
))
1669 vty_out(vty
, "%s\n", nb_debugs_conflines
[i
]);
1671 hook_call(nb_client_debug_config_write
, vty
);
1676 static struct debug_callbacks nb_dbg_cbs
= {.debug_set_all
= nb_debug_set_all
};
1677 static struct cmd_node nb_debug_node
= {NORTHBOUND_DEBUG_NODE
, "", 1};
1679 void nb_cli_install_default(int node
)
1681 install_element(node
, &show_config_candidate_section_cmd
);
1683 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL
)
1686 install_element(node
, &config_commit_cmd
);
1687 install_element(node
, &config_commit_comment_cmd
);
1688 install_element(node
, &config_commit_check_cmd
);
1689 install_element(node
, &config_update_cmd
);
1690 install_element(node
, &config_discard_cmd
);
1691 install_element(node
, &show_config_running_cmd
);
1692 install_element(node
, &show_config_candidate_cmd
);
1693 install_element(node
, &show_config_compare_cmd
);
1694 install_element(node
, &show_config_transaction_cmd
);
1697 /* YANG module autocomplete. */
1698 static void yang_module_autocomplete(vector comps
, struct cmd_token
*token
)
1700 const struct lys_module
*module
;
1701 struct yang_translator
*module_tr
;
1705 while ((module
= ly_ctx_get_module_iter(ly_native_ctx
, &idx
)))
1706 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1708 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
) {
1710 while ((module
= ly_ctx_get_module_iter(module_tr
->ly_ctx
,
1713 XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1717 /* YANG module translator autocomplete. */
1718 static void yang_translator_autocomplete(vector comps
, struct cmd_token
*token
)
1720 struct yang_translator
*module_tr
;
1722 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
)
1723 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module_tr
->family
));
1726 static const struct cmd_variable_handler yang_var_handlers
[] = {
1727 {.varname
= "module_name", .completions
= yang_module_autocomplete
},
1728 {.varname
= "translator_family",
1729 .completions
= yang_translator_autocomplete
},
1730 {.completions
= NULL
}};
1732 void nb_cli_init(struct thread_master
*tm
)
1736 /* Initialize the shared candidate configuration. */
1737 vty_shared_candidate_config
= nb_config_new(NULL
);
1739 debug_init(&nb_dbg_cbs
);
1741 install_node(&nb_debug_node
, nb_debug_config_write
);
1742 install_element(ENABLE_NODE
, &debug_nb_cmd
);
1743 install_element(CONFIG_NODE
, &debug_nb_cmd
);
1745 /* Install commands specific to the transaction-base mode. */
1746 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL
) {
1747 install_element(ENABLE_NODE
, &config_exclusive_cmd
);
1748 install_element(ENABLE_NODE
, &config_private_cmd
);
1749 install_element(ENABLE_NODE
, &show_config_running_cmd
);
1750 install_element(ENABLE_NODE
,
1751 &show_config_compare_without_candidate_cmd
);
1752 install_element(ENABLE_NODE
, &show_config_transaction_cmd
);
1753 install_element(ENABLE_NODE
, &rollback_config_cmd
);
1754 install_element(ENABLE_NODE
, &clear_config_transactions_cmd
);
1756 install_element(CONFIG_NODE
, &config_load_cmd
);
1757 install_element(CONFIG_NODE
,
1758 &config_database_max_transactions_cmd
);
1761 /* Other commands. */
1762 install_element(CONFIG_NODE
, &yang_module_translator_load_cmd
);
1763 install_element(CONFIG_NODE
, &yang_module_translator_unload_cmd
);
1764 install_element(ENABLE_NODE
, &show_yang_operational_data_cmd
);
1765 install_element(ENABLE_NODE
, &show_yang_module_cmd
);
1766 install_element(ENABLE_NODE
, &show_yang_module_detail_cmd
);
1767 install_element(ENABLE_NODE
, &show_yang_module_translator_cmd
);
1768 cmd_variable_handler_register(yang_var_handlers
);
1771 void nb_cli_terminate(void)
1773 nb_config_free(vty_shared_candidate_config
);