]>
Commit | Line | Data |
---|---|---|
d92f5fd8 RZ |
1 | .. _grpc-dev: |
2 | ||
3 | *************** | |
4 | Northbound gRPC | |
5 | *************** | |
6 | ||
640b04d9 CH |
7 | To enable gRPC support one needs to add `--enable-grpc` when running |
8 | `configure`. Additionally, when launching each daemon one needs to request | |
9 | the gRPC module be loaded and which port to bind to. This can be done by adding | |
10 | `-M grpc:<port>` to the daemon's CLI arguments. | |
11 | ||
12 | Currently there is no gRPC "routing" so you will need to bind your gRPC | |
13 | `channel` to the particular daemon's gRPC port to interact with that daemon's | |
14 | gRPC northbound interface. | |
15 | ||
16 | The minimum version of gRPC known to work is 1.16.1. | |
17 | ||
d92f5fd8 RZ |
18 | .. _grpc-languages-bindings: |
19 | ||
20 | Programming Language Bindings | |
21 | ============================= | |
22 | ||
23 | The gRPC supported programming language bindings can be found here: | |
24 | https://grpc.io/docs/languages/ | |
25 | ||
26 | After picking a programming language that supports gRPC bindings, the | |
27 | next step is to generate the FRR northbound bindings. To generate the | |
28 | northbound bindings you'll need the programming language binding | |
29 | generator tools and those are language specific. | |
30 | ||
640b04d9 CH |
31 | C++ Example |
32 | ----------- | |
33 | ||
34 | The next sections will use C++ as an example for accessing FRR | |
35 | northbound through gRPC. | |
36 | ||
37 | .. _grpc-c++-generate: | |
38 | ||
39 | Generating C++ FRR Bindings | |
40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
41 | ||
42 | Generating FRR northbound bindings for C++ example: | |
43 | ||
44 | :: | |
98059bbc | 45 | |
640b04d9 CH |
46 | # Install gRPC (e.g., on Ubuntu 20.04) |
47 | sudo apt-get install libgrpc++-dev libgrpc-dev | |
48 | ||
49 | mkdir /tmp/frr-cpp | |
50 | cd grpc | |
51 | ||
52 | protoc --cpp_out=/tmp/frr-cpp \ | |
53 | --grpc_out=/tmp/frr-cpp \ | |
54 | -I $(pwd) \ | |
55 | --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` \ | |
56 | frr-northbound.proto | |
57 | ||
58 | ||
59 | .. _grpc-c++-if-sample: | |
60 | ||
61 | Using C++ To Get Version and Interfaces State | |
62 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
63 | ||
64 | Below is a sample program to print all interfaces discovered. | |
65 | ||
66 | :: | |
67 | ||
68 | # test.cpp | |
69 | #include <string> | |
70 | #include <sstream> | |
71 | #include <grpc/grpc.h> | |
72 | #include <grpcpp/create_channel.h> | |
73 | #include "frr-northbound.pb.h" | |
74 | #include "frr-northbound.grpc.pb.h" | |
75 | ||
76 | int main() { | |
77 | frr::GetRequest request; | |
78 | frr::GetResponse reply; | |
79 | grpc::ClientContext context; | |
80 | grpc::Status status; | |
81 | ||
82 | auto channel = grpc::CreateChannel("localhost:50051", | |
83 | grpc::InsecureChannelCredentials()); | |
84 | auto stub = frr::Northbound::NewStub(channel); | |
85 | ||
86 | request.set_type(frr::GetRequest::ALL); | |
87 | request.set_encoding(frr::JSON); | |
88 | request.set_with_defaults(true); | |
89 | request.add_path("/frr-interface:lib"); | |
90 | auto stream = stub->Get(&context, request); | |
91 | ||
92 | std::ostringstream ss; | |
93 | while (stream->Read(&reply)) | |
94 | ss << reply.data().data() << std::endl; | |
95 | ||
96 | status = stream->Finish(); | |
97 | assert(status.ok()); | |
98 | std::cout << "Interface Info:\n" << ss.str() << std::endl; | |
99 | } | |
100 | ||
101 | Below is how to compile and run the program, with the example output: | |
102 | ||
103 | :: | |
104 | ||
105 | $ g++ -o test test.cpp frr-northbound.grpc.pb.cc frr-northbound.pb.cc -lgrpc++ -lprotobuf | |
106 | $ ./test | |
107 | Interface Info: | |
108 | { | |
109 | "frr-interface:lib": { | |
110 | "interface": [ | |
111 | { | |
112 | "name": "lo", | |
113 | "vrf": "default", | |
114 | "state": { | |
115 | "if-index": 1, | |
116 | "mtu": 0, | |
117 | "mtu6": 65536, | |
118 | "speed": 0, | |
119 | "metric": 0, | |
120 | "phy-address": "00:00:00:00:00:00" | |
121 | }, | |
122 | "frr-zebra:zebra": { | |
123 | "state": { | |
124 | "up-count": 0, | |
125 | "down-count": 0, | |
126 | "ptm-status": "disabled" | |
127 | } | |
128 | } | |
129 | }, | |
130 | { | |
131 | "name": "r1-eth0", | |
132 | "vrf": "default", | |
133 | "state": { | |
134 | "if-index": 2, | |
135 | "mtu": 1500, | |
136 | "mtu6": 1500, | |
137 | "speed": 10000, | |
138 | "metric": 0, | |
139 | "phy-address": "02:37:ac:63:59:b9" | |
140 | }, | |
141 | "frr-zebra:zebra": { | |
142 | "state": { | |
143 | "up-count": 0, | |
144 | "down-count": 0, | |
145 | "ptm-status": "disabled" | |
146 | } | |
147 | } | |
148 | } | |
149 | ] | |
150 | }, | |
151 | "frr-zebra:zebra": { | |
152 | "mcast-rpf-lookup": "mrib-then-urib", | |
153 | "workqueue-hold-timer": 10, | |
154 | "zapi-packets": 1000, | |
155 | "import-kernel-table": { | |
156 | "distance": 15 | |
157 | }, | |
158 | "dplane-queue-limit": 200 | |
159 | } | |
160 | } | |
161 | ||
162 | ||
163 | ||
164 | .. _grpc-python-example: | |
165 | ||
166 | Python Example | |
167 | -------------- | |
168 | ||
169 | The next sections will use Python as an example for writing scripts to use | |
d92f5fd8 RZ |
170 | the northbound. |
171 | ||
640b04d9 CH |
172 | .. _grpc-python-generate: |
173 | ||
174 | Generating Python FRR Bindings | |
175 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
176 | ||
177 | Generating FRR northbound bindings for Python example: | |
178 | ||
179 | :: | |
180 | ||
181 | # Install python3 virtual environment capability e.g., | |
182 | sudo apt-get install python3-venv | |
183 | ||
184 | # Create a virtual environment for python grpc and activate | |
185 | python3 -m venv venv-grpc | |
186 | source venv-grpc/bin/activate | |
187 | ||
188 | # Install grpc requirements | |
189 | pip install grpcio grpcio-tools | |
190 | ||
191 | mkdir /tmp/frr-python | |
192 | cd grpc | |
193 | ||
194 | python3 -m grpc_tools.protoc \ | |
195 | --python_out=/tmp/frr-python \ | |
196 | --grpc_python_out=/tmp/frr-python \ | |
197 | -I $(pwd) \ | |
198 | frr-northbound.proto | |
199 | ||
200 | .. _grpc-python-if-sample: | |
201 | ||
202 | Using Python To Get Capabilities and Interfaces State | |
203 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
204 | ||
205 | Below is a sample script to print capabilities and all interfaces Python | |
206 | discovered. This demostrates the 2 different RPC results one gets from gRPC, | |
207 | Unary (`GetCapabilities`) and Streaming (`Get`) for the interface state. | |
208 | ||
209 | :: | |
210 | ||
211 | import grpc | |
212 | import frr_northbound_pb2 | |
213 | import frr_northbound_pb2_grpc | |
214 | ||
215 | channel = grpc.insecure_channel('localhost:50051') | |
216 | stub = frr_northbound_pb2_grpc.NorthboundStub(channel) | |
217 | ||
218 | # Print Capabilities | |
219 | request = frr_northbound_pb2.GetCapabilitiesRequest() | |
220 | response = stub.GetCapabilities(request) | |
221 | print(response) | |
222 | ||
223 | # Print Interface State and Config | |
224 | request = frr_northbound_pb2.GetRequest() | |
225 | request.path.append("/frr-interface:lib") | |
226 | request.type=frr_northbound_pb2.GetRequest.ALL | |
227 | request.encoding=frr_northbound_pb2.XML | |
228 | ||
229 | for r in stub.Get(request): | |
230 | print(r.data.data) | |
231 | ||
232 | The previous script will output something like: | |
233 | ||
234 | :: | |
235 | ||
236 | frr_version: "7.7-dev-my-manual-build" | |
237 | rollback_support: true | |
238 | supported_modules { | |
239 | name: "frr-filter" | |
240 | organization: "FRRouting" | |
241 | revision: "2019-07-04" | |
242 | } | |
243 | supported_modules { | |
244 | name: "frr-interface" | |
245 | organization: "FRRouting" | |
246 | revision: "2020-02-05" | |
247 | } | |
248 | [...] | |
249 | supported_encodings: JSON | |
250 | supported_encodings: XML | |
251 | ||
252 | <lib xmlns="http://frrouting.org/yang/interface"> | |
253 | <interface> | |
254 | <name>lo</name> | |
255 | <vrf>default</vrf> | |
256 | <state> | |
257 | <if-index>1</if-index> | |
258 | <mtu>0</mtu> | |
259 | <mtu6>65536</mtu6> | |
260 | <speed>0</speed> | |
261 | <metric>0</metric> | |
262 | <phy-address>00:00:00:00:00:00</phy-address> | |
263 | </state> | |
264 | <zebra xmlns="http://frrouting.org/yang/zebra"> | |
265 | <state> | |
266 | <up-count>0</up-count> | |
267 | <down-count>0</down-count> | |
268 | </state> | |
269 | </zebra> | |
270 | </interface> | |
271 | <interface> | |
272 | <name>r1-eth0</name> | |
273 | <vrf>default</vrf> | |
274 | <state> | |
275 | <if-index>2</if-index> | |
276 | <mtu>1500</mtu> | |
277 | <mtu6>1500</mtu6> | |
278 | <speed>10000</speed> | |
279 | <metric>0</metric> | |
280 | <phy-address>f2:62:2e:f3:4c:e4</phy-address> | |
281 | </state> | |
282 | <zebra xmlns="http://frrouting.org/yang/zebra"> | |
283 | <state> | |
284 | <up-count>0</up-count> | |
285 | <down-count>0</down-count> | |
286 | </state> | |
287 | </zebra> | |
288 | </interface> | |
289 | </lib> | |
290 | ||
291 | .. _grpc-ruby-example: | |
292 | ||
293 | Ruby Example | |
294 | ------------ | |
295 | ||
296 | Next sections will use Ruby as an example for writing scripts to use | |
297 | the northbound. | |
d92f5fd8 RZ |
298 | |
299 | .. _grpc-ruby-generate: | |
300 | ||
301 | Generating Ruby FRR Bindings | |
640b04d9 | 302 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
d92f5fd8 RZ |
303 | |
304 | Generating FRR northbound bindings for Ruby example: | |
305 | ||
306 | :: | |
307 | ||
308 | # Install the required gems: | |
309 | # - grpc: the gem that will talk with FRR's gRPC plugin. | |
310 | # - grpc-tools: the gem that provides the code generator. | |
311 | gem install grpc | |
312 | gem install grpc-tools | |
313 | ||
314 | # Create your project/scripts directory: | |
315 | mkdir /tmp/frr-ruby | |
316 | ||
317 | # Go to FRR's grpc directory: | |
318 | cd grpc | |
319 | ||
320 | # Generate the ruby bindings: | |
321 | grpc_tools_ruby_protoc \ | |
322 | --ruby_out=/tmp/frr-ruby \ | |
323 | --grpc_out=/tmp/frr-ruby \ | |
324 | frr-northbound.proto | |
325 | ||
326 | ||
327 | .. _grpc-ruby-if-sample: | |
328 | ||
329 | Using Ruby To Get Interfaces State | |
640b04d9 | 330 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
d92f5fd8 RZ |
331 | |
332 | Here is a sample script to print all interfaces FRR discovered: | |
333 | ||
334 | :: | |
335 | ||
336 | require 'frr-northbound_services_pb' | |
337 | ||
338 | # Create the connection with FRR's gRPC: | |
339 | stub = Frr::Northbound::Stub.new('localhost:50051', :this_channel_is_insecure) | |
340 | ||
341 | # Create a new state request to get interface state: | |
342 | request = Frr::GetRequest.new | |
343 | request.type = :STATE | |
344 | request.path.push('/frr-interface:lib') | |
345 | ||
346 | # Ask FRR. | |
347 | response = stub.get(request) | |
348 | ||
349 | # Print the response. | |
350 | response.each do |result| | |
351 | result.data.data.each_line do |line| | |
352 | puts line | |
353 | end | |
354 | end | |
355 | ||
356 | ||
357 | .. note:: | |
358 | ||
359 | The generated files will assume that they are in the search path (e.g. | |
360 | inside gem) so you'll need to either edit it to use ``require_relative`` or | |
361 | tell Ruby where to look for them. For simplicity we'll use ``-I .`` to tell | |
362 | it is in the current directory. | |
363 | ||
364 | ||
365 | The previous script will output something like this: | |
366 | ||
367 | :: | |
368 | ||
369 | $ cd /tmp/frr-ruby | |
370 | # Add `-I.` so ruby finds the FRR generated file locally. | |
371 | $ ruby -I. interface.rb | |
372 | { | |
373 | "frr-interface:lib": { | |
374 | "interface": [ | |
375 | { | |
376 | "name": "eth0", | |
377 | "vrf": "default", | |
378 | "state": { | |
379 | "if-index": 2, | |
380 | "mtu": 1500, | |
381 | "mtu6": 1500, | |
382 | "speed": 1000, | |
383 | "metric": 0, | |
384 | "phy-address": "11:22:33:44:55:66" | |
385 | }, | |
386 | "frr-zebra:zebra": { | |
387 | "state": { | |
388 | "up-count": 0, | |
389 | "down-count": 0 | |
390 | } | |
391 | } | |
392 | }, | |
393 | { | |
394 | "name": "lo", | |
395 | "vrf": "default", | |
396 | "state": { | |
397 | "if-index": 1, | |
398 | "mtu": 0, | |
399 | "mtu6": 65536, | |
400 | "speed": 0, | |
401 | "metric": 0, | |
402 | "phy-address": "00:00:00:00:00:00" | |
403 | }, | |
404 | "frr-zebra:zebra": { | |
405 | "state": { | |
406 | "up-count": 0, | |
407 | "down-count": 0 | |
408 | } | |
409 | } | |
410 | } | |
411 | ] | |
412 | } | |
413 | } | |
414 | ||
415 | ||
416 | .. _grpc-ruby-bfd-profile-sample: | |
417 | ||
418 | Using Ruby To Create BFD Profiles | |
640b04d9 | 419 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
d92f5fd8 RZ |
420 | |
421 | In this example you'll learn how to edit configuration using JSON | |
422 | and programmatic (XPath) format. | |
423 | ||
424 | :: | |
425 | ||
426 | require 'frr-northbound_services_pb' | |
427 | ||
428 | # Create the connection with FRR's gRPC: | |
429 | stub = Frr::Northbound::Stub.new('localhost:50051', :this_channel_is_insecure) | |
430 | ||
431 | # Create a new candidate configuration change. | |
432 | new_candidate = stub.create_candidate(Frr::CreateCandidateRequest.new) | |
433 | ||
434 | # Use JSON to configure. | |
435 | request = Frr::LoadToCandidateRequest.new | |
436 | request.candidate_id = new_candidate.candidate_id | |
437 | request.type = :MERGE | |
438 | request.config = Frr::DataTree.new | |
439 | request.config.encoding = :JSON | |
440 | request.config.data = <<-EOJ | |
441 | { | |
442 | "frr-bfdd:bfdd": { | |
443 | "bfd": { | |
444 | "profile": [ | |
445 | { | |
446 | "name": "test-prof", | |
447 | "detection-multiplier": 4, | |
448 | "required-receive-interval": 800000 | |
449 | } | |
450 | ] | |
451 | } | |
452 | } | |
453 | } | |
454 | EOJ | |
455 | ||
456 | # Load configuration to candidate. | |
457 | stub.load_to_candidate(request) | |
458 | ||
459 | # Commit candidate. | |
460 | stub.commit( | |
461 | Frr::CommitRequest.new( | |
462 | candidate_id: new_candidate.candidate_id, | |
463 | phase: :ALL, | |
464 | comment: 'create test-prof' | |
465 | ) | |
466 | ) | |
467 | ||
468 | # | |
469 | # Now lets delete the previous profile and create a new one. | |
470 | # | |
471 | ||
472 | # Create a new candidate configuration change. | |
473 | new_candidate = stub.create_candidate(Frr::CreateCandidateRequest.new) | |
474 | ||
475 | # Edit the configuration candidate. | |
476 | request = Frr::EditCandidateRequest.new | |
477 | request.candidate_id = new_candidate.candidate_id | |
478 | ||
479 | # Delete previously created profile. | |
480 | request.delete.push( | |
481 | Frr::PathValue.new( | |
482 | path: "/frr-bfdd:bfdd/bfd/profile[name='test-prof']", | |
483 | ) | |
484 | ) | |
485 | ||
486 | # Add new profile with two configurations. | |
487 | request.update.push( | |
488 | Frr::PathValue.new( | |
489 | path: "/frr-bfdd:bfdd/bfd/profile[name='test-prof-2']/detection-multiplier", | |
490 | value: 5.to_s | |
491 | ) | |
492 | ) | |
493 | request.update.push( | |
494 | Frr::PathValue.new( | |
495 | path: "/frr-bfdd:bfdd/bfd/profile[name='test-prof-2']/desired-transmission-interval", | |
496 | value: 900_000.to_s | |
497 | ) | |
498 | ) | |
499 | ||
500 | # Modify the candidate. | |
501 | stub.edit_candidate(request) | |
502 | ||
503 | # Commit the candidate configuration. | |
504 | stub.commit( | |
505 | Frr::CommitRequest.new( | |
506 | candidate_id: new_candidate.candidate_id, | |
507 | phase: :ALL, | |
508 | comment: 'replace test-prof with test-prof-2' | |
509 | ) | |
510 | ) | |
511 | ||
512 | ||
513 | And here is the new FRR configuration: | |
514 | ||
515 | :: | |
516 | ||
517 | $ sudo vtysh -c 'show running-config' | |
518 | ... | |
519 | bfd | |
520 | profile test-prof-2 | |
521 | detect-multiplier 5 | |
522 | transmit-interval 900 | |
523 | ! | |
524 | ! |