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