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 pthread_rwlock_rdlock(&running_config
->lock
);
198 nb_config_replace(vty
->candidate_config
,
199 running_config
, true);
201 pthread_rwlock_unlock(&running_config
->lock
);
203 return CMD_WARNING_CONFIG_FAILED
;
210 int nb_cli_rpc(const char *xpath
, struct list
*input
, struct list
*output
)
212 struct nb_node
*nb_node
;
215 nb_node
= nb_node_find(xpath
);
217 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
218 "%s: unknown data path: %s", __func__
, xpath
);
222 ret
= nb_callback_rpc(nb_node
, xpath
, input
, output
);
231 void nb_cli_confirmed_commit_clean(struct vty
*vty
)
233 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
234 nb_config_free(vty
->confirmed_commit_rollback
);
235 vty
->confirmed_commit_rollback
= NULL
;
238 int nb_cli_confirmed_commit_rollback(struct vty
*vty
)
240 uint32_t transaction_id
;
243 /* Perform the rollback. */
244 ret
= nb_candidate_commit(
245 vty
->confirmed_commit_rollback
, NB_CLIENT_CLI
, vty
, true,
246 "Rollback to previous configuration - confirmed commit has timed out",
250 "Rollback performed successfully (Transaction ID #%u).\n",
253 vty_out(vty
, "Failed to rollback to previous configuration.\n");
258 static int nb_cli_confirmed_commit_timeout(struct thread
*thread
)
260 struct vty
*vty
= THREAD_ARG(thread
);
262 /* XXX: broadcast this message to all logged-in users? */
264 "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
266 nb_cli_confirmed_commit_rollback(vty
);
267 nb_cli_confirmed_commit_clean(vty
);
272 static int nb_cli_commit(struct vty
*vty
, bool force
,
273 unsigned int confirmed_timeout
, char *comment
)
275 uint32_t transaction_id
= 0;
278 /* Check if there's a pending confirmed commit. */
279 if (vty
->t_confirmed_commit_timeout
) {
280 if (confirmed_timeout
) {
281 /* Reset timeout if "commit confirmed" is used again. */
283 "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
286 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
287 thread_add_timer(master
,
288 nb_cli_confirmed_commit_timeout
, vty
,
289 confirmed_timeout
* 60,
290 &vty
->t_confirmed_commit_timeout
);
292 /* Accept commit confirmation. */
293 vty_out(vty
, "%% Commit complete.\n\n");
294 nb_cli_confirmed_commit_clean(vty
);
299 /* "force" parameter. */
300 if (!force
&& nb_candidate_needs_update(vty
->candidate_config
)) {
302 "%% Candidate configuration needs to be updated before commit.\n\n");
304 "Use the \"update\" command or \"commit force\".\n");
308 /* "confirm" parameter. */
309 if (confirmed_timeout
) {
310 pthread_rwlock_rdlock(&running_config
->lock
);
312 vty
->confirmed_commit_rollback
=
313 nb_config_dup(running_config
);
315 pthread_rwlock_unlock(&running_config
->lock
);
317 vty
->t_confirmed_commit_timeout
= NULL
;
318 thread_add_timer(master
, nb_cli_confirmed_commit_timeout
, vty
,
319 confirmed_timeout
* 60,
320 &vty
->t_confirmed_commit_timeout
);
323 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
, vty
,
324 true, comment
, &transaction_id
);
326 /* Map northbound return code to CLI return code. */
329 pthread_rwlock_rdlock(&running_config
->lock
);
331 nb_config_replace(vty
->candidate_config_base
,
332 running_config
, true);
334 pthread_rwlock_unlock(&running_config
->lock
);
337 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
340 case NB_ERR_NO_CHANGES
:
341 vty_out(vty
, "%% No configuration changes to commit.\n\n");
345 "%% Failed to commit candidate configuration: %s.\n\n",
347 vty_out(vty
, "Please check the logs for more details.\n");
352 static int nb_cli_candidate_load_file(struct vty
*vty
,
353 enum nb_cfg_format format
,
354 struct yang_translator
*translator
,
355 const char *path
, bool replace
)
357 struct nb_config
*loaded_config
= NULL
;
358 struct lyd_node
*dnode
;
359 struct ly_ctx
*ly_ctx
;
363 case NB_CFG_FMT_CMDS
:
364 loaded_config
= nb_config_new(NULL
);
365 if (!vty_read_config(loaded_config
, path
, config_default
)) {
366 vty_out(vty
, "%% Failed to load configuration.\n\n");
368 "Please check the logs for more details.\n");
369 nb_config_free(loaded_config
);
373 case NB_CFG_FMT_JSON
:
375 ly_format
= (format
== NB_CFG_FMT_JSON
) ? LYD_JSON
: LYD_XML
;
377 ly_ctx
= translator
? translator
->ly_ctx
: ly_native_ctx
;
378 dnode
= lyd_parse_path(ly_ctx
, path
, ly_format
, LYD_OPT_EDIT
);
380 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_parse_path() failed",
382 vty_out(vty
, "%% Failed to load configuration:\n\n");
383 vty_show_libyang_errors(vty
, ly_ctx
);
387 && yang_translate_dnode(translator
,
388 YANG_TRANSLATE_TO_NATIVE
, &dnode
)
389 != YANG_TRANSLATE_SUCCESS
) {
390 vty_out(vty
, "%% Failed to translate configuration\n");
391 yang_dnode_free(dnode
);
394 loaded_config
= nb_config_new(dnode
);
399 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
400 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
403 "%% Failed to merge the loaded configuration:\n\n");
404 vty_show_libyang_errors(vty
, ly_native_ctx
);
411 static int nb_cli_candidate_load_transaction(struct vty
*vty
,
412 uint32_t transaction_id
,
415 struct nb_config
*loaded_config
;
417 loaded_config
= nb_db_transaction_load(transaction_id
);
418 if (!loaded_config
) {
419 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
425 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
426 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
429 "%% Failed to merge the loaded configuration:\n\n");
430 vty_show_libyang_errors(vty
, ly_native_ctx
);
437 void nb_cli_show_dnode_cmds(struct vty
*vty
, struct lyd_node
*root
,
440 struct lyd_node
*next
, *child
;
442 LY_TREE_DFS_BEGIN (root
, next
, child
) {
443 struct nb_node
*nb_node
;
445 nb_node
= child
->schema
->priv
;
446 if (!nb_node
->cbs
.cli_show
)
449 /* Skip default values. */
450 if (!with_defaults
&& yang_dnode_is_default_recursive(child
))
453 (*nb_node
->cbs
.cli_show
)(vty
, child
, with_defaults
);
455 LY_TREE_DFS_END(root
, next
, child
);
459 static void nb_cli_show_config_cmds(struct vty
*vty
, struct nb_config
*config
,
462 struct lyd_node
*root
;
464 vty_out(vty
, "Configuration:\n");
466 vty_out(vty
, "frr version %s\n", FRR_VER_SHORT
);
467 vty_out(vty
, "frr defaults %s\n", DFLT_NAME
);
469 LY_TREE_FOR (config
->dnode
, root
)
470 nb_cli_show_dnode_cmds(vty
, root
, with_defaults
);
473 vty_out(vty
, "end\n");
476 static int nb_cli_show_config_libyang(struct vty
*vty
, LYD_FORMAT format
,
477 struct nb_config
*config
,
478 struct yang_translator
*translator
,
481 struct lyd_node
*dnode
;
485 dnode
= yang_dnode_dup(config
->dnode
);
487 && yang_translate_dnode(translator
, YANG_TRANSLATE_FROM_NATIVE
,
489 != YANG_TRANSLATE_SUCCESS
) {
490 vty_out(vty
, "%% Failed to translate configuration\n");
491 yang_dnode_free(dnode
);
495 SET_FLAG(options
, LYP_FORMAT
| LYP_WITHSIBLINGS
);
497 SET_FLAG(options
, LYP_WD_ALL
);
499 SET_FLAG(options
, LYP_WD_TRIM
);
501 if (lyd_print_mem(&strp
, dnode
, format
, options
) == 0 && strp
) {
502 vty_out(vty
, "%s", strp
);
506 yang_dnode_free(dnode
);
511 static int nb_cli_show_config(struct vty
*vty
, struct nb_config
*config
,
512 enum nb_cfg_format format
,
513 struct yang_translator
*translator
,
517 case NB_CFG_FMT_CMDS
:
518 nb_cli_show_config_cmds(vty
, config
, with_defaults
);
520 case NB_CFG_FMT_JSON
:
521 return nb_cli_show_config_libyang(vty
, LYD_JSON
, config
,
522 translator
, with_defaults
);
524 return nb_cli_show_config_libyang(vty
, LYD_XML
, config
,
525 translator
, with_defaults
);
531 static int nb_write_config(struct nb_config
*config
, enum nb_cfg_format format
,
532 struct yang_translator
*translator
, char *path
,
536 struct vty
*file_vty
;
539 snprintf(path
, pathlen
, "/tmp/frr.tmp.XXXXXXXX");
542 flog_warn(EC_LIB_SYSTEM_CALL
, "%s: mkstemp() failed: %s",
543 __func__
, safe_strerror(errno
));
547 /* Make vty for configuration file. */
548 file_vty
= vty_new();
550 file_vty
->type
= VTY_FILE
;
552 ret
= nb_cli_show_config(file_vty
, config
, format
, translator
,
559 static int nb_cli_show_config_compare(struct vty
*vty
,
560 struct nb_config
*config1
,
561 struct nb_config
*config2
,
562 enum nb_cfg_format format
,
563 struct yang_translator
*translator
)
565 char config1_path
[256];
566 char config2_path
[256];
567 char command
[BUFSIZ
];
572 if (nb_write_config(config1
, format
, translator
, config1_path
,
573 sizeof(config1_path
))
575 vty_out(vty
, "%% Failed to process configurations.\n\n");
578 if (nb_write_config(config2
, format
, translator
, config2_path
,
579 sizeof(config2_path
))
581 vty_out(vty
, "%% Failed to process configurations.\n\n");
582 unlink(config1_path
);
586 snprintf(command
, sizeof(command
), "diff -u %s %s", config1_path
,
588 fp
= popen(command
, "r");
590 vty_out(vty
, "%% Failed to generate configuration diff.\n\n");
591 unlink(config1_path
);
592 unlink(config2_path
);
595 /* Print diff line by line. */
596 while (fgets(line
, sizeof(line
), fp
) != NULL
) {
599 vty_out(vty
, "%s", line
);
603 unlink(config1_path
);
604 unlink(config2_path
);
609 /* Configure exclusively from this terminal. */
610 DEFUN (config_exclusive
,
611 config_exclusive_cmd
,
612 "configure exclusive",
613 "Configuration from vty interface\n"
614 "Configure exclusively from this terminal\n")
616 return vty_config_enter(vty
, true, true);
619 /* Configure using a private candidate configuration. */
620 DEFUN (config_private
,
623 "Configuration from vty interface\n"
624 "Configure using a private candidate configuration\n")
626 return vty_config_enter(vty
, true, false);
629 DEFPY (config_commit
,
631 "commit [{force$force|confirmed (1-60)}]",
632 "Commit changes into the running configuration\n"
633 "Force commit even if the candidate is outdated\n"
634 "Rollback this commit unless there is a confirming commit\n"
635 "Timeout in minutes for the commit to be confirmed\n")
637 return nb_cli_commit(vty
, !!force
, confirmed
, NULL
);
640 DEFPY (config_commit_comment
,
641 config_commit_comment_cmd
,
642 "commit [{force$force|confirmed (1-60)}] comment LINE...",
643 "Commit changes into the running configuration\n"
644 "Force commit even if the candidate is outdated\n"
645 "Rollback this commit unless there is a confirming commit\n"
646 "Timeout in minutes for the commit to be confirmed\n"
647 "Assign a comment to this commit\n"
648 "Comment for this commit (Max 80 characters)\n")
654 argv_find(argv
, argc
, "LINE", &idx
);
655 comment
= argv_concat(argv
, argc
, idx
);
656 ret
= nb_cli_commit(vty
, !!force
, confirmed
, comment
);
657 XFREE(MTYPE_TMP
, comment
);
662 DEFPY (config_commit_check
,
663 config_commit_check_cmd
,
665 "Commit changes into the running configuration\n"
666 "Check if the configuration changes are valid\n")
670 ret
= nb_candidate_validate(vty
->candidate_config
);
673 "%% Failed to validate candidate configuration.\n\n");
674 vty_show_libyang_errors(vty
, ly_native_ctx
);
678 vty_out(vty
, "%% Candidate configuration validated successfully.\n\n");
683 DEFPY (config_update
,
686 "Update candidate configuration\n")
688 if (!nb_candidate_needs_update(vty
->candidate_config
)) {
689 vty_out(vty
, "%% Update is not necessary.\n\n");
693 if (nb_candidate_update(vty
->candidate_config
) != NB_OK
) {
695 "%% Failed to update the candidate configuration.\n\n");
696 vty_out(vty
, "Please check the logs for more details.\n");
700 pthread_rwlock_rdlock(&running_config
->lock
);
702 nb_config_replace(vty
->candidate_config_base
, running_config
,
705 pthread_rwlock_unlock(&running_config
->lock
);
707 vty_out(vty
, "%% Candidate configuration updated successfully.\n\n");
712 DEFPY (config_discard
,
715 "Discard changes in the candidate configuration\n")
717 nb_config_replace(vty
->candidate_config
, vty
->candidate_config_base
,
727 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
728 |transaction (1-4294967296)$tid\
731 "Configuration related settings\n"
732 "Load configuration into candidate\n"
733 "Load configuration file into candidate\n"
734 "Load configuration file in JSON format\n"
735 "Load configuration file in XML format\n"
736 "Translate configuration file\n"
737 "YANG module translator\n"
738 "Configuration file name (full path)\n"
739 "Load configuration from transaction into candidate\n"
741 "Replace instead of merge\n")
744 enum nb_cfg_format format
;
745 struct yang_translator
*translator
= NULL
;
748 format
= NB_CFG_FMT_JSON
;
750 format
= NB_CFG_FMT_XML
;
752 format
= NB_CFG_FMT_CMDS
;
754 if (translator_family
) {
755 translator
= yang_translator_find(translator_family
);
758 "%% Module translator \"%s\" not found\n",
764 return nb_cli_candidate_load_file(vty
, format
, translator
,
765 filename
, !!replace
);
768 return nb_cli_candidate_load_transaction(vty
, tid
, !!replace
);
771 DEFPY (show_config_running
,
772 show_config_running_cmd
,
773 "show configuration running\
774 [<json$json|xml$xml> [translate WORD$translator_family]]\
775 [with-defaults$with_defaults]",
777 "Configuration information\n"
778 "Running configuration\n"
779 "Change output format to JSON\n"
780 "Change output format to XML\n"
782 "YANG module translator\n"
783 "Show default values\n")
786 enum nb_cfg_format format
;
787 struct yang_translator
*translator
= NULL
;
790 format
= NB_CFG_FMT_JSON
;
792 format
= NB_CFG_FMT_XML
;
794 format
= NB_CFG_FMT_CMDS
;
796 if (translator_family
) {
797 translator
= yang_translator_find(translator_family
);
799 vty_out(vty
, "%% Module translator \"%s\" not found\n",
805 pthread_rwlock_rdlock(&running_config
->lock
);
807 nb_cli_show_config(vty
, running_config
, format
, translator
,
810 pthread_rwlock_unlock(&running_config
->lock
);
815 DEFPY (show_config_candidate
,
816 show_config_candidate_cmd
,
817 "show configuration candidate\
818 [<json$json|xml$xml> [translate WORD$translator_family]]\
820 with-defaults$with_defaults\
824 "Configuration information\n"
825 "Candidate configuration\n"
826 "Change output format to JSON\n"
827 "Change output format to XML\n"
829 "YANG module translator\n"
830 "Show default values\n"
831 "Show changes applied in the candidate configuration\n")
834 enum nb_cfg_format format
;
835 struct yang_translator
*translator
= NULL
;
838 format
= NB_CFG_FMT_JSON
;
840 format
= NB_CFG_FMT_XML
;
842 format
= NB_CFG_FMT_CMDS
;
844 if (translator_family
) {
845 translator
= yang_translator_find(translator_family
);
847 vty_out(vty
, "%% Module translator \"%s\" not found\n",
854 return nb_cli_show_config_compare(
855 vty
, vty
->candidate_config_base
, vty
->candidate_config
,
858 nb_cli_show_config(vty
, vty
->candidate_config
, format
, translator
,
864 DEFPY (show_config_candidate_section
,
865 show_config_candidate_section_cmd
,
869 struct lyd_node
*dnode
;
871 /* Top-level configuration node, display everything. */
872 if (vty
->xpath_index
== 0)
873 return nb_cli_show_config(vty
, vty
->candidate_config
,
874 NB_CFG_FMT_CMDS
, NULL
, false);
876 /* Display only the current section of the candidate configuration. */
877 dnode
= yang_dnode_get(vty
->candidate_config
->dnode
, VTY_CURR_XPATH
);
879 /* Shouldn't happen. */
882 nb_cli_show_dnode_cmds(vty
, dnode
, 0);
888 DEFPY (show_config_compare
,
889 show_config_compare_cmd
,
890 "show configuration compare\
892 candidate$c1_candidate\
894 |transaction (1-4294967296)$c1_tid\
897 candidate$c2_candidate\
899 |transaction (1-4294967296)$c2_tid\
901 [<json$json|xml$xml> [translate WORD$translator_family]]",
903 "Configuration information\n"
904 "Compare two different configurations\n"
905 "Candidate configuration\n"
906 "Running configuration\n"
907 "Configuration transaction\n"
909 "Candidate configuration\n"
910 "Running configuration\n"
911 "Configuration transaction\n"
913 "Change output format to JSON\n"
914 "Change output format to XML\n"
916 "YANG module translator\n")
918 enum nb_cfg_format format
;
919 struct yang_translator
*translator
= NULL
;
920 struct nb_config
*config1
, *config_transaction1
= NULL
;
921 struct nb_config
*config2
, *config_transaction2
= NULL
;
922 int ret
= CMD_WARNING
;
925 * For simplicity, lock the running configuration regardless if it's
926 * going to be used or not.
928 pthread_rwlock_rdlock(&running_config
->lock
);
931 config1
= vty
->candidate_config
;
933 config1
= running_config
;
935 config_transaction1
= nb_db_transaction_load(c1_tid
);
936 if (!config_transaction1
) {
938 "%% Transaction %u does not exist\n\n",
939 (unsigned int)c1_tid
);
942 config1
= config_transaction1
;
946 config2
= vty
->candidate_config
;
948 config2
= running_config
;
950 config_transaction2
= nb_db_transaction_load(c2_tid
);
951 if (!config_transaction2
) {
953 "%% 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
);
971 "%% Module translator \"%s\" not found\n",
977 ret
= nb_cli_show_config_compare(vty
, config1
, config2
, format
,
980 if (config_transaction1
)
981 nb_config_free(config_transaction1
);
982 if (config_transaction2
)
983 nb_config_free(config_transaction2
);
985 pthread_rwlock_unlock(&running_config
->lock
);
991 * Stripped down version of the "show configuration compare" command.
992 * The "candidate" option is not present so the command can be installed in
995 ALIAS (show_config_compare
,
996 show_config_compare_without_candidate_cmd
,
997 "show configuration compare\
1000 |transaction (1-4294967296)$c1_tid\
1004 |transaction (1-4294967296)$c2_tid\
1006 [<json$json|xml$xml> [translate WORD$translator_family]]",
1008 "Configuration information\n"
1009 "Compare two different configurations\n"
1010 "Running configuration\n"
1011 "Configuration transaction\n"
1013 "Running configuration\n"
1014 "Configuration transaction\n"
1016 "Change output format to JSON\n"
1017 "Change output format to XML\n"
1018 "Translate output\n"
1019 "YANG module translator\n")
1021 DEFPY (clear_config_transactions
,
1022 clear_config_transactions_cmd
,
1023 "clear configuration transactions oldest (1-100)$n",
1025 "Configuration activity\n"
1026 "Delete transactions from the transactions log\n"
1027 "Delete oldest <n> transactions\n"
1028 "Number of transactions to delete\n")
1030 #ifdef HAVE_CONFIG_ROLLBACKS
1031 if (nb_db_clear_transactions(n
) != NB_OK
) {
1032 vty_out(vty
, "%% Failed to delete transactions.\n\n");
1037 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1038 #endif /* HAVE_CONFIG_ROLLBACKS */
1043 DEFPY (config_database_max_transactions
,
1044 config_database_max_transactions_cmd
,
1045 "configuration database max-transactions (1-100)$max",
1046 "Configuration related settings\n"
1047 "Configuration database\n"
1048 "Set the maximum number of transactions to store\n"
1049 "Number of transactions\n")
1051 #ifdef HAVE_CONFIG_ROLLBACKS
1052 if (nb_db_set_max_transactions(max
) != NB_OK
) {
1054 "%% Failed to update the maximum number of transactions.\n\n");
1058 "%% Maximum number of transactions updated successfully.\n\n");
1061 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1062 #endif /* HAVE_CONFIG_ROLLBACKS */
1067 DEFPY (yang_module_translator_load
,
1068 yang_module_translator_load_cmd
,
1069 "yang module-translator load FILENAME$filename",
1070 "YANG related settings\n"
1071 "YANG module translator\n"
1072 "Load YANG module translator\n"
1073 "File name (full path)\n")
1075 struct yang_translator
*translator
;
1077 translator
= yang_translator_load(filename
);
1079 vty_out(vty
, "%% Failed to load \"%s\"\n\n", filename
);
1080 vty_out(vty
, "Please check the logs for more details.\n");
1084 vty_out(vty
, "%% Module translator \"%s\" loaded successfully.\n\n",
1085 translator
->family
);
1090 DEFPY (yang_module_translator_unload_family
,
1091 yang_module_translator_unload_cmd
,
1092 "yang module-translator unload WORD$translator_family",
1093 "YANG related settings\n"
1094 "YANG module translator\n"
1095 "Unload YANG module translator\n"
1096 "Name of the module translator\n")
1098 struct yang_translator
*translator
;
1100 translator
= yang_translator_find(translator_family
);
1102 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1107 yang_translator_unload(translator
);
1112 #ifdef HAVE_CONFIG_ROLLBACKS
1113 static void nb_cli_show_transactions_cb(void *arg
, int transaction_id
,
1114 const char *client_name
,
1115 const char *date
, const char *comment
)
1117 struct ttable
*tt
= arg
;
1119 ttable_add_row(tt
, "%d|%s|%s|%s", transaction_id
, client_name
, date
,
1123 static int nb_cli_show_transactions(struct vty
*vty
)
1127 /* Prepare table. */
1128 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1129 ttable_add_row(tt
, "Transaction ID|Client|Date|Comment");
1130 tt
->style
.cell
.rpad
= 2;
1131 tt
->style
.corner
= '+';
1133 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1135 /* Fetch transactions from the northbound database. */
1136 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb
, tt
)
1139 "%% Failed to fetch configuration transactions.\n");
1143 /* Dump the generated table. */
1144 if (tt
->nrows
> 1) {
1147 table
= ttable_dump(tt
, "\n");
1148 vty_out(vty
, "%s\n", table
);
1149 XFREE(MTYPE_TMP
, table
);
1151 vty_out(vty
, "No configuration transactions to display.\n\n");
1157 #endif /* HAVE_CONFIG_ROLLBACKS */
1159 DEFPY (show_config_transaction
,
1160 show_config_transaction_cmd
,
1161 "show configuration transaction\
1163 (1-4294967296)$transaction_id\
1164 [<json$json|xml$xml> [translate WORD$translator_family]]\
1166 with-defaults$with_defaults\
1171 "Configuration information\n"
1172 "Configuration transaction\n"
1174 "Change output format to JSON\n"
1175 "Change output format to XML\n"
1176 "Translate output\n"
1177 "YANG module translator\n"
1178 "Show default values\n"
1179 "Show changes compared to the previous transaction\n")
1181 #ifdef HAVE_CONFIG_ROLLBACKS
1182 if (transaction_id
) {
1183 struct nb_config
*config
;
1184 enum nb_cfg_format format
;
1185 struct yang_translator
*translator
= NULL
;
1188 format
= NB_CFG_FMT_JSON
;
1190 format
= NB_CFG_FMT_XML
;
1192 format
= NB_CFG_FMT_CMDS
;
1194 if (translator_family
) {
1195 translator
= yang_translator_find(translator_family
);
1198 "%% Module translator \"%s\" not found\n",
1204 config
= nb_db_transaction_load(transaction_id
);
1206 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1207 (unsigned int)transaction_id
);
1212 struct nb_config
*prev_config
;
1215 /* NOTE: this can be NULL. */
1217 nb_db_transaction_load(transaction_id
- 1);
1219 ret
= nb_cli_show_config_compare(
1220 vty
, prev_config
, config
, format
, translator
);
1222 nb_config_free(prev_config
);
1223 nb_config_free(config
);
1228 nb_cli_show_config(vty
, config
, format
, translator
,
1230 nb_config_free(config
);
1235 return nb_cli_show_transactions(vty
);
1238 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1240 #endif /* HAVE_CONFIG_ROLLBACKS */
1243 static int nb_cli_oper_data_cb(const struct lys_node
*snode
,
1244 struct yang_translator
*translator
,
1245 struct yang_data
*data
, void *arg
)
1247 struct lyd_node
*dnode
= arg
;
1248 struct ly_ctx
*ly_ctx
;
1253 ret
= yang_translate_xpath(translator
,
1254 YANG_TRANSLATE_FROM_NATIVE
,
1255 data
->xpath
, sizeof(data
->xpath
));
1257 case YANG_TRANSLATE_SUCCESS
:
1259 case YANG_TRANSLATE_NOTFOUND
:
1261 case YANG_TRANSLATE_FAILURE
:
1265 ly_ctx
= translator
->ly_ctx
;
1267 ly_ctx
= ly_native_ctx
;
1270 dnode
= lyd_new_path(dnode
, ly_ctx
, data
->xpath
, (void *)data
->value
, 0,
1271 LYD_PATH_OPT_UPDATE
);
1272 if (!dnode
&& ly_errno
) {
1273 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
1279 yang_data_free(data
);
1283 yang_data_free(data
);
1287 DEFPY (show_yang_operational_data
,
1288 show_yang_operational_data_cmd
,
1289 "show yang operational-data XPATH$xpath\
1291 format <json$json|xml$xml>\
1292 |translate WORD$translator_family\
1295 "YANG information\n"
1296 "Show YANG operational data\n"
1297 "XPath expression specifying the YANG data path\n"
1298 "Set the output format\n"
1299 "JavaScript Object Notation\n"
1300 "Extensible Markup Language\n"
1301 "Translate operational data\n"
1302 "YANG module translator\n")
1305 struct yang_translator
*translator
= NULL
;
1306 struct ly_ctx
*ly_ctx
;
1307 struct lyd_node
*dnode
;
1315 if (translator_family
) {
1316 translator
= yang_translator_find(translator_family
);
1318 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1323 ly_ctx
= translator
->ly_ctx
;
1325 ly_ctx
= ly_native_ctx
;
1328 dnode
= yang_dnode_new(ly_ctx
, false);
1329 if (nb_oper_data_iterate(xpath
, translator
, 0, nb_cli_oper_data_cb
,
1332 vty_out(vty
, "%% Failed to fetch operational data.\n");
1333 yang_dnode_free(dnode
);
1336 lyd_validate(&dnode
, LYD_OPT_GET
, ly_ctx
);
1338 /* Display the data. */
1339 if (lyd_print_mem(&strp
, dnode
, format
,
1340 LYP_FORMAT
| LYP_WITHSIBLINGS
| LYP_WD_ALL
)
1343 vty_out(vty
, "%% Failed to display operational data.\n");
1344 yang_dnode_free(dnode
);
1347 vty_out(vty
, "%s", strp
);
1349 yang_dnode_free(dnode
);
1354 DEFPY (show_yang_module
,
1355 show_yang_module_cmd
,
1356 "show yang module [module-translator WORD$translator_family]",
1358 "YANG information\n"
1359 "Show loaded modules\n"
1360 "YANG module translator\n"
1361 "YANG module translator\n")
1363 struct ly_ctx
*ly_ctx
;
1364 struct yang_translator
*translator
= NULL
;
1365 const struct lys_module
*module
;
1369 if (translator_family
) {
1370 translator
= yang_translator_find(translator_family
);
1372 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1376 ly_ctx
= translator
->ly_ctx
;
1378 ly_ctx
= ly_native_ctx
;
1380 /* Prepare table. */
1381 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1382 ttable_add_row(tt
, "Module|Version|Revision|Flags|Namespace");
1383 tt
->style
.cell
.rpad
= 2;
1384 tt
->style
.corner
= '+';
1386 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1388 while ((module
= ly_ctx_get_module_iter(ly_ctx
, &idx
))) {
1391 snprintf(flags
, sizeof(flags
), "%c%c",
1392 module
->implemented
? 'I' : ' ',
1393 (module
->deviated
== 1) ? 'D' : ' ');
1395 ttable_add_row(tt
, "%s|%s|%s|%s|%s", module
->name
,
1396 (module
->version
== 2) ? "1.1" : "1.0",
1397 (module
->rev_size
> 0) ? module
->rev
[0].date
1402 /* Dump the generated table. */
1403 if (tt
->nrows
> 1) {
1406 vty_out(vty
, " Flags: I - Implemented, D - Deviated\n\n");
1408 table
= ttable_dump(tt
, "\n");
1409 vty_out(vty
, "%s\n", table
);
1410 XFREE(MTYPE_TMP
, table
);
1412 vty_out(vty
, "No YANG modules to display.\n\n");
1419 DEFPY (show_yang_module_detail
,
1420 show_yang_module_detail_cmd
,
1422 [module-translator WORD$translator_family]\
1423 WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1425 "YANG information\n"
1426 "Show loaded modules\n"
1427 "YANG module translator\n"
1428 "YANG module translator\n"
1430 "Display summary information about the module\n"
1431 "Display module in the tree (RFC 8340) format\n"
1432 "Display module in the YANG format\n"
1433 "Display module in the YIN format\n")
1435 struct ly_ctx
*ly_ctx
;
1436 struct yang_translator
*translator
= NULL
;
1437 const struct lys_module
*module
;
1438 LYS_OUTFORMAT format
;
1441 if (translator_family
) {
1442 translator
= yang_translator_find(translator_family
);
1444 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1448 ly_ctx
= translator
->ly_ctx
;
1450 ly_ctx
= ly_native_ctx
;
1452 module
= ly_ctx_get_module(ly_ctx
, module_name
, NULL
, 0);
1454 vty_out(vty
, "%% Module \"%s\" not found\n", module_name
);
1459 format
= LYS_OUT_YANG
;
1461 format
= LYS_OUT_YIN
;
1463 format
= LYS_OUT_TREE
;
1465 format
= LYS_OUT_INFO
;
1467 if (lys_print_mem(&strp
, module
, format
, NULL
, 0, 0) == 0) {
1468 vty_out(vty
, "%s\n", strp
);
1472 vty_out(vty
, "%% Error generating module information\n");
1479 DEFPY (show_yang_module_translator
,
1480 show_yang_module_translator_cmd
,
1481 "show yang module-translator",
1483 "YANG information\n"
1484 "Show loaded YANG module translators\n")
1486 struct yang_translator
*translator
;
1489 /* Prepare table. */
1490 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1491 ttable_add_row(tt
, "Family|Module|Deviations|Coverage (%%)");
1492 tt
->style
.cell
.rpad
= 2;
1493 tt
->style
.corner
= '+';
1495 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1497 RB_FOREACH (translator
, yang_translators
, &yang_translators
) {
1498 struct yang_tmodule
*tmodule
;
1499 struct listnode
*ln
;
1501 for (ALL_LIST_ELEMENTS_RO(translator
->modules
, ln
, tmodule
)) {
1502 ttable_add_row(tt
, "%s|%s|%s|%.2f", translator
->family
,
1503 tmodule
->module
->name
,
1504 tmodule
->deviations
->name
,
1509 /* Dump the generated table. */
1510 if (tt
->nrows
> 1) {
1513 table
= ttable_dump(tt
, "\n");
1514 vty_out(vty
, "%s\n", table
);
1515 XFREE(MTYPE_TMP
, table
);
1517 vty_out(vty
, "No YANG module translators to display.\n\n");
1524 #ifdef HAVE_CONFIG_ROLLBACKS
1525 static int nb_cli_rollback_configuration(struct vty
*vty
,
1526 uint32_t transaction_id
)
1528 struct nb_config
*candidate
;
1532 candidate
= nb_db_transaction_load(transaction_id
);
1534 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1539 snprintf(comment
, sizeof(comment
), "Rollback to transaction %u",
1542 ret
= nb_candidate_commit(candidate
, NB_CLIENT_CLI
, vty
, true, comment
,
1544 nb_config_free(candidate
);
1548 "%% Configuration was successfully rolled back.\n\n");
1550 case NB_ERR_NO_CHANGES
:
1552 "%% Aborting - no configuration changes detected.\n\n");
1555 vty_out(vty
, "%% Rollback failed.\n\n");
1556 vty_out(vty
, "Please check the logs for more details.\n");
1560 #endif /* HAVE_CONFIG_ROLLBACKS */
1562 DEFPY (rollback_config
,
1563 rollback_config_cmd
,
1564 "rollback configuration (1-4294967296)$transaction_id",
1565 "Rollback to a previous state\n"
1566 "Running configuration\n"
1569 #ifdef HAVE_CONFIG_ROLLBACKS
1570 return nb_cli_rollback_configuration(vty
, transaction_id
);
1573 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1575 #endif /* HAVE_CONFIG_ROLLBACKS */
1578 /* Debug CLI commands. */
1579 static struct debug
*nb_debugs
[] = {
1580 &nb_dbg_cbs_config
, &nb_dbg_cbs_state
, &nb_dbg_cbs_rpc
,
1581 &nb_dbg_notif
, &nb_dbg_events
,
1584 static const char *const nb_debugs_conflines
[] = {
1585 "debug northbound callbacks configuration",
1586 "debug northbound callbacks state",
1587 "debug northbound callbacks rpc",
1588 "debug northbound notifications",
1589 "debug northbound events",
1592 DEFINE_HOOK(nb_client_debug_set_all
, (uint32_t flags
, bool set
), (flags
, set
));
1594 static void nb_debug_set_all(uint32_t flags
, bool set
)
1596 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++) {
1597 DEBUG_FLAGS_SET(nb_debugs
[i
], flags
, set
);
1599 /* If all modes have been turned off, don't preserve options. */
1600 if (!DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_ALL
))
1601 DEBUG_CLEAR(nb_debugs
[i
]);
1604 hook_call(nb_client_debug_set_all
, flags
, set
);
1609 "[no] debug northbound\
1611 callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1612 |notifications$notifications\
1617 "Northbound debugging\n"
1625 uint32_t mode
= DEBUG_NODE2MODE(vty
->node
);
1628 bool none
= (!cbs_cfg
&& !cbs_state
&& !cbs_rpc
);
1630 if (none
|| cbs_cfg
)
1631 DEBUG_MODE_SET(&nb_dbg_cbs_config
, mode
, !no
);
1632 if (none
|| cbs_state
)
1633 DEBUG_MODE_SET(&nb_dbg_cbs_state
, mode
, !no
);
1634 if (none
|| cbs_rpc
)
1635 DEBUG_MODE_SET(&nb_dbg_cbs_rpc
, mode
, !no
);
1638 DEBUG_MODE_SET(&nb_dbg_notif
, mode
, !no
);
1640 DEBUG_MODE_SET(&nb_dbg_events
, mode
, !no
);
1642 /* no specific debug --> act on all of them */
1643 if (strmatch(argv
[argc
- 1]->text
, "northbound"))
1644 nb_debug_set_all(mode
, !no
);
1649 DEFINE_HOOK(nb_client_debug_config_write
, (struct vty
*vty
), (vty
));
1651 static int nb_debug_config_write(struct vty
*vty
)
1653 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++)
1654 if (DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_CONF
))
1655 vty_out(vty
, "%s\n", nb_debugs_conflines
[i
]);
1657 hook_call(nb_client_debug_config_write
, vty
);
1662 static struct debug_callbacks nb_dbg_cbs
= {.debug_set_all
= nb_debug_set_all
};
1663 static struct cmd_node nb_debug_node
= {NORTHBOUND_DEBUG_NODE
, "", 1};
1665 void nb_cli_install_default(int node
)
1667 install_element(node
, &show_config_candidate_section_cmd
);
1669 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL
)
1672 install_element(node
, &config_commit_cmd
);
1673 install_element(node
, &config_commit_comment_cmd
);
1674 install_element(node
, &config_commit_check_cmd
);
1675 install_element(node
, &config_update_cmd
);
1676 install_element(node
, &config_discard_cmd
);
1677 install_element(node
, &show_config_running_cmd
);
1678 install_element(node
, &show_config_candidate_cmd
);
1679 install_element(node
, &show_config_compare_cmd
);
1680 install_element(node
, &show_config_transaction_cmd
);
1683 /* YANG module autocomplete. */
1684 static void yang_module_autocomplete(vector comps
, struct cmd_token
*token
)
1686 const struct lys_module
*module
;
1687 struct yang_translator
*module_tr
;
1691 while ((module
= ly_ctx_get_module_iter(ly_native_ctx
, &idx
)))
1692 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1694 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
) {
1696 while ((module
= ly_ctx_get_module_iter(module_tr
->ly_ctx
,
1699 XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1703 /* YANG module translator autocomplete. */
1704 static void yang_translator_autocomplete(vector comps
, struct cmd_token
*token
)
1706 struct yang_translator
*module_tr
;
1708 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
)
1709 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module_tr
->family
));
1712 static const struct cmd_variable_handler yang_var_handlers
[] = {
1713 {.varname
= "module_name", .completions
= yang_module_autocomplete
},
1714 {.varname
= "translator_family",
1715 .completions
= yang_translator_autocomplete
},
1716 {.completions
= NULL
}};
1718 void nb_cli_init(struct thread_master
*tm
)
1722 /* Initialize the shared candidate configuration. */
1723 vty_shared_candidate_config
= nb_config_new(NULL
);
1725 /* Install debug commands */
1726 debug_init(&nb_dbg_cbs
);
1727 install_node(&nb_debug_node
, nb_debug_config_write
);
1728 install_element(ENABLE_NODE
, &debug_nb_cmd
);
1729 install_element(CONFIG_NODE
, &debug_nb_cmd
);
1731 /* Install commands specific to the transaction-base mode. */
1732 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL
) {
1733 install_element(ENABLE_NODE
, &config_exclusive_cmd
);
1734 install_element(ENABLE_NODE
, &config_private_cmd
);
1735 install_element(ENABLE_NODE
, &show_config_running_cmd
);
1736 install_element(ENABLE_NODE
,
1737 &show_config_compare_without_candidate_cmd
);
1738 install_element(ENABLE_NODE
, &show_config_transaction_cmd
);
1739 install_element(ENABLE_NODE
, &rollback_config_cmd
);
1740 install_element(ENABLE_NODE
, &clear_config_transactions_cmd
);
1742 install_element(CONFIG_NODE
, &config_load_cmd
);
1743 install_element(CONFIG_NODE
,
1744 &config_database_max_transactions_cmd
);
1747 /* Other commands. */
1748 install_element(CONFIG_NODE
, &yang_module_translator_load_cmd
);
1749 install_element(CONFIG_NODE
, &yang_module_translator_unload_cmd
);
1750 install_element(ENABLE_NODE
, &show_yang_operational_data_cmd
);
1751 install_element(ENABLE_NODE
, &show_yang_module_cmd
);
1752 install_element(ENABLE_NODE
, &show_yang_module_detail_cmd
);
1753 install_element(ENABLE_NODE
, &show_yang_module_translator_cmd
);
1754 cmd_variable_handler_register(yang_var_handlers
);
1757 void nb_cli_terminate(void)
1759 nb_config_free(vty_shared_candidate_config
);