2 // Copyright (C) 2019 NetDEF, Inc.
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)
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
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
21 #include <grpcpp/grpcpp.h>
22 #include "grpc/frr-northbound.grpc.pb.h"
28 #include "lib_errors.h"
29 #include "northbound.h"
30 #include "northbound_db.h"
37 #define GRPC_DEFAULT_PORT 50051
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.
44 static bool nb_dbg_client_grpc
= 1;
46 static pthread_t grpc_pthread
;
48 class NorthboundImpl final
: public frr::Northbound::Service
59 for (auto it
= _candidates
.begin(); it
!= _candidates
.end();
61 delete_candidate(&it
->second
);
65 GetCapabilities(grpc::ServerContext
*context
,
66 frr::GetCapabilitiesRequest
const *request
,
67 frr::GetCapabilitiesResponse
*response
) override
69 if (nb_dbg_client_grpc
)
70 zlog_debug("received RPC GetCapabilities()");
72 // Response: string frr_version = 1;
73 response
->set_frr_version(FRR_VERSION
);
75 // Response: bool rollback_support = 2;
76 #ifdef HAVE_CONFIG_ROLLBACKS
77 response
->set_rollback_support(true);
79 response
->set_rollback_support(false);
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();
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
);
93 // Response: repeated Encoding supported_encodings = 4;
94 response
->add_supported_encodings(frr::JSON
);
95 response
->add_supported_encodings(frr::XML
);
97 return grpc::Status::OK
;
100 grpc::Status
Get(grpc::ServerContext
*context
,
101 frr::GetRequest
const *request
,
102 grpc::ServerWriter
<frr::GetResponse
> *writer
) override
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();
111 if (nb_dbg_client_grpc
)
113 "received RPC Get(type: %u, encoding: %u, with_defaults: %u)",
114 type
, encoding
, with_defaults
);
116 // Request: repeated string path = 4;
117 auto paths
= request
->path();
118 for (const std::string
&path
: paths
) {
119 frr::GetResponse response
;
122 // Response: int64 timestamp = 1;
123 response
.set_timestamp(time(NULL
));
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
),
132 // Something went wrong...
136 writer
->Write(response
);
139 if (nb_dbg_client_grpc
)
140 zlog_debug("received RPC Get() end");
142 return grpc::Status::OK
;
146 CreateCandidate(grpc::ServerContext
*context
,
147 frr::CreateCandidateRequest
const *request
,
148 frr::CreateCandidateResponse
*response
) override
150 if (nb_dbg_client_grpc
)
151 zlog_debug("received RPC CreateCandidate()");
153 struct candidate
*candidate
= create_candidate();
156 grpc::StatusCode::RESOURCE_EXHAUSTED
,
157 "Can't create candidate configuration");
159 // Response: uint32 candidate_id = 1;
160 response
->set_candidate_id(candidate
->id
);
162 return grpc::Status::OK
;
166 DeleteCandidate(grpc::ServerContext
*context
,
167 frr::DeleteCandidateRequest
const *request
,
168 frr::DeleteCandidateResponse
*response
) override
170 // Request: uint32 candidate_id = 1;
171 uint32_t candidate_id
= request
->candidate_id();
173 if (nb_dbg_client_grpc
)
175 "received RPC DeleteCandidate(candidate_id: %u)",
178 struct candidate
*candidate
= get_candidate(candidate_id
);
181 grpc::StatusCode::NOT_FOUND
,
182 "candidate configuration not found");
184 delete_candidate(candidate
);
186 return grpc::Status::OK
;
190 UpdateCandidate(grpc::ServerContext
*context
,
191 frr::UpdateCandidateRequest
const *request
,
192 frr::UpdateCandidateResponse
*response
) override
194 // Request: uint32 candidate_id = 1;
195 uint32_t candidate_id
= request
->candidate_id();
197 if (nb_dbg_client_grpc
)
199 "received RPC UpdateCandidate(candidate_id: %u)",
202 struct candidate
*candidate
= get_candidate(candidate_id
);
205 grpc::StatusCode::NOT_FOUND
,
206 "candidate configuration not found");
208 if (candidate
->transaction
)
210 grpc::StatusCode::FAILED_PRECONDITION
,
211 "candidate is in the middle of a transaction");
213 if (nb_candidate_update(candidate
->config
) != NB_OK
)
215 grpc::StatusCode::INTERNAL
,
216 "failed to update candidate configuration");
218 return grpc::Status::OK
;
222 EditCandidate(grpc::ServerContext
*context
,
223 frr::EditCandidateRequest
const *request
,
224 frr::EditCandidateResponse
*response
) override
226 // Request: uint32 candidate_id = 1;
227 uint32_t candidate_id
= request
->candidate_id();
229 if (nb_dbg_client_grpc
)
231 "received RPC EditCandidate(candidate_id: %u)",
234 struct candidate
*candidate
= get_candidate(candidate_id
);
237 grpc::StatusCode::NOT_FOUND
,
238 "candidate configuration not found");
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
);
246 auto pvs
= request
->update();
247 for (const frr::PathValue
&pv
: pvs
) {
248 if (yang_dnode_edit(candidate_tmp
->dnode
, pv
.path(),
251 nb_config_free(candidate_tmp
);
253 grpc::StatusCode::INVALID_ARGUMENT
,
254 "Failed to update \"" + pv
.path()
259 pvs
= request
->delete_();
260 for (const frr::PathValue
&pv
: pvs
) {
261 if (yang_dnode_delete(candidate_tmp
->dnode
, pv
.path())
263 nb_config_free(candidate_tmp
);
265 grpc::StatusCode::INVALID_ARGUMENT
,
266 "Failed to remove \"" + pv
.path()
271 // No errors, accept all changes.
272 nb_config_replace(candidate
->config
, candidate_tmp
, false);
274 return grpc::Status::OK
;
278 LoadToCandidate(grpc::ServerContext
*context
,
279 frr::LoadToCandidateRequest
const *request
,
280 frr::LoadToCandidateResponse
*response
) override
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();
289 if (nb_dbg_client_grpc
)
291 "received RPC LoadToCandidate(candidate_id: %u)",
294 struct candidate
*candidate
= get_candidate(candidate_id
);
297 grpc::StatusCode::NOT_FOUND
,
298 "candidate configuration not found");
300 struct lyd_node
*dnode
= dnode_from_data_tree(&config
, true);
303 grpc::StatusCode::INTERNAL
,
304 "Failed to parse the configuration");
306 struct nb_config
*loaded_config
= nb_config_new(dnode
);
308 if (load_type
== frr::LoadToCandidateRequest::REPLACE
)
309 nb_config_replace(candidate
->config
, loaded_config
,
311 else if (nb_config_merge(candidate
->config
, loaded_config
,
315 grpc::StatusCode::INTERNAL
,
316 "Failed to merge the loaded configuration");
318 return grpc::Status::OK
;
321 grpc::Status
Commit(grpc::ServerContext
*context
,
322 frr::CommitRequest
const *request
,
323 frr::CommitResponse
*response
) override
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();
332 if (nb_dbg_client_grpc
)
333 zlog_debug("received RPC Commit(candidate_id: %u)",
336 // Find candidate configuration.
337 struct candidate
*candidate
= get_candidate(candidate_id
);
340 grpc::StatusCode::NOT_FOUND
,
341 "candidate configuration not found");
344 uint32_t transaction_id
= 0;
346 // Check for misuse of the two-phase commit protocol.
348 case frr::CommitRequest::PREPARE
:
349 case frr::CommitRequest::ALL
:
350 if (candidate
->transaction
)
352 grpc::StatusCode::FAILED_PRECONDITION
,
353 "pending transaction in progress");
355 case frr::CommitRequest::ABORT
:
356 case frr::CommitRequest::APPLY
:
357 if (!candidate
->transaction
)
359 grpc::StatusCode::FAILED_PRECONDITION
,
360 "no transaction in progress");
366 // Execute the user request.
368 case frr::CommitRequest::VALIDATE
:
369 ret
= nb_candidate_validate(candidate
->config
);
371 case frr::CommitRequest::PREPARE
:
372 ret
= nb_candidate_commit_prepare(
373 candidate
->config
, NB_CLIENT_GRPC
, NULL
,
374 comment
.c_str(), &candidate
->transaction
);
376 case frr::CommitRequest::ABORT
:
377 nb_candidate_commit_abort(candidate
->transaction
);
379 case frr::CommitRequest::APPLY
:
380 nb_candidate_commit_apply(candidate
->transaction
, true,
383 case frr::CommitRequest::ALL
:
384 ret
= nb_candidate_commit(
385 candidate
->config
, NB_CLIENT_GRPC
, NULL
, true,
386 comment
.c_str(), &transaction_id
);
390 // Map northbound error codes to gRPC error codes.
392 case NB_ERR_NO_CHANGES
:
394 grpc::StatusCode::ABORTED
,
395 "No configuration changes detected");
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
,
403 case NB_ERR_RESOURCE
:
405 grpc::StatusCode::RESOURCE_EXHAUSTED
,
406 "Failed do allocate resources");
408 return grpc::Status(grpc::StatusCode::INTERNAL
,
414 // Response: uint32 transaction_id = 1;
416 response
->set_transaction_id(transaction_id
);
418 return grpc::Status::OK
;
422 ListTransactions(grpc::ServerContext
*context
,
423 frr::ListTransactionsRequest
const *request
,
424 grpc::ServerWriter
<frr::ListTransactionsResponse
>
427 if (nb_dbg_client_grpc
)
428 zlog_debug("received RPC ListTransactions()");
430 nb_db_transactions_iterate(list_transactions_cb
, writer
);
432 return grpc::Status::OK
;
436 GetTransaction(grpc::ServerContext
*context
,
437 frr::GetTransactionRequest
const *request
,
438 frr::GetTransactionResponse
*response
) override
440 struct nb_config
*nb_config
;
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();
449 if (nb_dbg_client_grpc
)
451 "received RPC GetTransaction(transaction_id: %u, encoding: %u)",
452 transaction_id
, encoding
);
454 // Load configuration from the transactions database.
455 nb_config
= nb_db_transaction_load(transaction_id
);
457 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT
,
458 "Transaction not found");
460 // Response: DataTree config = 1;
461 auto config
= response
->mutable_config();
462 config
->set_encoding(encoding
);
464 // Dump data using the requested format.
465 if (data_tree_from_dnode(config
, nb_config
->dnode
,
466 encoding2lyd_format(encoding
),
469 nb_config_free(nb_config
);
470 return grpc::Status(grpc::StatusCode::INTERNAL
,
471 "Failed to dump data");
474 nb_config_free(nb_config
);
476 return grpc::Status::OK
;
479 grpc::Status
LockConfig(grpc::ServerContext
*context
,
480 frr::LockConfigRequest
const *request
,
481 frr::LockConfigResponse
*response
) override
483 if (nb_dbg_client_grpc
)
484 zlog_debug("received RPC LockConfig()");
486 if (nb_running_lock(NB_CLIENT_GRPC
, NULL
))
488 grpc::StatusCode::FAILED_PRECONDITION
,
489 "running configuration is locked already");
491 return grpc::Status::OK
;
494 grpc::Status
UnlockConfig(grpc::ServerContext
*context
,
495 frr::UnlockConfigRequest
const *request
,
496 frr::UnlockConfigResponse
*response
) override
498 if (nb_dbg_client_grpc
)
499 zlog_debug("received RPC UnlockConfig()");
501 if (nb_running_unlock(NB_CLIENT_GRPC
, NULL
))
503 grpc::StatusCode::FAILED_PRECONDITION
,
504 "failed to unlock the running configuration");
506 return grpc::Status::OK
;
509 grpc::Status
Execute(grpc::ServerContext
*context
,
510 frr::ExecuteRequest
const *request
,
511 frr::ExecuteResponse
*response
) override
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
;
520 // Request: string path = 1;
521 xpath
= request
->path().c_str();
523 if (nb_dbg_client_grpc
)
524 zlog_debug("received RPC Execute(path: \"%s\")", xpath
);
526 if (request
->path().empty())
527 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT
,
528 "Data path is empty");
530 nb_node
= nb_node_find(xpath
);
532 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT
,
533 "Unknown data path");
535 input_list
= yang_data_list_new();
536 output_list
= yang_data_list_new();
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(),
544 listnode_add(input_list
, data
);
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__
,
552 list_delete(&input_list
);
553 list_delete(&output_list
);
554 return grpc::Status(grpc::StatusCode::INTERNAL
,
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
);
567 list_delete(&input_list
);
568 list_delete(&output_list
);
570 return grpc::Status::OK
;
576 struct nb_config
*config
;
577 struct nb_transaction
*transaction
;
579 std::map
<uint32_t, struct candidate
> _candidates
;
580 uint32_t _nextCandidateId
;
582 static int yang_dnode_edit(struct lyd_node
*dnode
,
583 const std::string
&path
,
584 const std::string
&value
)
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",
600 static int yang_dnode_delete(struct lyd_node
*dnode
,
601 const std::string
&path
)
603 dnode
= yang_dnode_get(dnode
, path
.c_str());
612 static LYD_FORMAT
encoding2lyd_format(enum frr::Encoding encoding
)
620 flog_err(EC_LIB_DEVELOPMENT
,
621 "%s: unknown data encoding format (%u)",
627 static int get_oper_data_cb(const struct lys_node
*snode
,
628 struct yang_translator
*translator
,
629 struct yang_data
*data
, void *arg
)
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
);
635 return (ret
== 0) ? NB_OK
: NB_ERR
;
638 static void list_transactions_cb(void *arg
, int transaction_id
,
639 const char *client_name
,
640 const char *date
, const char *comment
)
642 grpc::ServerWriter
<frr::ListTransactionsResponse
> *writer
=
643 static_cast<grpc::ServerWriter
<
644 frr::ListTransactionsResponse
> *>(arg
);
645 frr::ListTransactionsResponse response
;
647 // Response: uint32 id = 1;
648 response
.set_id(transaction_id
);
650 // Response: string client = 2;
651 response
.set_client(client_name
);
653 // Response: string date = 3;
654 response
.set_date(date
);
656 // Response: string comment = 4;
657 response
.set_comment(comment
);
659 writer
->Write(response
);
662 static int data_tree_from_dnode(frr::DataTree
*dt
,
663 const struct lyd_node
*dnode
,
664 LYD_FORMAT lyd_format
,
670 SET_FLAG(options
, LYP_FORMAT
| LYP_WITHSIBLINGS
);
672 SET_FLAG(options
, LYP_WD_ALL
);
674 SET_FLAG(options
, LYP_WD_TRIM
);
676 if (lyd_print_mem(&strp
, dnode
, lyd_format
, options
) == 0) {
687 static struct lyd_node
*dnode_from_data_tree(const frr::DataTree
*dt
,
690 struct lyd_node
*dnode
;
694 options
= LYD_OPT_CONFIG
;
696 options
= LYD_OPT_DATA
| LYD_OPT_DATA_NO_YANGLIB
;
698 dnode
= lyd_parse_mem(ly_native_ctx
, dt
->data().c_str(),
699 encoding2lyd_format(dt
->encoding()),
705 static struct lyd_node
*get_dnode_config(const std::string
&path
)
707 struct lyd_node
*dnode
;
709 dnode
= yang_dnode_get(running_config
->dnode
,
710 path
.empty() ? NULL
: path
.c_str());
712 dnode
= yang_dnode_dup(dnode
);
717 static struct lyd_node
*get_dnode_state(const std::string
&path
)
719 struct lyd_node
*dnode
;
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
)
725 yang_dnode_free(dnode
);
732 static grpc::Status
get_path(frr::DataTree
*dt
, const std::string
&path
,
733 int type
, LYD_FORMAT lyd_format
,
736 struct lyd_node
*dnode_config
= NULL
;
737 struct lyd_node
*dnode_state
= NULL
;
738 struct lyd_node
*dnode_final
;
740 // Configuration data.
741 if (type
== frr::GetRequest_DataType_ALL
742 || type
== frr::GetRequest_DataType_CONFIG
) {
743 dnode_config
= get_dnode_config(path
);
746 grpc::StatusCode::INVALID_ARGUMENT
,
747 "Data path not found");
751 if (type
== frr::GetRequest_DataType_ALL
752 || type
== frr::GetRequest_DataType_STATE
) {
753 dnode_state
= get_dnode_state(path
);
756 yang_dnode_free(dnode_config
);
758 grpc::StatusCode::INVALID_ARGUMENT
,
759 "Failed to fetch operational data");
764 case frr::GetRequest_DataType_ALL
:
766 // Combine configuration and state data into a single
769 if (lyd_merge(dnode_state
, dnode_config
,
772 yang_dnode_free(dnode_state
);
773 yang_dnode_free(dnode_config
);
775 grpc::StatusCode::INTERNAL
,
776 "Failed to merge configuration and state data");
779 dnode_final
= dnode_state
;
781 case frr::GetRequest_DataType_CONFIG
:
782 dnode_final
= dnode_config
;
784 case frr::GetRequest_DataType_STATE
:
785 dnode_final
= dnode_state
;
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
;
794 validate_opts
= LYD_OPT_DATA
| LYD_OPT_DATA_NO_YANGLIB
;
795 lyd_validate(&dnode_final
, validate_opts
, ly_native_ctx
);
797 // Dump data using the requested format.
798 int ret
= data_tree_from_dnode(dt
, dnode_final
, lyd_format
,
800 yang_dnode_free(dnode_final
);
802 return grpc::Status(grpc::StatusCode::INTERNAL
,
803 "Failed to dump data");
805 return grpc::Status::OK
;
808 struct candidate
*create_candidate(void)
810 uint32_t candidate_id
= ++_nextCandidateId
;
812 // Check for overflow.
813 // TODO: implement an algorithm for unique reusable IDs.
814 if (candidate_id
== 0)
817 struct candidate
*candidate
= &_candidates
[candidate_id
];
818 candidate
->id
= candidate_id
;
819 candidate
->config
= nb_config_dup(running_config
);
820 candidate
->transaction
= NULL
;
825 void delete_candidate(struct candidate
*candidate
)
827 _candidates
.erase(candidate
->id
);
828 nb_config_free(candidate
->config
);
829 if (candidate
->transaction
)
830 nb_candidate_commit_abort(candidate
->transaction
);
833 struct candidate
*get_candidate(uint32_t candidate_id
)
835 struct candidate
*candidate
;
837 if (_candidates
.count(candidate_id
) == 0)
840 return &_candidates
[candidate_id
];
844 static void *grpc_pthread_start(void *arg
)
846 unsigned long *port
= static_cast<unsigned long *>(arg
);
847 NorthboundImpl service
;
848 std::stringstream server_address
;
850 server_address
<< "0.0.0.0:" << *port
;
852 grpc::ServerBuilder builder
;
853 builder
.AddListeningPort(server_address
.str(),
854 grpc::InsecureServerCredentials());
855 builder
.RegisterService(&service
);
857 std::unique_ptr
<grpc::Server
> server(builder
.BuildAndStart());
859 zlog_notice("gRPC server listening on %s",
860 server_address
.str().c_str());
867 static int frr_grpc_init(unsigned long *port
)
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
));
875 pthread_detach(grpc_pthread
);
880 static int frr_grpc_finish(void)
882 // TODO: cancel the gRPC pthreads gracefully.
888 * This is done this way because module_init and module_late_init are both
889 * called during daemon pre-fork initialization. Because the GRPC library
890 * spawns threads internally, we need to delay initializing it until after
891 * fork. This is done by scheduling this init function as an event task, since
892 * the event loop doesn't run until after fork.
894 static int frr_grpc_module_very_late_init(struct thread
*thread
)
896 static unsigned long port
= GRPC_DEFAULT_PORT
;
897 const char *args
= THIS_MODULE
->load_args
;
899 // Parse port number.
902 port
= std::stoul(args
);
904 throw std::invalid_argument(
905 "can't use privileged port");
906 if (port
> UINT16_MAX
)
907 throw std::invalid_argument(
908 "port number is too big");
909 } catch (std::exception
&e
) {
910 flog_err(EC_LIB_GRPC_INIT
,
911 "%s: failed to parse port number: %s",
917 if (frr_grpc_init(&port
) < 0)
921 flog_err(EC_LIB_GRPC_INIT
, "failed to initialize the gRPC module");
925 static int frr_grpc_module_late_init(struct thread_master
*tm
)
927 thread_add_event(tm
, frr_grpc_module_very_late_init
, NULL
, 0, NULL
);
928 hook_register(frr_fini
, frr_grpc_finish
);
933 static int frr_grpc_module_init(void)
935 hook_register(frr_late_init
, frr_grpc_module_late_init
);
940 FRR_MODULE_SETUP(.name
= "frr_grpc", .version
= FRR_VERSION
,
941 .description
= "FRR gRPC northbound module",
942 .init
= frr_grpc_module_init
, )