]> git.proxmox.com Git - mirror_frr.git/blame - lib/northbound_cli.c
lib: northbound cli show/cmd functions must not modify data nodes
[mirror_frr.git] / lib / northbound_cli.c
CommitLineData
1c2facd1
RW
1/*
2 * Copyright (C) 2018 NetDEF, Inc.
3 * Renato Westphal
4 *
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)
8 * any later version.
9 *
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
13 * more details.
14 *
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
18 */
19
20#include <zebra.h>
21
22#include "libfrr.h"
09781197 23#include "lib/version.h"
ac4adef4 24#include "defaults.h"
1c2facd1
RW
25#include "log.h"
26#include "lib_errors.h"
27#include "command.h"
28#include "termtable.h"
29#include "db.h"
9eb2c0a1 30#include "debug.h"
1c2facd1
RW
31#include "yang_translator.h"
32#include "northbound.h"
33#include "northbound_cli.h"
34#include "northbound_db.h"
35#ifndef VTYSH_EXTRACT_PL
36#include "lib/northbound_cli_clippy.c"
37#endif
38
9eb2c0a1
RW
39struct debug nb_dbg_cbs_config = {0, "Northbound callbacks: configuration"};
40struct debug nb_dbg_cbs_state = {0, "Northbound callbacks: state"};
41struct debug nb_dbg_cbs_rpc = {0, "Northbound callbacks: RPCs"};
42struct debug nb_dbg_notif = {0, "Northbound notifications"};
43struct debug nb_dbg_events = {0, "Northbound events"};
62ae9ade 44struct debug nb_dbg_libyang = {0, "libyang debugging"};
9eb2c0a1 45
1c2facd1 46struct nb_config *vty_shared_candidate_config;
fbdc1c0a 47static struct thread_master *master;
1c2facd1 48
df5eda3d 49static void vty_show_nb_errors(struct vty *vty, int error, const char *errmsg)
1c2facd1 50{
df5eda3d
RW
51 vty_out(vty, "Error type: %s\n", nb_err_name(error));
52 if (strlen(errmsg) > 0)
53 vty_out(vty, "Error description: %s\n", errmsg);
1c2facd1
RW
54}
55
b855e95f
RW
56static int nb_cli_classic_commit(struct vty *vty)
57{
58 struct nb_context context = {};
59 char errmsg[BUFSIZ] = {0};
60 int ret;
61
62 context.client = NB_CLIENT_CLI;
63 context.user = vty;
64 ret = nb_candidate_commit(&context, vty->candidate_config, true, NULL,
65 NULL, errmsg, sizeof(errmsg));
1bd43069
RW
66 switch (ret) {
67 case NB_OK:
68 /* Successful commit. Print warnings (if any). */
69 if (strlen(errmsg) > 0)
70 vty_out(vty, "%s\n", errmsg);
71 break;
72 case NB_ERR_NO_CHANGES:
73 break;
74 default:
b855e95f
RW
75 vty_out(vty, "%% Configuration failed.\n\n");
76 vty_show_nb_errors(vty, ret, errmsg);
fd396924 77 if (vty->pending_commit)
b855e95f
RW
78 vty_out(vty,
79 "The following commands were dynamically grouped into the same transaction and rejected:\n%s",
80 vty->pending_cmds_buf);
81
82 /* Regenerate candidate for consistency. */
83 nb_config_replace(vty->candidate_config, running_config, true);
84 return CMD_WARNING_CONFIG_FAILED;
1bd43069 85 }
b855e95f
RW
86
87 return CMD_SUCCESS;
88}
89
90static void nb_cli_pending_commit_clear(struct vty *vty)
91{
fd396924 92 vty->pending_commit = 0;
b855e95f
RW
93 XFREE(MTYPE_TMP, vty->pending_cmds_buf);
94 vty->pending_cmds_buflen = 0;
95 vty->pending_cmds_bufpos = 0;
96}
97
fd396924 98int nb_cli_pending_commit_check(struct vty *vty)
b855e95f 99{
fd396924 100 int ret = CMD_SUCCESS;
b855e95f 101
fd396924
CH
102 if (vty->pending_commit) {
103 ret = nb_cli_classic_commit(vty);
b855e95f
RW
104 nb_cli_pending_commit_clear(vty);
105 }
b855e95f 106
fd396924 107 return ret;
b855e95f
RW
108}
109
110static int nb_cli_schedule_command(struct vty *vty)
111{
112 /* Append command to dynamically sized buffer of scheduled commands. */
113 if (!vty->pending_cmds_buf) {
114 vty->pending_cmds_buflen = 4096;
115 vty->pending_cmds_buf =
116 XCALLOC(MTYPE_TMP, vty->pending_cmds_buflen);
117 }
118 if ((strlen(vty->buf) + 3)
119 > (vty->pending_cmds_buflen - vty->pending_cmds_bufpos)) {
120 vty->pending_cmds_buflen *= 2;
121 vty->pending_cmds_buf =
122 XREALLOC(MTYPE_TMP, vty->pending_cmds_buf,
123 vty->pending_cmds_buflen);
124 }
125 strlcat(vty->pending_cmds_buf, "- ", vty->pending_cmds_buflen);
126 vty->pending_cmds_bufpos = strlcat(vty->pending_cmds_buf, vty->buf,
127 vty->pending_cmds_buflen);
128
129 /* Schedule the commit operation. */
fd396924 130 vty->pending_commit = 1;
b855e95f
RW
131
132 return CMD_SUCCESS;
133}
134
a6233bfc
RW
135void nb_cli_enqueue_change(struct vty *vty, const char *xpath,
136 enum nb_operation operation, const char *value)
137{
138 struct vty_cfg_change *change;
139
140 if (vty->num_cfg_changes == VTY_MAXCFGCHANGES) {
141 /* Not expected to happen. */
142 vty_out(vty,
143 "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
144 VTY_MAXCFGCHANGES);
145 return;
146 }
147
148 change = &vty->cfg_changes[vty->num_cfg_changes++];
8427e0e6 149 strlcpy(change->xpath, xpath, sizeof(change->xpath));
a6233bfc
RW
150 change->operation = operation;
151 change->value = value;
152}
153
fd396924
CH
154static int nb_cli_apply_changes_internal(struct vty *vty,
155 const char *xpath_base,
156 bool clear_pending)
1c2facd1 157{
1c2facd1 158 bool error = false;
1c2facd1 159
fd396924
CH
160 if (xpath_base == NULL)
161 xpath_base = "";
3cb4162c 162
fd396924 163 VTY_CHECK_XPATH;
a6233bfc 164
1c2facd1 165 /* Edit candidate configuration. */
a6233bfc
RW
166 for (size_t i = 0; i < vty->num_cfg_changes; i++) {
167 struct vty_cfg_change *change = &vty->cfg_changes[i];
1c2facd1
RW
168 struct nb_node *nb_node;
169 char xpath[XPATH_MAXLEN];
170 struct yang_data *data;
b855e95f 171 int ret;
1c2facd1
RW
172
173 /* Handle relative XPaths. */
174 memset(xpath, 0, sizeof(xpath));
175 if (vty->xpath_index > 0
fd396924 176 && (xpath_base[0] == '.' || change->xpath[0] == '.'))
1c2facd1 177 strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
fd396924 178 if (xpath_base[0]) {
1c2facd1 179 if (xpath_base[0] == '.')
a6233bfc
RW
180 strlcat(xpath, xpath_base + 1, sizeof(xpath));
181 else
182 strlcat(xpath, xpath_base, sizeof(xpath));
1c2facd1
RW
183 }
184 if (change->xpath[0] == '.')
185 strlcat(xpath, change->xpath + 1, sizeof(xpath));
186 else
187 strlcpy(xpath, change->xpath, sizeof(xpath));
188
a6233bfc 189 /* Find the northbound node associated to the data path. */
1c2facd1
RW
190 nb_node = nb_node_find(xpath);
191 if (!nb_node) {
192 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
193 "%s: unknown data path: %s", __func__, xpath);
194 error = true;
c7c9103b 195 continue;
1c2facd1
RW
196 }
197
198 /* If the value is not set, get the default if it exists. */
199 if (change->value == NULL)
200 change->value = yang_snode_get_default(nb_node->snode);
201 data = yang_data_new(xpath, change->value);
202
203 /*
204 * Ignore "not found" errors when editing the candidate
205 * configuration.
206 */
c7c9103b 207 ret = nb_candidate_edit(vty->candidate_config, nb_node,
1c2facd1
RW
208 change->operation, xpath, NULL, data);
209 yang_data_free(data);
210 if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) {
211 flog_warn(
212 EC_LIB_NB_CANDIDATE_EDIT_ERROR,
213 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
214 __func__, nb_operation_name(change->operation),
215 xpath);
216 error = true;
c7c9103b 217 continue;
1c2facd1
RW
218 }
219 }
220
221 if (error) {
df5eda3d
RW
222 char buf[BUFSIZ];
223
c7c9103b
RW
224 /*
225 * Failure to edit the candidate configuration should never
226 * happen in practice, unless there's a bug in the code. When
227 * that happens, log the error but otherwise ignore it.
228 */
229 vty_out(vty, "%% Failed to edit configuration.\n\n");
df5eda3d
RW
230 vty_out(vty, "%s",
231 yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
1c2facd1
RW
232 }
233
b855e95f 234 /*
fd396924 235 * Maybe do an implicit commit when using the classic CLI mode.
b855e95f
RW
236 *
237 * NOTE: the implicit commit might be scheduled to run later when
238 * too many commands are being sent at the same time. This is a
239 * protection mechanism where multiple commands are grouped into the
240 * same configuration transaction, allowing them to be processed much
241 * faster.
242 */
1c2facd1 243 if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
fd396924
CH
244 if (clear_pending) {
245 if (vty->pending_commit)
246 return nb_cli_pending_commit_check(vty);
247 } else if (vty->pending_allowed)
b855e95f 248 return nb_cli_schedule_command(vty);
fd396924 249 assert(!vty->pending_commit);
b855e95f 250 return nb_cli_classic_commit(vty);
1c2facd1
RW
251 }
252
253 return CMD_SUCCESS;
254}
255
fd396924
CH
256int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
257{
258 char xpath_base[XPATH_MAXLEN] = {};
259
260 /* Parse the base XPath format string. */
261 if (xpath_base_fmt) {
262 va_list ap;
263
264 va_start(ap, xpath_base_fmt);
265 vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
266 va_end(ap);
267 }
268 return nb_cli_apply_changes_internal(vty, xpath_base, false);
269}
270
271int nb_cli_apply_changes_clear_pending(struct vty *vty,
272 const char *xpath_base_fmt, ...)
273{
274 char xpath_base[XPATH_MAXLEN] = {};
275
276 /* Parse the base XPath format string. */
277 if (xpath_base_fmt) {
278 va_list ap;
279
280 va_start(ap, xpath_base_fmt);
281 vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
282 va_end(ap);
283 }
284 return nb_cli_apply_changes_internal(vty, xpath_base, true);
285}
286
f63f5f19
CS
287int nb_cli_rpc(struct vty *vty, const char *xpath, struct list *input,
288 struct list *output)
1c2facd1
RW
289{
290 struct nb_node *nb_node;
291 int ret;
f63f5f19 292 char errmsg[BUFSIZ] = {0};
1c2facd1
RW
293
294 nb_node = nb_node_find(xpath);
295 if (!nb_node) {
296 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
297 "%s: unknown data path: %s", __func__, xpath);
298 return CMD_WARNING;
299 }
300
f63f5f19
CS
301 ret = nb_callback_rpc(nb_node, xpath, input, output, errmsg,
302 sizeof(errmsg));
1c2facd1
RW
303 switch (ret) {
304 case NB_OK:
305 return CMD_SUCCESS;
306 default:
f63f5f19
CS
307 if (strlen(errmsg))
308 vty_show_nb_errors(vty, ret, errmsg);
1c2facd1
RW
309 return CMD_WARNING;
310 }
311}
312
fbdc1c0a
RW
313void nb_cli_confirmed_commit_clean(struct vty *vty)
314{
50478845 315 thread_cancel(&vty->t_confirmed_commit_timeout);
fbdc1c0a
RW
316 nb_config_free(vty->confirmed_commit_rollback);
317 vty->confirmed_commit_rollback = NULL;
318}
319
320int nb_cli_confirmed_commit_rollback(struct vty *vty)
1c2facd1 321{
13d6b9c1 322 struct nb_context context = {};
1c2facd1 323 uint32_t transaction_id;
df5eda3d 324 char errmsg[BUFSIZ] = {0};
1c2facd1
RW
325 int ret;
326
fbdc1c0a 327 /* Perform the rollback. */
13d6b9c1
RW
328 context.client = NB_CLIENT_CLI;
329 context.user = vty;
fbdc1c0a 330 ret = nb_candidate_commit(
13d6b9c1 331 &context, vty->confirmed_commit_rollback, true,
fbdc1c0a 332 "Rollback to previous configuration - confirmed commit has timed out",
df5eda3d 333 &transaction_id, errmsg, sizeof(errmsg));
0fe5b904 334 if (ret == NB_OK) {
fbdc1c0a
RW
335 vty_out(vty,
336 "Rollback performed successfully (Transaction ID #%u).\n",
337 transaction_id);
0fe5b904
RW
338 /* Print warnings (if any). */
339 if (strlen(errmsg) > 0)
340 vty_out(vty, "%s\n", errmsg);
341 } else {
df5eda3d
RW
342 vty_out(vty,
343 "Failed to rollback to previous configuration.\n\n");
344 vty_show_nb_errors(vty, ret, errmsg);
345 }
fbdc1c0a
RW
346
347 return ret;
348}
349
350static int nb_cli_confirmed_commit_timeout(struct thread *thread)
351{
352 struct vty *vty = THREAD_ARG(thread);
353
354 /* XXX: broadcast this message to all logged-in users? */
355 vty_out(vty,
356 "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
357
358 nb_cli_confirmed_commit_rollback(vty);
359 nb_cli_confirmed_commit_clean(vty);
360
361 return 0;
362}
363
364static int nb_cli_commit(struct vty *vty, bool force,
365 unsigned int confirmed_timeout, char *comment)
366{
13d6b9c1 367 struct nb_context context = {};
f65fb6b4 368 uint32_t transaction_id = 0;
df5eda3d 369 char errmsg[BUFSIZ] = {0};
fbdc1c0a
RW
370 int ret;
371
372 /* Check if there's a pending confirmed commit. */
373 if (vty->t_confirmed_commit_timeout) {
374 if (confirmed_timeout) {
375 /* Reset timeout if "commit confirmed" is used again. */
376 vty_out(vty,
377 "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
378 confirmed_timeout);
379
50478845 380 thread_cancel(&vty->t_confirmed_commit_timeout);
fbdc1c0a
RW
381 thread_add_timer(master,
382 nb_cli_confirmed_commit_timeout, vty,
383 confirmed_timeout * 60,
384 &vty->t_confirmed_commit_timeout);
385 } else {
386 /* Accept commit confirmation. */
387 vty_out(vty, "%% Commit complete.\n\n");
388 nb_cli_confirmed_commit_clean(vty);
389 }
390 return CMD_SUCCESS;
391 }
392
fbdc1c0a 393 /* "force" parameter. */
1c2facd1
RW
394 if (!force && nb_candidate_needs_update(vty->candidate_config)) {
395 vty_out(vty,
396 "%% Candidate configuration needs to be updated before commit.\n\n");
397 vty_out(vty,
398 "Use the \"update\" command or \"commit force\".\n");
399 return CMD_WARNING;
400 }
401
fbdc1c0a
RW
402 /* "confirm" parameter. */
403 if (confirmed_timeout) {
8685be73 404 vty->confirmed_commit_rollback = nb_config_dup(running_config);
fbdc1c0a
RW
405
406 vty->t_confirmed_commit_timeout = NULL;
407 thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty,
408 confirmed_timeout * 60,
409 &vty->t_confirmed_commit_timeout);
410 }
411
13d6b9c1
RW
412 context.client = NB_CLIENT_CLI;
413 context.user = vty;
414 ret = nb_candidate_commit(&context, vty->candidate_config, true,
df5eda3d
RW
415 comment, &transaction_id, errmsg,
416 sizeof(errmsg));
1c2facd1
RW
417
418 /* Map northbound return code to CLI return code. */
419 switch (ret) {
420 case NB_OK:
8685be73
RW
421 nb_config_replace(vty->candidate_config_base, running_config,
422 true);
1c2facd1
RW
423 vty_out(vty,
424 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
425 transaction_id);
0fe5b904
RW
426 /* Print warnings (if any). */
427 if (strlen(errmsg) > 0)
428 vty_out(vty, "%s\n", errmsg);
1c2facd1
RW
429 return CMD_SUCCESS;
430 case NB_ERR_NO_CHANGES:
431 vty_out(vty, "%% No configuration changes to commit.\n\n");
432 return CMD_SUCCESS;
433 default:
434 vty_out(vty,
df5eda3d
RW
435 "%% Failed to commit candidate configuration.\n\n");
436 vty_show_nb_errors(vty, ret, errmsg);
1c2facd1
RW
437 return CMD_WARNING;
438 }
439}
440
441static int nb_cli_candidate_load_file(struct vty *vty,
442 enum nb_cfg_format format,
443 struct yang_translator *translator,
444 const char *path, bool replace)
445{
446 struct nb_config *loaded_config = NULL;
447 struct lyd_node *dnode;
448 struct ly_ctx *ly_ctx;
449 int ly_format;
df5eda3d 450 char buf[BUFSIZ];
3bb513c3 451 LY_ERR err;
1c2facd1
RW
452
453 switch (format) {
454 case NB_CFG_FMT_CMDS:
455 loaded_config = nb_config_new(NULL);
456 if (!vty_read_config(loaded_config, path, config_default)) {
457 vty_out(vty, "%% Failed to load configuration.\n\n");
458 vty_out(vty,
459 "Please check the logs for more details.\n");
460 nb_config_free(loaded_config);
461 return CMD_WARNING;
462 }
463 break;
464 case NB_CFG_FMT_JSON:
465 case NB_CFG_FMT_XML:
466 ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML;
467
468 ly_ctx = translator ? translator->ly_ctx : ly_native_ctx;
3bb513c3
CH
469 err = lyd_parse_data_path(ly_ctx, path, ly_format,
470 LYD_PARSE_ONLY | LYD_PARSE_NO_STATE,
471 0, &dnode);
472 if (err || !dnode) {
1c2facd1
RW
473 flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed",
474 __func__);
475 vty_out(vty, "%% Failed to load configuration:\n\n");
df5eda3d
RW
476 vty_out(vty, "%s",
477 yang_print_errors(ly_native_ctx, buf,
478 sizeof(buf)));
1c2facd1
RW
479 return CMD_WARNING;
480 }
481 if (translator
482 && yang_translate_dnode(translator,
483 YANG_TRANSLATE_TO_NATIVE, &dnode)
484 != YANG_TRANSLATE_SUCCESS) {
485 vty_out(vty, "%% Failed to translate configuration\n");
486 yang_dnode_free(dnode);
487 return CMD_WARNING;
488 }
489 loaded_config = nb_config_new(dnode);
490 break;
491 }
492
493 if (replace)
494 nb_config_replace(vty->candidate_config, loaded_config, false);
495 else if (nb_config_merge(vty->candidate_config, loaded_config, false)
496 != NB_OK) {
497 vty_out(vty,
498 "%% Failed to merge the loaded configuration:\n\n");
df5eda3d
RW
499 vty_out(vty, "%s",
500 yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
1c2facd1
RW
501 return CMD_WARNING;
502 }
503
504 return CMD_SUCCESS;
505}
506
507static int nb_cli_candidate_load_transaction(struct vty *vty,
508 uint32_t transaction_id,
509 bool replace)
510{
511 struct nb_config *loaded_config;
df5eda3d 512 char buf[BUFSIZ];
1c2facd1
RW
513
514 loaded_config = nb_db_transaction_load(transaction_id);
515 if (!loaded_config) {
516 vty_out(vty, "%% Transaction %u does not exist.\n\n",
517 transaction_id);
518 return CMD_WARNING;
519 }
520
521 if (replace)
522 nb_config_replace(vty->candidate_config, loaded_config, false);
523 else if (nb_config_merge(vty->candidate_config, loaded_config, false)
524 != NB_OK) {
525 vty_out(vty,
526 "%% Failed to merge the loaded configuration:\n\n");
df5eda3d
RW
527 vty_out(vty, "%s",
528 yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
1c2facd1
RW
529 return CMD_WARNING;
530 }
531
532 return CMD_SUCCESS;
533}
534
5e6a9350
RW
535/* Prepare the configuration for display. */
536void nb_cli_show_config_prepare(struct nb_config *config, bool with_defaults)
537{
b83d7134
RW
538 /* Nothing to do for daemons that don't implement any YANG module. */
539 if (config->dnode == NULL)
540 return;
541
5e6a9350 542 /*
083be18c
RW
543 * Call lyd_validate() only to create default child nodes, ignoring
544 * any possible validation error. This doesn't need to be done when
545 * displaying the running configuration since it's always fully
546 * validated.
5e6a9350 547 */
083be18c 548 if (config != running_config)
3bb513c3
CH
549 (void)lyd_validate_all(&config->dnode, ly_native_ctx,
550 LYD_VALIDATE_NO_STATE, NULL);
5e6a9350
RW
551}
552
25605051
IR
553static int lyd_node_cmp(const struct lyd_node **dnode1,
554 const struct lyd_node **dnode2)
0de76236
IR
555{
556 struct nb_node *nb_node = (*dnode1)->schema->priv;
557
558 return nb_node->cbs.cli_cmp(*dnode1, *dnode2);
559}
560
25605051
IR
561static void show_dnode_children_cmds(struct vty *vty,
562 const struct lyd_node *root,
634aa825
IR
563 bool with_defaults)
564{
c6f1f711
IR
565 struct nb_node *nb_node, *sort_node = NULL;
566 struct listnode *listnode;
634aa825 567 struct lyd_node *child;
c6f1f711
IR
568 struct list *sort_list;
569 void *data;
570
3bb513c3 571 LY_LIST_FOR (lyd_child(root), child) {
c6f1f711
IR
572 nb_node = child->schema->priv;
573
574 /*
575 * We finished processing current list,
576 * it's time to print the config.
577 */
578 if (sort_node && nb_node != sort_node) {
0de76236
IR
579 list_sort(sort_list,
580 (int (*)(const void **,
581 const void **))lyd_node_cmp);
582
c6f1f711
IR
583 for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
584 nb_cli_show_dnode_cmds(vty, data,
585 with_defaults);
586
587 list_delete(&sort_list);
588 sort_node = NULL;
589 }
590
591 /*
592 * If the config needs to be sorted,
593 * then add the dnode to the sorting
594 * list for later processing.
595 */
596 if (nb_node && nb_node->cbs.cli_cmp) {
597 if (!sort_node) {
598 sort_node = nb_node;
599 sort_list = list_new();
c6f1f711
IR
600 }
601
0de76236 602 listnode_add(sort_list, child);
c6f1f711
IR
603 continue;
604 }
634aa825 605
634aa825 606 nb_cli_show_dnode_cmds(vty, child, with_defaults);
c6f1f711
IR
607 }
608
609 if (sort_node) {
0de76236
IR
610 list_sort(sort_list,
611 (int (*)(const void **, const void **))lyd_node_cmp);
612
c6f1f711
IR
613 for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
614 nb_cli_show_dnode_cmds(vty, data, with_defaults);
615
616 list_delete(&sort_list);
617 sort_node = NULL;
618 }
634aa825
IR
619}
620
25605051 621void nb_cli_show_dnode_cmds(struct vty *vty, const struct lyd_node *root,
1c2facd1
RW
622 bool with_defaults)
623{
634aa825 624 struct nb_node *nb_node;
1c2facd1 625
634aa825
IR
626 if (!with_defaults && yang_dnode_is_default_recursive(root))
627 return;
1c2facd1 628
634aa825 629 nb_node = root->schema->priv;
1c2facd1 630
634aa825
IR
631 if (nb_node && nb_node->cbs.cli_show)
632 (*nb_node->cbs.cli_show)(vty, root, with_defaults);
1c2facd1 633
634aa825
IR
634 if (!(root->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)))
635 show_dnode_children_cmds(vty, root, with_defaults);
a4d3c1d4 636
634aa825
IR
637 if (nb_node && nb_node->cbs.cli_show_end)
638 (*nb_node->cbs.cli_show_end)(vty, root);
1c2facd1
RW
639}
640
641static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config,
642 bool with_defaults)
643{
644 struct lyd_node *root;
645
646 vty_out(vty, "Configuration:\n");
647 vty_out(vty, "!\n");
648 vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
ac4adef4 649 vty_out(vty, "frr defaults %s\n", frr_defaults_profile());
1c2facd1 650
3bb513c3 651 LY_LIST_FOR (config->dnode, root) {
1c2facd1 652 nb_cli_show_dnode_cmds(vty, root, with_defaults);
3bb513c3 653 }
1c2facd1
RW
654
655 vty_out(vty, "!\n");
656 vty_out(vty, "end\n");
657}
658
659static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
660 struct nb_config *config,
661 struct yang_translator *translator,
662 bool with_defaults)
663{
664 struct lyd_node *dnode;
665 char *strp;
db452508 666 int options = 0;
1c2facd1
RW
667
668 dnode = yang_dnode_dup(config->dnode);
669 if (translator
670 && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE,
671 &dnode)
672 != YANG_TRANSLATE_SUCCESS) {
673 vty_out(vty, "%% Failed to translate configuration\n");
674 yang_dnode_free(dnode);
675 return CMD_WARNING;
676 }
677
3bb513c3 678 SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
1c2facd1 679 if (with_defaults)
3bb513c3 680 SET_FLAG(options, LYD_PRINT_WD_ALL);
1c2facd1 681 else
3bb513c3 682 SET_FLAG(options, LYD_PRINT_WD_TRIM);
1c2facd1
RW
683
684 if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
685 vty_out(vty, "%s", strp);
686 free(strp);
687 }
688
689 yang_dnode_free(dnode);
690
691 return CMD_SUCCESS;
692}
693
694static int nb_cli_show_config(struct vty *vty, struct nb_config *config,
695 enum nb_cfg_format format,
696 struct yang_translator *translator,
697 bool with_defaults)
698{
5e6a9350
RW
699 nb_cli_show_config_prepare(config, with_defaults);
700
1c2facd1
RW
701 switch (format) {
702 case NB_CFG_FMT_CMDS:
703 nb_cli_show_config_cmds(vty, config, with_defaults);
704 break;
705 case NB_CFG_FMT_JSON:
706 return nb_cli_show_config_libyang(vty, LYD_JSON, config,
707 translator, with_defaults);
708 case NB_CFG_FMT_XML:
709 return nb_cli_show_config_libyang(vty, LYD_XML, config,
710 translator, with_defaults);
711 }
712
713 return CMD_SUCCESS;
714}
715
716static int nb_write_config(struct nb_config *config, enum nb_cfg_format format,
717 struct yang_translator *translator, char *path,
718 size_t pathlen)
719{
720 int fd;
721 struct vty *file_vty;
722 int ret = 0;
723
724 snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX");
725 fd = mkstemp(path);
726 if (fd < 0) {
727 flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s",
728 __func__, safe_strerror(errno));
729 return -1;
730 }
46e6f9f2
DS
731 if (fchmod(fd, CONFIGFILE_MASK) != 0) {
732 flog_warn(EC_LIB_SYSTEM_CALL,
733 "%s: fchmod() failed: %s(%d):", __func__,
734 safe_strerror(errno), errno);
735 return -1;
736 }
1c2facd1
RW
737
738 /* Make vty for configuration file. */
739 file_vty = vty_new();
740 file_vty->wfd = fd;
741 file_vty->type = VTY_FILE;
742 if (config)
743 ret = nb_cli_show_config(file_vty, config, format, translator,
744 false);
745 vty_close(file_vty);
746
747 return ret;
748}
749
750static int nb_cli_show_config_compare(struct vty *vty,
751 struct nb_config *config1,
752 struct nb_config *config2,
753 enum nb_cfg_format format,
754 struct yang_translator *translator)
755{
756 char config1_path[256];
757 char config2_path[256];
758 char command[BUFSIZ];
759 FILE *fp;
760 char line[1024];
761 int lineno = 0;
762
763 if (nb_write_config(config1, format, translator, config1_path,
764 sizeof(config1_path))
765 != 0) {
766 vty_out(vty, "%% Failed to process configurations.\n\n");
767 return CMD_WARNING;
768 }
769 if (nb_write_config(config2, format, translator, config2_path,
770 sizeof(config2_path))
771 != 0) {
772 vty_out(vty, "%% Failed to process configurations.\n\n");
773 unlink(config1_path);
774 return CMD_WARNING;
775 }
776
777 snprintf(command, sizeof(command), "diff -u %s %s", config1_path,
778 config2_path);
779 fp = popen(command, "r");
780 if (!fp) {
781 vty_out(vty, "%% Failed to generate configuration diff.\n\n");
782 unlink(config1_path);
783 unlink(config2_path);
784 return CMD_WARNING;
785 }
786 /* Print diff line by line. */
787 while (fgets(line, sizeof(line), fp) != NULL) {
788 if (lineno++ < 2)
789 continue;
790 vty_out(vty, "%s", line);
791 }
792 pclose(fp);
793
794 unlink(config1_path);
795 unlink(config2_path);
796
797 return CMD_SUCCESS;
798}
799
800/* Configure exclusively from this terminal. */
801DEFUN (config_exclusive,
802 config_exclusive_cmd,
803 "configure exclusive",
804 "Configuration from vty interface\n"
805 "Configure exclusively from this terminal\n")
806{
f344c66e 807 return vty_config_enter(vty, true, true);
1c2facd1
RW
808}
809
810/* Configure using a private candidate configuration. */
811DEFUN (config_private,
812 config_private_cmd,
813 "configure private",
814 "Configuration from vty interface\n"
815 "Configure using a private candidate configuration\n")
816{
f344c66e 817 return vty_config_enter(vty, true, false);
1c2facd1
RW
818}
819
820DEFPY (config_commit,
821 config_commit_cmd,
fbdc1c0a 822 "commit [{force$force|confirmed (1-60)}]",
1c2facd1 823 "Commit changes into the running configuration\n"
fbdc1c0a
RW
824 "Force commit even if the candidate is outdated\n"
825 "Rollback this commit unless there is a confirming commit\n"
826 "Timeout in minutes for the commit to be confirmed\n")
1c2facd1 827{
fbdc1c0a 828 return nb_cli_commit(vty, !!force, confirmed, NULL);
1c2facd1
RW
829}
830
831DEFPY (config_commit_comment,
832 config_commit_comment_cmd,
fbdc1c0a 833 "commit [{force$force|confirmed (1-60)}] comment LINE...",
1c2facd1
RW
834 "Commit changes into the running configuration\n"
835 "Force commit even if the candidate is outdated\n"
fbdc1c0a
RW
836 "Rollback this commit unless there is a confirming commit\n"
837 "Timeout in minutes for the commit to be confirmed\n"
1c2facd1
RW
838 "Assign a comment to this commit\n"
839 "Comment for this commit (Max 80 characters)\n")
840{
841 char *comment;
842 int idx = 0;
843 int ret;
844
845 argv_find(argv, argc, "LINE", &idx);
846 comment = argv_concat(argv, argc, idx);
fbdc1c0a 847 ret = nb_cli_commit(vty, !!force, confirmed, comment);
1c2facd1
RW
848 XFREE(MTYPE_TMP, comment);
849
850 return ret;
851}
852
853DEFPY (config_commit_check,
854 config_commit_check_cmd,
855 "commit check",
856 "Commit changes into the running configuration\n"
857 "Check if the configuration changes are valid\n")
858{
13d6b9c1 859 struct nb_context context = {};
df5eda3d 860 char errmsg[BUFSIZ] = {0};
1c2facd1
RW
861 int ret;
862
13d6b9c1
RW
863 context.client = NB_CLIENT_CLI;
864 context.user = vty;
df5eda3d
RW
865 ret = nb_candidate_validate(&context, vty->candidate_config, errmsg,
866 sizeof(errmsg));
1c2facd1
RW
867 if (ret != NB_OK) {
868 vty_out(vty,
869 "%% Failed to validate candidate configuration.\n\n");
df5eda3d 870 vty_show_nb_errors(vty, ret, errmsg);
1c2facd1
RW
871 return CMD_WARNING;
872 }
873
874 vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
875
876 return CMD_SUCCESS;
877}
878
879DEFPY (config_update,
880 config_update_cmd,
881 "update",
882 "Update candidate configuration\n")
883{
884 if (!nb_candidate_needs_update(vty->candidate_config)) {
885 vty_out(vty, "%% Update is not necessary.\n\n");
886 return CMD_SUCCESS;
887 }
888
889 if (nb_candidate_update(vty->candidate_config) != NB_OK) {
890 vty_out(vty,
891 "%% Failed to update the candidate configuration.\n\n");
892 vty_out(vty, "Please check the logs for more details.\n");
893 return CMD_WARNING;
894 }
895
8685be73 896 nb_config_replace(vty->candidate_config_base, running_config, true);
1c2facd1
RW
897
898 vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
899
900 return CMD_SUCCESS;
901}
902
903DEFPY (config_discard,
904 config_discard_cmd,
905 "discard",
906 "Discard changes in the candidate configuration\n")
907{
908 nb_config_replace(vty->candidate_config, vty->candidate_config_base,
909 true);
910
911 return CMD_SUCCESS;
912}
913
914DEFPY (config_load,
915 config_load_cmd,
916 "configuration load\
917 <\
918 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
20054cb4 919 |transaction (1-4294967295)$tid\
1c2facd1
RW
920 >\
921 [replace$replace]",
922 "Configuration related settings\n"
923 "Load configuration into candidate\n"
924 "Load configuration file into candidate\n"
925 "Load configuration file in JSON format\n"
926 "Load configuration file in XML format\n"
927 "Translate configuration file\n"
928 "YANG module translator\n"
929 "Configuration file name (full path)\n"
930 "Load configuration from transaction into candidate\n"
931 "Transaction ID\n"
932 "Replace instead of merge\n")
933{
934 if (filename) {
935 enum nb_cfg_format format;
936 struct yang_translator *translator = NULL;
937
938 if (json)
939 format = NB_CFG_FMT_JSON;
940 else if (xml)
941 format = NB_CFG_FMT_XML;
942 else
943 format = NB_CFG_FMT_CMDS;
944
945 if (translator_family) {
946 translator = yang_translator_find(translator_family);
947 if (!translator) {
948 vty_out(vty,
949 "%% Module translator \"%s\" not found\n",
950 translator_family);
951 return CMD_WARNING;
952 }
953 }
954
955 return nb_cli_candidate_load_file(vty, format, translator,
956 filename, !!replace);
957 }
958
959 return nb_cli_candidate_load_transaction(vty, tid, !!replace);
960}
961
962DEFPY (show_config_running,
963 show_config_running_cmd,
964 "show configuration running\
965 [<json$json|xml$xml> [translate WORD$translator_family]]\
966 [with-defaults$with_defaults]",
967 SHOW_STR
968 "Configuration information\n"
969 "Running configuration\n"
970 "Change output format to JSON\n"
971 "Change output format to XML\n"
972 "Translate output\n"
973 "YANG module translator\n"
974 "Show default values\n")
975
976{
977 enum nb_cfg_format format;
978 struct yang_translator *translator = NULL;
979
980 if (json)
981 format = NB_CFG_FMT_JSON;
982 else if (xml)
983 format = NB_CFG_FMT_XML;
984 else
985 format = NB_CFG_FMT_CMDS;
986
987 if (translator_family) {
988 translator = yang_translator_find(translator_family);
989 if (!translator) {
990 vty_out(vty, "%% Module translator \"%s\" not found\n",
991 translator_family);
992 return CMD_WARNING;
993 }
994 }
995
8685be73
RW
996 nb_cli_show_config(vty, running_config, format, translator,
997 !!with_defaults);
1c2facd1
RW
998
999 return CMD_SUCCESS;
1000}
1001
1002DEFPY (show_config_candidate,
1003 show_config_candidate_cmd,
1004 "show configuration candidate\
1005 [<json$json|xml$xml> [translate WORD$translator_family]]\
1006 [<\
1007 with-defaults$with_defaults\
1008 |changes$changes\
1009 >]",
1010 SHOW_STR
1011 "Configuration information\n"
1012 "Candidate configuration\n"
1013 "Change output format to JSON\n"
1014 "Change output format to XML\n"
1015 "Translate output\n"
1016 "YANG module translator\n"
1017 "Show default values\n"
1018 "Show changes applied in the candidate configuration\n")
1019
1020{
1021 enum nb_cfg_format format;
1022 struct yang_translator *translator = NULL;
1023
1024 if (json)
1025 format = NB_CFG_FMT_JSON;
1026 else if (xml)
1027 format = NB_CFG_FMT_XML;
1028 else
1029 format = NB_CFG_FMT_CMDS;
1030
1031 if (translator_family) {
1032 translator = yang_translator_find(translator_family);
1033 if (!translator) {
1034 vty_out(vty, "%% Module translator \"%s\" not found\n",
1035 translator_family);
1036 return CMD_WARNING;
1037 }
1038 }
1039
1040 if (changes)
1041 return nb_cli_show_config_compare(
1042 vty, vty->candidate_config_base, vty->candidate_config,
1043 format, translator);
1044
1045 nb_cli_show_config(vty, vty->candidate_config, format, translator,
1046 !!with_defaults);
1047
1048 return CMD_SUCCESS;
1049}
1050
18bf258a
RW
1051DEFPY (show_config_candidate_section,
1052 show_config_candidate_section_cmd,
1053 "show",
1054 SHOW_STR)
1055{
1056 struct lyd_node *dnode;
1057
1058 /* Top-level configuration node, display everything. */
1059 if (vty->xpath_index == 0)
1060 return nb_cli_show_config(vty, vty->candidate_config,
1061 NB_CFG_FMT_CMDS, NULL, false);
1062
1063 /* Display only the current section of the candidate configuration. */
1064 dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH);
1065 if (!dnode)
1066 /* Shouldn't happen. */
1067 return CMD_WARNING;
1068
1069 nb_cli_show_dnode_cmds(vty, dnode, 0);
1070 vty_out(vty, "!\n");
1071
1072 return CMD_SUCCESS;
1073}
1074
1c2facd1
RW
1075DEFPY (show_config_compare,
1076 show_config_compare_cmd,
1077 "show configuration compare\
1078 <\
1079 candidate$c1_candidate\
1080 |running$c1_running\
20054cb4 1081 |transaction (1-4294967295)$c1_tid\
1c2facd1
RW
1082 >\
1083 <\
1084 candidate$c2_candidate\
1085 |running$c2_running\
20054cb4 1086 |transaction (1-4294967295)$c2_tid\
1c2facd1
RW
1087 >\
1088 [<json$json|xml$xml> [translate WORD$translator_family]]",
1089 SHOW_STR
1090 "Configuration information\n"
1091 "Compare two different configurations\n"
1092 "Candidate configuration\n"
1093 "Running configuration\n"
1094 "Configuration transaction\n"
1095 "Transaction ID\n"
1096 "Candidate configuration\n"
1097 "Running configuration\n"
1098 "Configuration transaction\n"
1099 "Transaction ID\n"
1100 "Change output format to JSON\n"
1101 "Change output format to XML\n"
1102 "Translate output\n"
1103 "YANG module translator\n")
1104{
1105 enum nb_cfg_format format;
1106 struct yang_translator *translator = NULL;
1107 struct nb_config *config1, *config_transaction1 = NULL;
1108 struct nb_config *config2, *config_transaction2 = NULL;
1109 int ret = CMD_WARNING;
1110
8685be73
RW
1111 if (c1_candidate)
1112 config1 = vty->candidate_config;
1113 else if (c1_running)
1114 config1 = running_config;
1115 else {
1116 config_transaction1 = nb_db_transaction_load(c1_tid);
1117 if (!config_transaction1) {
1118 vty_out(vty, "%% Transaction %u does not exist\n\n",
1119 (unsigned int)c1_tid);
1120 goto exit;
1c2facd1 1121 }
8685be73
RW
1122 config1 = config_transaction1;
1123 }
1124
1125 if (c2_candidate)
1126 config2 = vty->candidate_config;
1127 else if (c2_running)
1128 config2 = running_config;
1129 else {
1130 config_transaction2 = nb_db_transaction_load(c2_tid);
1131 if (!config_transaction2) {
1132 vty_out(vty, "%% Transaction %u does not exist\n\n",
1133 (unsigned int)c2_tid);
1134 goto exit;
1c2facd1 1135 }
8685be73
RW
1136 config2 = config_transaction2;
1137 }
1c2facd1 1138
8685be73
RW
1139 if (json)
1140 format = NB_CFG_FMT_JSON;
1141 else if (xml)
1142 format = NB_CFG_FMT_XML;
1143 else
1144 format = NB_CFG_FMT_CMDS;
1c2facd1 1145
8685be73
RW
1146 if (translator_family) {
1147 translator = yang_translator_find(translator_family);
1148 if (!translator) {
1149 vty_out(vty, "%% Module translator \"%s\" not found\n",
1150 translator_family);
1151 goto exit;
1c2facd1 1152 }
83981138 1153 }
8685be73
RW
1154
1155 ret = nb_cli_show_config_compare(vty, config1, config2, format,
1156 translator);
1157exit:
1158 if (config_transaction1)
1159 nb_config_free(config_transaction1);
1160 if (config_transaction2)
1161 nb_config_free(config_transaction2);
1c2facd1
RW
1162
1163 return ret;
1164}
1165
1166/*
1167 * Stripped down version of the "show configuration compare" command.
1168 * The "candidate" option is not present so the command can be installed in
1169 * the enable node.
1170 */
1171ALIAS (show_config_compare,
1172 show_config_compare_without_candidate_cmd,
1173 "show configuration compare\
1174 <\
1175 running$c1_running\
20054cb4 1176 |transaction (1-4294967295)$c1_tid\
1c2facd1
RW
1177 >\
1178 <\
1179 running$c2_running\
20054cb4 1180 |transaction (1-4294967295)$c2_tid\
1c2facd1
RW
1181 >\
1182 [<json$json|xml$xml> [translate WORD$translator_family]]",
1183 SHOW_STR
1184 "Configuration information\n"
1185 "Compare two different configurations\n"
1186 "Running configuration\n"
1187 "Configuration transaction\n"
1188 "Transaction ID\n"
1189 "Running configuration\n"
1190 "Configuration transaction\n"
1191 "Transaction ID\n"
1192 "Change output format to JSON\n"
1193 "Change output format to XML\n"
1194 "Translate output\n"
1195 "YANG module translator\n")
1196
1197DEFPY (clear_config_transactions,
1198 clear_config_transactions_cmd,
1199 "clear configuration transactions oldest (1-100)$n",
1200 CLEAR_STR
1201 "Configuration activity\n"
1202 "Delete transactions from the transactions log\n"
1203 "Delete oldest <n> transactions\n"
1204 "Number of transactions to delete\n")
1205{
1206#ifdef HAVE_CONFIG_ROLLBACKS
1207 if (nb_db_clear_transactions(n) != NB_OK) {
1208 vty_out(vty, "%% Failed to delete transactions.\n\n");
1209 return CMD_WARNING;
1210 }
1211#else
1212 vty_out(vty,
1213 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1214#endif /* HAVE_CONFIG_ROLLBACKS */
1215
1216 return CMD_SUCCESS;
1217}
1218
1219DEFPY (config_database_max_transactions,
1220 config_database_max_transactions_cmd,
1221 "configuration database max-transactions (1-100)$max",
1222 "Configuration related settings\n"
1223 "Configuration database\n"
1224 "Set the maximum number of transactions to store\n"
1225 "Number of transactions\n")
1226{
1227#ifdef HAVE_CONFIG_ROLLBACKS
1228 if (nb_db_set_max_transactions(max) != NB_OK) {
1229 vty_out(vty,
1230 "%% Failed to update the maximum number of transactions.\n\n");
1231 return CMD_WARNING;
1232 }
1233 vty_out(vty,
1234 "%% Maximum number of transactions updated successfully.\n\n");
1235#else
1236 vty_out(vty,
1237 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1238#endif /* HAVE_CONFIG_ROLLBACKS */
1239
1240 return CMD_SUCCESS;
1241}
1242
1243DEFPY (yang_module_translator_load,
1244 yang_module_translator_load_cmd,
1245 "yang module-translator load FILENAME$filename",
1246 "YANG related settings\n"
1247 "YANG module translator\n"
1248 "Load YANG module translator\n"
1249 "File name (full path)\n")
1250{
1251 struct yang_translator *translator;
1252
1253 translator = yang_translator_load(filename);
1254 if (!translator) {
1255 vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
1256 vty_out(vty, "Please check the logs for more details.\n");
1257 return CMD_WARNING;
1258 }
1259
1260 vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
1261 translator->family);
1262
1263 return CMD_SUCCESS;
1264}
1265
1266DEFPY (yang_module_translator_unload_family,
1267 yang_module_translator_unload_cmd,
1268 "yang module-translator unload WORD$translator_family",
1269 "YANG related settings\n"
1270 "YANG module translator\n"
1271 "Unload YANG module translator\n"
1272 "Name of the module translator\n")
1273{
1274 struct yang_translator *translator;
1275
1276 translator = yang_translator_find(translator_family);
1277 if (!translator) {
1278 vty_out(vty, "%% Module translator \"%s\" not found\n",
1279 translator_family);
1280 return CMD_WARNING;
1281 }
1282
1283 yang_translator_unload(translator);
1284
1285 return CMD_SUCCESS;
1286}
1287
1288#ifdef HAVE_CONFIG_ROLLBACKS
1289static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
1290 const char *client_name,
1291 const char *date, const char *comment)
1292{
1293 struct ttable *tt = arg;
1294
1295 ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
1296 comment);
1297}
1298
1299static int nb_cli_show_transactions(struct vty *vty)
1300{
1301 struct ttable *tt;
1302
1303 /* Prepare table. */
1304 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1305 ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
1306 tt->style.cell.rpad = 2;
1307 tt->style.corner = '+';
1308 ttable_restyle(tt);
1309 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1310
1311 /* Fetch transactions from the northbound database. */
1312 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
1313 != NB_OK) {
1314 vty_out(vty,
1315 "%% Failed to fetch configuration transactions.\n");
1316 return CMD_WARNING;
1317 }
1318
1319 /* Dump the generated table. */
1320 if (tt->nrows > 1) {
1321 char *table;
1322
1323 table = ttable_dump(tt, "\n");
1324 vty_out(vty, "%s\n", table);
1325 XFREE(MTYPE_TMP, table);
1326 } else
1327 vty_out(vty, "No configuration transactions to display.\n\n");
1328
1329 ttable_del(tt);
1330
1331 return CMD_SUCCESS;
1332}
1333#endif /* HAVE_CONFIG_ROLLBACKS */
1334
1335DEFPY (show_config_transaction,
1336 show_config_transaction_cmd,
1337 "show configuration transaction\
1338 [\
20054cb4 1339 (1-4294967295)$transaction_id\
1c2facd1
RW
1340 [<json$json|xml$xml> [translate WORD$translator_family]]\
1341 [<\
1342 with-defaults$with_defaults\
1343 |changes$changes\
1344 >]\
1345 ]",
1346 SHOW_STR
1347 "Configuration information\n"
1348 "Configuration transaction\n"
1349 "Transaction ID\n"
1350 "Change output format to JSON\n"
1351 "Change output format to XML\n"
1352 "Translate output\n"
1353 "YANG module translator\n"
1354 "Show default values\n"
1355 "Show changes compared to the previous transaction\n")
1356{
1357#ifdef HAVE_CONFIG_ROLLBACKS
1358 if (transaction_id) {
1359 struct nb_config *config;
1360 enum nb_cfg_format format;
1361 struct yang_translator *translator = NULL;
1362
1363 if (json)
1364 format = NB_CFG_FMT_JSON;
1365 else if (xml)
1366 format = NB_CFG_FMT_XML;
1367 else
1368 format = NB_CFG_FMT_CMDS;
1369
1370 if (translator_family) {
1371 translator = yang_translator_find(translator_family);
1372 if (!translator) {
1373 vty_out(vty,
1374 "%% Module translator \"%s\" not found\n",
1375 translator_family);
1376 return CMD_WARNING;
1377 }
1378 }
1379
1380 config = nb_db_transaction_load(transaction_id);
1381 if (!config) {
1382 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1383 (unsigned int)transaction_id);
1384 return CMD_WARNING;
1385 }
1386
1387 if (changes) {
1388 struct nb_config *prev_config;
1389 int ret;
1390
1391 /* NOTE: this can be NULL. */
1392 prev_config =
1393 nb_db_transaction_load(transaction_id - 1);
1394
1395 ret = nb_cli_show_config_compare(
1396 vty, prev_config, config, format, translator);
1397 if (prev_config)
1398 nb_config_free(prev_config);
1399 nb_config_free(config);
1400
1401 return ret;
1402 }
1403
1404 nb_cli_show_config(vty, config, format, translator,
1405 !!with_defaults);
1406 nb_config_free(config);
1407
1408 return CMD_SUCCESS;
1409 }
1410
1411 return nb_cli_show_transactions(vty);
1412#else
1413 vty_out(vty,
1414 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1415 return CMD_WARNING;
1416#endif /* HAVE_CONFIG_ROLLBACKS */
1417}
1418
3bb513c3 1419static int nb_cli_oper_data_cb(const struct lysc_node *snode,
1a4bc045
RW
1420 struct yang_translator *translator,
1421 struct yang_data *data, void *arg)
1422{
1423 struct lyd_node *dnode = arg;
1424 struct ly_ctx *ly_ctx;
1425
1426 if (translator) {
1427 int ret;
1428
1429 ret = yang_translate_xpath(translator,
1430 YANG_TRANSLATE_FROM_NATIVE,
1431 data->xpath, sizeof(data->xpath));
1432 switch (ret) {
1433 case YANG_TRANSLATE_SUCCESS:
1434 break;
1435 case YANG_TRANSLATE_NOTFOUND:
1436 goto exit;
1437 case YANG_TRANSLATE_FAILURE:
1438 goto error;
1439 }
1440
1441 ly_ctx = translator->ly_ctx;
1442 } else
1443 ly_ctx = ly_native_ctx;
1444
3bb513c3
CH
1445 LY_ERR err =
1446 lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value,
1447 LYD_NEW_PATH_UPDATE, &dnode);
1448 if (err) {
1449 flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path(%s) failed: %s",
1450 __func__, data->xpath, ly_errmsg(ly_native_ctx));
1a4bc045
RW
1451 goto error;
1452 }
1453
1454exit:
1455 yang_data_free(data);
1456 return NB_OK;
1457
1458error:
1459 yang_data_free(data);
1460 return NB_ERR;
1461}
1462
1463DEFPY (show_yang_operational_data,
1464 show_yang_operational_data_cmd,
1465 "show yang operational-data XPATH$xpath\
1466 [{\
1467 format <json$json|xml$xml>\
1468 |translate WORD$translator_family\
1469 }]",
1470 SHOW_STR
1471 "YANG information\n"
1472 "Show YANG operational data\n"
1473 "XPath expression specifying the YANG data path\n"
1474 "Set the output format\n"
1475 "JavaScript Object Notation\n"
1476 "Extensible Markup Language\n"
1477 "Translate operational data\n"
1478 "YANG module translator\n")
1479{
1480 LYD_FORMAT format;
1481 struct yang_translator *translator = NULL;
1482 struct ly_ctx *ly_ctx;
1483 struct lyd_node *dnode;
1484 char *strp;
1485
1486 if (xml)
1487 format = LYD_XML;
1488 else
1489 format = LYD_JSON;
1490
1491 if (translator_family) {
1492 translator = yang_translator_find(translator_family);
1493 if (!translator) {
1494 vty_out(vty, "%% Module translator \"%s\" not found\n",
1495 translator_family);
1496 return CMD_WARNING;
1497 }
1498
1499 ly_ctx = translator->ly_ctx;
1500 } else
1501 ly_ctx = ly_native_ctx;
1502
1503 /* Obtain data. */
1504 dnode = yang_dnode_new(ly_ctx, false);
1505 if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
1506 dnode)
1507 != NB_OK) {
1508 vty_out(vty, "%% Failed to fetch operational data.\n");
1509 yang_dnode_free(dnode);
1510 return CMD_WARNING;
1511 }
3bb513c3 1512 (void)lyd_validate_all(&dnode, ly_ctx, 0, NULL);
1a4bc045
RW
1513
1514 /* Display the data. */
1515 if (lyd_print_mem(&strp, dnode, format,
3bb513c3 1516 LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)
1a4bc045
RW
1517 != 0
1518 || !strp) {
1519 vty_out(vty, "%% Failed to display operational data.\n");
1520 yang_dnode_free(dnode);
1521 return CMD_WARNING;
1522 }
1523 vty_out(vty, "%s", strp);
1524 free(strp);
1525 yang_dnode_free(dnode);
1526
1527 return CMD_SUCCESS;
1528}
1529
1c2facd1
RW
1530DEFPY (show_yang_module,
1531 show_yang_module_cmd,
1532 "show yang module [module-translator WORD$translator_family]",
1533 SHOW_STR
1534 "YANG information\n"
1535 "Show loaded modules\n"
1536 "YANG module translator\n"
1537 "YANG module translator\n")
1538{
1539 struct ly_ctx *ly_ctx;
1540 struct yang_translator *translator = NULL;
1541 const struct lys_module *module;
1542 struct ttable *tt;
1543 uint32_t idx = 0;
1544
1545 if (translator_family) {
1546 translator = yang_translator_find(translator_family);
1547 if (!translator) {
1548 vty_out(vty, "%% Module translator \"%s\" not found\n",
1549 translator_family);
1550 return CMD_WARNING;
1551 }
1552 ly_ctx = translator->ly_ctx;
1553 } else
1554 ly_ctx = ly_native_ctx;
1555
1556 /* Prepare table. */
1557 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1558 ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
1559 tt->style.cell.rpad = 2;
1560 tt->style.corner = '+';
1561 ttable_restyle(tt);
1562 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1563
1564 while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
1565 char flags[8];
1566
1567 snprintf(flags, sizeof(flags), "%c%c",
1568 module->implemented ? 'I' : ' ',
3bb513c3 1569 LY_ARRAY_COUNT(module->deviated_by) ? 'D' : ' ');
1c2facd1
RW
1570
1571 ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
3bb513c3
CH
1572 (module->parsed->version == 2) ? "1.1" : "1.0",
1573 module->revision ? module->revision : "-", flags,
1574 module->ns);
1c2facd1
RW
1575 }
1576
1577 /* Dump the generated table. */
1578 if (tt->nrows > 1) {
1579 char *table;
1580
1581 vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
1582
1583 table = ttable_dump(tt, "\n");
1584 vty_out(vty, "%s\n", table);
1585 XFREE(MTYPE_TMP, table);
1586 } else
1587 vty_out(vty, "No YANG modules to display.\n\n");
1588
1589 ttable_del(tt);
1590
1591 return CMD_SUCCESS;
1592}
1593
3bb513c3
CH
1594DEFPY(show_yang_module_detail, show_yang_module_detail_cmd,
1595 "show yang module\
1c2facd1 1596 [module-translator WORD$translator_family]\
3bb513c3
CH
1597 WORD$module_name <compiled$compiled|summary|tree$tree|yang$yang|yin$yin>",
1598 SHOW_STR
1599 "YANG information\n"
1600 "Show loaded modules\n"
1601 "YANG module translator\n"
1602 "YANG module translator\n"
1603 "Module name\n"
1604 "Display compiled module in YANG format\n"
1605 "Display summary information about the module\n"
1606 "Display module in the tree (RFC 8340) format\n"
1607 "Display module in the YANG format\n"
1608 "Display module in the YIN format\n")
1c2facd1
RW
1609{
1610 struct ly_ctx *ly_ctx;
1611 struct yang_translator *translator = NULL;
1612 const struct lys_module *module;
1613 LYS_OUTFORMAT format;
1614 char *strp;
1615
1616 if (translator_family) {
1617 translator = yang_translator_find(translator_family);
1618 if (!translator) {
1619 vty_out(vty, "%% Module translator \"%s\" not found\n",
1620 translator_family);
1621 return CMD_WARNING;
1622 }
1623 ly_ctx = translator->ly_ctx;
1624 } else
1625 ly_ctx = ly_native_ctx;
1626
3bb513c3 1627 module = ly_ctx_get_module_latest(ly_ctx, module_name);
1c2facd1
RW
1628 if (!module) {
1629 vty_out(vty, "%% Module \"%s\" not found\n", module_name);
1630 return CMD_WARNING;
1631 }
1632
1633 if (yang)
1634 format = LYS_OUT_YANG;
1635 else if (yin)
1636 format = LYS_OUT_YIN;
3bb513c3
CH
1637 else if (compiled)
1638 format = LYS_OUT_YANG_COMPILED;
1c2facd1
RW
1639 else if (tree)
1640 format = LYS_OUT_TREE;
3bb513c3
CH
1641 else {
1642 vty_out(vty,
1643 "%% libyang v2 does not currently support summary\n");
1644 return CMD_WARNING;
1645 }
1c2facd1 1646
3bb513c3 1647 if (lys_print_mem(&strp, module, format, 0) == 0) {
1c2facd1
RW
1648 vty_out(vty, "%s\n", strp);
1649 free(strp);
1650 } else {
1651 /* Unexpected. */
1652 vty_out(vty, "%% Error generating module information\n");
1653 return CMD_WARNING;
1654 }
1655
1656 return CMD_SUCCESS;
1657}
1658
1659DEFPY (show_yang_module_translator,
1660 show_yang_module_translator_cmd,
1661 "show yang module-translator",
1662 SHOW_STR
1663 "YANG information\n"
1664 "Show loaded YANG module translators\n")
1665{
1666 struct yang_translator *translator;
1667 struct ttable *tt;
1668
1669 /* Prepare table. */
1670 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1671 ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
1672 tt->style.cell.rpad = 2;
1673 tt->style.corner = '+';
1674 ttable_restyle(tt);
1675 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1676
1677 RB_FOREACH (translator, yang_translators, &yang_translators) {
1678 struct yang_tmodule *tmodule;
1679 struct listnode *ln;
1680
1681 for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
1682 ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
1683 tmodule->module->name,
1684 tmodule->deviations->name,
1685 tmodule->coverage);
1686 }
1687 }
1688
1689 /* Dump the generated table. */
1690 if (tt->nrows > 1) {
1691 char *table;
1692
1693 table = ttable_dump(tt, "\n");
1694 vty_out(vty, "%s\n", table);
1695 XFREE(MTYPE_TMP, table);
1696 } else
1697 vty_out(vty, "No YANG module translators to display.\n\n");
1698
1699 ttable_del(tt);
1700
1701 return CMD_SUCCESS;
1702}
1703
1704#ifdef HAVE_CONFIG_ROLLBACKS
1705static int nb_cli_rollback_configuration(struct vty *vty,
1706 uint32_t transaction_id)
1707{
13d6b9c1 1708 struct nb_context context = {};
1c2facd1
RW
1709 struct nb_config *candidate;
1710 char comment[80];
df5eda3d 1711 char errmsg[BUFSIZ] = {0};
1c2facd1
RW
1712 int ret;
1713
1714 candidate = nb_db_transaction_load(transaction_id);
1715 if (!candidate) {
1716 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1717 transaction_id);
1718 return CMD_WARNING;
1719 }
1720
1721 snprintf(comment, sizeof(comment), "Rollback to transaction %u",
1722 transaction_id);
1723
13d6b9c1
RW
1724 context.client = NB_CLIENT_CLI;
1725 context.user = vty;
df5eda3d
RW
1726 ret = nb_candidate_commit(&context, candidate, true, comment, NULL,
1727 errmsg, sizeof(errmsg));
1c2facd1
RW
1728 nb_config_free(candidate);
1729 switch (ret) {
1730 case NB_OK:
1731 vty_out(vty,
1732 "%% Configuration was successfully rolled back.\n\n");
0fe5b904
RW
1733 /* Print warnings (if any). */
1734 if (strlen(errmsg) > 0)
1735 vty_out(vty, "%s\n", errmsg);
1c2facd1
RW
1736 return CMD_SUCCESS;
1737 case NB_ERR_NO_CHANGES:
1738 vty_out(vty,
1739 "%% Aborting - no configuration changes detected.\n\n");
1740 return CMD_WARNING;
1741 default:
1742 vty_out(vty, "%% Rollback failed.\n\n");
df5eda3d 1743 vty_show_nb_errors(vty, ret, errmsg);
1c2facd1
RW
1744 return CMD_WARNING;
1745 }
1746}
1747#endif /* HAVE_CONFIG_ROLLBACKS */
1748
1749DEFPY (rollback_config,
1750 rollback_config_cmd,
20054cb4 1751 "rollback configuration (1-4294967295)$transaction_id",
1c2facd1
RW
1752 "Rollback to a previous state\n"
1753 "Running configuration\n"
1754 "Transaction ID\n")
1755{
1756#ifdef HAVE_CONFIG_ROLLBACKS
1757 return nb_cli_rollback_configuration(vty, transaction_id);
1758#else
1759 vty_out(vty,
1760 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1761 return CMD_SUCCESS;
1762#endif /* HAVE_CONFIG_ROLLBACKS */
1763}
1764
1765/* Debug CLI commands. */
9eb2c0a1
RW
1766static struct debug *nb_debugs[] = {
1767 &nb_dbg_cbs_config, &nb_dbg_cbs_state, &nb_dbg_cbs_rpc,
62ae9ade 1768 &nb_dbg_notif, &nb_dbg_events, &nb_dbg_libyang,
9eb2c0a1
RW
1769};
1770
1771static const char *const nb_debugs_conflines[] = {
1772 "debug northbound callbacks configuration",
1773 "debug northbound callbacks state",
1774 "debug northbound callbacks rpc",
1775 "debug northbound notifications",
1776 "debug northbound events",
62ae9ade 1777 "debug northbound libyang",
9eb2c0a1
RW
1778};
1779
1780DEFINE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set));
1781
1782static void nb_debug_set_all(uint32_t flags, bool set)
1c2facd1 1783{
9eb2c0a1
RW
1784 for (unsigned int i = 0; i < array_size(nb_debugs); i++) {
1785 DEBUG_FLAGS_SET(nb_debugs[i], flags, set);
1c2facd1 1786
9eb2c0a1
RW
1787 /* If all modes have been turned off, don't preserve options. */
1788 if (!DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_ALL))
1789 DEBUG_CLEAR(nb_debugs[i]);
1790 }
1791
1792 hook_call(nb_client_debug_set_all, flags, set);
1c2facd1
RW
1793}
1794
9eb2c0a1
RW
1795DEFPY (debug_nb,
1796 debug_nb_cmd,
1797 "[no] debug northbound\
1798 [<\
1799 callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1800 |notifications$notifications\
1801 |events$events\
62ae9ade 1802 |libyang$libyang\
9eb2c0a1
RW
1803 >]",
1804 NO_STR
1805 DEBUG_STR
1806 "Northbound debugging\n"
1807 "Callbacks\n"
1808 "Configuration\n"
1809 "State\n"
1810 "RPC\n"
1811 "Notifications\n"
62ae9ade
RW
1812 "Events\n"
1813 "libyang debugging\n")
1c2facd1 1814{
9eb2c0a1
RW
1815 uint32_t mode = DEBUG_NODE2MODE(vty->node);
1816
1817 if (cbs) {
1818 bool none = (!cbs_cfg && !cbs_state && !cbs_rpc);
1819
1820 if (none || cbs_cfg)
1821 DEBUG_MODE_SET(&nb_dbg_cbs_config, mode, !no);
1822 if (none || cbs_state)
1823 DEBUG_MODE_SET(&nb_dbg_cbs_state, mode, !no);
1824 if (none || cbs_rpc)
1825 DEBUG_MODE_SET(&nb_dbg_cbs_rpc, mode, !no);
1826 }
1827 if (notifications)
1828 DEBUG_MODE_SET(&nb_dbg_notif, mode, !no);
1829 if (events)
1830 DEBUG_MODE_SET(&nb_dbg_events, mode, !no);
62ae9ade
RW
1831 if (libyang) {
1832 DEBUG_MODE_SET(&nb_dbg_libyang, mode, !no);
1833 yang_debugging_set(!no);
1834 }
9eb2c0a1
RW
1835
1836 /* no specific debug --> act on all of them */
62ae9ade 1837 if (strmatch(argv[argc - 1]->text, "northbound")) {
9eb2c0a1 1838 nb_debug_set_all(mode, !no);
62ae9ade
RW
1839 yang_debugging_set(!no);
1840 }
1c2facd1
RW
1841
1842 return CMD_SUCCESS;
1843}
1844
9eb2c0a1
RW
1845DEFINE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty));
1846
1c2facd1
RW
1847static int nb_debug_config_write(struct vty *vty)
1848{
9eb2c0a1
RW
1849 for (unsigned int i = 0; i < array_size(nb_debugs); i++)
1850 if (DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_CONF))
1851 vty_out(vty, "%s\n", nb_debugs_conflines[i]);
1852
1853 hook_call(nb_client_debug_config_write, vty);
1c2facd1
RW
1854
1855 return 1;
1856}
1857
9eb2c0a1 1858static struct debug_callbacks nb_dbg_cbs = {.debug_set_all = nb_debug_set_all};
62b346ee 1859static struct cmd_node nb_debug_node = {
f4b8291f 1860 .name = "northbound debug",
62b346ee
DL
1861 .node = NORTHBOUND_DEBUG_NODE,
1862 .prompt = "",
612c2c15 1863 .config_write = nb_debug_config_write,
62b346ee 1864};
1c2facd1
RW
1865
1866void nb_cli_install_default(int node)
1867{
01485adb 1868 _install_element(node, &show_config_candidate_section_cmd);
18bf258a 1869
1c2facd1
RW
1870 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
1871 return;
1872
01485adb
DL
1873 _install_element(node, &config_commit_cmd);
1874 _install_element(node, &config_commit_comment_cmd);
1875 _install_element(node, &config_commit_check_cmd);
1876 _install_element(node, &config_update_cmd);
1877 _install_element(node, &config_discard_cmd);
1878 _install_element(node, &show_config_running_cmd);
1879 _install_element(node, &show_config_candidate_cmd);
1880 _install_element(node, &show_config_compare_cmd);
1881 _install_element(node, &show_config_transaction_cmd);
1c2facd1
RW
1882}
1883
1884/* YANG module autocomplete. */
1885static void yang_module_autocomplete(vector comps, struct cmd_token *token)
1886{
1887 const struct lys_module *module;
1888 struct yang_translator *module_tr;
1889 uint32_t idx;
1890
1891 idx = 0;
1892 while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
1893 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
1894
1895 RB_FOREACH (module_tr, yang_translators, &yang_translators) {
1896 idx = 0;
1897 while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
1898 &idx)))
1899 vector_set(comps,
1900 XSTRDUP(MTYPE_COMPLETION, module->name));
1901 }
1902}
1903
1904/* YANG module translator autocomplete. */
1905static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
1906{
1907 struct yang_translator *module_tr;
1908
1909 RB_FOREACH (module_tr, yang_translators, &yang_translators)
1910 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
1911}
1912
1913static const struct cmd_variable_handler yang_var_handlers[] = {
1914 {.varname = "module_name", .completions = yang_module_autocomplete},
1915 {.varname = "translator_family",
1916 .completions = yang_translator_autocomplete},
1917 {.completions = NULL}};
1918
fbdc1c0a 1919void nb_cli_init(struct thread_master *tm)
1c2facd1 1920{
fbdc1c0a
RW
1921 master = tm;
1922
1c2facd1
RW
1923 /* Initialize the shared candidate configuration. */
1924 vty_shared_candidate_config = nb_config_new(NULL);
1925
9eb2c0a1 1926 debug_init(&nb_dbg_cbs);
ae0994f6 1927
612c2c15 1928 install_node(&nb_debug_node);
1c2facd1 1929 install_element(ENABLE_NODE, &debug_nb_cmd);
1c2facd1 1930 install_element(CONFIG_NODE, &debug_nb_cmd);
1c2facd1
RW
1931
1932 /* Install commands specific to the transaction-base mode. */
1933 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
1934 install_element(ENABLE_NODE, &config_exclusive_cmd);
1935 install_element(ENABLE_NODE, &config_private_cmd);
1c2facd1
RW
1936 install_element(ENABLE_NODE,
1937 &show_config_compare_without_candidate_cmd);
1938 install_element(ENABLE_NODE, &show_config_transaction_cmd);
1939 install_element(ENABLE_NODE, &rollback_config_cmd);
1940 install_element(ENABLE_NODE, &clear_config_transactions_cmd);
1941
1942 install_element(CONFIG_NODE, &config_load_cmd);
1943 install_element(CONFIG_NODE,
1944 &config_database_max_transactions_cmd);
1945 }
1946
1947 /* Other commands. */
bcbe60d4 1948 install_element(ENABLE_NODE, &show_config_running_cmd);
1c2facd1
RW
1949 install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
1950 install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
1a4bc045 1951 install_element(ENABLE_NODE, &show_yang_operational_data_cmd);
1c2facd1
RW
1952 install_element(ENABLE_NODE, &show_yang_module_cmd);
1953 install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
1954 install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
1955 cmd_variable_handler_register(yang_var_handlers);
1956}
1957
1958void nb_cli_terminate(void)
1959{
1960 nb_config_free(vty_shared_candidate_config);
1961}