]> git.proxmox.com Git - mirror_frr.git/blame - tests/lib/test_grpc.cpp
Merge pull request #8808 from ton31337/feature/tracepoints_for_bgp_dest_lock_unlock
[mirror_frr.git] / tests / lib / test_grpc.cpp
CommitLineData
deca28a3
CH
1/*
2 * May 16 2021, Christian Hopps <chopps@labn.net>
3 *
4 * Copyright (c) 2021, LabN Consulting, L.L.C
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; see the file COPYING; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#include <time.h>
22#include <unistd.h>
23#include <zebra.h>
24
25#include "filter.h"
26#include "frr_pthread.h"
27#include "libfrr.h"
28#include "routing_nb.h"
29#include "northbound_cli.h"
30#include "thread.h"
31#include "vrf.h"
32#include "vty.h"
33
34#include "staticd/static_debug.h"
35#include "staticd/static_nb.h"
36#include "staticd/static_vrf.h"
37#include "staticd/static_vty.h"
38#include "staticd/static_zebra.h"
39
40// GRPC C++ includes
41#include <string>
42#include <sstream>
43#include <grpc/grpc.h>
44#include <grpcpp/channel.h>
45#include <grpcpp/client_context.h>
46#include <grpcpp/create_channel.h>
47#include <grpcpp/security/credentials.h>
48#include "grpc/frr-northbound.grpc.pb.h"
49
50DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm));
51DEFINE_KOOH(frr_fini, (), ());
52
53struct vty *vty;
54
55bool mpls_enabled;
56struct thread_master *master;
57struct zebra_privs_t static_privs = {0};
58struct frrmod_runtime *grpc_module;
59char binpath[2 * MAXPATHLEN + 1];
60
61extern const char *json_expect1;
62extern const char *json_expect2;
63extern const char *json_expect3;
64extern const char *json_loadconf1;
65
66int test_dbg = 1;
67
68void inline test_debug(const std::string &s)
69{
70 if (test_dbg)
71 std::cout << s << std::endl;
72}
73
74// static struct option_chain modules[] = {{ .arg = "grpc:50051" }]
75// static struct option_chain **modnext = modules->next;
76
77static const struct frr_yang_module_info *const staticd_yang_modules[] = {
78 &frr_interface_info, &frr_filter_info, &frr_routing_info,
79 &frr_staticd_info, &frr_vrf_info,
80};
81
82static int grpc_thread_stop(struct thread *thread);
83
84static void static_startup(void)
85{
86 // struct frrmod_runtime module;
87 // static struct option_chain *oc;
88 char moderr[256] = {};
89 cmd_init(1);
90
91 zlog_aux_init("NONE: ", LOG_DEBUG);
92 zprivs_preinit(&static_privs);
93 zprivs_init(&static_privs);
94
95 /* Load the server side module -- check libtool path first */
96 std::string modpath = std::string(binpath) + std::string("../../../lib/.libs");
97 grpc_module = frrmod_load("grpc:50051", modpath.c_str(), moderr, sizeof(moderr));
98 if (!grpc_module) {
99 modpath = std::string(binpath) + std::string("../../lib");
100 grpc_module = frrmod_load("grpc:50051", modpath.c_str(), moderr,
101 sizeof(moderr));
102 }
103 if (!grpc_module) {
104 std::cout << "Failed to load grpc module:" << moderr
105 << std::endl;
106 exit(1);
107 }
108
109 static_debug_init();
110
111 master = thread_master_create(NULL);
112 nb_init(master, staticd_yang_modules, array_size(staticd_yang_modules),
113 false);
114
115 static_zebra_init();
116 vty_init(master, true);
117 static_vrf_init();
118 static_vty_init();
119
120 hook_register(routing_conf_event,
121 routing_control_plane_protocols_name_validate);
122
123 routing_control_plane_protocols_register_vrf_dependency();
124
125 // Add a route
126 vty = vty_new();
127 vty->type = vty::VTY_TERM;
128 vty_config_enter(vty, true, false);
129
130 auto ret = cmd_execute(vty, "ip route 11.0.0.0/8 Null0", NULL, 0);
131 assert(!ret);
132
133 ret = cmd_execute(vty, "end", NULL, 0);
134 assert(!ret);
135
136 nb_cli_pending_commit_check(vty);
137
138 frr_pthread_init();
139
140 // frr_config_fork();
141 hook_call(frr_late_init, master);
142}
143
144static void static_shutdown(void)
145{
146 hook_call(frr_fini);
147 vty_close(vty);
148 vrf_terminate();
149 vty_terminate();
150 cmd_terminate();
151 nb_terminate();
152 yang_terminate();
153 thread_master_free(master);
154 master = NULL;
155}
156
157using frr::Northbound;
158using grpc::Channel;
159using grpc::ClientAsyncResponseReader;
160using grpc::ClientContext;
161using grpc::CompletionQueue;
162using grpc::Status;
163
164class NorthboundClient
165{
166 public:
167 NorthboundClient(std::shared_ptr<Channel> channel)
168 : stub_(frr::Northbound::NewStub(channel))
169 {
170 }
171
172 void Commit(uint32_t candidate_id)
173 {
174 frr::CommitRequest request;
175 frr::CommitResponse reply;
176 ClientContext context;
177 Status status;
178
179 request.set_candidate_id(candidate_id);
180
181 request.set_phase(frr::CommitRequest::ALL);
182 status = stub_->Commit(&context, request, &reply);
183 _throw_if_not_ok(status);
184#if 0
185 request.set_phase(frr::CommitRequest::VALIDATE);
186 status = stub_->Commit(&context, request, &reply);
187 _throw_if_not_ok(status);
188
189 request.set_phase(frr::CommitRequest::PREPARE);
190 status = stub_->Commit(&context, request, &reply);
191 _throw_if_not_ok(status);
192
193 request.set_phase(frr::CommitRequest::APPLY);
194 status = stub_->Commit(&context, request, &reply);
195 _throw_if_not_ok(status);
196#endif
197 }
198
199 uint32_t CreateCandidate()
200 {
201 frr::CreateCandidateRequest request;
202 frr::CreateCandidateResponse reply;
203 ClientContext context;
204 Status status;
205
206 status = stub_->CreateCandidate(&context, request, &reply);
207 _throw_if_not_ok(status);
208 return reply.candidate_id();
209 }
210
211 void DeleteCandidate(uint32_t candidate_id)
212 {
213 frr::DeleteCandidateRequest request;
214 frr::DeleteCandidateResponse reply;
215 ClientContext context;
216 Status status;
217
218 request.set_candidate_id(candidate_id);
219 status = stub_->DeleteCandidate(&context, request, &reply);
220 _throw_if_not_ok(status);
221 }
222
223 void EditCandidate(uint32_t candidate_id, const std::string &path,
224 const std::string &value)
225 {
226 frr::EditCandidateRequest request;
227 frr::EditCandidateResponse reply;
228 ClientContext context;
229
230 request.set_candidate_id(candidate_id);
231 frr::PathValue *pv = request.add_update();
232 pv->set_path(path);
233 pv->set_value(value);
234
235 Status status = stub_->EditCandidate(&context, request, &reply);
236 _throw_if_not_ok(status);
237 }
238
239 std::string Get(const std::string &path,
240 frr::GetRequest::DataType dtype, frr::Encoding enc,
241 bool with_defaults)
242 {
243 frr::GetRequest request;
244 frr::GetResponse reply;
245 ClientContext context;
246 std::ostringstream ss;
247
248 request.set_type(dtype);
249 request.set_encoding(enc);
250 request.set_with_defaults(with_defaults);
251 request.add_path(path);
252
253 auto stream = stub_->Get(&context, request);
254 while (stream->Read(&reply)) {
255 ss << reply.data().data() << std::endl;
256 }
257 auto status = stream->Finish();
258 _throw_if_not_ok(status);
259 return ss.str();
260 }
261
262 std::string GetCapabilities()
263 {
264 frr::GetCapabilitiesRequest request;
265 frr::GetCapabilitiesResponse reply;
266 ClientContext context;
267
268 Status status =
269 stub_->GetCapabilities(&context, request, &reply);
270 _throw_if_not_ok(status);
271
272 std::ostringstream ss;
273 ss << "Capabilities:" << std::endl
274 << "\tVersion: " << reply.frr_version() << std::endl
275 << "\tRollback Support: " << reply.rollback_support()
276 << std::endl
277 << "\tSupported Modules:";
278
279 for (int i = 0; i < reply.supported_modules_size(); i++) {
280 auto sm = reply.supported_modules(i);
281 ss << std::endl
282 << "\t\tName: \"" << sm.name()
283 << "\" Revision: " << sm.revision() << " Org: \""
284 << sm.organization() << "\"";
285 }
286
287 ss << std::endl << "\tSupported Encodings:";
288
289 for (int i = 0; i < reply.supported_encodings_size(); i++) {
290 auto se = reply.supported_encodings(i);
291 auto desc =
292 google::protobuf::GetEnumDescriptor<decltype(
293 se)>();
294 ss << std::endl
295 << "\t\t" << desc->FindValueByNumber(se)->name();
296 }
297
298 ss << std::endl;
299
300 return ss.str();
301 }
302
303 void LoadToCandidate(uint32_t candidate_id, bool is_replace,
304 bool is_json, const std::string &data)
305 {
306 frr::LoadToCandidateRequest request;
307 frr::LoadToCandidateResponse reply;
308 frr::DataTree *dt = new frr::DataTree;
309 ClientContext context;
310
311 request.set_candidate_id(candidate_id);
312 request.set_type(is_replace
313 ? frr::LoadToCandidateRequest::REPLACE
314 : frr::LoadToCandidateRequest::MERGE);
315 dt->set_encoding(is_json ? frr::JSON : frr::XML);
316 dt->set_data(data);
317 request.set_allocated_config(dt);
318
319 Status status =
320 stub_->LoadToCandidate(&context, request, &reply);
321 _throw_if_not_ok(status);
322 }
323
324 std::string ListTransactions()
325 {
326 frr::ListTransactionsRequest request;
327 frr::ListTransactionsResponse reply;
328 ClientContext context;
329 std::ostringstream ss;
330
331 auto stream = stub_->ListTransactions(&context, request);
332
333 while (stream->Read(&reply)) {
334 ss << "Tx ID: " << reply.id()
335 << " client: " << reply.client()
336 << " date: " << reply.date()
337 << " comment: " << reply.comment() << std::endl;
338 }
339
340 auto status = stream->Finish();
341 _throw_if_not_ok(status);
342 return ss.str();
343 }
344
345 private:
346 std::unique_ptr<frr::Northbound::Stub> stub_;
347
348 void _throw_if_not_ok(Status &status)
349 {
350 if (!status.ok())
351 throw std::runtime_error(
352 std::to_string(status.error_code()) + ": "
353 + status.error_message());
354 }
355};
356
357
358bool stop = false;
359
360int grpc_client_test_stop(struct frr_pthread *fpt, void **result)
361{
362 test_debug("client: STOP pthread");
363
364 assert(fpt->running);
365 atomic_store_explicit(&fpt->running, false, memory_order_relaxed);
366
367 test_debug("client: joining pthread");
368 pthread_join(fpt->thread, result);
369
370 test_debug("client: joined pthread");
371 return 0;
372}
373
374int find_first_diff(const std::string &s1, const std::string &s2)
375{
376 int s1len = s1.length();
377 int s2len = s2.length();
378 int mlen = std::min(s1len, s2len);
379
380 for (int i = 0; i < mlen; i++)
381 if (s1[i] != s2[i])
382 return i;
383 return s1len == s2len ? -1 : mlen;
384}
385
386void assert_no_diff(const std::string &s1, const std::string &s2)
387{
388 int pos = find_first_diff(s1, s2);
389 if (pos == -1)
390 return;
391 std::cout << "not ok" << std::endl;
392 std::cout << "Same: " << s1.substr(0, pos) << std::endl;
393 std::cout << "Diff s1: " << s1.substr(pos) << std::endl;
394 std::cout << "Diff s2: " << s2.substr(pos) << std::endl;
395 assert(false);
396}
397
398void assert_config_same(NorthboundClient &client, const std::string &compare)
399{
400 std::string confs = client.Get("/frr-routing:routing",
401 frr::GetRequest::ALL, frr::JSON, true);
402 assert_no_diff(confs, compare);
403 std::cout << "ok" << std::endl;
404}
405
406void grpc_client_run_test(void)
407{
408 NorthboundClient client(grpc::CreateChannel(
409 "localhost:50051", grpc::InsecureChannelCredentials()));
410
411 std::string reply = client.GetCapabilities();
412
413 uint32_t cid;
414 cid = client.CreateCandidate();
415 std::cout << "CreateCandidate -> " << cid << std::endl;
416 assert(cid == 1);
417 client.DeleteCandidate(cid);
418 std::cout << "DeleteCandidate(" << cid << ")" << std::endl;
419 cid = client.CreateCandidate();
420 assert(cid == 2);
421 std::cout << "CreateCandidate -> " << cid << std::endl;
422
423 /*
424 * Get initial configuration
425 */
426 std::cout << "Comparing initial config...";
427 assert_config_same(client, json_expect1);
428
429 /*
430 * Add config using EditCandidate
431 */
432
433 char xpath_buf[1024];
434 strlcpy(xpath_buf,
435 "/frr-routing:routing/control-plane-protocols/"
436 "control-plane-protocol[type='frr-staticd:staticd']"
437 "[name='staticd'][vrf='default']/frr-staticd:staticd/route-list",
438 sizeof(xpath_buf));
439 int slen = strlen(xpath_buf);
440 for (int i = 0; i < 4; i++) {
441 snprintf(xpath_buf + slen, sizeof(xpath_buf) - slen,
442 "[prefix='13.0.%d.0/24']"
443 "[afi-safi='frr-routing:ipv4-unicast']/"
444 "path-list[table-id='0'][distance='1']/"
445 "frr-nexthops/nexthop[nh-type='blackhole']"
446 "[vrf='default'][gateway=''][interface='(null)']",
447 i);
448 client.EditCandidate(cid, xpath_buf, "");
449 }
450 client.Commit(cid);
451 std::cout << "Comparing EditCandidate config...";
452 assert_config_same(client, json_expect2);
453
454 client.DeleteCandidate(cid);
455 std::cout << "DeleteCandidate(" << cid << ")" << std::endl;
456
457 /*
458 * Add config using LoadToCandidate
459 */
460
461 cid = client.CreateCandidate();
462 std::cout << "CreateCandidate -> " << cid << std::endl;
463
464 client.LoadToCandidate(cid, false, true, json_loadconf1);
465 client.Commit(cid);
466
467 std::cout << "Comparing LoadToCandidate config...";
468 assert_config_same(client, json_expect3);
469
470 client.DeleteCandidate(cid);
471 std::cout << "DeleteCandidate(" << cid << ")" << std::endl;
472
473 std::string ltxreply = client.ListTransactions();
474 // std::cout << "client: pthread received: " << ltxreply << std::endl;
475}
476
477void *grpc_client_test_start(void *arg)
478{
479 struct frr_pthread *fpt = (struct frr_pthread *)arg;
480 fpt->master->owner = pthread_self();
481 frr_pthread_set_name(fpt);
482 frr_pthread_notify_running(fpt);
483
484 try {
485 grpc_client_run_test();
486 std::cout << "TEST PASSED" << std::endl;
487 } catch (std::exception &e) {
488 std::cout << "Exception in test: " << e.what() << std::endl;
489 }
490
491 // Signal FRR event loop to stop
492 test_debug("client: pthread: adding event to stop us");
493 thread_add_event(master, grpc_thread_stop, NULL, 0, NULL);
494
495 test_debug("client: pthread: DONE (returning)");
496
497 return NULL;
498}
499
500static int grpc_thread_start(struct thread *thread)
501{
502 struct frr_pthread_attr client = {
503 .start = grpc_client_test_start,
504 .stop = grpc_client_test_stop,
505 };
506
507 auto pth = frr_pthread_new(&client, "GRPC Client thread", "grpc");
508 frr_pthread_run(pth, NULL);
509 frr_pthread_wait_running(pth);
510
511 return 0;
512}
513
514static int grpc_thread_stop(struct thread *thread)
515{
516 std::cout << __func__ << ": frr_pthread_stop_all" << std::endl;
517 frr_pthread_stop_all();
518 std::cout << __func__ << ": static_shutdown" << std::endl;
519 static_shutdown();
520 std::cout << __func__ << ": exit cleanly" << std::endl;
521 exit(0);
522}
523
524/*
525 * return abs path to this binary with trailing `/`. Does not parse path
526 * environment to find in path, which should not matter for unit testing.
527 */
528static int get_binpath(const char *argv0, char cwd[2 * MAXPATHLEN + 1])
529{
530 const char *rch;
531 if (argv0[0] == '/') {
532 *cwd = 0;
533 rch = strrchr(argv0, '/');
534 strlcpy(cwd, argv0, MIN(rch - argv0 + 2, 2 * MAXPATHLEN + 1));
535 return 0;
536 }
537 if (!(rch = strrchr(argv0, '/'))) {
538 /* Does not handle using PATH, shouldn't matter for test */
539 errno = EINVAL;
540 return -1;
541 }
542 if (!getcwd(cwd, MAXPATHLEN))
543 return -1;
544 int len = strlen(cwd);
545 cwd[len++] = '/';
546 strlcpy(cwd + len, argv0, MIN(rch - argv0 + 2, 2 * MAXPATHLEN + 1));
547 return 0;
548}
549
550int main(int argc, char **argv)
551{
552 assert(argc >= 1);
553 if (get_binpath(argv[0], binpath) < 0)
554 exit(1);
555
556 static_startup();
557
558 thread_add_event(master, grpc_thread_start, NULL, 0, NULL);
559
560 /* Event Loop */
561 struct thread thread;
562 while (thread_fetch(master, &thread))
563 thread_call(&thread);
564 return 0;
565}
566
567// clang-format off
568
569const char *json_expect1 = R"NONCE({
570 "frr-routing:routing": {
571 "control-plane-protocols": {
572 "control-plane-protocol": [
573 {
574 "type": "frr-staticd:staticd",
575 "name": "staticd",
576 "vrf": "default",
577 "frr-staticd:staticd": {
578 "route-list": [
579 {
580 "prefix": "11.0.0.0/8",
581 "afi-safi": "frr-routing:ipv4-unicast",
582 "path-list": [
583 {
584 "table-id": 0,
585 "distance": 1,
586 "tag": 0,
587 "frr-nexthops": {
588 "nexthop": [
589 {
590 "nh-type": "blackhole",
591 "vrf": "default",
592 "gateway": "",
593 "interface": "(null)",
594 "bh-type": "null",
595 "onlink": false
596 }
597 ]
598 }
599 }
600 ]
601 }
602 ]
603 }
604 }
605 ]
606 }
607 },
608 "frr-vrf:lib": {
609 "vrf": [
610 {
611 "name": "default",
612 "state": {
613 "active": false
614 }
615 }
616 ]
617 }
618}
619
620)NONCE";
621
622const char *json_loadconf1 = R"NONCE(
623{
624 "frr-routing:routing": {
625 "control-plane-protocols": {
626 "control-plane-protocol": [
627 {
628 "type": "frr-staticd:staticd",
629 "name": "staticd",
630 "vrf": "default",
631 "frr-staticd:staticd": {
632 "route-list": [
633 {
634 "prefix": "10.0.0.0/13",
635 "afi-safi": "frr-routing:ipv4-unicast",
636 "path-list": [
637 {
638 "table-id": 0,
639 "distance": 1,
640 "frr-nexthops": {
641 "nexthop": [
642 {
643 "nh-type": "blackhole",
644 "vrf": "default",
645 "gateway": "",
646 "interface": "(null)"
647 }
648 ]
649 }
650 }
651 ]
652 }
653 ]
654 }
655 }
656 ]
657 }
658 },
659 "frr-vrf:lib": {
660 "vrf": [
661 {
662 "name": "default"
663 }
664 ]
665 }
666})NONCE";
667
668const char *json_expect2 = R"NONCE({
669 "frr-routing:routing": {
670 "control-plane-protocols": {
671 "control-plane-protocol": [
672 {
673 "type": "frr-staticd:staticd",
674 "name": "staticd",
675 "vrf": "default",
676 "frr-staticd:staticd": {
677 "route-list": [
678 {
679 "prefix": "11.0.0.0/8",
680 "afi-safi": "frr-routing:ipv4-unicast",
681 "path-list": [
682 {
683 "table-id": 0,
684 "distance": 1,
685 "tag": 0,
686 "frr-nexthops": {
687 "nexthop": [
688 {
689 "nh-type": "blackhole",
690 "vrf": "default",
691 "gateway": "",
692 "interface": "(null)",
693 "bh-type": "null",
694 "onlink": false
695 }
696 ]
697 }
698 }
699 ]
700 },
701 {
702 "prefix": "13.0.0.0/24",
703 "afi-safi": "frr-routing:ipv4-unicast",
704 "path-list": [
705 {
706 "table-id": 0,
707 "distance": 1,
708 "tag": 0,
709 "frr-nexthops": {
710 "nexthop": [
711 {
712 "nh-type": "blackhole",
713 "vrf": "default",
714 "gateway": "",
715 "interface": "(null)",
716 "bh-type": "null",
717 "onlink": false
718 }
719 ]
720 }
721 }
722 ]
723 },
724 {
725 "prefix": "13.0.1.0/24",
726 "afi-safi": "frr-routing:ipv4-unicast",
727 "path-list": [
728 {
729 "table-id": 0,
730 "distance": 1,
731 "tag": 0,
732 "frr-nexthops": {
733 "nexthop": [
734 {
735 "nh-type": "blackhole",
736 "vrf": "default",
737 "gateway": "",
738 "interface": "(null)",
739 "bh-type": "null",
740 "onlink": false
741 }
742 ]
743 }
744 }
745 ]
746 },
747 {
748 "prefix": "13.0.2.0/24",
749 "afi-safi": "frr-routing:ipv4-unicast",
750 "path-list": [
751 {
752 "table-id": 0,
753 "distance": 1,
754 "tag": 0,
755 "frr-nexthops": {
756 "nexthop": [
757 {
758 "nh-type": "blackhole",
759 "vrf": "default",
760 "gateway": "",
761 "interface": "(null)",
762 "bh-type": "null",
763 "onlink": false
764 }
765 ]
766 }
767 }
768 ]
769 },
770 {
771 "prefix": "13.0.3.0/24",
772 "afi-safi": "frr-routing:ipv4-unicast",
773 "path-list": [
774 {
775 "table-id": 0,
776 "distance": 1,
777 "tag": 0,
778 "frr-nexthops": {
779 "nexthop": [
780 {
781 "nh-type": "blackhole",
782 "vrf": "default",
783 "gateway": "",
784 "interface": "(null)",
785 "bh-type": "null",
786 "onlink": false
787 }
788 ]
789 }
790 }
791 ]
792 }
793 ]
794 }
795 }
796 ]
797 }
798 },
799 "frr-vrf:lib": {
800 "vrf": [
801 {
802 "name": "default",
803 "state": {
804 "active": false
805 }
806 }
807 ]
808 }
809}
810
811)NONCE";
812
813const char *json_expect3 = R"NONCE({
814 "frr-routing:routing": {
815 "control-plane-protocols": {
816 "control-plane-protocol": [
817 {
818 "type": "frr-staticd:staticd",
819 "name": "staticd",
820 "vrf": "default",
821 "frr-staticd:staticd": {
822 "route-list": [
823 {
824 "prefix": "11.0.0.0/8",
825 "afi-safi": "frr-routing:ipv4-unicast",
826 "path-list": [
827 {
828 "table-id": 0,
829 "distance": 1,
830 "tag": 0,
831 "frr-nexthops": {
832 "nexthop": [
833 {
834 "nh-type": "blackhole",
835 "vrf": "default",
836 "gateway": "",
837 "interface": "(null)",
838 "bh-type": "null",
839 "onlink": false
840 }
841 ]
842 }
843 }
844 ]
845 },
846 {
847 "prefix": "13.0.0.0/24",
848 "afi-safi": "frr-routing:ipv4-unicast",
849 "path-list": [
850 {
851 "table-id": 0,
852 "distance": 1,
853 "tag": 0,
854 "frr-nexthops": {
855 "nexthop": [
856 {
857 "nh-type": "blackhole",
858 "vrf": "default",
859 "gateway": "",
860 "interface": "(null)",
861 "bh-type": "null",
862 "onlink": false
863 }
864 ]
865 }
866 }
867 ]
868 },
869 {
870 "prefix": "13.0.1.0/24",
871 "afi-safi": "frr-routing:ipv4-unicast",
872 "path-list": [
873 {
874 "table-id": 0,
875 "distance": 1,
876 "tag": 0,
877 "frr-nexthops": {
878 "nexthop": [
879 {
880 "nh-type": "blackhole",
881 "vrf": "default",
882 "gateway": "",
883 "interface": "(null)",
884 "bh-type": "null",
885 "onlink": false
886 }
887 ]
888 }
889 }
890 ]
891 },
892 {
893 "prefix": "13.0.2.0/24",
894 "afi-safi": "frr-routing:ipv4-unicast",
895 "path-list": [
896 {
897 "table-id": 0,
898 "distance": 1,
899 "tag": 0,
900 "frr-nexthops": {
901 "nexthop": [
902 {
903 "nh-type": "blackhole",
904 "vrf": "default",
905 "gateway": "",
906 "interface": "(null)",
907 "bh-type": "null",
908 "onlink": false
909 }
910 ]
911 }
912 }
913 ]
914 },
915 {
916 "prefix": "13.0.3.0/24",
917 "afi-safi": "frr-routing:ipv4-unicast",
918 "path-list": [
919 {
920 "table-id": 0,
921 "distance": 1,
922 "tag": 0,
923 "frr-nexthops": {
924 "nexthop": [
925 {
926 "nh-type": "blackhole",
927 "vrf": "default",
928 "gateway": "",
929 "interface": "(null)",
930 "bh-type": "null",
931 "onlink": false
932 }
933 ]
934 }
935 }
936 ]
937 },
938 {
939 "prefix": "10.0.0.0/13",
940 "afi-safi": "frr-routing:ipv4-unicast",
941 "path-list": [
942 {
943 "table-id": 0,
944 "distance": 1,
945 "tag": 0,
946 "frr-nexthops": {
947 "nexthop": [
948 {
949 "nh-type": "blackhole",
950 "vrf": "default",
951 "gateway": "",
952 "interface": "(null)",
953 "bh-type": "null",
954 "onlink": false
955 }
956 ]
957 }
958 }
959 ]
960 }
961 ]
962 }
963 }
964 ]
965 }
966 },
967 "frr-vrf:lib": {
968 "vrf": [
969 {
970 "name": "default",
971 "state": {
972 "active": false
973 }
974 }
975 ]
976 }
977}
978
979)NONCE";