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"
29 #include "yang_translator.h"
30 #include "northbound.h"
31 #include "northbound_cli.h"
32 #include "northbound_db.h"
33 #ifndef VTYSH_EXTRACT_PL
34 #include "lib/northbound_cli_clippy.c"
38 struct nb_config
*vty_shared_candidate_config
;
39 static struct thread_master
*master
;
41 static void vty_show_libyang_errors(struct vty
*vty
, struct ly_ctx
*ly_ctx
)
43 struct ly_err_item
*ei
;
46 ei
= ly_err_first(ly_ctx
);
50 for (; ei
; ei
= ei
->next
)
51 vty_out(vty
, "%s\n", ei
->msg
);
53 path
= ly_errpath(ly_ctx
);
55 vty_out(vty
, "YANG path: %s\n", path
);
57 ly_err_clean(ly_ctx
, NULL
);
60 void nb_cli_enqueue_change(struct vty
*vty
, const char *xpath
,
61 enum nb_operation operation
, const char *value
)
63 struct vty_cfg_change
*change
;
65 if (vty
->num_cfg_changes
== VTY_MAXCFGCHANGES
) {
66 /* Not expected to happen. */
68 "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
73 change
= &vty
->cfg_changes
[vty
->num_cfg_changes
++];
74 strlcpy(change
->xpath
, xpath
, sizeof(change
->xpath
));
75 change
->operation
= operation
;
76 change
->value
= value
;
79 int nb_cli_apply_changes(struct vty
*vty
, const char *xpath_base_fmt
, ...)
81 struct nb_config
*candidate_transitory
;
82 char xpath_base
[XPATH_MAXLEN
];
90 * Create a copy of the candidate configuration. For consistency, we
91 * need to ensure that either all changes made by the command are
92 * accepted or none are.
94 candidate_transitory
= nb_config_dup(vty
->candidate_config
);
96 /* Parse the base XPath format string. */
97 va_start(ap
, xpath_base_fmt
);
98 vsnprintf(xpath_base
, sizeof(xpath_base
), xpath_base_fmt
, ap
);
101 /* Edit candidate configuration. */
102 for (size_t i
= 0; i
< vty
->num_cfg_changes
; i
++) {
103 struct vty_cfg_change
*change
= &vty
->cfg_changes
[i
];
104 struct nb_node
*nb_node
;
105 char xpath
[XPATH_MAXLEN
];
106 struct yang_data
*data
;
108 /* Handle relative XPaths. */
109 memset(xpath
, 0, sizeof(xpath
));
110 if (vty
->xpath_index
> 0
111 && ((xpath_base_fmt
&& xpath_base
[0] == '.')
112 || change
->xpath
[0] == '.'))
113 strlcpy(xpath
, VTY_CURR_XPATH
, sizeof(xpath
));
114 if (xpath_base_fmt
) {
115 if (xpath_base
[0] == '.')
116 strlcat(xpath
, xpath_base
+ 1, sizeof(xpath
));
118 strlcat(xpath
, xpath_base
, sizeof(xpath
));
120 if (change
->xpath
[0] == '.')
121 strlcat(xpath
, change
->xpath
+ 1, sizeof(xpath
));
123 strlcpy(xpath
, change
->xpath
, sizeof(xpath
));
125 /* Find the northbound node associated to the data path. */
126 nb_node
= nb_node_find(xpath
);
128 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
129 "%s: unknown data path: %s", __func__
, xpath
);
134 /* If the value is not set, get the default if it exists. */
135 if (change
->value
== NULL
)
136 change
->value
= yang_snode_get_default(nb_node
->snode
);
137 data
= yang_data_new(xpath
, change
->value
);
140 * Ignore "not found" errors when editing the candidate
143 ret
= nb_candidate_edit(candidate_transitory
, nb_node
,
144 change
->operation
, xpath
, NULL
, data
);
145 yang_data_free(data
);
146 if (ret
!= NB_OK
&& ret
!= NB_ERR_NOT_FOUND
) {
148 EC_LIB_NB_CANDIDATE_EDIT_ERROR
,
149 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
150 __func__
, nb_operation_name(change
->operation
),
158 nb_config_free(candidate_transitory
);
160 switch (frr_get_cli_mode()) {
161 case FRR_CLI_CLASSIC
:
162 vty_out(vty
, "%% Configuration failed.\n\n");
164 case FRR_CLI_TRANSACTIONAL
:
166 "%% Failed to edit candidate configuration.\n\n");
169 vty_show_libyang_errors(vty
, ly_native_ctx
);
171 return CMD_WARNING_CONFIG_FAILED
;
174 nb_config_replace(vty
->candidate_config
, candidate_transitory
, false);
176 /* Do an implicit "commit" when using the classic CLI mode. */
177 if (frr_get_cli_mode() == FRR_CLI_CLASSIC
) {
178 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
,
180 if (ret
!= NB_OK
&& ret
!= NB_ERR_NO_CHANGES
) {
181 vty_out(vty
, "%% Configuration failed: %s.\n\n",
184 "Please check the logs for more details.\n");
186 /* Regenerate candidate for consistency. */
187 nb_config_replace(vty
->candidate_config
, running_config
,
189 return CMD_WARNING_CONFIG_FAILED
;
196 int nb_cli_rpc(const char *xpath
, struct list
*input
, struct list
*output
)
198 struct nb_node
*nb_node
;
201 nb_node
= nb_node_find(xpath
);
203 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
204 "%s: unknown data path: %s", __func__
, xpath
);
208 ret
= nb_node
->cbs
.rpc(xpath
, input
, output
);
217 void nb_cli_confirmed_commit_clean(struct vty
*vty
)
219 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
220 nb_config_free(vty
->confirmed_commit_rollback
);
221 vty
->confirmed_commit_rollback
= NULL
;
224 int nb_cli_confirmed_commit_rollback(struct vty
*vty
)
226 uint32_t transaction_id
;
229 /* Perform the rollback. */
230 ret
= nb_candidate_commit(
231 vty
->confirmed_commit_rollback
, NB_CLIENT_CLI
, true,
232 "Rollback to previous configuration - confirmed commit has timed out",
236 "Rollback performed successfully (Transaction ID #%u).\n",
239 vty_out(vty
, "Failed to rollback to previous configuration.\n");
244 static int nb_cli_confirmed_commit_timeout(struct thread
*thread
)
246 struct vty
*vty
= THREAD_ARG(thread
);
248 /* XXX: broadcast this message to all logged-in users? */
250 "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
252 nb_cli_confirmed_commit_rollback(vty
);
253 nb_cli_confirmed_commit_clean(vty
);
258 static int nb_cli_commit(struct vty
*vty
, bool force
,
259 unsigned int confirmed_timeout
, char *comment
)
261 uint32_t transaction_id
;
264 /* Check if there's a pending confirmed commit. */
265 if (vty
->t_confirmed_commit_timeout
) {
266 if (confirmed_timeout
) {
267 /* Reset timeout if "commit confirmed" is used again. */
269 "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
272 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
273 thread_add_timer(master
,
274 nb_cli_confirmed_commit_timeout
, vty
,
275 confirmed_timeout
* 60,
276 &vty
->t_confirmed_commit_timeout
);
278 /* Accept commit confirmation. */
279 vty_out(vty
, "%% Commit complete.\n\n");
280 nb_cli_confirmed_commit_clean(vty
);
285 if (vty_exclusive_lock
!= NULL
&& vty_exclusive_lock
!= vty
) {
286 vty_out(vty
, "%% Configuration is locked by another VTY.\n\n");
290 /* "force" parameter. */
291 if (!force
&& nb_candidate_needs_update(vty
->candidate_config
)) {
293 "%% Candidate configuration needs to be updated before commit.\n\n");
295 "Use the \"update\" command or \"commit force\".\n");
299 /* "confirm" parameter. */
300 if (confirmed_timeout
) {
301 vty
->confirmed_commit_rollback
= nb_config_dup(running_config
);
303 vty
->t_confirmed_commit_timeout
= NULL
;
304 thread_add_timer(master
, nb_cli_confirmed_commit_timeout
, vty
,
305 confirmed_timeout
* 60,
306 &vty
->t_confirmed_commit_timeout
);
309 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
, true,
310 comment
, &transaction_id
);
312 /* Map northbound return code to CLI return code. */
315 nb_config_replace(vty
->candidate_config_base
, running_config
,
318 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
321 case NB_ERR_NO_CHANGES
:
322 vty_out(vty
, "%% No configuration changes to commit.\n\n");
326 "%% Failed to commit candidate configuration: %s.\n\n",
328 vty_out(vty
, "Please check the logs for more details.\n");
333 static int nb_cli_candidate_load_file(struct vty
*vty
,
334 enum nb_cfg_format format
,
335 struct yang_translator
*translator
,
336 const char *path
, bool replace
)
338 struct nb_config
*loaded_config
= NULL
;
339 struct lyd_node
*dnode
;
340 struct ly_ctx
*ly_ctx
;
344 case NB_CFG_FMT_CMDS
:
345 loaded_config
= nb_config_new(NULL
);
346 if (!vty_read_config(loaded_config
, path
, config_default
)) {
347 vty_out(vty
, "%% Failed to load configuration.\n\n");
349 "Please check the logs for more details.\n");
350 nb_config_free(loaded_config
);
354 case NB_CFG_FMT_JSON
:
356 ly_format
= (format
== NB_CFG_FMT_JSON
) ? LYD_JSON
: LYD_XML
;
358 ly_ctx
= translator
? translator
->ly_ctx
: ly_native_ctx
;
359 dnode
= lyd_parse_path(ly_ctx
, path
, ly_format
, LYD_OPT_EDIT
);
361 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_parse_path() failed",
363 vty_out(vty
, "%% Failed to load configuration:\n\n");
364 vty_show_libyang_errors(vty
, ly_ctx
);
368 && yang_translate_dnode(translator
,
369 YANG_TRANSLATE_TO_NATIVE
, &dnode
)
370 != YANG_TRANSLATE_SUCCESS
) {
371 vty_out(vty
, "%% Failed to translate configuration\n");
372 yang_dnode_free(dnode
);
375 loaded_config
= nb_config_new(dnode
);
380 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
381 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
384 "%% Failed to merge the loaded configuration:\n\n");
385 vty_show_libyang_errors(vty
, ly_native_ctx
);
392 static int nb_cli_candidate_load_transaction(struct vty
*vty
,
393 uint32_t transaction_id
,
396 struct nb_config
*loaded_config
;
398 loaded_config
= nb_db_transaction_load(transaction_id
);
399 if (!loaded_config
) {
400 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
406 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
407 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
410 "%% Failed to merge the loaded configuration:\n\n");
411 vty_show_libyang_errors(vty
, ly_native_ctx
);
418 void nb_cli_show_dnode_cmds(struct vty
*vty
, struct lyd_node
*root
,
421 struct lyd_node
*next
, *child
;
423 LY_TREE_DFS_BEGIN (root
, next
, child
) {
424 struct nb_node
*nb_node
;
426 nb_node
= child
->schema
->priv
;
427 if (!nb_node
->cbs
.cli_show
)
430 /* Skip default values. */
431 if (!with_defaults
&& yang_dnode_is_default_recursive(child
))
434 (*nb_node
->cbs
.cli_show
)(vty
, child
, with_defaults
);
436 LY_TREE_DFS_END(root
, next
, child
);
440 static void nb_cli_show_config_cmds(struct vty
*vty
, struct nb_config
*config
,
443 struct lyd_node
*root
;
445 vty_out(vty
, "Configuration:\n");
447 vty_out(vty
, "frr version %s\n", FRR_VER_SHORT
);
448 vty_out(vty
, "frr defaults %s\n", DFLT_NAME
);
450 LY_TREE_FOR (config
->dnode
, root
)
451 nb_cli_show_dnode_cmds(vty
, root
, with_defaults
);
454 vty_out(vty
, "end\n");
457 static int nb_cli_show_config_libyang(struct vty
*vty
, LYD_FORMAT format
,
458 struct nb_config
*config
,
459 struct yang_translator
*translator
,
462 struct lyd_node
*dnode
;
466 dnode
= yang_dnode_dup(config
->dnode
);
468 && yang_translate_dnode(translator
, YANG_TRANSLATE_FROM_NATIVE
,
470 != YANG_TRANSLATE_SUCCESS
) {
471 vty_out(vty
, "%% Failed to translate configuration\n");
472 yang_dnode_free(dnode
);
476 SET_FLAG(options
, LYP_FORMAT
| LYP_WITHSIBLINGS
);
478 SET_FLAG(options
, LYP_WD_ALL
);
480 SET_FLAG(options
, LYP_WD_TRIM
);
482 if (lyd_print_mem(&strp
, dnode
, format
, options
) == 0 && strp
) {
483 vty_out(vty
, "%s", strp
);
487 yang_dnode_free(dnode
);
492 static int nb_cli_show_config(struct vty
*vty
, struct nb_config
*config
,
493 enum nb_cfg_format format
,
494 struct yang_translator
*translator
,
498 case NB_CFG_FMT_CMDS
:
499 nb_cli_show_config_cmds(vty
, config
, with_defaults
);
501 case NB_CFG_FMT_JSON
:
502 return nb_cli_show_config_libyang(vty
, LYD_JSON
, config
,
503 translator
, with_defaults
);
505 return nb_cli_show_config_libyang(vty
, LYD_XML
, config
,
506 translator
, with_defaults
);
512 static int nb_write_config(struct nb_config
*config
, enum nb_cfg_format format
,
513 struct yang_translator
*translator
, char *path
,
517 struct vty
*file_vty
;
520 snprintf(path
, pathlen
, "/tmp/frr.tmp.XXXXXXXX");
523 flog_warn(EC_LIB_SYSTEM_CALL
, "%s: mkstemp() failed: %s",
524 __func__
, safe_strerror(errno
));
528 /* Make vty for configuration file. */
529 file_vty
= vty_new();
531 file_vty
->type
= VTY_FILE
;
533 ret
= nb_cli_show_config(file_vty
, config
, format
, translator
,
540 static int nb_cli_show_config_compare(struct vty
*vty
,
541 struct nb_config
*config1
,
542 struct nb_config
*config2
,
543 enum nb_cfg_format format
,
544 struct yang_translator
*translator
)
546 char config1_path
[256];
547 char config2_path
[256];
548 char command
[BUFSIZ
];
553 if (nb_write_config(config1
, format
, translator
, config1_path
,
554 sizeof(config1_path
))
556 vty_out(vty
, "%% Failed to process configurations.\n\n");
559 if (nb_write_config(config2
, format
, translator
, config2_path
,
560 sizeof(config2_path
))
562 vty_out(vty
, "%% Failed to process configurations.\n\n");
563 unlink(config1_path
);
567 snprintf(command
, sizeof(command
), "diff -u %s %s", config1_path
,
569 fp
= popen(command
, "r");
571 vty_out(vty
, "%% Failed to generate configuration diff.\n\n");
572 unlink(config1_path
);
573 unlink(config2_path
);
576 /* Print diff line by line. */
577 while (fgets(line
, sizeof(line
), fp
) != NULL
) {
580 vty_out(vty
, "%s", line
);
584 unlink(config1_path
);
585 unlink(config2_path
);
590 /* Configure exclusively from this terminal. */
591 DEFUN (config_exclusive
,
592 config_exclusive_cmd
,
593 "configure exclusive",
594 "Configuration from vty interface\n"
595 "Configure exclusively from this terminal\n")
597 return vty_config_enter(vty
, true, true);
600 /* Configure using a private candidate configuration. */
601 DEFUN (config_private
,
604 "Configuration from vty interface\n"
605 "Configure using a private candidate configuration\n")
607 return vty_config_enter(vty
, true, false);
610 DEFPY (config_commit
,
612 "commit [{force$force|confirmed (1-60)}]",
613 "Commit changes into the running configuration\n"
614 "Force commit even if the candidate is outdated\n"
615 "Rollback this commit unless there is a confirming commit\n"
616 "Timeout in minutes for the commit to be confirmed\n")
618 return nb_cli_commit(vty
, !!force
, confirmed
, NULL
);
621 DEFPY (config_commit_comment
,
622 config_commit_comment_cmd
,
623 "commit [{force$force|confirmed (1-60)}] comment LINE...",
624 "Commit changes into the running configuration\n"
625 "Force commit even if the candidate is outdated\n"
626 "Rollback this commit unless there is a confirming commit\n"
627 "Timeout in minutes for the commit to be confirmed\n"
628 "Assign a comment to this commit\n"
629 "Comment for this commit (Max 80 characters)\n")
635 argv_find(argv
, argc
, "LINE", &idx
);
636 comment
= argv_concat(argv
, argc
, idx
);
637 ret
= nb_cli_commit(vty
, !!force
, confirmed
, comment
);
638 XFREE(MTYPE_TMP
, comment
);
643 DEFPY (config_commit_check
,
644 config_commit_check_cmd
,
646 "Commit changes into the running configuration\n"
647 "Check if the configuration changes are valid\n")
651 ret
= nb_candidate_validate(vty
->candidate_config
);
654 "%% Failed to validate candidate configuration.\n\n");
655 vty_show_libyang_errors(vty
, ly_native_ctx
);
659 vty_out(vty
, "%% Candidate configuration validated successfully.\n\n");
664 DEFPY (config_update
,
667 "Update candidate configuration\n")
669 if (!nb_candidate_needs_update(vty
->candidate_config
)) {
670 vty_out(vty
, "%% Update is not necessary.\n\n");
674 if (nb_candidate_update(vty
->candidate_config
) != NB_OK
) {
676 "%% Failed to update the candidate configuration.\n\n");
677 vty_out(vty
, "Please check the logs for more details.\n");
681 nb_config_replace(vty
->candidate_config_base
, running_config
, true);
683 vty_out(vty
, "%% Candidate configuration updated successfully.\n\n");
688 DEFPY (config_discard
,
691 "Discard changes in the candidate configuration\n")
693 nb_config_replace(vty
->candidate_config
, vty
->candidate_config_base
,
703 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
704 |transaction (1-4294967296)$tid\
707 "Configuration related settings\n"
708 "Load configuration into candidate\n"
709 "Load configuration file into candidate\n"
710 "Load configuration file in JSON format\n"
711 "Load configuration file in XML format\n"
712 "Translate configuration file\n"
713 "YANG module translator\n"
714 "Configuration file name (full path)\n"
715 "Load configuration from transaction into candidate\n"
717 "Replace instead of merge\n")
720 enum nb_cfg_format format
;
721 struct yang_translator
*translator
= NULL
;
724 format
= NB_CFG_FMT_JSON
;
726 format
= NB_CFG_FMT_XML
;
728 format
= NB_CFG_FMT_CMDS
;
730 if (translator_family
) {
731 translator
= yang_translator_find(translator_family
);
734 "%% Module translator \"%s\" not found\n",
740 return nb_cli_candidate_load_file(vty
, format
, translator
,
741 filename
, !!replace
);
744 return nb_cli_candidate_load_transaction(vty
, tid
, !!replace
);
747 DEFPY (show_config_running
,
748 show_config_running_cmd
,
749 "show configuration running\
750 [<json$json|xml$xml> [translate WORD$translator_family]]\
751 [with-defaults$with_defaults]",
753 "Configuration information\n"
754 "Running configuration\n"
755 "Change output format to JSON\n"
756 "Change output format to XML\n"
758 "YANG module translator\n"
759 "Show default values\n")
762 enum nb_cfg_format format
;
763 struct yang_translator
*translator
= NULL
;
766 format
= NB_CFG_FMT_JSON
;
768 format
= NB_CFG_FMT_XML
;
770 format
= NB_CFG_FMT_CMDS
;
772 if (translator_family
) {
773 translator
= yang_translator_find(translator_family
);
775 vty_out(vty
, "%% Module translator \"%s\" not found\n",
781 nb_cli_show_config(vty
, running_config
, format
, translator
,
787 DEFPY (show_config_candidate
,
788 show_config_candidate_cmd
,
789 "show configuration candidate\
790 [<json$json|xml$xml> [translate WORD$translator_family]]\
792 with-defaults$with_defaults\
796 "Configuration information\n"
797 "Candidate configuration\n"
798 "Change output format to JSON\n"
799 "Change output format to XML\n"
801 "YANG module translator\n"
802 "Show default values\n"
803 "Show changes applied in the candidate configuration\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",
826 return nb_cli_show_config_compare(
827 vty
, vty
->candidate_config_base
, vty
->candidate_config
,
830 nb_cli_show_config(vty
, vty
->candidate_config
, format
, translator
,
836 DEFPY (show_config_candidate_section
,
837 show_config_candidate_section_cmd
,
841 struct lyd_node
*dnode
;
843 /* Top-level configuration node, display everything. */
844 if (vty
->xpath_index
== 0)
845 return nb_cli_show_config(vty
, vty
->candidate_config
,
846 NB_CFG_FMT_CMDS
, NULL
, false);
848 /* Display only the current section of the candidate configuration. */
849 dnode
= yang_dnode_get(vty
->candidate_config
->dnode
, VTY_CURR_XPATH
);
851 /* Shouldn't happen. */
854 nb_cli_show_dnode_cmds(vty
, dnode
, 0);
860 DEFPY (show_config_compare
,
861 show_config_compare_cmd
,
862 "show configuration compare\
864 candidate$c1_candidate\
866 |transaction (1-4294967296)$c1_tid\
869 candidate$c2_candidate\
871 |transaction (1-4294967296)$c2_tid\
873 [<json$json|xml$xml> [translate WORD$translator_family]]",
875 "Configuration information\n"
876 "Compare two different configurations\n"
877 "Candidate configuration\n"
878 "Running configuration\n"
879 "Configuration transaction\n"
881 "Candidate configuration\n"
882 "Running configuration\n"
883 "Configuration transaction\n"
885 "Change output format to JSON\n"
886 "Change output format to XML\n"
888 "YANG module translator\n")
890 enum nb_cfg_format format
;
891 struct yang_translator
*translator
= NULL
;
892 struct nb_config
*config1
, *config_transaction1
= NULL
;
893 struct nb_config
*config2
, *config_transaction2
= NULL
;
894 int ret
= CMD_WARNING
;
897 config1
= vty
->candidate_config
;
899 config1
= running_config
;
901 config_transaction1
= nb_db_transaction_load(c1_tid
);
902 if (!config_transaction1
) {
903 vty_out(vty
, "%% Transaction %u does not exist\n\n",
904 (unsigned int)c1_tid
);
907 config1
= config_transaction1
;
911 config2
= vty
->candidate_config
;
913 config2
= running_config
;
915 config_transaction2
= nb_db_transaction_load(c2_tid
);
916 if (!config_transaction2
) {
917 vty_out(vty
, "%% Transaction %u does not exist\n\n",
918 (unsigned int)c2_tid
);
921 config2
= config_transaction2
;
925 format
= NB_CFG_FMT_JSON
;
927 format
= NB_CFG_FMT_XML
;
929 format
= NB_CFG_FMT_CMDS
;
931 if (translator_family
) {
932 translator
= yang_translator_find(translator_family
);
934 vty_out(vty
, "%% Module translator \"%s\" not found\n",
940 ret
= nb_cli_show_config_compare(vty
, config1
, config2
, format
,
943 if (config_transaction1
)
944 nb_config_free(config_transaction1
);
945 if (config_transaction2
)
946 nb_config_free(config_transaction2
);
952 * Stripped down version of the "show configuration compare" command.
953 * The "candidate" option is not present so the command can be installed in
956 ALIAS (show_config_compare
,
957 show_config_compare_without_candidate_cmd
,
958 "show configuration compare\
961 |transaction (1-4294967296)$c1_tid\
965 |transaction (1-4294967296)$c2_tid\
967 [<json$json|xml$xml> [translate WORD$translator_family]]",
969 "Configuration information\n"
970 "Compare two different configurations\n"
971 "Running configuration\n"
972 "Configuration transaction\n"
974 "Running configuration\n"
975 "Configuration transaction\n"
977 "Change output format to JSON\n"
978 "Change output format to XML\n"
980 "YANG module translator\n")
982 DEFPY (clear_config_transactions
,
983 clear_config_transactions_cmd
,
984 "clear configuration transactions oldest (1-100)$n",
986 "Configuration activity\n"
987 "Delete transactions from the transactions log\n"
988 "Delete oldest <n> transactions\n"
989 "Number of transactions to delete\n")
991 #ifdef HAVE_CONFIG_ROLLBACKS
992 if (nb_db_clear_transactions(n
) != NB_OK
) {
993 vty_out(vty
, "%% Failed to delete transactions.\n\n");
998 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
999 #endif /* HAVE_CONFIG_ROLLBACKS */
1004 DEFPY (config_database_max_transactions
,
1005 config_database_max_transactions_cmd
,
1006 "configuration database max-transactions (1-100)$max",
1007 "Configuration related settings\n"
1008 "Configuration database\n"
1009 "Set the maximum number of transactions to store\n"
1010 "Number of transactions\n")
1012 #ifdef HAVE_CONFIG_ROLLBACKS
1013 if (nb_db_set_max_transactions(max
) != NB_OK
) {
1015 "%% Failed to update the maximum number of transactions.\n\n");
1019 "%% Maximum number of transactions updated successfully.\n\n");
1022 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1023 #endif /* HAVE_CONFIG_ROLLBACKS */
1028 DEFPY (yang_module_translator_load
,
1029 yang_module_translator_load_cmd
,
1030 "yang module-translator load FILENAME$filename",
1031 "YANG related settings\n"
1032 "YANG module translator\n"
1033 "Load YANG module translator\n"
1034 "File name (full path)\n")
1036 struct yang_translator
*translator
;
1038 translator
= yang_translator_load(filename
);
1040 vty_out(vty
, "%% Failed to load \"%s\"\n\n", filename
);
1041 vty_out(vty
, "Please check the logs for more details.\n");
1045 vty_out(vty
, "%% Module translator \"%s\" loaded successfully.\n\n",
1046 translator
->family
);
1051 DEFPY (yang_module_translator_unload_family
,
1052 yang_module_translator_unload_cmd
,
1053 "yang module-translator unload WORD$translator_family",
1054 "YANG related settings\n"
1055 "YANG module translator\n"
1056 "Unload YANG module translator\n"
1057 "Name of the module translator\n")
1059 struct yang_translator
*translator
;
1061 translator
= yang_translator_find(translator_family
);
1063 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1068 yang_translator_unload(translator
);
1073 #ifdef HAVE_CONFIG_ROLLBACKS
1074 static void nb_cli_show_transactions_cb(void *arg
, int transaction_id
,
1075 const char *client_name
,
1076 const char *date
, const char *comment
)
1078 struct ttable
*tt
= arg
;
1080 ttable_add_row(tt
, "%d|%s|%s|%s", transaction_id
, client_name
, date
,
1084 static int nb_cli_show_transactions(struct vty
*vty
)
1088 /* Prepare table. */
1089 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1090 ttable_add_row(tt
, "Transaction ID|Client|Date|Comment");
1091 tt
->style
.cell
.rpad
= 2;
1092 tt
->style
.corner
= '+';
1094 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1096 /* Fetch transactions from the northbound database. */
1097 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb
, tt
)
1100 "%% Failed to fetch configuration transactions.\n");
1104 /* Dump the generated table. */
1105 if (tt
->nrows
> 1) {
1108 table
= ttable_dump(tt
, "\n");
1109 vty_out(vty
, "%s\n", table
);
1110 XFREE(MTYPE_TMP
, table
);
1112 vty_out(vty
, "No configuration transactions to display.\n\n");
1118 #endif /* HAVE_CONFIG_ROLLBACKS */
1120 DEFPY (show_config_transaction
,
1121 show_config_transaction_cmd
,
1122 "show configuration transaction\
1124 (1-4294967296)$transaction_id\
1125 [<json$json|xml$xml> [translate WORD$translator_family]]\
1127 with-defaults$with_defaults\
1132 "Configuration information\n"
1133 "Configuration transaction\n"
1135 "Change output format to JSON\n"
1136 "Change output format to XML\n"
1137 "Translate output\n"
1138 "YANG module translator\n"
1139 "Show default values\n"
1140 "Show changes compared to the previous transaction\n")
1142 #ifdef HAVE_CONFIG_ROLLBACKS
1143 if (transaction_id
) {
1144 struct nb_config
*config
;
1145 enum nb_cfg_format format
;
1146 struct yang_translator
*translator
= NULL
;
1149 format
= NB_CFG_FMT_JSON
;
1151 format
= NB_CFG_FMT_XML
;
1153 format
= NB_CFG_FMT_CMDS
;
1155 if (translator_family
) {
1156 translator
= yang_translator_find(translator_family
);
1159 "%% Module translator \"%s\" not found\n",
1165 config
= nb_db_transaction_load(transaction_id
);
1167 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1168 (unsigned int)transaction_id
);
1173 struct nb_config
*prev_config
;
1176 /* NOTE: this can be NULL. */
1178 nb_db_transaction_load(transaction_id
- 1);
1180 ret
= nb_cli_show_config_compare(
1181 vty
, prev_config
, config
, format
, translator
);
1183 nb_config_free(prev_config
);
1184 nb_config_free(config
);
1189 nb_cli_show_config(vty
, config
, format
, translator
,
1191 nb_config_free(config
);
1196 return nb_cli_show_transactions(vty
);
1199 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1201 #endif /* HAVE_CONFIG_ROLLBACKS */
1204 static int nb_cli_oper_data_cb(const struct lys_node
*snode
,
1205 struct yang_translator
*translator
,
1206 struct yang_data
*data
, void *arg
)
1208 struct lyd_node
*dnode
= arg
;
1209 struct ly_ctx
*ly_ctx
;
1214 ret
= yang_translate_xpath(translator
,
1215 YANG_TRANSLATE_FROM_NATIVE
,
1216 data
->xpath
, sizeof(data
->xpath
));
1218 case YANG_TRANSLATE_SUCCESS
:
1220 case YANG_TRANSLATE_NOTFOUND
:
1222 case YANG_TRANSLATE_FAILURE
:
1226 ly_ctx
= translator
->ly_ctx
;
1228 ly_ctx
= ly_native_ctx
;
1231 dnode
= lyd_new_path(dnode
, ly_ctx
, data
->xpath
, (void *)data
->value
, 0,
1232 LYD_PATH_OPT_UPDATE
);
1233 if (!dnode
&& ly_errno
) {
1234 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
1240 yang_data_free(data
);
1244 yang_data_free(data
);
1248 DEFPY (show_yang_operational_data
,
1249 show_yang_operational_data_cmd
,
1250 "show yang operational-data XPATH$xpath\
1252 format <json$json|xml$xml>\
1253 |translate WORD$translator_family\
1256 "YANG information\n"
1257 "Show YANG operational data\n"
1258 "XPath expression specifying the YANG data path\n"
1259 "Set the output format\n"
1260 "JavaScript Object Notation\n"
1261 "Extensible Markup Language\n"
1262 "Translate operational data\n"
1263 "YANG module translator\n")
1266 struct yang_translator
*translator
= NULL
;
1267 struct ly_ctx
*ly_ctx
;
1268 struct lyd_node
*dnode
;
1276 if (translator_family
) {
1277 translator
= yang_translator_find(translator_family
);
1279 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1284 ly_ctx
= translator
->ly_ctx
;
1286 ly_ctx
= ly_native_ctx
;
1289 dnode
= yang_dnode_new(ly_ctx
, false);
1290 if (nb_oper_data_iterate(xpath
, translator
, 0, nb_cli_oper_data_cb
,
1293 vty_out(vty
, "%% Failed to fetch operational data.\n");
1294 yang_dnode_free(dnode
);
1297 lyd_validate(&dnode
, LYD_OPT_DATA
| LYD_OPT_DATA_NO_YANGLIB
, ly_ctx
);
1299 /* Display the data. */
1300 if (lyd_print_mem(&strp
, dnode
, format
,
1301 LYP_FORMAT
| LYP_WITHSIBLINGS
| LYP_WD_ALL
)
1304 vty_out(vty
, "%% Failed to display operational data.\n");
1305 yang_dnode_free(dnode
);
1308 vty_out(vty
, "%s", strp
);
1310 yang_dnode_free(dnode
);
1315 DEFPY (show_yang_module
,
1316 show_yang_module_cmd
,
1317 "show yang module [module-translator WORD$translator_family]",
1319 "YANG information\n"
1320 "Show loaded modules\n"
1321 "YANG module translator\n"
1322 "YANG module translator\n")
1324 struct ly_ctx
*ly_ctx
;
1325 struct yang_translator
*translator
= NULL
;
1326 const struct lys_module
*module
;
1330 if (translator_family
) {
1331 translator
= yang_translator_find(translator_family
);
1333 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1337 ly_ctx
= translator
->ly_ctx
;
1339 ly_ctx
= ly_native_ctx
;
1341 /* Prepare table. */
1342 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1343 ttable_add_row(tt
, "Module|Version|Revision|Flags|Namespace");
1344 tt
->style
.cell
.rpad
= 2;
1345 tt
->style
.corner
= '+';
1347 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1349 while ((module
= ly_ctx_get_module_iter(ly_ctx
, &idx
))) {
1352 snprintf(flags
, sizeof(flags
), "%c%c",
1353 module
->implemented
? 'I' : ' ',
1354 (module
->deviated
== 1) ? 'D' : ' ');
1356 ttable_add_row(tt
, "%s|%s|%s|%s|%s", module
->name
,
1357 (module
->version
== 2) ? "1.1" : "1.0",
1358 (module
->rev_size
> 0) ? module
->rev
[0].date
1363 /* Dump the generated table. */
1364 if (tt
->nrows
> 1) {
1367 vty_out(vty
, " Flags: I - Implemented, D - Deviated\n\n");
1369 table
= ttable_dump(tt
, "\n");
1370 vty_out(vty
, "%s\n", table
);
1371 XFREE(MTYPE_TMP
, table
);
1373 vty_out(vty
, "No YANG modules to display.\n\n");
1380 DEFPY (show_yang_module_detail
,
1381 show_yang_module_detail_cmd
,
1383 [module-translator WORD$translator_family]\
1384 WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1386 "YANG information\n"
1387 "Show loaded modules\n"
1388 "YANG module translator\n"
1389 "YANG module translator\n"
1391 "Display summary information about the module\n"
1392 "Display module in the tree (RFC 8340) format\n"
1393 "Display module in the YANG format\n"
1394 "Display module in the YIN format\n")
1396 struct ly_ctx
*ly_ctx
;
1397 struct yang_translator
*translator
= NULL
;
1398 const struct lys_module
*module
;
1399 LYS_OUTFORMAT format
;
1402 if (translator_family
) {
1403 translator
= yang_translator_find(translator_family
);
1405 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1409 ly_ctx
= translator
->ly_ctx
;
1411 ly_ctx
= ly_native_ctx
;
1413 module
= ly_ctx_get_module(ly_ctx
, module_name
, NULL
, 0);
1415 vty_out(vty
, "%% Module \"%s\" not found\n", module_name
);
1420 format
= LYS_OUT_YANG
;
1422 format
= LYS_OUT_YIN
;
1424 format
= LYS_OUT_TREE
;
1426 format
= LYS_OUT_INFO
;
1428 if (lys_print_mem(&strp
, module
, format
, NULL
, 0, 0) == 0) {
1429 vty_out(vty
, "%s\n", strp
);
1433 vty_out(vty
, "%% Error generating module information\n");
1440 DEFPY (show_yang_module_translator
,
1441 show_yang_module_translator_cmd
,
1442 "show yang module-translator",
1444 "YANG information\n"
1445 "Show loaded YANG module translators\n")
1447 struct yang_translator
*translator
;
1450 /* Prepare table. */
1451 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1452 ttable_add_row(tt
, "Family|Module|Deviations|Coverage (%%)");
1453 tt
->style
.cell
.rpad
= 2;
1454 tt
->style
.corner
= '+';
1456 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1458 RB_FOREACH (translator
, yang_translators
, &yang_translators
) {
1459 struct yang_tmodule
*tmodule
;
1460 struct listnode
*ln
;
1462 for (ALL_LIST_ELEMENTS_RO(translator
->modules
, ln
, tmodule
)) {
1463 ttable_add_row(tt
, "%s|%s|%s|%.2f", translator
->family
,
1464 tmodule
->module
->name
,
1465 tmodule
->deviations
->name
,
1470 /* Dump the generated table. */
1471 if (tt
->nrows
> 1) {
1474 table
= ttable_dump(tt
, "\n");
1475 vty_out(vty
, "%s\n", table
);
1476 XFREE(MTYPE_TMP
, table
);
1478 vty_out(vty
, "No YANG module translators to display.\n\n");
1485 #ifdef HAVE_CONFIG_ROLLBACKS
1486 static int nb_cli_rollback_configuration(struct vty
*vty
,
1487 uint32_t transaction_id
)
1489 struct nb_config
*candidate
;
1493 candidate
= nb_db_transaction_load(transaction_id
);
1495 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1500 snprintf(comment
, sizeof(comment
), "Rollback to transaction %u",
1503 ret
= nb_candidate_commit(candidate
, NB_CLIENT_CLI
, true, comment
,
1505 nb_config_free(candidate
);
1509 "%% Configuration was successfully rolled back.\n\n");
1511 case NB_ERR_NO_CHANGES
:
1513 "%% Aborting - no configuration changes detected.\n\n");
1516 vty_out(vty
, "%% Rollback failed.\n\n");
1517 vty_out(vty
, "Please check the logs for more details.\n");
1521 #endif /* HAVE_CONFIG_ROLLBACKS */
1523 DEFPY (rollback_config
,
1524 rollback_config_cmd
,
1525 "rollback configuration (1-4294967296)$transaction_id",
1526 "Rollback to a previous state\n"
1527 "Running configuration\n"
1530 #ifdef HAVE_CONFIG_ROLLBACKS
1531 return nb_cli_rollback_configuration(vty
, transaction_id
);
1534 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1536 #endif /* HAVE_CONFIG_ROLLBACKS */
1539 /* Debug CLI commands. */
1544 "Northbound Debugging\n")
1546 debug_northbound
= 1;
1553 "no debug northbound",
1555 "Northbound Debugging\n")
1557 debug_northbound
= 0;
1562 static int nb_debug_config_write(struct vty
*vty
)
1564 if (debug_northbound
)
1565 vty_out(vty
, "debug northbound\n");
1570 static struct cmd_node nb_debug_node
= {NORTHBOUND_DEBUG_NODE
, "", 1};
1572 void nb_cli_install_default(int node
)
1574 install_element(node
, &show_config_candidate_section_cmd
);
1576 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL
)
1579 install_element(node
, &config_commit_cmd
);
1580 install_element(node
, &config_commit_comment_cmd
);
1581 install_element(node
, &config_commit_check_cmd
);
1582 install_element(node
, &config_update_cmd
);
1583 install_element(node
, &config_discard_cmd
);
1584 install_element(node
, &show_config_running_cmd
);
1585 install_element(node
, &show_config_candidate_cmd
);
1586 install_element(node
, &show_config_compare_cmd
);
1587 install_element(node
, &show_config_transaction_cmd
);
1590 /* YANG module autocomplete. */
1591 static void yang_module_autocomplete(vector comps
, struct cmd_token
*token
)
1593 const struct lys_module
*module
;
1594 struct yang_translator
*module_tr
;
1598 while ((module
= ly_ctx_get_module_iter(ly_native_ctx
, &idx
)))
1599 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1601 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
) {
1603 while ((module
= ly_ctx_get_module_iter(module_tr
->ly_ctx
,
1606 XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1610 /* YANG module translator autocomplete. */
1611 static void yang_translator_autocomplete(vector comps
, struct cmd_token
*token
)
1613 struct yang_translator
*module_tr
;
1615 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
)
1616 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module_tr
->family
));
1619 static const struct cmd_variable_handler yang_var_handlers
[] = {
1620 {.varname
= "module_name", .completions
= yang_module_autocomplete
},
1621 {.varname
= "translator_family",
1622 .completions
= yang_translator_autocomplete
},
1623 {.completions
= NULL
}};
1625 void nb_cli_init(struct thread_master
*tm
)
1629 /* Initialize the shared candidate configuration. */
1630 vty_shared_candidate_config
= nb_config_new(NULL
);
1632 /* Install debug commands */
1633 install_node(&nb_debug_node
, nb_debug_config_write
);
1634 install_element(ENABLE_NODE
, &debug_nb_cmd
);
1635 install_element(ENABLE_NODE
, &no_debug_nb_cmd
);
1636 install_element(CONFIG_NODE
, &debug_nb_cmd
);
1637 install_element(CONFIG_NODE
, &no_debug_nb_cmd
);
1639 /* Install commands specific to the transaction-base mode. */
1640 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL
) {
1641 install_element(ENABLE_NODE
, &config_exclusive_cmd
);
1642 install_element(ENABLE_NODE
, &config_private_cmd
);
1643 install_element(ENABLE_NODE
, &show_config_running_cmd
);
1644 install_element(ENABLE_NODE
,
1645 &show_config_compare_without_candidate_cmd
);
1646 install_element(ENABLE_NODE
, &show_config_transaction_cmd
);
1647 install_element(ENABLE_NODE
, &rollback_config_cmd
);
1648 install_element(ENABLE_NODE
, &clear_config_transactions_cmd
);
1650 install_element(CONFIG_NODE
, &config_load_cmd
);
1651 install_element(CONFIG_NODE
,
1652 &config_database_max_transactions_cmd
);
1655 /* Other commands. */
1656 install_element(CONFIG_NODE
, &yang_module_translator_load_cmd
);
1657 install_element(CONFIG_NODE
, &yang_module_translator_unload_cmd
);
1658 install_element(ENABLE_NODE
, &show_yang_operational_data_cmd
);
1659 install_element(ENABLE_NODE
, &show_yang_module_cmd
);
1660 install_element(ENABLE_NODE
, &show_yang_module_detail_cmd
);
1661 install_element(ENABLE_NODE
, &show_yang_module_translator_cmd
);
1662 cmd_variable_handler_register(yang_var_handlers
);
1665 void nb_cli_terminate(void)
1667 nb_config_free(vty_shared_candidate_config
);