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