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