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