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