]> git.proxmox.com Git - mirror_frr.git/blame - lib/northbound_grpc.cpp
pimd: Add missing yang callbacks for route-maps
[mirror_frr.git] / lib / northbound_grpc.cpp
CommitLineData
ec2ac5f2
RW
1//
2// Copyright (C) 2019 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>
63d12a7d
QY
21#include <grpcpp/grpcpp.h>
22#include "grpc/frr-northbound.grpc.pb.h"
ec2ac5f2
RW
23
24#include "log.h"
25#include "libfrr.h"
26#include "version.h"
27#include "command.h"
28#include "lib_errors.h"
29#include "northbound.h"
30#include "northbound_db.h"
31
32#include <iostream>
33#include <sstream>
34#include <memory>
35#include <string>
36
ec2ac5f2
RW
37#define GRPC_DEFAULT_PORT 50051
38
39/*
40 * NOTE: we can't use the FRR debugging infrastructure here since it uses
41 * atomics and C++ has a different atomics API. Enable gRPC debugging
42 * unconditionally until we figure out a way to solve this problem.
43 */
44static bool nb_dbg_client_grpc = 1;
45
46static pthread_t grpc_pthread;
47
48class NorthboundImpl final : public frr::Northbound::Service
49{
50 public:
51 NorthboundImpl(void)
52 {
53 _nextCandidateId = 0;
54 }
55
56 ~NorthboundImpl(void)
57 {
58 // Delete candidates.
59 for (auto it = _candidates.begin(); it != _candidates.end();
60 it++)
61 delete_candidate(&it->second);
62 }
63
64 grpc::Status
65 GetCapabilities(grpc::ServerContext *context,
66 frr::GetCapabilitiesRequest const *request,
67 frr::GetCapabilitiesResponse *response) override
68 {
69 if (nb_dbg_client_grpc)
70 zlog_debug("received RPC GetCapabilities()");
71
72 // Response: string frr_version = 1;
73 response->set_frr_version(FRR_VERSION);
74
75 // Response: bool rollback_support = 2;
76#ifdef HAVE_CONFIG_ROLLBACKS
77 response->set_rollback_support(true);
78#else
79 response->set_rollback_support(false);
80#endif
81
82 // Response: repeated ModuleData supported_modules = 3;
83 struct yang_module *module;
84 RB_FOREACH (module, yang_modules, &yang_modules) {
85 auto m = response->add_supported_modules();
86
87 m->set_name(module->name);
88 if (module->info->rev_size)
89 m->set_revision(module->info->rev[0].date);
90 m->set_organization(module->info->org);
91 }
92
93 // Response: repeated Encoding supported_encodings = 4;
94 response->add_supported_encodings(frr::JSON);
95 response->add_supported_encodings(frr::XML);
96
97 return grpc::Status::OK;
98 }
99
100 grpc::Status Get(grpc::ServerContext *context,
101 frr::GetRequest const *request,
102 grpc::ServerWriter<frr::GetResponse> *writer) override
103 {
104 // Request: DataType type = 1;
105 int type = request->type();
106 // Request: Encoding encoding = 2;
107 frr::Encoding encoding = request->encoding();
108 // Request: bool with_defaults = 3;
109 bool with_defaults = request->with_defaults();
110
111 if (nb_dbg_client_grpc)
112 zlog_debug(
113 "received RPC Get(type: %u, encoding: %u, with_defaults: %u)",
114 type, encoding, with_defaults);
115
116 // Request: repeated string path = 4;
117 auto paths = request->path();
118 for (const std::string &path : paths) {
119 frr::GetResponse response;
120 grpc::Status status;
121
122 // Response: int64 timestamp = 1;
123 response.set_timestamp(time(NULL));
124
125 // Response: DataTree data = 2;
126 auto *data = response.mutable_data();
127 data->set_encoding(request->encoding());
128 status = get_path(data, path, type,
129 encoding2lyd_format(encoding),
130 with_defaults);
131
132 // Something went wrong...
133 if (!status.ok())
134 return status;
135
136 writer->Write(response);
137 }
138
139 if (nb_dbg_client_grpc)
140 zlog_debug("received RPC Get() end");
141
142 return grpc::Status::OK;
143 }
144
145 grpc::Status
146 CreateCandidate(grpc::ServerContext *context,
147 frr::CreateCandidateRequest const *request,
148 frr::CreateCandidateResponse *response) override
149 {
150 if (nb_dbg_client_grpc)
151 zlog_debug("received RPC CreateCandidate()");
152
153 struct candidate *candidate = create_candidate();
154 if (!candidate)
155 return grpc::Status(
156 grpc::StatusCode::RESOURCE_EXHAUSTED,
157 "Can't create candidate configuration");
158
159 // Response: uint32 candidate_id = 1;
160 response->set_candidate_id(candidate->id);
161
162 return grpc::Status::OK;
163 }
164
165 grpc::Status
166 DeleteCandidate(grpc::ServerContext *context,
167 frr::DeleteCandidateRequest const *request,
168 frr::DeleteCandidateResponse *response) override
169 {
170 // Request: uint32 candidate_id = 1;
171 uint32_t candidate_id = request->candidate_id();
172
173 if (nb_dbg_client_grpc)
174 zlog_debug(
175 "received RPC DeleteCandidate(candidate_id: %u)",
176 candidate_id);
177
178 struct candidate *candidate = get_candidate(candidate_id);
179 if (!candidate)
180 return grpc::Status(
181 grpc::StatusCode::NOT_FOUND,
182 "candidate configuration not found");
183
184 delete_candidate(candidate);
185
186 return grpc::Status::OK;
187 }
188
189 grpc::Status
190 UpdateCandidate(grpc::ServerContext *context,
191 frr::UpdateCandidateRequest const *request,
192 frr::UpdateCandidateResponse *response) override
193 {
194 // Request: uint32 candidate_id = 1;
195 uint32_t candidate_id = request->candidate_id();
196
197 if (nb_dbg_client_grpc)
198 zlog_debug(
199 "received RPC UpdateCandidate(candidate_id: %u)",
200 candidate_id);
201
202 struct candidate *candidate = get_candidate(candidate_id);
203 if (!candidate)
204 return grpc::Status(
205 grpc::StatusCode::NOT_FOUND,
206 "candidate configuration not found");
207
208 if (candidate->transaction)
209 return grpc::Status(
210 grpc::StatusCode::FAILED_PRECONDITION,
211 "candidate is in the middle of a transaction");
212
213 if (nb_candidate_update(candidate->config) != NB_OK)
214 return grpc::Status(
215 grpc::StatusCode::INTERNAL,
216 "failed to update candidate configuration");
217
218 return grpc::Status::OK;
219 }
220
221 grpc::Status
222 EditCandidate(grpc::ServerContext *context,
223 frr::EditCandidateRequest const *request,
224 frr::EditCandidateResponse *response) override
225 {
226 // Request: uint32 candidate_id = 1;
227 uint32_t candidate_id = request->candidate_id();
228
229 if (nb_dbg_client_grpc)
230 zlog_debug(
231 "received RPC EditCandidate(candidate_id: %u)",
232 candidate_id);
233
234 struct candidate *candidate = get_candidate(candidate_id);
235 if (!candidate)
236 return grpc::Status(
237 grpc::StatusCode::NOT_FOUND,
238 "candidate configuration not found");
239
240 // Create a copy of the candidate. For consistency, we need to
241 // ensure that either all changes are accepted or none are (in
242 // the event of an error).
243 struct nb_config *candidate_tmp =
244 nb_config_dup(candidate->config);
245
246 auto pvs = request->update();
247 for (const frr::PathValue &pv : pvs) {
248 if (yang_dnode_edit(candidate_tmp->dnode, pv.path(),
249 pv.value())
250 != 0) {
251 nb_config_free(candidate_tmp);
252 return grpc::Status(
253 grpc::StatusCode::INVALID_ARGUMENT,
254 "Failed to update \"" + pv.path()
255 + "\"");
256 }
257 }
258
259 pvs = request->delete_();
260 for (const frr::PathValue &pv : pvs) {
261 if (yang_dnode_delete(candidate_tmp->dnode, pv.path())
262 != 0) {
263 nb_config_free(candidate_tmp);
264 return grpc::Status(
265 grpc::StatusCode::INVALID_ARGUMENT,
266 "Failed to remove \"" + pv.path()
267 + "\"");
268 }
269 }
270
271 // No errors, accept all changes.
272 nb_config_replace(candidate->config, candidate_tmp, false);
273
274 return grpc::Status::OK;
275 }
276
277 grpc::Status
278 LoadToCandidate(grpc::ServerContext *context,
279 frr::LoadToCandidateRequest const *request,
280 frr::LoadToCandidateResponse *response) override
281 {
282 // Request: uint32 candidate_id = 1;
283 uint32_t candidate_id = request->candidate_id();
284 // Request: LoadType type = 2;
285 int load_type = request->type();
286 // Request: DataTree config = 3;
287 auto config = request->config();
288
289 if (nb_dbg_client_grpc)
290 zlog_debug(
291 "received RPC LoadToCandidate(candidate_id: %u)",
292 candidate_id);
293
294 struct candidate *candidate = get_candidate(candidate_id);
295 if (!candidate)
296 return grpc::Status(
297 grpc::StatusCode::NOT_FOUND,
298 "candidate configuration not found");
299
300 struct lyd_node *dnode = dnode_from_data_tree(&config, true);
301 if (!dnode)
302 return grpc::Status(
303 grpc::StatusCode::INTERNAL,
304 "Failed to parse the configuration");
305
306 struct nb_config *loaded_config = nb_config_new(dnode);
307
308 if (load_type == frr::LoadToCandidateRequest::REPLACE)
309 nb_config_replace(candidate->config, loaded_config,
310 false);
311 else if (nb_config_merge(candidate->config, loaded_config,
312 false)
313 != NB_OK)
314 return grpc::Status(
315 grpc::StatusCode::INTERNAL,
316 "Failed to merge the loaded configuration");
317
318 return grpc::Status::OK;
319 }
320
321 grpc::Status Commit(grpc::ServerContext *context,
322 frr::CommitRequest const *request,
323 frr::CommitResponse *response) override
324 {
325 // Request: uint32 candidate_id = 1;
326 uint32_t candidate_id = request->candidate_id();
327 // Request: Phase phase = 2;
328 int phase = request->phase();
329 // Request: string comment = 3;
330 const std::string comment = request->comment();
331
332 if (nb_dbg_client_grpc)
333 zlog_debug("received RPC Commit(candidate_id: %u)",
334 candidate_id);
335
336 // Find candidate configuration.
337 struct candidate *candidate = get_candidate(candidate_id);
338 if (!candidate)
339 return grpc::Status(
340 grpc::StatusCode::NOT_FOUND,
341 "candidate configuration not found");
342
343 int ret = NB_OK;
344 uint32_t transaction_id = 0;
345
346 // Check for misuse of the two-phase commit protocol.
347 switch (phase) {
348 case frr::CommitRequest::PREPARE:
349 case frr::CommitRequest::ALL:
350 if (candidate->transaction)
351 return grpc::Status(
352 grpc::StatusCode::FAILED_PRECONDITION,
353 "pending transaction in progress");
354 break;
355 case frr::CommitRequest::ABORT:
356 case frr::CommitRequest::APPLY:
357 if (!candidate->transaction)
358 return grpc::Status(
359 grpc::StatusCode::FAILED_PRECONDITION,
360 "no transaction in progress");
361 break;
362 default:
363 break;
364 }
365
366 // Execute the user request.
367 switch (phase) {
368 case frr::CommitRequest::VALIDATE:
369 ret = nb_candidate_validate(candidate->config);
370 break;
371 case frr::CommitRequest::PREPARE:
372 ret = nb_candidate_commit_prepare(
373 candidate->config, NB_CLIENT_GRPC, NULL,
374 comment.c_str(), &candidate->transaction);
375 break;
376 case frr::CommitRequest::ABORT:
377 nb_candidate_commit_abort(candidate->transaction);
378 break;
379 case frr::CommitRequest::APPLY:
380 nb_candidate_commit_apply(candidate->transaction, true,
381 &transaction_id);
382 break;
383 case frr::CommitRequest::ALL:
384 ret = nb_candidate_commit(
385 candidate->config, NB_CLIENT_GRPC, NULL, true,
386 comment.c_str(), &transaction_id);
387 break;
388 }
389
390 // Map northbound error codes to gRPC error codes.
391 switch (ret) {
392 case NB_ERR_NO_CHANGES:
393 return grpc::Status(
394 grpc::StatusCode::ABORTED,
395 "No configuration changes detected");
396 case NB_ERR_LOCKED:
397 return grpc::Status(
398 grpc::StatusCode::UNAVAILABLE,
399 "There's already a transaction in progress");
400 case NB_ERR_VALIDATION:
401 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
402 "Validation error");
403 case NB_ERR_RESOURCE:
404 return grpc::Status(
405 grpc::StatusCode::RESOURCE_EXHAUSTED,
406 "Failed do allocate resources");
407 case NB_ERR:
408 return grpc::Status(grpc::StatusCode::INTERNAL,
409 "Internal error");
410 default:
411 break;
412 }
413
414 // Response: uint32 transaction_id = 1;
415 if (transaction_id)
416 response->set_transaction_id(transaction_id);
417
418 return grpc::Status::OK;
419 }
420
421 grpc::Status
422 ListTransactions(grpc::ServerContext *context,
423 frr::ListTransactionsRequest const *request,
424 grpc::ServerWriter<frr::ListTransactionsResponse>
425 *writer) override
426 {
427 if (nb_dbg_client_grpc)
428 zlog_debug("received RPC ListTransactions()");
429
430 nb_db_transactions_iterate(list_transactions_cb, writer);
431
432 return grpc::Status::OK;
433 }
434
435 grpc::Status
436 GetTransaction(grpc::ServerContext *context,
437 frr::GetTransactionRequest const *request,
438 frr::GetTransactionResponse *response) override
439 {
440 struct nb_config *nb_config;
441
442 // Request: uint32 transaction_id = 1;
443 uint32_t transaction_id = request->transaction_id();
444 // Request: Encoding encoding = 2;
445 frr::Encoding encoding = request->encoding();
446 // Request: bool with_defaults = 3;
447 bool with_defaults = request->with_defaults();
448
449 if (nb_dbg_client_grpc)
450 zlog_debug(
451 "received RPC GetTransaction(transaction_id: %u, encoding: %u)",
452 transaction_id, encoding);
453
454 // Load configuration from the transactions database.
455 nb_config = nb_db_transaction_load(transaction_id);
456 if (!nb_config)
457 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
458 "Transaction not found");
459
460 // Response: DataTree config = 1;
461 auto config = response->mutable_config();
462 config->set_encoding(encoding);
463
464 // Dump data using the requested format.
465 if (data_tree_from_dnode(config, nb_config->dnode,
466 encoding2lyd_format(encoding),
467 with_defaults)
468 != 0) {
469 nb_config_free(nb_config);
470 return grpc::Status(grpc::StatusCode::INTERNAL,
471 "Failed to dump data");
472 }
473
474 nb_config_free(nb_config);
475
476 return grpc::Status::OK;
477 }
478
479 grpc::Status LockConfig(grpc::ServerContext *context,
480 frr::LockConfigRequest const *request,
481 frr::LockConfigResponse *response) override
482 {
483 if (nb_dbg_client_grpc)
484 zlog_debug("received RPC LockConfig()");
485
486 if (nb_running_lock(NB_CLIENT_GRPC, NULL))
487 return grpc::Status(
488 grpc::StatusCode::FAILED_PRECONDITION,
489 "running configuration is locked already");
490
491 return grpc::Status::OK;
492 }
493
494 grpc::Status UnlockConfig(grpc::ServerContext *context,
495 frr::UnlockConfigRequest const *request,
496 frr::UnlockConfigResponse *response) override
497 {
498 if (nb_dbg_client_grpc)
499 zlog_debug("received RPC UnlockConfig()");
500
501 if (nb_running_unlock(NB_CLIENT_GRPC, NULL))
502 return grpc::Status(
503 grpc::StatusCode::FAILED_PRECONDITION,
504 "failed to unlock the running configuration");
505
506 return grpc::Status::OK;
507 }
508
509 grpc::Status Execute(grpc::ServerContext *context,
510 frr::ExecuteRequest const *request,
511 frr::ExecuteResponse *response) override
512 {
513 struct nb_node *nb_node;
514 struct list *input_list;
515 struct list *output_list;
516 struct listnode *node;
517 struct yang_data *data;
518 const char *xpath;
519
520 // Request: string path = 1;
521 xpath = request->path().c_str();
522
523 if (nb_dbg_client_grpc)
524 zlog_debug("received RPC Execute(path: \"%s\")", xpath);
525
526 if (request->path().empty())
527 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
528 "Data path is empty");
529
530 nb_node = nb_node_find(xpath);
531 if (!nb_node)
532 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
533 "Unknown data path");
534
535 input_list = yang_data_list_new();
536 output_list = yang_data_list_new();
537
538 // Read input parameters.
539 auto input = request->input();
540 for (const frr::PathValue &pv : input) {
541 // Request: repeated PathValue input = 2;
542 data = yang_data_new(pv.path().c_str(),
543 pv.value().c_str());
544 listnode_add(input_list, data);
545 }
546
547 // Execute callback registered for this XPath.
548 if (nb_node->cbs.rpc(xpath, input_list, output_list) != NB_OK) {
549 flog_warn(EC_LIB_NB_CB_RPC,
550 "%s: rpc callback failed: %s", __func__,
551 xpath);
552 list_delete(&input_list);
553 list_delete(&output_list);
554 return grpc::Status(grpc::StatusCode::INTERNAL,
555 "RPC failed");
556 }
557
558 // Process output parameters.
559 for (ALL_LIST_ELEMENTS_RO(output_list, node, data)) {
560 // Response: repeated PathValue output = 1;
561 frr::PathValue *pv = response->add_output();
562 pv->set_path(data->xpath);
563 pv->set_value(data->value);
564 }
565
566 // Release memory.
567 list_delete(&input_list);
568 list_delete(&output_list);
569
570 return grpc::Status::OK;
571 }
572
573 private:
574 struct candidate {
575 uint32_t id;
576 struct nb_config *config;
577 struct nb_transaction *transaction;
578 };
579 std::map<uint32_t, struct candidate> _candidates;
580 uint32_t _nextCandidateId;
581
582 static int yang_dnode_edit(struct lyd_node *dnode,
583 const std::string &path,
584 const std::string &value)
585 {
586 ly_errno = LY_SUCCESS;
587 dnode = lyd_new_path(dnode, ly_native_ctx, path.c_str(),
588 (void *)value.c_str(),
589 (LYD_ANYDATA_VALUETYPE)0,
590 LYD_PATH_OPT_UPDATE);
591 if (!dnode && ly_errno != LY_SUCCESS) {
592 flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
593 __func__);
594 return -1;
595 }
596
597 return 0;
598 }
599
600 static int yang_dnode_delete(struct lyd_node *dnode,
601 const std::string &path)
602 {
603 dnode = yang_dnode_get(dnode, path.c_str());
604 if (!dnode)
605 return -1;
606
607 lyd_free(dnode);
608
609 return 0;
610 }
611
612 static LYD_FORMAT encoding2lyd_format(enum frr::Encoding encoding)
613 {
614 switch (encoding) {
615 case frr::JSON:
616 return LYD_JSON;
617 case frr::XML:
618 return LYD_XML;
07705c8b
RW
619 default:
620 flog_err(EC_LIB_DEVELOPMENT,
621 "%s: unknown data encoding format (%u)",
622 __func__, encoding);
623 exit(1);
ec2ac5f2
RW
624 }
625 }
626
627 static int get_oper_data_cb(const struct lys_node *snode,
628 struct yang_translator *translator,
629 struct yang_data *data, void *arg)
630 {
631 struct lyd_node *dnode = static_cast<struct lyd_node *>(arg);
632 int ret = yang_dnode_edit(dnode, data->xpath, data->value);
633 yang_data_free(data);
634
635 return (ret == 0) ? NB_OK : NB_ERR;
636 }
637
638 static void list_transactions_cb(void *arg, int transaction_id,
639 const char *client_name,
640 const char *date, const char *comment)
641 {
642 grpc::ServerWriter<frr::ListTransactionsResponse> *writer =
643 static_cast<grpc::ServerWriter<
644 frr::ListTransactionsResponse> *>(arg);
645 frr::ListTransactionsResponse response;
646
647 // Response: uint32 id = 1;
648 response.set_id(transaction_id);
649
650 // Response: string client = 2;
651 response.set_client(client_name);
652
653 // Response: string date = 3;
654 response.set_date(date);
655
656 // Response: string comment = 4;
657 response.set_comment(comment);
658
659 writer->Write(response);
660 }
661
662 static int data_tree_from_dnode(frr::DataTree *dt,
663 const struct lyd_node *dnode,
664 LYD_FORMAT lyd_format,
665 bool with_defaults)
666 {
667 char *strp;
668 int options = 0;
669
670 SET_FLAG(options, LYP_FORMAT | LYP_WITHSIBLINGS);
671 if (with_defaults)
672 SET_FLAG(options, LYP_WD_ALL);
673 else
674 SET_FLAG(options, LYP_WD_TRIM);
675
676 if (lyd_print_mem(&strp, dnode, lyd_format, options) == 0) {
677 if (strp) {
678 dt->set_data(strp);
679 free(strp);
680 }
681 return 0;
682 }
683
684 return -1;
685 }
686
687 static struct lyd_node *dnode_from_data_tree(const frr::DataTree *dt,
688 bool config_only)
689 {
690 struct lyd_node *dnode;
691 int options;
692
693 if (config_only)
694 options = LYD_OPT_CONFIG;
695 else
696 options = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB;
697
698 dnode = lyd_parse_mem(ly_native_ctx, dt->data().c_str(),
699 encoding2lyd_format(dt->encoding()),
700 options);
701
702 return dnode;
703 }
704
705 static struct lyd_node *get_dnode_config(const std::string &path)
706 {
707 struct lyd_node *dnode;
708
8685be73
RW
709 dnode = yang_dnode_get(running_config->dnode,
710 path.empty() ? NULL : path.c_str());
711 if (dnode)
712 dnode = yang_dnode_dup(dnode);
ec2ac5f2
RW
713
714 return dnode;
715 }
716
717 static struct lyd_node *get_dnode_state(const std::string &path)
718 {
719 struct lyd_node *dnode;
720
721 dnode = yang_dnode_new(ly_native_ctx, false);
722 if (nb_oper_data_iterate(path.c_str(), NULL, 0,
723 get_oper_data_cb, dnode)
724 != NB_OK) {
725 yang_dnode_free(dnode);
726 return NULL;
727 }
728
729 return dnode;
730 }
731
732 static grpc::Status get_path(frr::DataTree *dt, const std::string &path,
733 int type, LYD_FORMAT lyd_format,
734 bool with_defaults)
735 {
736 struct lyd_node *dnode_config = NULL;
737 struct lyd_node *dnode_state = NULL;
738 struct lyd_node *dnode_final;
739
740 // Configuration data.
741 if (type == frr::GetRequest_DataType_ALL
742 || type == frr::GetRequest_DataType_CONFIG) {
743 dnode_config = get_dnode_config(path);
744 if (!dnode_config)
745 return grpc::Status(
746 grpc::StatusCode::INVALID_ARGUMENT,
747 "Data path not found");
748 }
749
750 // Operational data.
751 if (type == frr::GetRequest_DataType_ALL
752 || type == frr::GetRequest_DataType_STATE) {
753 dnode_state = get_dnode_state(path);
754 if (!dnode_state) {
755 if (dnode_config)
756 yang_dnode_free(dnode_config);
757 return grpc::Status(
758 grpc::StatusCode::INVALID_ARGUMENT,
759 "Failed to fetch operational data");
760 }
761 }
762
763 switch (type) {
764 case frr::GetRequest_DataType_ALL:
765 //
766 // Combine configuration and state data into a single
767 // dnode.
768 //
769 if (lyd_merge(dnode_state, dnode_config,
770 LYD_OPT_EXPLICIT)
771 != 0) {
772 yang_dnode_free(dnode_state);
773 yang_dnode_free(dnode_config);
774 return grpc::Status(
775 grpc::StatusCode::INTERNAL,
776 "Failed to merge configuration and state data");
777 }
778
779 dnode_final = dnode_state;
780 break;
781 case frr::GetRequest_DataType_CONFIG:
782 dnode_final = dnode_config;
783 break;
784 case frr::GetRequest_DataType_STATE:
785 dnode_final = dnode_state;
786 break;
787 }
788
789 // Validate data to create implicit default nodes if necessary.
790 int validate_opts = 0;
791 if (type == frr::GetRequest_DataType_CONFIG)
792 validate_opts = LYD_OPT_CONFIG;
793 else
794 validate_opts = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB;
795 lyd_validate(&dnode_final, validate_opts, ly_native_ctx);
796
797 // Dump data using the requested format.
798 int ret = data_tree_from_dnode(dt, dnode_final, lyd_format,
799 with_defaults);
800 yang_dnode_free(dnode_final);
801 if (ret != 0)
802 return grpc::Status(grpc::StatusCode::INTERNAL,
803 "Failed to dump data");
804
805 return grpc::Status::OK;
806 }
807
808 struct candidate *create_candidate(void)
809 {
810 uint32_t candidate_id = ++_nextCandidateId;
811
812 // Check for overflow.
813 // TODO: implement an algorithm for unique reusable IDs.
814 if (candidate_id == 0)
815 return NULL;
816
817 struct candidate *candidate = &_candidates[candidate_id];
818 candidate->id = candidate_id;
8685be73 819 candidate->config = nb_config_dup(running_config);
ec2ac5f2
RW
820 candidate->transaction = NULL;
821
822 return candidate;
823 }
824
825 void delete_candidate(struct candidate *candidate)
826 {
827 _candidates.erase(candidate->id);
828 nb_config_free(candidate->config);
829 if (candidate->transaction)
830 nb_candidate_commit_abort(candidate->transaction);
831 }
832
833 struct candidate *get_candidate(uint32_t candidate_id)
834 {
835 struct candidate *candidate;
836
837 if (_candidates.count(candidate_id) == 0)
838 return NULL;
839
840 return &_candidates[candidate_id];
841 }
842};
843
844static void *grpc_pthread_start(void *arg)
845{
846 unsigned long *port = static_cast<unsigned long *>(arg);
847 NorthboundImpl service;
848 std::stringstream server_address;
849
850 server_address << "0.0.0.0:" << *port;
851
852 grpc::ServerBuilder builder;
853 builder.AddListeningPort(server_address.str(),
854 grpc::InsecureServerCredentials());
855 builder.RegisterService(&service);
856
857 std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
858
859 zlog_notice("gRPC server listening on %s",
860 server_address.str().c_str());
861
862 server->Wait();
863
864 return NULL;
865}
866
867static int frr_grpc_init(unsigned long *port)
868{
869 /* Create a pthread for gRPC since it runs its own event loop. */
870 if (pthread_create(&grpc_pthread, NULL, grpc_pthread_start, port)) {
871 flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s",
872 __func__, safe_strerror(errno));
873 return -1;
874 }
875 pthread_detach(grpc_pthread);
876
877 return 0;
878}
879
880static int frr_grpc_finish(void)
881{
882 // TODO: cancel the gRPC pthreads gracefully.
883
884 return 0;
885}
886
887static int frr_grpc_module_late_init(struct thread_master *tm)
888{
889 static unsigned long port = GRPC_DEFAULT_PORT;
890 const char *args = THIS_MODULE->load_args;
891
892 // Parse port number.
893 if (args) {
894 try {
895 port = std::stoul(args);
896 if (port < 1024)
897 throw std::invalid_argument(
898 "can't use privileged port");
899 if (port > UINT16_MAX)
900 throw std::invalid_argument(
901 "port number is too big");
902 } catch (std::exception &e) {
903 flog_err(EC_LIB_GRPC_INIT,
904 "%s: failed to parse port number: %s",
905 __func__, e.what());
906 goto error;
907 }
908 }
909
910 if (frr_grpc_init(&port) < 0)
911 goto error;
912
913 hook_register(frr_fini, frr_grpc_finish);
914
915 return 0;
916
917error:
918 flog_err(EC_LIB_GRPC_INIT, "failed to initialize the gRPC module");
919 return -1;
920}
921
922static int frr_grpc_module_init(void)
923{
924 hook_register(frr_late_init, frr_grpc_module_late_init);
925
926 return 0;
927}
928
929FRR_MODULE_SETUP(.name = "frr_grpc", .version = FRR_VERSION,
930 .description = "FRR gRPC northbound module",
931 .init = frr_grpc_module_init, )