]> git.proxmox.com Git - mirror_frr.git/blame - lib/northbound_cli.c
Merge pull request #9614 from idryzhov/bgp-aspath-coverity
[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
634aa825
IR
553static void show_dnode_children_cmds(struct vty *vty, struct lyd_node *root,
554 bool with_defaults)
555{
c6f1f711
IR
556 struct nb_node *nb_node, *sort_node = NULL;
557 struct listnode *listnode;
634aa825 558 struct lyd_node *child;
c6f1f711
IR
559 struct list *sort_list;
560 void *data;
561
3bb513c3 562 LY_LIST_FOR (lyd_child(root), child) {
c6f1f711
IR
563 nb_node = child->schema->priv;
564
565 /*
566 * We finished processing current list,
567 * it's time to print the config.
568 */
569 if (sort_node && nb_node != sort_node) {
570 for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
571 nb_cli_show_dnode_cmds(vty, data,
572 with_defaults);
573
574 list_delete(&sort_list);
575 sort_node = NULL;
576 }
577
578 /*
579 * If the config needs to be sorted,
580 * then add the dnode to the sorting
581 * list for later processing.
582 */
583 if (nb_node && nb_node->cbs.cli_cmp) {
584 if (!sort_node) {
585 sort_node = nb_node;
586 sort_list = list_new();
587 sort_list->cmp = (int (*)(void *, void *))
588 nb_node->cbs.cli_cmp;
589 }
590
591 listnode_add_sort(sort_list, child);
592 continue;
593 }
634aa825 594
634aa825 595 nb_cli_show_dnode_cmds(vty, child, with_defaults);
c6f1f711
IR
596 }
597
598 if (sort_node) {
599 for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
600 nb_cli_show_dnode_cmds(vty, data, with_defaults);
601
602 list_delete(&sort_list);
603 sort_node = NULL;
604 }
634aa825
IR
605}
606
1c2facd1
RW
607void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *root,
608 bool with_defaults)
609{
634aa825 610 struct nb_node *nb_node;
1c2facd1 611
634aa825
IR
612 if (!with_defaults && yang_dnode_is_default_recursive(root))
613 return;
1c2facd1 614
634aa825 615 nb_node = root->schema->priv;
1c2facd1 616
634aa825
IR
617 if (nb_node && nb_node->cbs.cli_show)
618 (*nb_node->cbs.cli_show)(vty, root, with_defaults);
1c2facd1 619
634aa825
IR
620 if (!(root->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)))
621 show_dnode_children_cmds(vty, root, with_defaults);
a4d3c1d4 622
634aa825
IR
623 if (nb_node && nb_node->cbs.cli_show_end)
624 (*nb_node->cbs.cli_show_end)(vty, root);
1c2facd1
RW
625}
626
627static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config,
628 bool with_defaults)
629{
630 struct lyd_node *root;
631
632 vty_out(vty, "Configuration:\n");
633 vty_out(vty, "!\n");
634 vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
ac4adef4 635 vty_out(vty, "frr defaults %s\n", frr_defaults_profile());
1c2facd1 636
3bb513c3 637 LY_LIST_FOR (config->dnode, root) {
1c2facd1 638 nb_cli_show_dnode_cmds(vty, root, with_defaults);
3bb513c3 639 }
1c2facd1
RW
640
641 vty_out(vty, "!\n");
642 vty_out(vty, "end\n");
643}
644
645static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
646 struct nb_config *config,
647 struct yang_translator *translator,
648 bool with_defaults)
649{
650 struct lyd_node *dnode;
651 char *strp;
db452508 652 int options = 0;
1c2facd1
RW
653
654 dnode = yang_dnode_dup(config->dnode);
655 if (translator
656 && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE,
657 &dnode)
658 != YANG_TRANSLATE_SUCCESS) {
659 vty_out(vty, "%% Failed to translate configuration\n");
660 yang_dnode_free(dnode);
661 return CMD_WARNING;
662 }
663
3bb513c3 664 SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
1c2facd1 665 if (with_defaults)
3bb513c3 666 SET_FLAG(options, LYD_PRINT_WD_ALL);
1c2facd1 667 else
3bb513c3 668 SET_FLAG(options, LYD_PRINT_WD_TRIM);
1c2facd1
RW
669
670 if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
671 vty_out(vty, "%s", strp);
672 free(strp);
673 }
674
675 yang_dnode_free(dnode);
676
677 return CMD_SUCCESS;
678}
679
680static int nb_cli_show_config(struct vty *vty, struct nb_config *config,
681 enum nb_cfg_format format,
682 struct yang_translator *translator,
683 bool with_defaults)
684{
5e6a9350
RW
685 nb_cli_show_config_prepare(config, with_defaults);
686
1c2facd1
RW
687 switch (format) {
688 case NB_CFG_FMT_CMDS:
689 nb_cli_show_config_cmds(vty, config, with_defaults);
690 break;
691 case NB_CFG_FMT_JSON:
692 return nb_cli_show_config_libyang(vty, LYD_JSON, config,
693 translator, with_defaults);
694 case NB_CFG_FMT_XML:
695 return nb_cli_show_config_libyang(vty, LYD_XML, config,
696 translator, with_defaults);
697 }
698
699 return CMD_SUCCESS;
700}
701
702static int nb_write_config(struct nb_config *config, enum nb_cfg_format format,
703 struct yang_translator *translator, char *path,
704 size_t pathlen)
705{
706 int fd;
707 struct vty *file_vty;
708 int ret = 0;
709
710 snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX");
711 fd = mkstemp(path);
712 if (fd < 0) {
713 flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s",
714 __func__, safe_strerror(errno));
715 return -1;
716 }
46e6f9f2
DS
717 if (fchmod(fd, CONFIGFILE_MASK) != 0) {
718 flog_warn(EC_LIB_SYSTEM_CALL,
719 "%s: fchmod() failed: %s(%d):", __func__,
720 safe_strerror(errno), errno);
721 return -1;
722 }
1c2facd1
RW
723
724 /* Make vty for configuration file. */
725 file_vty = vty_new();
726 file_vty->wfd = fd;
727 file_vty->type = VTY_FILE;
728 if (config)
729 ret = nb_cli_show_config(file_vty, config, format, translator,
730 false);
731 vty_close(file_vty);
732
733 return ret;
734}
735
736static int nb_cli_show_config_compare(struct vty *vty,
737 struct nb_config *config1,
738 struct nb_config *config2,
739 enum nb_cfg_format format,
740 struct yang_translator *translator)
741{
742 char config1_path[256];
743 char config2_path[256];
744 char command[BUFSIZ];
745 FILE *fp;
746 char line[1024];
747 int lineno = 0;
748
749 if (nb_write_config(config1, format, translator, config1_path,
750 sizeof(config1_path))
751 != 0) {
752 vty_out(vty, "%% Failed to process configurations.\n\n");
753 return CMD_WARNING;
754 }
755 if (nb_write_config(config2, format, translator, config2_path,
756 sizeof(config2_path))
757 != 0) {
758 vty_out(vty, "%% Failed to process configurations.\n\n");
759 unlink(config1_path);
760 return CMD_WARNING;
761 }
762
763 snprintf(command, sizeof(command), "diff -u %s %s", config1_path,
764 config2_path);
765 fp = popen(command, "r");
766 if (!fp) {
767 vty_out(vty, "%% Failed to generate configuration diff.\n\n");
768 unlink(config1_path);
769 unlink(config2_path);
770 return CMD_WARNING;
771 }
772 /* Print diff line by line. */
773 while (fgets(line, sizeof(line), fp) != NULL) {
774 if (lineno++ < 2)
775 continue;
776 vty_out(vty, "%s", line);
777 }
778 pclose(fp);
779
780 unlink(config1_path);
781 unlink(config2_path);
782
783 return CMD_SUCCESS;
784}
785
786/* Configure exclusively from this terminal. */
787DEFUN (config_exclusive,
788 config_exclusive_cmd,
789 "configure exclusive",
790 "Configuration from vty interface\n"
791 "Configure exclusively from this terminal\n")
792{
f344c66e 793 return vty_config_enter(vty, true, true);
1c2facd1
RW
794}
795
796/* Configure using a private candidate configuration. */
797DEFUN (config_private,
798 config_private_cmd,
799 "configure private",
800 "Configuration from vty interface\n"
801 "Configure using a private candidate configuration\n")
802{
f344c66e 803 return vty_config_enter(vty, true, false);
1c2facd1
RW
804}
805
806DEFPY (config_commit,
807 config_commit_cmd,
fbdc1c0a 808 "commit [{force$force|confirmed (1-60)}]",
1c2facd1 809 "Commit changes into the running configuration\n"
fbdc1c0a
RW
810 "Force commit even if the candidate is outdated\n"
811 "Rollback this commit unless there is a confirming commit\n"
812 "Timeout in minutes for the commit to be confirmed\n")
1c2facd1 813{
fbdc1c0a 814 return nb_cli_commit(vty, !!force, confirmed, NULL);
1c2facd1
RW
815}
816
817DEFPY (config_commit_comment,
818 config_commit_comment_cmd,
fbdc1c0a 819 "commit [{force$force|confirmed (1-60)}] comment LINE...",
1c2facd1
RW
820 "Commit changes into the running configuration\n"
821 "Force commit even if the candidate is outdated\n"
fbdc1c0a
RW
822 "Rollback this commit unless there is a confirming commit\n"
823 "Timeout in minutes for the commit to be confirmed\n"
1c2facd1
RW
824 "Assign a comment to this commit\n"
825 "Comment for this commit (Max 80 characters)\n")
826{
827 char *comment;
828 int idx = 0;
829 int ret;
830
831 argv_find(argv, argc, "LINE", &idx);
832 comment = argv_concat(argv, argc, idx);
fbdc1c0a 833 ret = nb_cli_commit(vty, !!force, confirmed, comment);
1c2facd1
RW
834 XFREE(MTYPE_TMP, comment);
835
836 return ret;
837}
838
839DEFPY (config_commit_check,
840 config_commit_check_cmd,
841 "commit check",
842 "Commit changes into the running configuration\n"
843 "Check if the configuration changes are valid\n")
844{
13d6b9c1 845 struct nb_context context = {};
df5eda3d 846 char errmsg[BUFSIZ] = {0};
1c2facd1
RW
847 int ret;
848
13d6b9c1
RW
849 context.client = NB_CLIENT_CLI;
850 context.user = vty;
df5eda3d
RW
851 ret = nb_candidate_validate(&context, vty->candidate_config, errmsg,
852 sizeof(errmsg));
1c2facd1
RW
853 if (ret != NB_OK) {
854 vty_out(vty,
855 "%% Failed to validate candidate configuration.\n\n");
df5eda3d 856 vty_show_nb_errors(vty, ret, errmsg);
1c2facd1
RW
857 return CMD_WARNING;
858 }
859
860 vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
861
862 return CMD_SUCCESS;
863}
864
865DEFPY (config_update,
866 config_update_cmd,
867 "update",
868 "Update candidate configuration\n")
869{
870 if (!nb_candidate_needs_update(vty->candidate_config)) {
871 vty_out(vty, "%% Update is not necessary.\n\n");
872 return CMD_SUCCESS;
873 }
874
875 if (nb_candidate_update(vty->candidate_config) != NB_OK) {
876 vty_out(vty,
877 "%% Failed to update the candidate configuration.\n\n");
878 vty_out(vty, "Please check the logs for more details.\n");
879 return CMD_WARNING;
880 }
881
8685be73 882 nb_config_replace(vty->candidate_config_base, running_config, true);
1c2facd1
RW
883
884 vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
885
886 return CMD_SUCCESS;
887}
888
889DEFPY (config_discard,
890 config_discard_cmd,
891 "discard",
892 "Discard changes in the candidate configuration\n")
893{
894 nb_config_replace(vty->candidate_config, vty->candidate_config_base,
895 true);
896
897 return CMD_SUCCESS;
898}
899
900DEFPY (config_load,
901 config_load_cmd,
902 "configuration load\
903 <\
904 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
20054cb4 905 |transaction (1-4294967295)$tid\
1c2facd1
RW
906 >\
907 [replace$replace]",
908 "Configuration related settings\n"
909 "Load configuration into candidate\n"
910 "Load configuration file into candidate\n"
911 "Load configuration file in JSON format\n"
912 "Load configuration file in XML format\n"
913 "Translate configuration file\n"
914 "YANG module translator\n"
915 "Configuration file name (full path)\n"
916 "Load configuration from transaction into candidate\n"
917 "Transaction ID\n"
918 "Replace instead of merge\n")
919{
920 if (filename) {
921 enum nb_cfg_format format;
922 struct yang_translator *translator = NULL;
923
924 if (json)
925 format = NB_CFG_FMT_JSON;
926 else if (xml)
927 format = NB_CFG_FMT_XML;
928 else
929 format = NB_CFG_FMT_CMDS;
930
931 if (translator_family) {
932 translator = yang_translator_find(translator_family);
933 if (!translator) {
934 vty_out(vty,
935 "%% Module translator \"%s\" not found\n",
936 translator_family);
937 return CMD_WARNING;
938 }
939 }
940
941 return nb_cli_candidate_load_file(vty, format, translator,
942 filename, !!replace);
943 }
944
945 return nb_cli_candidate_load_transaction(vty, tid, !!replace);
946}
947
948DEFPY (show_config_running,
949 show_config_running_cmd,
950 "show configuration running\
951 [<json$json|xml$xml> [translate WORD$translator_family]]\
952 [with-defaults$with_defaults]",
953 SHOW_STR
954 "Configuration information\n"
955 "Running configuration\n"
956 "Change output format to JSON\n"
957 "Change output format to XML\n"
958 "Translate output\n"
959 "YANG module translator\n"
960 "Show default values\n")
961
962{
963 enum nb_cfg_format format;
964 struct yang_translator *translator = NULL;
965
966 if (json)
967 format = NB_CFG_FMT_JSON;
968 else if (xml)
969 format = NB_CFG_FMT_XML;
970 else
971 format = NB_CFG_FMT_CMDS;
972
973 if (translator_family) {
974 translator = yang_translator_find(translator_family);
975 if (!translator) {
976 vty_out(vty, "%% Module translator \"%s\" not found\n",
977 translator_family);
978 return CMD_WARNING;
979 }
980 }
981
8685be73
RW
982 nb_cli_show_config(vty, running_config, format, translator,
983 !!with_defaults);
1c2facd1
RW
984
985 return CMD_SUCCESS;
986}
987
988DEFPY (show_config_candidate,
989 show_config_candidate_cmd,
990 "show configuration candidate\
991 [<json$json|xml$xml> [translate WORD$translator_family]]\
992 [<\
993 with-defaults$with_defaults\
994 |changes$changes\
995 >]",
996 SHOW_STR
997 "Configuration information\n"
998 "Candidate configuration\n"
999 "Change output format to JSON\n"
1000 "Change output format to XML\n"
1001 "Translate output\n"
1002 "YANG module translator\n"
1003 "Show default values\n"
1004 "Show changes applied in the candidate configuration\n")
1005
1006{
1007 enum nb_cfg_format format;
1008 struct yang_translator *translator = NULL;
1009
1010 if (json)
1011 format = NB_CFG_FMT_JSON;
1012 else if (xml)
1013 format = NB_CFG_FMT_XML;
1014 else
1015 format = NB_CFG_FMT_CMDS;
1016
1017 if (translator_family) {
1018 translator = yang_translator_find(translator_family);
1019 if (!translator) {
1020 vty_out(vty, "%% Module translator \"%s\" not found\n",
1021 translator_family);
1022 return CMD_WARNING;
1023 }
1024 }
1025
1026 if (changes)
1027 return nb_cli_show_config_compare(
1028 vty, vty->candidate_config_base, vty->candidate_config,
1029 format, translator);
1030
1031 nb_cli_show_config(vty, vty->candidate_config, format, translator,
1032 !!with_defaults);
1033
1034 return CMD_SUCCESS;
1035}
1036
18bf258a
RW
1037DEFPY (show_config_candidate_section,
1038 show_config_candidate_section_cmd,
1039 "show",
1040 SHOW_STR)
1041{
1042 struct lyd_node *dnode;
1043
1044 /* Top-level configuration node, display everything. */
1045 if (vty->xpath_index == 0)
1046 return nb_cli_show_config(vty, vty->candidate_config,
1047 NB_CFG_FMT_CMDS, NULL, false);
1048
1049 /* Display only the current section of the candidate configuration. */
1050 dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH);
1051 if (!dnode)
1052 /* Shouldn't happen. */
1053 return CMD_WARNING;
1054
1055 nb_cli_show_dnode_cmds(vty, dnode, 0);
1056 vty_out(vty, "!\n");
1057
1058 return CMD_SUCCESS;
1059}
1060
1c2facd1
RW
1061DEFPY (show_config_compare,
1062 show_config_compare_cmd,
1063 "show configuration compare\
1064 <\
1065 candidate$c1_candidate\
1066 |running$c1_running\
20054cb4 1067 |transaction (1-4294967295)$c1_tid\
1c2facd1
RW
1068 >\
1069 <\
1070 candidate$c2_candidate\
1071 |running$c2_running\
20054cb4 1072 |transaction (1-4294967295)$c2_tid\
1c2facd1
RW
1073 >\
1074 [<json$json|xml$xml> [translate WORD$translator_family]]",
1075 SHOW_STR
1076 "Configuration information\n"
1077 "Compare two different configurations\n"
1078 "Candidate configuration\n"
1079 "Running configuration\n"
1080 "Configuration transaction\n"
1081 "Transaction ID\n"
1082 "Candidate configuration\n"
1083 "Running configuration\n"
1084 "Configuration transaction\n"
1085 "Transaction ID\n"
1086 "Change output format to JSON\n"
1087 "Change output format to XML\n"
1088 "Translate output\n"
1089 "YANG module translator\n")
1090{
1091 enum nb_cfg_format format;
1092 struct yang_translator *translator = NULL;
1093 struct nb_config *config1, *config_transaction1 = NULL;
1094 struct nb_config *config2, *config_transaction2 = NULL;
1095 int ret = CMD_WARNING;
1096
8685be73
RW
1097 if (c1_candidate)
1098 config1 = vty->candidate_config;
1099 else if (c1_running)
1100 config1 = running_config;
1101 else {
1102 config_transaction1 = nb_db_transaction_load(c1_tid);
1103 if (!config_transaction1) {
1104 vty_out(vty, "%% Transaction %u does not exist\n\n",
1105 (unsigned int)c1_tid);
1106 goto exit;
1c2facd1 1107 }
8685be73
RW
1108 config1 = config_transaction1;
1109 }
1110
1111 if (c2_candidate)
1112 config2 = vty->candidate_config;
1113 else if (c2_running)
1114 config2 = running_config;
1115 else {
1116 config_transaction2 = nb_db_transaction_load(c2_tid);
1117 if (!config_transaction2) {
1118 vty_out(vty, "%% Transaction %u does not exist\n\n",
1119 (unsigned int)c2_tid);
1120 goto exit;
1c2facd1 1121 }
8685be73
RW
1122 config2 = config_transaction2;
1123 }
1c2facd1 1124
8685be73
RW
1125 if (json)
1126 format = NB_CFG_FMT_JSON;
1127 else if (xml)
1128 format = NB_CFG_FMT_XML;
1129 else
1130 format = NB_CFG_FMT_CMDS;
1c2facd1 1131
8685be73
RW
1132 if (translator_family) {
1133 translator = yang_translator_find(translator_family);
1134 if (!translator) {
1135 vty_out(vty, "%% Module translator \"%s\" not found\n",
1136 translator_family);
1137 goto exit;
1c2facd1 1138 }
83981138 1139 }
8685be73
RW
1140
1141 ret = nb_cli_show_config_compare(vty, config1, config2, format,
1142 translator);
1143exit:
1144 if (config_transaction1)
1145 nb_config_free(config_transaction1);
1146 if (config_transaction2)
1147 nb_config_free(config_transaction2);
1c2facd1
RW
1148
1149 return ret;
1150}
1151
1152/*
1153 * Stripped down version of the "show configuration compare" command.
1154 * The "candidate" option is not present so the command can be installed in
1155 * the enable node.
1156 */
1157ALIAS (show_config_compare,
1158 show_config_compare_without_candidate_cmd,
1159 "show configuration compare\
1160 <\
1161 running$c1_running\
20054cb4 1162 |transaction (1-4294967295)$c1_tid\
1c2facd1
RW
1163 >\
1164 <\
1165 running$c2_running\
20054cb4 1166 |transaction (1-4294967295)$c2_tid\
1c2facd1
RW
1167 >\
1168 [<json$json|xml$xml> [translate WORD$translator_family]]",
1169 SHOW_STR
1170 "Configuration information\n"
1171 "Compare two different configurations\n"
1172 "Running configuration\n"
1173 "Configuration transaction\n"
1174 "Transaction ID\n"
1175 "Running configuration\n"
1176 "Configuration transaction\n"
1177 "Transaction ID\n"
1178 "Change output format to JSON\n"
1179 "Change output format to XML\n"
1180 "Translate output\n"
1181 "YANG module translator\n")
1182
1183DEFPY (clear_config_transactions,
1184 clear_config_transactions_cmd,
1185 "clear configuration transactions oldest (1-100)$n",
1186 CLEAR_STR
1187 "Configuration activity\n"
1188 "Delete transactions from the transactions log\n"
1189 "Delete oldest <n> transactions\n"
1190 "Number of transactions to delete\n")
1191{
1192#ifdef HAVE_CONFIG_ROLLBACKS
1193 if (nb_db_clear_transactions(n) != NB_OK) {
1194 vty_out(vty, "%% Failed to delete transactions.\n\n");
1195 return CMD_WARNING;
1196 }
1197#else
1198 vty_out(vty,
1199 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1200#endif /* HAVE_CONFIG_ROLLBACKS */
1201
1202 return CMD_SUCCESS;
1203}
1204
1205DEFPY (config_database_max_transactions,
1206 config_database_max_transactions_cmd,
1207 "configuration database max-transactions (1-100)$max",
1208 "Configuration related settings\n"
1209 "Configuration database\n"
1210 "Set the maximum number of transactions to store\n"
1211 "Number of transactions\n")
1212{
1213#ifdef HAVE_CONFIG_ROLLBACKS
1214 if (nb_db_set_max_transactions(max) != NB_OK) {
1215 vty_out(vty,
1216 "%% Failed to update the maximum number of transactions.\n\n");
1217 return CMD_WARNING;
1218 }
1219 vty_out(vty,
1220 "%% Maximum number of transactions updated successfully.\n\n");
1221#else
1222 vty_out(vty,
1223 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1224#endif /* HAVE_CONFIG_ROLLBACKS */
1225
1226 return CMD_SUCCESS;
1227}
1228
1229DEFPY (yang_module_translator_load,
1230 yang_module_translator_load_cmd,
1231 "yang module-translator load FILENAME$filename",
1232 "YANG related settings\n"
1233 "YANG module translator\n"
1234 "Load YANG module translator\n"
1235 "File name (full path)\n")
1236{
1237 struct yang_translator *translator;
1238
1239 translator = yang_translator_load(filename);
1240 if (!translator) {
1241 vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
1242 vty_out(vty, "Please check the logs for more details.\n");
1243 return CMD_WARNING;
1244 }
1245
1246 vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
1247 translator->family);
1248
1249 return CMD_SUCCESS;
1250}
1251
1252DEFPY (yang_module_translator_unload_family,
1253 yang_module_translator_unload_cmd,
1254 "yang module-translator unload WORD$translator_family",
1255 "YANG related settings\n"
1256 "YANG module translator\n"
1257 "Unload YANG module translator\n"
1258 "Name of the module translator\n")
1259{
1260 struct yang_translator *translator;
1261
1262 translator = yang_translator_find(translator_family);
1263 if (!translator) {
1264 vty_out(vty, "%% Module translator \"%s\" not found\n",
1265 translator_family);
1266 return CMD_WARNING;
1267 }
1268
1269 yang_translator_unload(translator);
1270
1271 return CMD_SUCCESS;
1272}
1273
1274#ifdef HAVE_CONFIG_ROLLBACKS
1275static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
1276 const char *client_name,
1277 const char *date, const char *comment)
1278{
1279 struct ttable *tt = arg;
1280
1281 ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
1282 comment);
1283}
1284
1285static int nb_cli_show_transactions(struct vty *vty)
1286{
1287 struct ttable *tt;
1288
1289 /* Prepare table. */
1290 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1291 ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
1292 tt->style.cell.rpad = 2;
1293 tt->style.corner = '+';
1294 ttable_restyle(tt);
1295 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1296
1297 /* Fetch transactions from the northbound database. */
1298 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
1299 != NB_OK) {
1300 vty_out(vty,
1301 "%% Failed to fetch configuration transactions.\n");
1302 return CMD_WARNING;
1303 }
1304
1305 /* Dump the generated table. */
1306 if (tt->nrows > 1) {
1307 char *table;
1308
1309 table = ttable_dump(tt, "\n");
1310 vty_out(vty, "%s\n", table);
1311 XFREE(MTYPE_TMP, table);
1312 } else
1313 vty_out(vty, "No configuration transactions to display.\n\n");
1314
1315 ttable_del(tt);
1316
1317 return CMD_SUCCESS;
1318}
1319#endif /* HAVE_CONFIG_ROLLBACKS */
1320
1321DEFPY (show_config_transaction,
1322 show_config_transaction_cmd,
1323 "show configuration transaction\
1324 [\
20054cb4 1325 (1-4294967295)$transaction_id\
1c2facd1
RW
1326 [<json$json|xml$xml> [translate WORD$translator_family]]\
1327 [<\
1328 with-defaults$with_defaults\
1329 |changes$changes\
1330 >]\
1331 ]",
1332 SHOW_STR
1333 "Configuration information\n"
1334 "Configuration transaction\n"
1335 "Transaction ID\n"
1336 "Change output format to JSON\n"
1337 "Change output format to XML\n"
1338 "Translate output\n"
1339 "YANG module translator\n"
1340 "Show default values\n"
1341 "Show changes compared to the previous transaction\n")
1342{
1343#ifdef HAVE_CONFIG_ROLLBACKS
1344 if (transaction_id) {
1345 struct nb_config *config;
1346 enum nb_cfg_format format;
1347 struct yang_translator *translator = NULL;
1348
1349 if (json)
1350 format = NB_CFG_FMT_JSON;
1351 else if (xml)
1352 format = NB_CFG_FMT_XML;
1353 else
1354 format = NB_CFG_FMT_CMDS;
1355
1356 if (translator_family) {
1357 translator = yang_translator_find(translator_family);
1358 if (!translator) {
1359 vty_out(vty,
1360 "%% Module translator \"%s\" not found\n",
1361 translator_family);
1362 return CMD_WARNING;
1363 }
1364 }
1365
1366 config = nb_db_transaction_load(transaction_id);
1367 if (!config) {
1368 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1369 (unsigned int)transaction_id);
1370 return CMD_WARNING;
1371 }
1372
1373 if (changes) {
1374 struct nb_config *prev_config;
1375 int ret;
1376
1377 /* NOTE: this can be NULL. */
1378 prev_config =
1379 nb_db_transaction_load(transaction_id - 1);
1380
1381 ret = nb_cli_show_config_compare(
1382 vty, prev_config, config, format, translator);
1383 if (prev_config)
1384 nb_config_free(prev_config);
1385 nb_config_free(config);
1386
1387 return ret;
1388 }
1389
1390 nb_cli_show_config(vty, config, format, translator,
1391 !!with_defaults);
1392 nb_config_free(config);
1393
1394 return CMD_SUCCESS;
1395 }
1396
1397 return nb_cli_show_transactions(vty);
1398#else
1399 vty_out(vty,
1400 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1401 return CMD_WARNING;
1402#endif /* HAVE_CONFIG_ROLLBACKS */
1403}
1404
3bb513c3 1405static int nb_cli_oper_data_cb(const struct lysc_node *snode,
1a4bc045
RW
1406 struct yang_translator *translator,
1407 struct yang_data *data, void *arg)
1408{
1409 struct lyd_node *dnode = arg;
1410 struct ly_ctx *ly_ctx;
1411
1412 if (translator) {
1413 int ret;
1414
1415 ret = yang_translate_xpath(translator,
1416 YANG_TRANSLATE_FROM_NATIVE,
1417 data->xpath, sizeof(data->xpath));
1418 switch (ret) {
1419 case YANG_TRANSLATE_SUCCESS:
1420 break;
1421 case YANG_TRANSLATE_NOTFOUND:
1422 goto exit;
1423 case YANG_TRANSLATE_FAILURE:
1424 goto error;
1425 }
1426
1427 ly_ctx = translator->ly_ctx;
1428 } else
1429 ly_ctx = ly_native_ctx;
1430
3bb513c3
CH
1431 LY_ERR err =
1432 lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value,
1433 LYD_NEW_PATH_UPDATE, &dnode);
1434 if (err) {
1435 flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path(%s) failed: %s",
1436 __func__, data->xpath, ly_errmsg(ly_native_ctx));
1a4bc045
RW
1437 goto error;
1438 }
1439
1440exit:
1441 yang_data_free(data);
1442 return NB_OK;
1443
1444error:
1445 yang_data_free(data);
1446 return NB_ERR;
1447}
1448
1449DEFPY (show_yang_operational_data,
1450 show_yang_operational_data_cmd,
1451 "show yang operational-data XPATH$xpath\
1452 [{\
1453 format <json$json|xml$xml>\
1454 |translate WORD$translator_family\
1455 }]",
1456 SHOW_STR
1457 "YANG information\n"
1458 "Show YANG operational data\n"
1459 "XPath expression specifying the YANG data path\n"
1460 "Set the output format\n"
1461 "JavaScript Object Notation\n"
1462 "Extensible Markup Language\n"
1463 "Translate operational data\n"
1464 "YANG module translator\n")
1465{
1466 LYD_FORMAT format;
1467 struct yang_translator *translator = NULL;
1468 struct ly_ctx *ly_ctx;
1469 struct lyd_node *dnode;
1470 char *strp;
1471
1472 if (xml)
1473 format = LYD_XML;
1474 else
1475 format = LYD_JSON;
1476
1477 if (translator_family) {
1478 translator = yang_translator_find(translator_family);
1479 if (!translator) {
1480 vty_out(vty, "%% Module translator \"%s\" not found\n",
1481 translator_family);
1482 return CMD_WARNING;
1483 }
1484
1485 ly_ctx = translator->ly_ctx;
1486 } else
1487 ly_ctx = ly_native_ctx;
1488
1489 /* Obtain data. */
1490 dnode = yang_dnode_new(ly_ctx, false);
1491 if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
1492 dnode)
1493 != NB_OK) {
1494 vty_out(vty, "%% Failed to fetch operational data.\n");
1495 yang_dnode_free(dnode);
1496 return CMD_WARNING;
1497 }
3bb513c3 1498 (void)lyd_validate_all(&dnode, ly_ctx, 0, NULL);
1a4bc045
RW
1499
1500 /* Display the data. */
1501 if (lyd_print_mem(&strp, dnode, format,
3bb513c3 1502 LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)
1a4bc045
RW
1503 != 0
1504 || !strp) {
1505 vty_out(vty, "%% Failed to display operational data.\n");
1506 yang_dnode_free(dnode);
1507 return CMD_WARNING;
1508 }
1509 vty_out(vty, "%s", strp);
1510 free(strp);
1511 yang_dnode_free(dnode);
1512
1513 return CMD_SUCCESS;
1514}
1515
1c2facd1
RW
1516DEFPY (show_yang_module,
1517 show_yang_module_cmd,
1518 "show yang module [module-translator WORD$translator_family]",
1519 SHOW_STR
1520 "YANG information\n"
1521 "Show loaded modules\n"
1522 "YANG module translator\n"
1523 "YANG module translator\n")
1524{
1525 struct ly_ctx *ly_ctx;
1526 struct yang_translator *translator = NULL;
1527 const struct lys_module *module;
1528 struct ttable *tt;
1529 uint32_t idx = 0;
1530
1531 if (translator_family) {
1532 translator = yang_translator_find(translator_family);
1533 if (!translator) {
1534 vty_out(vty, "%% Module translator \"%s\" not found\n",
1535 translator_family);
1536 return CMD_WARNING;
1537 }
1538 ly_ctx = translator->ly_ctx;
1539 } else
1540 ly_ctx = ly_native_ctx;
1541
1542 /* Prepare table. */
1543 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1544 ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
1545 tt->style.cell.rpad = 2;
1546 tt->style.corner = '+';
1547 ttable_restyle(tt);
1548 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1549
1550 while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
1551 char flags[8];
1552
1553 snprintf(flags, sizeof(flags), "%c%c",
1554 module->implemented ? 'I' : ' ',
3bb513c3 1555 LY_ARRAY_COUNT(module->deviated_by) ? 'D' : ' ');
1c2facd1
RW
1556
1557 ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
3bb513c3
CH
1558 (module->parsed->version == 2) ? "1.1" : "1.0",
1559 module->revision ? module->revision : "-", flags,
1560 module->ns);
1c2facd1
RW
1561 }
1562
1563 /* Dump the generated table. */
1564 if (tt->nrows > 1) {
1565 char *table;
1566
1567 vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
1568
1569 table = ttable_dump(tt, "\n");
1570 vty_out(vty, "%s\n", table);
1571 XFREE(MTYPE_TMP, table);
1572 } else
1573 vty_out(vty, "No YANG modules to display.\n\n");
1574
1575 ttable_del(tt);
1576
1577 return CMD_SUCCESS;
1578}
1579
3bb513c3
CH
1580DEFPY(show_yang_module_detail, show_yang_module_detail_cmd,
1581 "show yang module\
1c2facd1 1582 [module-translator WORD$translator_family]\
3bb513c3
CH
1583 WORD$module_name <compiled$compiled|summary|tree$tree|yang$yang|yin$yin>",
1584 SHOW_STR
1585 "YANG information\n"
1586 "Show loaded modules\n"
1587 "YANG module translator\n"
1588 "YANG module translator\n"
1589 "Module name\n"
1590 "Display compiled module in YANG format\n"
1591 "Display summary information about the module\n"
1592 "Display module in the tree (RFC 8340) format\n"
1593 "Display module in the YANG format\n"
1594 "Display module in the YIN format\n")
1c2facd1
RW
1595{
1596 struct ly_ctx *ly_ctx;
1597 struct yang_translator *translator = NULL;
1598 const struct lys_module *module;
1599 LYS_OUTFORMAT format;
1600 char *strp;
1601
1602 if (translator_family) {
1603 translator = yang_translator_find(translator_family);
1604 if (!translator) {
1605 vty_out(vty, "%% Module translator \"%s\" not found\n",
1606 translator_family);
1607 return CMD_WARNING;
1608 }
1609 ly_ctx = translator->ly_ctx;
1610 } else
1611 ly_ctx = ly_native_ctx;
1612
3bb513c3 1613 module = ly_ctx_get_module_latest(ly_ctx, module_name);
1c2facd1
RW
1614 if (!module) {
1615 vty_out(vty, "%% Module \"%s\" not found\n", module_name);
1616 return CMD_WARNING;
1617 }
1618
1619 if (yang)
1620 format = LYS_OUT_YANG;
1621 else if (yin)
1622 format = LYS_OUT_YIN;
3bb513c3
CH
1623 else if (compiled)
1624 format = LYS_OUT_YANG_COMPILED;
1c2facd1
RW
1625 else if (tree)
1626 format = LYS_OUT_TREE;
3bb513c3
CH
1627 else {
1628 vty_out(vty,
1629 "%% libyang v2 does not currently support summary\n");
1630 return CMD_WARNING;
1631 }
1c2facd1 1632
3bb513c3 1633 if (lys_print_mem(&strp, module, format, 0) == 0) {
1c2facd1
RW
1634 vty_out(vty, "%s\n", strp);
1635 free(strp);
1636 } else {
1637 /* Unexpected. */
1638 vty_out(vty, "%% Error generating module information\n");
1639 return CMD_WARNING;
1640 }
1641
1642 return CMD_SUCCESS;
1643}
1644
1645DEFPY (show_yang_module_translator,
1646 show_yang_module_translator_cmd,
1647 "show yang module-translator",
1648 SHOW_STR
1649 "YANG information\n"
1650 "Show loaded YANG module translators\n")
1651{
1652 struct yang_translator *translator;
1653 struct ttable *tt;
1654
1655 /* Prepare table. */
1656 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1657 ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
1658 tt->style.cell.rpad = 2;
1659 tt->style.corner = '+';
1660 ttable_restyle(tt);
1661 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1662
1663 RB_FOREACH (translator, yang_translators, &yang_translators) {
1664 struct yang_tmodule *tmodule;
1665 struct listnode *ln;
1666
1667 for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
1668 ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
1669 tmodule->module->name,
1670 tmodule->deviations->name,
1671 tmodule->coverage);
1672 }
1673 }
1674
1675 /* Dump the generated table. */
1676 if (tt->nrows > 1) {
1677 char *table;
1678
1679 table = ttable_dump(tt, "\n");
1680 vty_out(vty, "%s\n", table);
1681 XFREE(MTYPE_TMP, table);
1682 } else
1683 vty_out(vty, "No YANG module translators to display.\n\n");
1684
1685 ttable_del(tt);
1686
1687 return CMD_SUCCESS;
1688}
1689
1690#ifdef HAVE_CONFIG_ROLLBACKS
1691static int nb_cli_rollback_configuration(struct vty *vty,
1692 uint32_t transaction_id)
1693{
13d6b9c1 1694 struct nb_context context = {};
1c2facd1
RW
1695 struct nb_config *candidate;
1696 char comment[80];
df5eda3d 1697 char errmsg[BUFSIZ] = {0};
1c2facd1
RW
1698 int ret;
1699
1700 candidate = nb_db_transaction_load(transaction_id);
1701 if (!candidate) {
1702 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1703 transaction_id);
1704 return CMD_WARNING;
1705 }
1706
1707 snprintf(comment, sizeof(comment), "Rollback to transaction %u",
1708 transaction_id);
1709
13d6b9c1
RW
1710 context.client = NB_CLIENT_CLI;
1711 context.user = vty;
df5eda3d
RW
1712 ret = nb_candidate_commit(&context, candidate, true, comment, NULL,
1713 errmsg, sizeof(errmsg));
1c2facd1
RW
1714 nb_config_free(candidate);
1715 switch (ret) {
1716 case NB_OK:
1717 vty_out(vty,
1718 "%% Configuration was successfully rolled back.\n\n");
0fe5b904
RW
1719 /* Print warnings (if any). */
1720 if (strlen(errmsg) > 0)
1721 vty_out(vty, "%s\n", errmsg);
1c2facd1
RW
1722 return CMD_SUCCESS;
1723 case NB_ERR_NO_CHANGES:
1724 vty_out(vty,
1725 "%% Aborting - no configuration changes detected.\n\n");
1726 return CMD_WARNING;
1727 default:
1728 vty_out(vty, "%% Rollback failed.\n\n");
df5eda3d 1729 vty_show_nb_errors(vty, ret, errmsg);
1c2facd1
RW
1730 return CMD_WARNING;
1731 }
1732}
1733#endif /* HAVE_CONFIG_ROLLBACKS */
1734
1735DEFPY (rollback_config,
1736 rollback_config_cmd,
20054cb4 1737 "rollback configuration (1-4294967295)$transaction_id",
1c2facd1
RW
1738 "Rollback to a previous state\n"
1739 "Running configuration\n"
1740 "Transaction ID\n")
1741{
1742#ifdef HAVE_CONFIG_ROLLBACKS
1743 return nb_cli_rollback_configuration(vty, transaction_id);
1744#else
1745 vty_out(vty,
1746 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1747 return CMD_SUCCESS;
1748#endif /* HAVE_CONFIG_ROLLBACKS */
1749}
1750
1751/* Debug CLI commands. */
9eb2c0a1
RW
1752static struct debug *nb_debugs[] = {
1753 &nb_dbg_cbs_config, &nb_dbg_cbs_state, &nb_dbg_cbs_rpc,
62ae9ade 1754 &nb_dbg_notif, &nb_dbg_events, &nb_dbg_libyang,
9eb2c0a1
RW
1755};
1756
1757static const char *const nb_debugs_conflines[] = {
1758 "debug northbound callbacks configuration",
1759 "debug northbound callbacks state",
1760 "debug northbound callbacks rpc",
1761 "debug northbound notifications",
1762 "debug northbound events",
62ae9ade 1763 "debug northbound libyang",
9eb2c0a1
RW
1764};
1765
1766DEFINE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set));
1767
1768static void nb_debug_set_all(uint32_t flags, bool set)
1c2facd1 1769{
9eb2c0a1
RW
1770 for (unsigned int i = 0; i < array_size(nb_debugs); i++) {
1771 DEBUG_FLAGS_SET(nb_debugs[i], flags, set);
1c2facd1 1772
9eb2c0a1
RW
1773 /* If all modes have been turned off, don't preserve options. */
1774 if (!DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_ALL))
1775 DEBUG_CLEAR(nb_debugs[i]);
1776 }
1777
1778 hook_call(nb_client_debug_set_all, flags, set);
1c2facd1
RW
1779}
1780
9eb2c0a1
RW
1781DEFPY (debug_nb,
1782 debug_nb_cmd,
1783 "[no] debug northbound\
1784 [<\
1785 callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1786 |notifications$notifications\
1787 |events$events\
62ae9ade 1788 |libyang$libyang\
9eb2c0a1
RW
1789 >]",
1790 NO_STR
1791 DEBUG_STR
1792 "Northbound debugging\n"
1793 "Callbacks\n"
1794 "Configuration\n"
1795 "State\n"
1796 "RPC\n"
1797 "Notifications\n"
62ae9ade
RW
1798 "Events\n"
1799 "libyang debugging\n")
1c2facd1 1800{
9eb2c0a1
RW
1801 uint32_t mode = DEBUG_NODE2MODE(vty->node);
1802
1803 if (cbs) {
1804 bool none = (!cbs_cfg && !cbs_state && !cbs_rpc);
1805
1806 if (none || cbs_cfg)
1807 DEBUG_MODE_SET(&nb_dbg_cbs_config, mode, !no);
1808 if (none || cbs_state)
1809 DEBUG_MODE_SET(&nb_dbg_cbs_state, mode, !no);
1810 if (none || cbs_rpc)
1811 DEBUG_MODE_SET(&nb_dbg_cbs_rpc, mode, !no);
1812 }
1813 if (notifications)
1814 DEBUG_MODE_SET(&nb_dbg_notif, mode, !no);
1815 if (events)
1816 DEBUG_MODE_SET(&nb_dbg_events, mode, !no);
62ae9ade
RW
1817 if (libyang) {
1818 DEBUG_MODE_SET(&nb_dbg_libyang, mode, !no);
1819 yang_debugging_set(!no);
1820 }
9eb2c0a1
RW
1821
1822 /* no specific debug --> act on all of them */
62ae9ade 1823 if (strmatch(argv[argc - 1]->text, "northbound")) {
9eb2c0a1 1824 nb_debug_set_all(mode, !no);
62ae9ade
RW
1825 yang_debugging_set(!no);
1826 }
1c2facd1
RW
1827
1828 return CMD_SUCCESS;
1829}
1830
9eb2c0a1
RW
1831DEFINE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty));
1832
1c2facd1
RW
1833static int nb_debug_config_write(struct vty *vty)
1834{
9eb2c0a1
RW
1835 for (unsigned int i = 0; i < array_size(nb_debugs); i++)
1836 if (DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_CONF))
1837 vty_out(vty, "%s\n", nb_debugs_conflines[i]);
1838
1839 hook_call(nb_client_debug_config_write, vty);
1c2facd1
RW
1840
1841 return 1;
1842}
1843
9eb2c0a1 1844static struct debug_callbacks nb_dbg_cbs = {.debug_set_all = nb_debug_set_all};
62b346ee 1845static struct cmd_node nb_debug_node = {
f4b8291f 1846 .name = "northbound debug",
62b346ee
DL
1847 .node = NORTHBOUND_DEBUG_NODE,
1848 .prompt = "",
612c2c15 1849 .config_write = nb_debug_config_write,
62b346ee 1850};
1c2facd1
RW
1851
1852void nb_cli_install_default(int node)
1853{
01485adb 1854 _install_element(node, &show_config_candidate_section_cmd);
18bf258a 1855
1c2facd1
RW
1856 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
1857 return;
1858
01485adb
DL
1859 _install_element(node, &config_commit_cmd);
1860 _install_element(node, &config_commit_comment_cmd);
1861 _install_element(node, &config_commit_check_cmd);
1862 _install_element(node, &config_update_cmd);
1863 _install_element(node, &config_discard_cmd);
1864 _install_element(node, &show_config_running_cmd);
1865 _install_element(node, &show_config_candidate_cmd);
1866 _install_element(node, &show_config_compare_cmd);
1867 _install_element(node, &show_config_transaction_cmd);
1c2facd1
RW
1868}
1869
1870/* YANG module autocomplete. */
1871static void yang_module_autocomplete(vector comps, struct cmd_token *token)
1872{
1873 const struct lys_module *module;
1874 struct yang_translator *module_tr;
1875 uint32_t idx;
1876
1877 idx = 0;
1878 while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
1879 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
1880
1881 RB_FOREACH (module_tr, yang_translators, &yang_translators) {
1882 idx = 0;
1883 while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
1884 &idx)))
1885 vector_set(comps,
1886 XSTRDUP(MTYPE_COMPLETION, module->name));
1887 }
1888}
1889
1890/* YANG module translator autocomplete. */
1891static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
1892{
1893 struct yang_translator *module_tr;
1894
1895 RB_FOREACH (module_tr, yang_translators, &yang_translators)
1896 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
1897}
1898
1899static const struct cmd_variable_handler yang_var_handlers[] = {
1900 {.varname = "module_name", .completions = yang_module_autocomplete},
1901 {.varname = "translator_family",
1902 .completions = yang_translator_autocomplete},
1903 {.completions = NULL}};
1904
fbdc1c0a 1905void nb_cli_init(struct thread_master *tm)
1c2facd1 1906{
fbdc1c0a
RW
1907 master = tm;
1908
1c2facd1
RW
1909 /* Initialize the shared candidate configuration. */
1910 vty_shared_candidate_config = nb_config_new(NULL);
1911
9eb2c0a1 1912 debug_init(&nb_dbg_cbs);
ae0994f6 1913
612c2c15 1914 install_node(&nb_debug_node);
1c2facd1 1915 install_element(ENABLE_NODE, &debug_nb_cmd);
1c2facd1 1916 install_element(CONFIG_NODE, &debug_nb_cmd);
1c2facd1
RW
1917
1918 /* Install commands specific to the transaction-base mode. */
1919 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
1920 install_element(ENABLE_NODE, &config_exclusive_cmd);
1921 install_element(ENABLE_NODE, &config_private_cmd);
1c2facd1
RW
1922 install_element(ENABLE_NODE,
1923 &show_config_compare_without_candidate_cmd);
1924 install_element(ENABLE_NODE, &show_config_transaction_cmd);
1925 install_element(ENABLE_NODE, &rollback_config_cmd);
1926 install_element(ENABLE_NODE, &clear_config_transactions_cmd);
1927
1928 install_element(CONFIG_NODE, &config_load_cmd);
1929 install_element(CONFIG_NODE,
1930 &config_database_max_transactions_cmd);
1931 }
1932
1933 /* Other commands. */
bcbe60d4 1934 install_element(ENABLE_NODE, &show_config_running_cmd);
1c2facd1
RW
1935 install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
1936 install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
1a4bc045 1937 install_element(ENABLE_NODE, &show_yang_operational_data_cmd);
1c2facd1
RW
1938 install_element(ENABLE_NODE, &show_yang_module_cmd);
1939 install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
1940 install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
1941 cmd_variable_handler_register(yang_var_handlers);
1942}
1943
1944void nb_cli_terminate(void)
1945{
1946 nb_config_free(vty_shared_candidate_config);
1947}