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
25 #include "lib_errors.h"
27 #include "termtable.h"
30 #include "yang_translator.h"
31 #include "northbound.h"
32 #include "northbound_cli.h"
33 #include "northbound_db.h"
34 #ifndef VTYSH_EXTRACT_PL
35 #include "lib/northbound_cli_clippy.c"
38 struct debug nb_dbg_cbs_config
= {0, "Northbound callbacks: configuration"};
39 struct debug nb_dbg_cbs_state
= {0, "Northbound callbacks: state"};
40 struct debug nb_dbg_cbs_rpc
= {0, "Northbound callbacks: RPCs"};
41 struct debug nb_dbg_notif
= {0, "Northbound notifications"};
42 struct debug nb_dbg_events
= {0, "Northbound events"};
44 struct nb_config
*vty_shared_candidate_config
;
45 static struct thread_master
*master
;
47 static void vty_show_libyang_errors(struct vty
*vty
, struct ly_ctx
*ly_ctx
)
49 struct ly_err_item
*ei
;
52 ei
= ly_err_first(ly_ctx
);
56 for (; ei
; ei
= ei
->next
)
57 vty_out(vty
, "%s\n", ei
->msg
);
59 path
= ly_errpath(ly_ctx
);
61 vty_out(vty
, "YANG path: %s\n", path
);
63 ly_err_clean(ly_ctx
, NULL
);
66 void nb_cli_enqueue_change(struct vty
*vty
, const char *xpath
,
67 enum nb_operation operation
, const char *value
)
69 struct vty_cfg_change
*change
;
71 if (vty
->num_cfg_changes
== VTY_MAXCFGCHANGES
) {
72 /* Not expected to happen. */
74 "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
79 change
= &vty
->cfg_changes
[vty
->num_cfg_changes
++];
80 strlcpy(change
->xpath
, xpath
, sizeof(change
->xpath
));
81 change
->operation
= operation
;
82 change
->value
= value
;
85 int nb_cli_apply_changes(struct vty
*vty
, const char *xpath_base_fmt
, ...)
87 struct nb_config
*candidate_transitory
;
88 char xpath_base
[XPATH_MAXLEN
] = {};
95 * Create a copy of the candidate configuration. For consistency, we
96 * need to ensure that either all changes made by the command are
97 * accepted or none are.
99 candidate_transitory
= nb_config_dup(vty
->candidate_config
);
101 /* Parse the base XPath format string. */
102 if (xpath_base_fmt
) {
105 va_start(ap
, xpath_base_fmt
);
106 vsnprintf(xpath_base
, sizeof(xpath_base
), xpath_base_fmt
, ap
);
110 /* Edit candidate configuration. */
111 for (size_t i
= 0; i
< vty
->num_cfg_changes
; i
++) {
112 struct vty_cfg_change
*change
= &vty
->cfg_changes
[i
];
113 struct nb_node
*nb_node
;
114 char xpath
[XPATH_MAXLEN
];
115 struct yang_data
*data
;
117 /* Handle relative XPaths. */
118 memset(xpath
, 0, sizeof(xpath
));
119 if (vty
->xpath_index
> 0
120 && ((xpath_base_fmt
&& xpath_base
[0] == '.')
121 || change
->xpath
[0] == '.'))
122 strlcpy(xpath
, VTY_CURR_XPATH
, sizeof(xpath
));
123 if (xpath_base_fmt
) {
124 if (xpath_base
[0] == '.')
125 strlcat(xpath
, xpath_base
+ 1, sizeof(xpath
));
127 strlcat(xpath
, xpath_base
, sizeof(xpath
));
129 if (change
->xpath
[0] == '.')
130 strlcat(xpath
, change
->xpath
+ 1, sizeof(xpath
));
132 strlcpy(xpath
, change
->xpath
, sizeof(xpath
));
134 /* Find the northbound node associated to the data path. */
135 nb_node
= nb_node_find(xpath
);
137 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
138 "%s: unknown data path: %s", __func__
, xpath
);
143 /* If the value is not set, get the default if it exists. */
144 if (change
->value
== NULL
)
145 change
->value
= yang_snode_get_default(nb_node
->snode
);
146 data
= yang_data_new(xpath
, change
->value
);
149 * Ignore "not found" errors when editing the candidate
152 ret
= nb_candidate_edit(candidate_transitory
, nb_node
,
153 change
->operation
, xpath
, NULL
, data
);
154 yang_data_free(data
);
155 if (ret
!= NB_OK
&& ret
!= NB_ERR_NOT_FOUND
) {
157 EC_LIB_NB_CANDIDATE_EDIT_ERROR
,
158 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
159 __func__
, nb_operation_name(change
->operation
),
167 nb_config_free(candidate_transitory
);
169 switch (frr_get_cli_mode()) {
170 case FRR_CLI_CLASSIC
:
171 vty_out(vty
, "%% Configuration failed.\n\n");
173 case FRR_CLI_TRANSACTIONAL
:
175 "%% Failed to edit candidate configuration.\n\n");
178 vty_show_libyang_errors(vty
, ly_native_ctx
);
180 return CMD_WARNING_CONFIG_FAILED
;
183 nb_config_replace(vty
->candidate_config
, candidate_transitory
, false);
185 /* Do an implicit "commit" when using the classic CLI mode. */
186 if (frr_get_cli_mode() == FRR_CLI_CLASSIC
) {
187 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
,
188 vty
, false, NULL
, NULL
);
189 if (ret
!= NB_OK
&& ret
!= NB_ERR_NO_CHANGES
) {
190 vty_out(vty
, "%% Configuration failed: %s.\n\n",
193 "Please check the logs for more details.\n");
195 /* Regenerate candidate for consistency. */
196 nb_config_replace(vty
->candidate_config
, running_config
,
198 return CMD_WARNING_CONFIG_FAILED
;
205 int nb_cli_rpc(const char *xpath
, struct list
*input
, struct list
*output
)
207 struct nb_node
*nb_node
;
210 nb_node
= nb_node_find(xpath
);
212 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
213 "%s: unknown data path: %s", __func__
, xpath
);
217 ret
= nb_callback_rpc(nb_node
, xpath
, input
, output
);
226 void nb_cli_confirmed_commit_clean(struct vty
*vty
)
228 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
229 nb_config_free(vty
->confirmed_commit_rollback
);
230 vty
->confirmed_commit_rollback
= NULL
;
233 int nb_cli_confirmed_commit_rollback(struct vty
*vty
)
235 uint32_t transaction_id
;
238 /* Perform the rollback. */
239 ret
= nb_candidate_commit(
240 vty
->confirmed_commit_rollback
, NB_CLIENT_CLI
, vty
, true,
241 "Rollback to previous configuration - confirmed commit has timed out",
245 "Rollback performed successfully (Transaction ID #%u).\n",
248 vty_out(vty
, "Failed to rollback to previous configuration.\n");
253 static int nb_cli_confirmed_commit_timeout(struct thread
*thread
)
255 struct vty
*vty
= THREAD_ARG(thread
);
257 /* XXX: broadcast this message to all logged-in users? */
259 "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
261 nb_cli_confirmed_commit_rollback(vty
);
262 nb_cli_confirmed_commit_clean(vty
);
267 static int nb_cli_commit(struct vty
*vty
, bool force
,
268 unsigned int confirmed_timeout
, char *comment
)
270 uint32_t transaction_id
= 0;
273 /* Check if there's a pending confirmed commit. */
274 if (vty
->t_confirmed_commit_timeout
) {
275 if (confirmed_timeout
) {
276 /* Reset timeout if "commit confirmed" is used again. */
278 "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
281 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
282 thread_add_timer(master
,
283 nb_cli_confirmed_commit_timeout
, vty
,
284 confirmed_timeout
* 60,
285 &vty
->t_confirmed_commit_timeout
);
287 /* Accept commit confirmation. */
288 vty_out(vty
, "%% Commit complete.\n\n");
289 nb_cli_confirmed_commit_clean(vty
);
294 /* "force" parameter. */
295 if (!force
&& nb_candidate_needs_update(vty
->candidate_config
)) {
297 "%% Candidate configuration needs to be updated before commit.\n\n");
299 "Use the \"update\" command or \"commit force\".\n");
303 /* "confirm" parameter. */
304 if (confirmed_timeout
) {
305 vty
->confirmed_commit_rollback
= nb_config_dup(running_config
);
307 vty
->t_confirmed_commit_timeout
= NULL
;
308 thread_add_timer(master
, nb_cli_confirmed_commit_timeout
, vty
,
309 confirmed_timeout
* 60,
310 &vty
->t_confirmed_commit_timeout
);
313 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
, vty
,
314 true, comment
, &transaction_id
);
316 /* Map northbound return code to CLI return code. */
319 nb_config_replace(vty
->candidate_config_base
, running_config
,
322 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
325 case NB_ERR_NO_CHANGES
:
326 vty_out(vty
, "%% No configuration changes to commit.\n\n");
330 "%% Failed to commit candidate configuration: %s.\n\n",
332 vty_out(vty
, "Please check the logs for more details.\n");
337 static int nb_cli_candidate_load_file(struct vty
*vty
,
338 enum nb_cfg_format format
,
339 struct yang_translator
*translator
,
340 const char *path
, bool replace
)
342 struct nb_config
*loaded_config
= NULL
;
343 struct lyd_node
*dnode
;
344 struct ly_ctx
*ly_ctx
;
348 case NB_CFG_FMT_CMDS
:
349 loaded_config
= nb_config_new(NULL
);
350 if (!vty_read_config(loaded_config
, path
, config_default
)) {
351 vty_out(vty
, "%% Failed to load configuration.\n\n");
353 "Please check the logs for more details.\n");
354 nb_config_free(loaded_config
);
358 case NB_CFG_FMT_JSON
:
360 ly_format
= (format
== NB_CFG_FMT_JSON
) ? LYD_JSON
: LYD_XML
;
362 ly_ctx
= translator
? translator
->ly_ctx
: ly_native_ctx
;
363 dnode
= lyd_parse_path(ly_ctx
, path
, ly_format
, LYD_OPT_EDIT
);
365 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_parse_path() failed",
367 vty_out(vty
, "%% Failed to load configuration:\n\n");
368 vty_show_libyang_errors(vty
, ly_ctx
);
372 && yang_translate_dnode(translator
,
373 YANG_TRANSLATE_TO_NATIVE
, &dnode
)
374 != YANG_TRANSLATE_SUCCESS
) {
375 vty_out(vty
, "%% Failed to translate configuration\n");
376 yang_dnode_free(dnode
);
379 loaded_config
= nb_config_new(dnode
);
384 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
385 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
388 "%% Failed to merge the loaded configuration:\n\n");
389 vty_show_libyang_errors(vty
, ly_native_ctx
);
396 static int nb_cli_candidate_load_transaction(struct vty
*vty
,
397 uint32_t transaction_id
,
400 struct nb_config
*loaded_config
;
402 loaded_config
= nb_db_transaction_load(transaction_id
);
403 if (!loaded_config
) {
404 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
410 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
411 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
414 "%% Failed to merge the loaded configuration:\n\n");
415 vty_show_libyang_errors(vty
, ly_native_ctx
);
423 * ly_iter_next_is_up: detects when iterating up on the yang model.
425 * This function detects whether next node in the iteration is upwards,
426 * then return the node otherwise return NULL.
428 static struct lyd_node
*ly_iter_next_up(const struct lyd_node
*elem
)
430 /* Are we going downwards? Is this still not a leaf? */
431 if (!(elem
->schema
->nodetype
& (LYS_LEAF
| LYS_LEAFLIST
| LYS_ANYDATA
)))
434 /* Are there still leaves in this branch? */
435 if (elem
->next
!= NULL
)
441 void nb_cli_show_dnode_cmds(struct vty
*vty
, struct lyd_node
*root
,
444 struct lyd_node
*next
, *child
, *parent
;
446 LY_TREE_DFS_BEGIN (root
, next
, child
) {
447 struct nb_node
*nb_node
;
449 nb_node
= child
->schema
->priv
;
450 if (!nb_node
->cbs
.cli_show
)
453 /* Skip default values. */
454 if (!with_defaults
&& yang_dnode_is_default_recursive(child
))
457 (*nb_node
->cbs
.cli_show
)(vty
, child
, with_defaults
);
460 * When transiting upwards in the yang model we should
461 * give the previous container/list node a chance to
462 * print its close vty output (e.g. "!" or "end-family"
465 parent
= ly_iter_next_up(child
);
466 if (parent
!= NULL
) {
467 nb_node
= parent
->schema
->priv
;
468 if (nb_node
->cbs
.cli_show_end
)
469 (*nb_node
->cbs
.cli_show_end
)(vty
, parent
);
472 LY_TREE_DFS_END(root
, next
, child
);
476 static void nb_cli_show_config_cmds(struct vty
*vty
, struct nb_config
*config
,
479 struct lyd_node
*root
;
481 vty_out(vty
, "Configuration:\n");
483 vty_out(vty
, "frr version %s\n", FRR_VER_SHORT
);
484 vty_out(vty
, "frr defaults %s\n", DFLT_NAME
);
486 LY_TREE_FOR (config
->dnode
, root
)
487 nb_cli_show_dnode_cmds(vty
, root
, with_defaults
);
490 vty_out(vty
, "end\n");
493 static int nb_cli_show_config_libyang(struct vty
*vty
, LYD_FORMAT format
,
494 struct nb_config
*config
,
495 struct yang_translator
*translator
,
498 struct lyd_node
*dnode
;
502 dnode
= yang_dnode_dup(config
->dnode
);
504 && yang_translate_dnode(translator
, YANG_TRANSLATE_FROM_NATIVE
,
506 != YANG_TRANSLATE_SUCCESS
) {
507 vty_out(vty
, "%% Failed to translate configuration\n");
508 yang_dnode_free(dnode
);
512 SET_FLAG(options
, LYP_FORMAT
| LYP_WITHSIBLINGS
);
514 SET_FLAG(options
, LYP_WD_ALL
);
516 SET_FLAG(options
, LYP_WD_TRIM
);
518 if (lyd_print_mem(&strp
, dnode
, format
, options
) == 0 && strp
) {
519 vty_out(vty
, "%s", strp
);
523 yang_dnode_free(dnode
);
528 static int nb_cli_show_config(struct vty
*vty
, struct nb_config
*config
,
529 enum nb_cfg_format format
,
530 struct yang_translator
*translator
,
534 case NB_CFG_FMT_CMDS
:
535 nb_cli_show_config_cmds(vty
, config
, with_defaults
);
537 case NB_CFG_FMT_JSON
:
538 return nb_cli_show_config_libyang(vty
, LYD_JSON
, config
,
539 translator
, with_defaults
);
541 return nb_cli_show_config_libyang(vty
, LYD_XML
, config
,
542 translator
, with_defaults
);
548 static int nb_write_config(struct nb_config
*config
, enum nb_cfg_format format
,
549 struct yang_translator
*translator
, char *path
,
553 struct vty
*file_vty
;
556 snprintf(path
, pathlen
, "/tmp/frr.tmp.XXXXXXXX");
559 flog_warn(EC_LIB_SYSTEM_CALL
, "%s: mkstemp() failed: %s",
560 __func__
, safe_strerror(errno
));
564 /* Make vty for configuration file. */
565 file_vty
= vty_new();
567 file_vty
->type
= VTY_FILE
;
569 ret
= nb_cli_show_config(file_vty
, config
, format
, translator
,
576 static int nb_cli_show_config_compare(struct vty
*vty
,
577 struct nb_config
*config1
,
578 struct nb_config
*config2
,
579 enum nb_cfg_format format
,
580 struct yang_translator
*translator
)
582 char config1_path
[256];
583 char config2_path
[256];
584 char command
[BUFSIZ
];
589 if (nb_write_config(config1
, format
, translator
, config1_path
,
590 sizeof(config1_path
))
592 vty_out(vty
, "%% Failed to process configurations.\n\n");
595 if (nb_write_config(config2
, format
, translator
, config2_path
,
596 sizeof(config2_path
))
598 vty_out(vty
, "%% Failed to process configurations.\n\n");
599 unlink(config1_path
);
603 snprintf(command
, sizeof(command
), "diff -u %s %s", config1_path
,
605 fp
= popen(command
, "r");
607 vty_out(vty
, "%% Failed to generate configuration diff.\n\n");
608 unlink(config1_path
);
609 unlink(config2_path
);
612 /* Print diff line by line. */
613 while (fgets(line
, sizeof(line
), fp
) != NULL
) {
616 vty_out(vty
, "%s", line
);
620 unlink(config1_path
);
621 unlink(config2_path
);
626 /* Configure exclusively from this terminal. */
627 DEFUN (config_exclusive
,
628 config_exclusive_cmd
,
629 "configure exclusive",
630 "Configuration from vty interface\n"
631 "Configure exclusively from this terminal\n")
633 return vty_config_enter(vty
, true, true);
636 /* Configure using a private candidate configuration. */
637 DEFUN (config_private
,
640 "Configuration from vty interface\n"
641 "Configure using a private candidate configuration\n")
643 return vty_config_enter(vty
, true, false);
646 DEFPY (config_commit
,
648 "commit [{force$force|confirmed (1-60)}]",
649 "Commit changes into the running configuration\n"
650 "Force commit even if the candidate is outdated\n"
651 "Rollback this commit unless there is a confirming commit\n"
652 "Timeout in minutes for the commit to be confirmed\n")
654 return nb_cli_commit(vty
, !!force
, confirmed
, NULL
);
657 DEFPY (config_commit_comment
,
658 config_commit_comment_cmd
,
659 "commit [{force$force|confirmed (1-60)}] comment LINE...",
660 "Commit changes into the running configuration\n"
661 "Force commit even if the candidate is outdated\n"
662 "Rollback this commit unless there is a confirming commit\n"
663 "Timeout in minutes for the commit to be confirmed\n"
664 "Assign a comment to this commit\n"
665 "Comment for this commit (Max 80 characters)\n")
671 argv_find(argv
, argc
, "LINE", &idx
);
672 comment
= argv_concat(argv
, argc
, idx
);
673 ret
= nb_cli_commit(vty
, !!force
, confirmed
, comment
);
674 XFREE(MTYPE_TMP
, comment
);
679 DEFPY (config_commit_check
,
680 config_commit_check_cmd
,
682 "Commit changes into the running configuration\n"
683 "Check if the configuration changes are valid\n")
687 ret
= nb_candidate_validate(vty
->candidate_config
);
690 "%% Failed to validate candidate configuration.\n\n");
691 vty_show_libyang_errors(vty
, ly_native_ctx
);
695 vty_out(vty
, "%% Candidate configuration validated successfully.\n\n");
700 DEFPY (config_update
,
703 "Update candidate configuration\n")
705 if (!nb_candidate_needs_update(vty
->candidate_config
)) {
706 vty_out(vty
, "%% Update is not necessary.\n\n");
710 if (nb_candidate_update(vty
->candidate_config
) != NB_OK
) {
712 "%% Failed to update the candidate configuration.\n\n");
713 vty_out(vty
, "Please check the logs for more details.\n");
717 nb_config_replace(vty
->candidate_config_base
, running_config
, true);
719 vty_out(vty
, "%% Candidate configuration updated successfully.\n\n");
724 DEFPY (config_discard
,
727 "Discard changes in the candidate configuration\n")
729 nb_config_replace(vty
->candidate_config
, vty
->candidate_config_base
,
739 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
740 |transaction (1-4294967295)$tid\
743 "Configuration related settings\n"
744 "Load configuration into candidate\n"
745 "Load configuration file into candidate\n"
746 "Load configuration file in JSON format\n"
747 "Load configuration file in XML format\n"
748 "Translate configuration file\n"
749 "YANG module translator\n"
750 "Configuration file name (full path)\n"
751 "Load configuration from transaction into candidate\n"
753 "Replace instead of merge\n")
756 enum nb_cfg_format format
;
757 struct yang_translator
*translator
= NULL
;
760 format
= NB_CFG_FMT_JSON
;
762 format
= NB_CFG_FMT_XML
;
764 format
= NB_CFG_FMT_CMDS
;
766 if (translator_family
) {
767 translator
= yang_translator_find(translator_family
);
770 "%% Module translator \"%s\" not found\n",
776 return nb_cli_candidate_load_file(vty
, format
, translator
,
777 filename
, !!replace
);
780 return nb_cli_candidate_load_transaction(vty
, tid
, !!replace
);
783 DEFPY (show_config_running
,
784 show_config_running_cmd
,
785 "show configuration running\
786 [<json$json|xml$xml> [translate WORD$translator_family]]\
787 [with-defaults$with_defaults]",
789 "Configuration information\n"
790 "Running configuration\n"
791 "Change output format to JSON\n"
792 "Change output format to XML\n"
794 "YANG module translator\n"
795 "Show default values\n")
798 enum nb_cfg_format format
;
799 struct yang_translator
*translator
= NULL
;
802 format
= NB_CFG_FMT_JSON
;
804 format
= NB_CFG_FMT_XML
;
806 format
= NB_CFG_FMT_CMDS
;
808 if (translator_family
) {
809 translator
= yang_translator_find(translator_family
);
811 vty_out(vty
, "%% Module translator \"%s\" not found\n",
817 nb_cli_show_config(vty
, running_config
, format
, translator
,
823 DEFPY (show_config_candidate
,
824 show_config_candidate_cmd
,
825 "show configuration candidate\
826 [<json$json|xml$xml> [translate WORD$translator_family]]\
828 with-defaults$with_defaults\
832 "Configuration information\n"
833 "Candidate configuration\n"
834 "Change output format to JSON\n"
835 "Change output format to XML\n"
837 "YANG module translator\n"
838 "Show default values\n"
839 "Show changes applied in the candidate configuration\n")
842 enum nb_cfg_format format
;
843 struct yang_translator
*translator
= NULL
;
846 format
= NB_CFG_FMT_JSON
;
848 format
= NB_CFG_FMT_XML
;
850 format
= NB_CFG_FMT_CMDS
;
852 if (translator_family
) {
853 translator
= yang_translator_find(translator_family
);
855 vty_out(vty
, "%% Module translator \"%s\" not found\n",
862 return nb_cli_show_config_compare(
863 vty
, vty
->candidate_config_base
, vty
->candidate_config
,
866 nb_cli_show_config(vty
, vty
->candidate_config
, format
, translator
,
872 DEFPY (show_config_candidate_section
,
873 show_config_candidate_section_cmd
,
877 struct lyd_node
*dnode
;
879 /* Top-level configuration node, display everything. */
880 if (vty
->xpath_index
== 0)
881 return nb_cli_show_config(vty
, vty
->candidate_config
,
882 NB_CFG_FMT_CMDS
, NULL
, false);
884 /* Display only the current section of the candidate configuration. */
885 dnode
= yang_dnode_get(vty
->candidate_config
->dnode
, VTY_CURR_XPATH
);
887 /* Shouldn't happen. */
890 nb_cli_show_dnode_cmds(vty
, dnode
, 0);
896 DEFPY (show_config_compare
,
897 show_config_compare_cmd
,
898 "show configuration compare\
900 candidate$c1_candidate\
902 |transaction (1-4294967295)$c1_tid\
905 candidate$c2_candidate\
907 |transaction (1-4294967295)$c2_tid\
909 [<json$json|xml$xml> [translate WORD$translator_family]]",
911 "Configuration information\n"
912 "Compare two different configurations\n"
913 "Candidate configuration\n"
914 "Running configuration\n"
915 "Configuration transaction\n"
917 "Candidate configuration\n"
918 "Running configuration\n"
919 "Configuration transaction\n"
921 "Change output format to JSON\n"
922 "Change output format to XML\n"
924 "YANG module translator\n")
926 enum nb_cfg_format format
;
927 struct yang_translator
*translator
= NULL
;
928 struct nb_config
*config1
, *config_transaction1
= NULL
;
929 struct nb_config
*config2
, *config_transaction2
= NULL
;
930 int ret
= CMD_WARNING
;
933 config1
= vty
->candidate_config
;
935 config1
= running_config
;
937 config_transaction1
= nb_db_transaction_load(c1_tid
);
938 if (!config_transaction1
) {
939 vty_out(vty
, "%% Transaction %u does not exist\n\n",
940 (unsigned int)c1_tid
);
943 config1
= config_transaction1
;
947 config2
= vty
->candidate_config
;
949 config2
= running_config
;
951 config_transaction2
= nb_db_transaction_load(c2_tid
);
952 if (!config_transaction2
) {
953 vty_out(vty
, "%% Transaction %u does not exist\n\n",
954 (unsigned int)c2_tid
);
957 config2
= config_transaction2
;
961 format
= NB_CFG_FMT_JSON
;
963 format
= NB_CFG_FMT_XML
;
965 format
= NB_CFG_FMT_CMDS
;
967 if (translator_family
) {
968 translator
= yang_translator_find(translator_family
);
970 vty_out(vty
, "%% Module translator \"%s\" not found\n",
976 ret
= nb_cli_show_config_compare(vty
, config1
, config2
, format
,
979 if (config_transaction1
)
980 nb_config_free(config_transaction1
);
981 if (config_transaction2
)
982 nb_config_free(config_transaction2
);
988 * Stripped down version of the "show configuration compare" command.
989 * The "candidate" option is not present so the command can be installed in
992 ALIAS (show_config_compare
,
993 show_config_compare_without_candidate_cmd
,
994 "show configuration compare\
997 |transaction (1-4294967295)$c1_tid\
1001 |transaction (1-4294967295)$c2_tid\
1003 [<json$json|xml$xml> [translate WORD$translator_family]]",
1005 "Configuration information\n"
1006 "Compare two different configurations\n"
1007 "Running configuration\n"
1008 "Configuration transaction\n"
1010 "Running configuration\n"
1011 "Configuration transaction\n"
1013 "Change output format to JSON\n"
1014 "Change output format to XML\n"
1015 "Translate output\n"
1016 "YANG module translator\n")
1018 DEFPY (clear_config_transactions
,
1019 clear_config_transactions_cmd
,
1020 "clear configuration transactions oldest (1-100)$n",
1022 "Configuration activity\n"
1023 "Delete transactions from the transactions log\n"
1024 "Delete oldest <n> transactions\n"
1025 "Number of transactions to delete\n")
1027 #ifdef HAVE_CONFIG_ROLLBACKS
1028 if (nb_db_clear_transactions(n
) != NB_OK
) {
1029 vty_out(vty
, "%% Failed to delete transactions.\n\n");
1034 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1035 #endif /* HAVE_CONFIG_ROLLBACKS */
1040 DEFPY (config_database_max_transactions
,
1041 config_database_max_transactions_cmd
,
1042 "configuration database max-transactions (1-100)$max",
1043 "Configuration related settings\n"
1044 "Configuration database\n"
1045 "Set the maximum number of transactions to store\n"
1046 "Number of transactions\n")
1048 #ifdef HAVE_CONFIG_ROLLBACKS
1049 if (nb_db_set_max_transactions(max
) != NB_OK
) {
1051 "%% Failed to update the maximum number of transactions.\n\n");
1055 "%% Maximum number of transactions updated successfully.\n\n");
1058 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1059 #endif /* HAVE_CONFIG_ROLLBACKS */
1064 DEFPY (yang_module_translator_load
,
1065 yang_module_translator_load_cmd
,
1066 "yang module-translator load FILENAME$filename",
1067 "YANG related settings\n"
1068 "YANG module translator\n"
1069 "Load YANG module translator\n"
1070 "File name (full path)\n")
1072 struct yang_translator
*translator
;
1074 translator
= yang_translator_load(filename
);
1076 vty_out(vty
, "%% Failed to load \"%s\"\n\n", filename
);
1077 vty_out(vty
, "Please check the logs for more details.\n");
1081 vty_out(vty
, "%% Module translator \"%s\" loaded successfully.\n\n",
1082 translator
->family
);
1087 DEFPY (yang_module_translator_unload_family
,
1088 yang_module_translator_unload_cmd
,
1089 "yang module-translator unload WORD$translator_family",
1090 "YANG related settings\n"
1091 "YANG module translator\n"
1092 "Unload YANG module translator\n"
1093 "Name of the module translator\n")
1095 struct yang_translator
*translator
;
1097 translator
= yang_translator_find(translator_family
);
1099 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1104 yang_translator_unload(translator
);
1109 #ifdef HAVE_CONFIG_ROLLBACKS
1110 static void nb_cli_show_transactions_cb(void *arg
, int transaction_id
,
1111 const char *client_name
,
1112 const char *date
, const char *comment
)
1114 struct ttable
*tt
= arg
;
1116 ttable_add_row(tt
, "%d|%s|%s|%s", transaction_id
, client_name
, date
,
1120 static int nb_cli_show_transactions(struct vty
*vty
)
1124 /* Prepare table. */
1125 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1126 ttable_add_row(tt
, "Transaction ID|Client|Date|Comment");
1127 tt
->style
.cell
.rpad
= 2;
1128 tt
->style
.corner
= '+';
1130 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1132 /* Fetch transactions from the northbound database. */
1133 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb
, tt
)
1136 "%% Failed to fetch configuration transactions.\n");
1140 /* Dump the generated table. */
1141 if (tt
->nrows
> 1) {
1144 table
= ttable_dump(tt
, "\n");
1145 vty_out(vty
, "%s\n", table
);
1146 XFREE(MTYPE_TMP
, table
);
1148 vty_out(vty
, "No configuration transactions to display.\n\n");
1154 #endif /* HAVE_CONFIG_ROLLBACKS */
1156 DEFPY (show_config_transaction
,
1157 show_config_transaction_cmd
,
1158 "show configuration transaction\
1160 (1-4294967295)$transaction_id\
1161 [<json$json|xml$xml> [translate WORD$translator_family]]\
1163 with-defaults$with_defaults\
1168 "Configuration information\n"
1169 "Configuration transaction\n"
1171 "Change output format to JSON\n"
1172 "Change output format to XML\n"
1173 "Translate output\n"
1174 "YANG module translator\n"
1175 "Show default values\n"
1176 "Show changes compared to the previous transaction\n")
1178 #ifdef HAVE_CONFIG_ROLLBACKS
1179 if (transaction_id
) {
1180 struct nb_config
*config
;
1181 enum nb_cfg_format format
;
1182 struct yang_translator
*translator
= NULL
;
1185 format
= NB_CFG_FMT_JSON
;
1187 format
= NB_CFG_FMT_XML
;
1189 format
= NB_CFG_FMT_CMDS
;
1191 if (translator_family
) {
1192 translator
= yang_translator_find(translator_family
);
1195 "%% Module translator \"%s\" not found\n",
1201 config
= nb_db_transaction_load(transaction_id
);
1203 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1204 (unsigned int)transaction_id
);
1209 struct nb_config
*prev_config
;
1212 /* NOTE: this can be NULL. */
1214 nb_db_transaction_load(transaction_id
- 1);
1216 ret
= nb_cli_show_config_compare(
1217 vty
, prev_config
, config
, format
, translator
);
1219 nb_config_free(prev_config
);
1220 nb_config_free(config
);
1225 nb_cli_show_config(vty
, config
, format
, translator
,
1227 nb_config_free(config
);
1232 return nb_cli_show_transactions(vty
);
1235 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1237 #endif /* HAVE_CONFIG_ROLLBACKS */
1240 static int nb_cli_oper_data_cb(const struct lys_node
*snode
,
1241 struct yang_translator
*translator
,
1242 struct yang_data
*data
, void *arg
)
1244 struct lyd_node
*dnode
= arg
;
1245 struct ly_ctx
*ly_ctx
;
1250 ret
= yang_translate_xpath(translator
,
1251 YANG_TRANSLATE_FROM_NATIVE
,
1252 data
->xpath
, sizeof(data
->xpath
));
1254 case YANG_TRANSLATE_SUCCESS
:
1256 case YANG_TRANSLATE_NOTFOUND
:
1258 case YANG_TRANSLATE_FAILURE
:
1262 ly_ctx
= translator
->ly_ctx
;
1264 ly_ctx
= ly_native_ctx
;
1267 dnode
= lyd_new_path(dnode
, ly_ctx
, data
->xpath
, (void *)data
->value
, 0,
1268 LYD_PATH_OPT_UPDATE
);
1269 if (!dnode
&& ly_errno
) {
1270 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
1276 yang_data_free(data
);
1280 yang_data_free(data
);
1284 DEFPY (show_yang_operational_data
,
1285 show_yang_operational_data_cmd
,
1286 "show yang operational-data XPATH$xpath\
1288 format <json$json|xml$xml>\
1289 |translate WORD$translator_family\
1292 "YANG information\n"
1293 "Show YANG operational data\n"
1294 "XPath expression specifying the YANG data path\n"
1295 "Set the output format\n"
1296 "JavaScript Object Notation\n"
1297 "Extensible Markup Language\n"
1298 "Translate operational data\n"
1299 "YANG module translator\n")
1302 struct yang_translator
*translator
= NULL
;
1303 struct ly_ctx
*ly_ctx
;
1304 struct lyd_node
*dnode
;
1312 if (translator_family
) {
1313 translator
= yang_translator_find(translator_family
);
1315 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1320 ly_ctx
= translator
->ly_ctx
;
1322 ly_ctx
= ly_native_ctx
;
1325 dnode
= yang_dnode_new(ly_ctx
, false);
1326 if (nb_oper_data_iterate(xpath
, translator
, 0, nb_cli_oper_data_cb
,
1329 vty_out(vty
, "%% Failed to fetch operational data.\n");
1330 yang_dnode_free(dnode
);
1333 lyd_validate(&dnode
, LYD_OPT_GET
, ly_ctx
);
1335 /* Display the data. */
1336 if (lyd_print_mem(&strp
, dnode
, format
,
1337 LYP_FORMAT
| LYP_WITHSIBLINGS
| LYP_WD_ALL
)
1340 vty_out(vty
, "%% Failed to display operational data.\n");
1341 yang_dnode_free(dnode
);
1344 vty_out(vty
, "%s", strp
);
1346 yang_dnode_free(dnode
);
1351 DEFPY (show_yang_module
,
1352 show_yang_module_cmd
,
1353 "show yang module [module-translator WORD$translator_family]",
1355 "YANG information\n"
1356 "Show loaded modules\n"
1357 "YANG module translator\n"
1358 "YANG module translator\n")
1360 struct ly_ctx
*ly_ctx
;
1361 struct yang_translator
*translator
= NULL
;
1362 const struct lys_module
*module
;
1366 if (translator_family
) {
1367 translator
= yang_translator_find(translator_family
);
1369 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1373 ly_ctx
= translator
->ly_ctx
;
1375 ly_ctx
= ly_native_ctx
;
1377 /* Prepare table. */
1378 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1379 ttable_add_row(tt
, "Module|Version|Revision|Flags|Namespace");
1380 tt
->style
.cell
.rpad
= 2;
1381 tt
->style
.corner
= '+';
1383 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1385 while ((module
= ly_ctx_get_module_iter(ly_ctx
, &idx
))) {
1388 snprintf(flags
, sizeof(flags
), "%c%c",
1389 module
->implemented
? 'I' : ' ',
1390 (module
->deviated
== 1) ? 'D' : ' ');
1392 ttable_add_row(tt
, "%s|%s|%s|%s|%s", module
->name
,
1393 (module
->version
== 2) ? "1.1" : "1.0",
1394 (module
->rev_size
> 0) ? module
->rev
[0].date
1399 /* Dump the generated table. */
1400 if (tt
->nrows
> 1) {
1403 vty_out(vty
, " Flags: I - Implemented, D - Deviated\n\n");
1405 table
= ttable_dump(tt
, "\n");
1406 vty_out(vty
, "%s\n", table
);
1407 XFREE(MTYPE_TMP
, table
);
1409 vty_out(vty
, "No YANG modules to display.\n\n");
1416 DEFPY (show_yang_module_detail
,
1417 show_yang_module_detail_cmd
,
1419 [module-translator WORD$translator_family]\
1420 WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1422 "YANG information\n"
1423 "Show loaded modules\n"
1424 "YANG module translator\n"
1425 "YANG module translator\n"
1427 "Display summary information about the module\n"
1428 "Display module in the tree (RFC 8340) format\n"
1429 "Display module in the YANG format\n"
1430 "Display module in the YIN format\n")
1432 struct ly_ctx
*ly_ctx
;
1433 struct yang_translator
*translator
= NULL
;
1434 const struct lys_module
*module
;
1435 LYS_OUTFORMAT format
;
1438 if (translator_family
) {
1439 translator
= yang_translator_find(translator_family
);
1441 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1445 ly_ctx
= translator
->ly_ctx
;
1447 ly_ctx
= ly_native_ctx
;
1449 module
= ly_ctx_get_module(ly_ctx
, module_name
, NULL
, 0);
1451 vty_out(vty
, "%% Module \"%s\" not found\n", module_name
);
1456 format
= LYS_OUT_YANG
;
1458 format
= LYS_OUT_YIN
;
1460 format
= LYS_OUT_TREE
;
1462 format
= LYS_OUT_INFO
;
1464 if (lys_print_mem(&strp
, module
, format
, NULL
, 0, 0) == 0) {
1465 vty_out(vty
, "%s\n", strp
);
1469 vty_out(vty
, "%% Error generating module information\n");
1476 DEFPY (show_yang_module_translator
,
1477 show_yang_module_translator_cmd
,
1478 "show yang module-translator",
1480 "YANG information\n"
1481 "Show loaded YANG module translators\n")
1483 struct yang_translator
*translator
;
1486 /* Prepare table. */
1487 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1488 ttable_add_row(tt
, "Family|Module|Deviations|Coverage (%%)");
1489 tt
->style
.cell
.rpad
= 2;
1490 tt
->style
.corner
= '+';
1492 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1494 RB_FOREACH (translator
, yang_translators
, &yang_translators
) {
1495 struct yang_tmodule
*tmodule
;
1496 struct listnode
*ln
;
1498 for (ALL_LIST_ELEMENTS_RO(translator
->modules
, ln
, tmodule
)) {
1499 ttable_add_row(tt
, "%s|%s|%s|%.2f", translator
->family
,
1500 tmodule
->module
->name
,
1501 tmodule
->deviations
->name
,
1506 /* Dump the generated table. */
1507 if (tt
->nrows
> 1) {
1510 table
= ttable_dump(tt
, "\n");
1511 vty_out(vty
, "%s\n", table
);
1512 XFREE(MTYPE_TMP
, table
);
1514 vty_out(vty
, "No YANG module translators to display.\n\n");
1521 #ifdef HAVE_CONFIG_ROLLBACKS
1522 static int nb_cli_rollback_configuration(struct vty
*vty
,
1523 uint32_t transaction_id
)
1525 struct nb_config
*candidate
;
1529 candidate
= nb_db_transaction_load(transaction_id
);
1531 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1536 snprintf(comment
, sizeof(comment
), "Rollback to transaction %u",
1539 ret
= nb_candidate_commit(candidate
, NB_CLIENT_CLI
, vty
, true, comment
,
1541 nb_config_free(candidate
);
1545 "%% Configuration was successfully rolled back.\n\n");
1547 case NB_ERR_NO_CHANGES
:
1549 "%% Aborting - no configuration changes detected.\n\n");
1552 vty_out(vty
, "%% Rollback failed.\n\n");
1553 vty_out(vty
, "Please check the logs for more details.\n");
1557 #endif /* HAVE_CONFIG_ROLLBACKS */
1559 DEFPY (rollback_config
,
1560 rollback_config_cmd
,
1561 "rollback configuration (1-4294967295)$transaction_id",
1562 "Rollback to a previous state\n"
1563 "Running configuration\n"
1566 #ifdef HAVE_CONFIG_ROLLBACKS
1567 return nb_cli_rollback_configuration(vty
, transaction_id
);
1570 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1572 #endif /* HAVE_CONFIG_ROLLBACKS */
1575 /* Debug CLI commands. */
1576 static struct debug
*nb_debugs
[] = {
1577 &nb_dbg_cbs_config
, &nb_dbg_cbs_state
, &nb_dbg_cbs_rpc
,
1578 &nb_dbg_notif
, &nb_dbg_events
,
1581 static const char *const nb_debugs_conflines
[] = {
1582 "debug northbound callbacks configuration",
1583 "debug northbound callbacks state",
1584 "debug northbound callbacks rpc",
1585 "debug northbound notifications",
1586 "debug northbound events",
1589 DEFINE_HOOK(nb_client_debug_set_all
, (uint32_t flags
, bool set
), (flags
, set
));
1591 static void nb_debug_set_all(uint32_t flags
, bool set
)
1593 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++) {
1594 DEBUG_FLAGS_SET(nb_debugs
[i
], flags
, set
);
1596 /* If all modes have been turned off, don't preserve options. */
1597 if (!DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_ALL
))
1598 DEBUG_CLEAR(nb_debugs
[i
]);
1601 hook_call(nb_client_debug_set_all
, flags
, set
);
1606 "[no] debug northbound\
1608 callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1609 |notifications$notifications\
1614 "Northbound debugging\n"
1622 uint32_t mode
= DEBUG_NODE2MODE(vty
->node
);
1625 bool none
= (!cbs_cfg
&& !cbs_state
&& !cbs_rpc
);
1627 if (none
|| cbs_cfg
)
1628 DEBUG_MODE_SET(&nb_dbg_cbs_config
, mode
, !no
);
1629 if (none
|| cbs_state
)
1630 DEBUG_MODE_SET(&nb_dbg_cbs_state
, mode
, !no
);
1631 if (none
|| cbs_rpc
)
1632 DEBUG_MODE_SET(&nb_dbg_cbs_rpc
, mode
, !no
);
1635 DEBUG_MODE_SET(&nb_dbg_notif
, mode
, !no
);
1637 DEBUG_MODE_SET(&nb_dbg_events
, mode
, !no
);
1639 /* no specific debug --> act on all of them */
1640 if (strmatch(argv
[argc
- 1]->text
, "northbound"))
1641 nb_debug_set_all(mode
, !no
);
1646 DEFINE_HOOK(nb_client_debug_config_write
, (struct vty
*vty
), (vty
));
1648 static int nb_debug_config_write(struct vty
*vty
)
1650 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++)
1651 if (DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_CONF
))
1652 vty_out(vty
, "%s\n", nb_debugs_conflines
[i
]);
1654 hook_call(nb_client_debug_config_write
, vty
);
1659 static struct debug_callbacks nb_dbg_cbs
= {.debug_set_all
= nb_debug_set_all
};
1660 static struct cmd_node nb_debug_node
= {NORTHBOUND_DEBUG_NODE
, "", 1};
1662 void nb_cli_install_default(int node
)
1664 install_element(node
, &show_config_candidate_section_cmd
);
1666 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL
)
1669 install_element(node
, &config_commit_cmd
);
1670 install_element(node
, &config_commit_comment_cmd
);
1671 install_element(node
, &config_commit_check_cmd
);
1672 install_element(node
, &config_update_cmd
);
1673 install_element(node
, &config_discard_cmd
);
1674 install_element(node
, &show_config_running_cmd
);
1675 install_element(node
, &show_config_candidate_cmd
);
1676 install_element(node
, &show_config_compare_cmd
);
1677 install_element(node
, &show_config_transaction_cmd
);
1680 /* YANG module autocomplete. */
1681 static void yang_module_autocomplete(vector comps
, struct cmd_token
*token
)
1683 const struct lys_module
*module
;
1684 struct yang_translator
*module_tr
;
1688 while ((module
= ly_ctx_get_module_iter(ly_native_ctx
, &idx
)))
1689 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1691 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
) {
1693 while ((module
= ly_ctx_get_module_iter(module_tr
->ly_ctx
,
1696 XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1700 /* YANG module translator autocomplete. */
1701 static void yang_translator_autocomplete(vector comps
, struct cmd_token
*token
)
1703 struct yang_translator
*module_tr
;
1705 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
)
1706 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module_tr
->family
));
1709 static const struct cmd_variable_handler yang_var_handlers
[] = {
1710 {.varname
= "module_name", .completions
= yang_module_autocomplete
},
1711 {.varname
= "translator_family",
1712 .completions
= yang_translator_autocomplete
},
1713 {.completions
= NULL
}};
1715 void nb_cli_init(struct thread_master
*tm
)
1719 /* Initialize the shared candidate configuration. */
1720 vty_shared_candidate_config
= nb_config_new(NULL
);
1722 debug_init(&nb_dbg_cbs
);
1724 install_node(&nb_debug_node
, nb_debug_config_write
);
1725 install_element(ENABLE_NODE
, &debug_nb_cmd
);
1726 install_element(CONFIG_NODE
, &debug_nb_cmd
);
1728 /* Install commands specific to the transaction-base mode. */
1729 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL
) {
1730 install_element(ENABLE_NODE
, &config_exclusive_cmd
);
1731 install_element(ENABLE_NODE
, &config_private_cmd
);
1732 install_element(ENABLE_NODE
, &show_config_running_cmd
);
1733 install_element(ENABLE_NODE
,
1734 &show_config_compare_without_candidate_cmd
);
1735 install_element(ENABLE_NODE
, &show_config_transaction_cmd
);
1736 install_element(ENABLE_NODE
, &rollback_config_cmd
);
1737 install_element(ENABLE_NODE
, &clear_config_transactions_cmd
);
1739 install_element(CONFIG_NODE
, &config_load_cmd
);
1740 install_element(CONFIG_NODE
,
1741 &config_database_max_transactions_cmd
);
1744 /* Other commands. */
1745 install_element(CONFIG_NODE
, &yang_module_translator_load_cmd
);
1746 install_element(CONFIG_NODE
, &yang_module_translator_unload_cmd
);
1747 install_element(ENABLE_NODE
, &show_yang_operational_data_cmd
);
1748 install_element(ENABLE_NODE
, &show_yang_module_cmd
);
1749 install_element(ENABLE_NODE
, &show_yang_module_detail_cmd
);
1750 install_element(ENABLE_NODE
, &show_yang_module_translator_cmd
);
1751 cmd_variable_handler_register(yang_var_handlers
);
1754 void nb_cli_terminate(void)
1756 nb_config_free(vty_shared_candidate_config
);