diff --git a/lib/mgmt.proto b/lib/mgmt.proto index 01a99ab63b98..c95301118bab 100644 --- a/lib/mgmt.proto +++ b/lib/mgmt.proto @@ -79,6 +79,7 @@ message BeSubscribeReq { repeated string config_xpaths = 2; repeated string oper_xpaths = 3; repeated string notif_xpaths = 4; + repeated string rpc_xpaths = 5; } message BeSubscribeReply { diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c index f483d48d8d0e..6e2fb05e846e 100644 --- a/lib/mgmt_be_client.c +++ b/lib/mgmt_be_client.c @@ -915,6 +915,143 @@ static void be_client_handle_get_tree(struct mgmt_be_client *client, be_client_send_tree_data_batch, args); } +static void be_client_send_rpc_reply(struct mgmt_be_client *client, + uint64_t txn_id, uint64_t req_id, + uint8_t result_type, + struct lyd_node *output) +{ + struct mgmt_msg_rpc_reply *rpc_reply_msg; + uint8_t **darrp; + LY_ERR err; + int ret = NB_OK; + + rpc_reply_msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, + MTYPE_MSG_NATIVE_RPC_REPLY); + rpc_reply_msg->refer_id = txn_id; + rpc_reply_msg->req_id = req_id; + rpc_reply_msg->code = MGMT_MSG_CODE_RPC_REPLY; + rpc_reply_msg->result_type = result_type; + + if (output) { + darrp = mgmt_msg_native_get_darrp(rpc_reply_msg); + err = yang_print_tree_append(darrp, output, result_type, + LYD_PRINT_SHRINK); + lyd_free_all(output); + if (err) { + ret = NB_ERR; + goto done; + } + } + + (void)be_client_send_native_msg(client, rpc_reply_msg, + mgmt_msg_native_get_msg_len( + rpc_reply_msg), + false); +done: + mgmt_msg_native_free_msg(rpc_reply_msg); + if (ret != NB_OK) + be_client_send_error(client, txn_id, req_id, false, -EINVAL, + "Can't format RPC reply"); +} + +/* + * Process the RPC request. + */ +static void be_client_handle_rpc(struct mgmt_be_client *client, uint64_t txn_id, + void *msgbuf, size_t msg_len) +{ + struct mgmt_msg_rpc *rpc_msg = msgbuf; + struct nb_node *nb_node; + struct lyd_node *input, *output; + const char *xpath; + const char *data; + char errmsg[BUFSIZ] = { 0 }; + LY_ERR err; + int ret; + + debug_be_client("Received RPC request for client %s txn-id %" PRIu64 + " req-id %" PRIu64, + client->name, txn_id, rpc_msg->req_id); + + xpath = mgmt_msg_native_xpath_data_decode(rpc_msg, msg_len, data); + if (!xpath) { + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "Corrupt RPC message"); + return; + } + + nb_node = nb_node_find(xpath); + if (!nb_node) { + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "No schema found for RPC: %s", + xpath); + return; + } + + if (!nb_node->cbs.rpc) { + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "No RPC callback for: %s", xpath); + return; + } + + if (data) { + err = yang_parse_rpc(xpath, rpc_msg->request_type, data, false, + &input); + if (err) { + be_client_send_error(client, txn_id, rpc_msg->req_id, + false, -EINVAL, + "Can't parse RPC data for: %s", + xpath); + return; + } + } else { + /* + * If there's no input data, create an empty input container. + * It is especially needed for actions, because their parents + * may hold necessary information. + */ + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, + NULL, &input); + if (err) { + be_client_send_error(client, txn_id, rpc_msg->req_id, + false, -EINVAL, + "Can't create input node for RPC: %s", + xpath); + return; + } + } + + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, NULL, + &output); + if (err) { + lyd_free_all(input); + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, + "Can't create output node for RPC: %s", + xpath); + return; + } + + ret = nb_callback_rpc(nb_node, xpath, input, output, errmsg, + sizeof(errmsg)); + if (ret != NB_OK) { + lyd_free_all(input); + lyd_free_all(output); + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "%s", errmsg); + return; + } + + lyd_free_all(input); + if (!lyd_child(output)) { + lyd_free_all(output); + output = NULL; + } + + be_client_send_rpc_reply(client, txn_id, rpc_msg->req_id, + rpc_msg->request_type, output); +} + /* * Process the notification. */ @@ -975,6 +1112,9 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client, case MGMT_MSG_CODE_GET_TREE: be_client_handle_get_tree(client, txn_id, msg, msg_len); break; + case MGMT_MSG_CODE_RPC: + be_client_handle_rpc(client, txn_id, msg, msg_len); + break; case MGMT_MSG_CODE_NOTIFY: be_client_handle_notify(client, msg, msg_len); break; @@ -1040,6 +1180,9 @@ int mgmt_be_send_subscr_req(struct mgmt_be_client *client_ctx, subscr_req.n_notif_xpaths = client_ctx->cbs.nnotif_xpaths; subscr_req.notif_xpaths = (char **)client_ctx->cbs.notif_xpaths; + subscr_req.n_rpc_xpaths = client_ctx->cbs.nrpc_xpaths; + subscr_req.rpc_xpaths = (char **)client_ctx->cbs.rpc_xpaths; + mgmtd__be_message__init(&be_msg); be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REQ; be_msg.subscr_req = &subscr_req; diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h index cd8b23752671..7ad0589bdbcb 100644 --- a/lib/mgmt_be_client.h +++ b/lib/mgmt_be_client.h @@ -75,6 +75,8 @@ struct mgmt_be_client_cbs { const char **notif_xpaths; uint nnotif_xpaths; + const char **rpc_xpaths; + uint nrpc_xpaths; }; /*************************************************************** diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index 3345505213ff..8cfb025f7225 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -360,6 +360,33 @@ int mgmt_fe_send_edit_req(struct mgmt_fe_client *client, uint64_t session_id, return ret; } +int mgmt_fe_send_rpc_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, LYD_FORMAT request_type, + const char *xpath, const char *data) +{ + struct mgmt_msg_rpc *msg; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc, 0, + MTYPE_MSG_NATIVE_RPC); + msg->refer_id = session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_RPC; + msg->request_type = request_type; + + mgmt_msg_native_xpath_encode(msg, xpath); + if (data) + mgmt_msg_native_append(msg, data, strlen(data) + 1); + + debug_fe_client("Sending RPC_REQ session-id %" PRIu64 " req-id %" PRIu64 + " xpath: %s", + session_id, req_id, xpath); + + ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false); + mgmt_msg_native_free_msg(msg); + return ret; +} + static int mgmt_fe_client_handle_msg(struct mgmt_fe_client *client, Mgmtd__FeMessage *fe_msg) { @@ -534,6 +561,7 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, struct mgmt_msg_notify_data *notify_msg; struct mgmt_msg_tree_data *tree_msg; struct mgmt_msg_edit_reply *edit_msg; + struct mgmt_msg_rpc_reply *rpc_msg; struct mgmt_msg_error *err_msg; const char *xpath = NULL; const char *data = NULL; @@ -608,6 +636,23 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, session->user_ctx, msg->req_id, xpath); break; + case MGMT_MSG_CODE_RPC_REPLY: + if (!session->client->cbs.rpc_notify) + return; + + rpc_msg = (typeof(rpc_msg))msg; + if (msg_len < sizeof(*rpc_msg)) { + log_err_fe_client("Corrupt rpc-reply msg recv"); + return; + } + dlen = msg_len - sizeof(*rpc_msg); + + session->client->cbs.rpc_notify(client, client->user_data, + session->client_id, + msg->refer_id, + session->user_ctx, msg->req_id, + dlen ? rpc_msg->data : NULL); + break; case MGMT_MSG_CODE_NOTIFY: if (!session->client->cbs.async_notification) return; diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h index 9d569348ae58..20c87044a5e4 100644 --- a/lib/mgmt_fe_client.h +++ b/lib/mgmt_fe_client.h @@ -120,6 +120,12 @@ struct mgmt_fe_client_cbs { uintptr_t session_ctx, uint64_t req_id, const char *xpath); + /* Called when RPC result is returned */ + int (*rpc_notify)(struct mgmt_fe_client *client, uintptr_t user_data, + uint64_t client_id, uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *result); + /* Called with asynchronous notifications from backends */ int (*async_notification)(struct mgmt_fe_client *client, uintptr_t user_data, uint64_t client_id, @@ -454,6 +460,35 @@ extern int mgmt_fe_send_edit_req(struct mgmt_fe_client *client, uint8_t flags, uint8_t operation, const char *xpath, const char *data); +/* + * Send RPC request to MGMTD daemon. + * + * client + * Client object. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * result_type + * The LYD_FORMAT of the result. + * + * xpath + * the xpath of the RPC. + * + * data + * the data tree. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_rpc_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + LYD_FORMAT request_type, const char *xpath, + const char *data); + /* * Destroy library and cleanup everything. */ diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c index 09ea43ece0bf..39ce9abae649 100644 --- a/lib/mgmt_msg_native.c +++ b/lib/mgmt_msg_native.c @@ -16,6 +16,8 @@ DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native get data msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT, "native edit msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT_REPLY, "native edit reply msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_RPC, "native RPC msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_RPC_REPLY, "native RPC reply msg"); int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, uint64_t req_id, bool short_circuit_ok, diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h index b7c29862aadd..cf528a635638 100644 --- a/lib/mgmt_msg_native.h +++ b/lib/mgmt_msg_native.h @@ -152,6 +152,8 @@ DECLARE_MTYPE(MSG_NATIVE_GET_DATA); DECLARE_MTYPE(MSG_NATIVE_NOTIFY); DECLARE_MTYPE(MSG_NATIVE_EDIT); DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY); +DECLARE_MTYPE(MSG_NATIVE_RPC); +DECLARE_MTYPE(MSG_NATIVE_RPC_REPLY); /* * Native message codes @@ -163,6 +165,8 @@ DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY); #define MGMT_MSG_CODE_NOTIFY 4 #define MGMT_MSG_CODE_EDIT 5 #define MGMT_MSG_CODE_EDIT_REPLY 6 +#define MGMT_MSG_CODE_RPC 7 +#define MGMT_MSG_CODE_RPC_REPLY 8 /* * Datastores @@ -377,6 +381,42 @@ _Static_assert(sizeof(struct mgmt_msg_edit_reply) == offsetof(struct mgmt_msg_edit_reply, data), "Size mismatch"); +/** + * struct mgmt_msg_rpc - RPC/action request. + * + * @request_type: ``LYD_FORMAT`` for the @data. + * @data: the xpath followed by the tree data for the operation. + */ +struct mgmt_msg_rpc { + struct mgmt_msg_header; + uint8_t request_type; + uint8_t resv2[7]; + + alignas(8) char data[]; +}; + +_Static_assert(sizeof(struct mgmt_msg_rpc) == + offsetof(struct mgmt_msg_rpc, data), + "Size mismatch"); + +/** + * struct mgmt_msg_rpc_reply - RPC/action reply. + * + * @result_type: ``LYD_FORMAT`` for the @data. + * @data: the tree data for the reply. + */ +struct mgmt_msg_rpc_reply { + struct mgmt_msg_header; + uint8_t result_type; + uint8_t resv2[7]; + + alignas(8) char data[]; +}; + +_Static_assert(sizeof(struct mgmt_msg_rpc_reply) == + offsetof(struct mgmt_msg_rpc_reply, data), + "Size mismatch"); + /* * Validate that the message ends in a NUL terminating byte */ @@ -569,7 +609,10 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn, const char *__s = NULL; \ if (msg->vsplit && msg->vsplit <= __len && \ msg->data[msg->vsplit - 1] == 0) { \ - (__data) = msg->data + msg->vsplit; \ + if (msg->vsplit < __len) \ + (__data) = msg->data + msg->vsplit; \ + else \ + (__data) = NULL; \ __s = msg->data; \ } \ __s; \ diff --git a/lib/northbound.c b/lib/northbound.c index 9a5d67cd1bb2..0bc79d027740 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -932,14 +932,22 @@ static int nb_candidate_edit_tree_add(struct nb_config *candidate, /* if replace failed, restore the original node */ if (existing) { if (root) { + /* Restoring the whole config. */ candidate->dnode = existing; + } else if (ex_parent) { + /* + * Restoring a nested node. Insert it as a + * child. + */ + lyd_insert_child(ex_parent, existing); } else { - if (ex_parent) - lyd_insert_child(ex_parent, existing); - else - lyd_insert_sibling(candidate->dnode, - existing, - &candidate->dnode); + /* + * Restoring a top-level node. Insert it as a + * sibling to candidate->dnode to make sure + * the linkage is correct. + */ + lyd_insert_sibling(candidate->dnode, existing, + &candidate->dnode); } } yang_print_errors(ly_native_ctx, errmsg, errmsg_len); @@ -1820,14 +1828,11 @@ const void *nb_callback_lookup_next(const struct nb_node *nb_node, } int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath, - const struct list *input, struct list *output, char *errmsg, - size_t errmsg_len) + const struct lyd_node *input, struct lyd_node *output, + char *errmsg, size_t errmsg_len) { struct nb_cb_rpc_args args = {}; - if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) - return 0; - DEBUGD(&nb_dbg_cbs_rpc, "northbound RPC: %s", xpath); args.xpath = xpath; diff --git a/lib/northbound.h b/lib/northbound.h index 3bf1eacd610c..34d17a587caa 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -274,11 +274,11 @@ struct nb_cb_rpc_args { /* XPath of the YANG RPC or action. */ const char *xpath; - /* Read-only list of input parameters. */ - const struct list *input; + /* Read-only "input" tree of the RPC/action. */ + const struct lyd_node *input; - /* List of output parameters to be populated by the callback. */ - struct list *output; + /* The "output" tree of the RPC/action to be populated by the callback. */ + struct lyd_node *output; /* Buffer to store human-readable error message in case of error. */ char *errmsg; @@ -833,7 +833,7 @@ extern const void *nb_callback_lookup_next(const struct nb_node *nb_node, const void *parent_list_entry, const struct yang_list_keys *keys); extern int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath, - const struct list *input, struct list *output, + const struct lyd_node *input, struct lyd_node *output, char *errmsg, size_t errmsg_len); extern void nb_callback_notify(const struct nb_node *nb_node, const char *xpath, struct lyd_node *dnode); diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c index 8809ec2ad87d..4f962cda5c1d 100644 --- a/lib/northbound_cli.c +++ b/lib/northbound_cli.c @@ -275,10 +275,31 @@ int nb_cli_apply_changes_clear_pending(struct vty *vty, return nb_cli_apply_changes_internal(vty, xpath_base_abs, true); } -int nb_cli_rpc(struct vty *vty, const char *xpath, struct list *input, - struct list *output) +int nb_cli_rpc_enqueue(struct vty *vty, const char *xpath, const char *value) +{ + struct nb_cfg_change *param; + + if (vty->num_rpc_params == VTY_MAXCFGCHANGES) { + /* Not expected to happen. */ + vty_out(vty, + "%% Exceeded the maximum number of params (%u) for a single command\n\n", + VTY_MAXCFGCHANGES); + return CMD_WARNING; + } + + param = &vty->rpc_params[vty->num_rpc_params++]; + strlcpy(param->xpath, xpath, sizeof(param->xpath)); + param->value = value; + + return CMD_SUCCESS; +} + +int nb_cli_rpc(struct vty *vty, const char *xpath, struct lyd_node **output_p) { struct nb_node *nb_node; + struct lyd_node *input = NULL; + struct lyd_node *output = NULL; + LY_ERR err; int ret; char errmsg[BUFSIZ] = {0}; @@ -289,12 +310,62 @@ int nb_cli_rpc(struct vty *vty, const char *xpath, struct list *input, return CMD_WARNING; } + /* create input tree */ + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, NULL, + &input); + assert(err == LY_SUCCESS); + + for (size_t i = 0; i < vty->num_rpc_params; i++) { + err = lyd_new_path(input, ly_native_ctx, + vty->rpc_params[i].xpath, + vty->rpc_params[i].value, 0, NULL); + assert(err == LY_SUCCESS); + } + + if (vty_mgmt_fe_enabled()) { + char *data = NULL; + + err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK); + assert(err == LY_SUCCESS); + + ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data); + + free(data); + lyd_free_all(input); + + if (ret < 0) + return CMD_WARNING; + return CMD_SUCCESS; + } + + /* validate input tree to create implicit defaults */ + err = lyd_validate_op(input, NULL, LYD_TYPE_RPC_YANG, NULL); + assert(err == LY_SUCCESS); + + /* create output tree root for population in the callback */ + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, NULL, + &output); + assert(err == LY_SUCCESS); + ret = nb_callback_rpc(nb_node, xpath, input, output, errmsg, sizeof(errmsg)); + + /* validate output tree to create implicit defaults */ + err = lyd_validate_op(output, NULL, LYD_TYPE_REPLY_YANG, NULL); + assert(err == LY_SUCCESS); + + lyd_free_all(input); + vty->num_rpc_params = 0; + switch (ret) { case NB_OK: + if (output_p) + *output_p = output; + else + lyd_free_all(output); return CMD_SUCCESS; default: + lyd_free_all(output); if (strlen(errmsg)) vty_show_nb_errors(vty, ret, errmsg); return CMD_WARNING; diff --git a/lib/northbound_cli.h b/lib/northbound_cli.h index 1a45794d4526..4c8dc50bd232 100644 --- a/lib/northbound_cli.h +++ b/lib/northbound_cli.h @@ -80,7 +80,23 @@ extern int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...) PRINTFRR(2, 3); /* - * Execute a YANG RPC or Action. + * Add an input child node for an RPC or an action. + * + * vty + * The vty context. + * + * xpath + * XPath of the child being added, relative to the input container. + * + * value + * Value of the child being added. Can be NULL for containers and leafs of + * type 'empty'. + */ +extern int nb_cli_rpc_enqueue(struct vty *vty, const char *xpath, + const char *value); + +/* + * Execute a YANG RPC or Action using the enqueued input parameters. * * vty * The vty terminal to dump any error. @@ -88,20 +104,16 @@ extern int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, * xpath * XPath of the YANG RPC or Action node. * - * input - * List of 'yang_data' structures containing the RPC input parameters. It - * can be set to NULL when there are no input parameters. - * - * output - * List of 'yang_data' structures used to retrieve the RPC output parameters. - * It can be set to NULL when it's known that the given YANG RPC or Action - * doesn't have any output parameters. + * output_p + * A pointer to the libyang data node that will hold the output data tree. + * It can be set to NULL if the caller is not interested in processing the + * output. The caller is responsible for freeing the output data tree. * * Returns: * CMD_SUCCESS on success, CMD_WARNING otherwise. */ -extern int nb_cli_rpc(struct vty *vty, const char *xpath, struct list *input, - struct list *output); +extern int nb_cli_rpc(struct vty *vty, const char *xpath, + struct lyd_node **output_p); /* * Show CLI commands associated to the given YANG data node. diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp index 7957752589c4..612ec3981b82 100644 --- a/lib/northbound_grpc.cpp +++ b/lib/northbound_grpc.cpp @@ -1011,12 +1011,11 @@ grpc::Status HandleUnaryExecute( grpc_debug("%s: entered", __func__); struct nb_node *nb_node; - struct list *input_list; - struct list *output_list; - struct listnode *node; - struct yang_data *data; + struct lyd_node *input_tree, *output_tree, *child; const char *xpath; char errmsg[BUFSIZ] = {0}; + char path[XPATH_MAXLEN]; + LY_ERR err; // Request: string path = 1; xpath = tag->request.path().c_str(); @@ -1032,40 +1031,66 @@ grpc::Status HandleUnaryExecute( return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Unknown data path"); - input_list = yang_data_list_new(); - output_list = yang_data_list_new(); + // Create input data tree. + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, + (LYD_ANYDATA_VALUETYPE)0, 0, NULL, &input_tree); + if (err != LY_SUCCESS) { + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid data path"); + } // Read input parameters. auto input = tag->request.input(); for (const frr::PathValue &pv : input) { // Request: repeated PathValue input = 2; - data = yang_data_new(pv.path().c_str(), pv.value().c_str()); - listnode_add(input_list, data); + err = lyd_new_path(input_tree, ly_native_ctx, pv.path().c_str(), + pv.value().c_str(), 0, NULL); + if (err != LY_SUCCESS) { + lyd_free_tree(input_tree); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid input data"); + } + } + + // Validate input data. + err = lyd_validate_op(input_tree, NULL, LYD_TYPE_RPC_YANG, NULL); + if (err != LY_SUCCESS) { + lyd_free_tree(input_tree); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid input data"); + } + + // Create output data tree. + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, + (LYD_ANYDATA_VALUETYPE)0, 0, NULL, &output_tree); + if (err != LY_SUCCESS) { + lyd_free_tree(input_tree); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid data path"); } // Execute callback registered for this XPath. - if (nb_callback_rpc(nb_node, xpath, input_list, output_list, errmsg, - sizeof(errmsg)) - != NB_OK) { + if (nb_callback_rpc(nb_node, xpath, input_tree, output_tree, errmsg, + sizeof(errmsg)) != NB_OK) { flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", __func__, xpath); - list_delete(&input_list); - list_delete(&output_list); + lyd_free_tree(input_tree); + lyd_free_tree(output_tree); return grpc::Status(grpc::StatusCode::INTERNAL, "RPC failed"); } // Process output parameters. - for (ALL_LIST_ELEMENTS_RO(output_list, node, data)) { + LY_LIST_FOR (lyd_child(output_tree), child) { // Response: repeated PathValue output = 1; frr::PathValue *pv = tag->response.add_output(); - pv->set_path(data->xpath); - pv->set_value(data->value); + pv->set_path(lyd_path(child, LYD_PATH_STD, path, sizeof(path))); + pv->set_value(yang_dnode_get_string(child, NULL)); } // Release memory. - list_delete(&input_list); - list_delete(&output_list); + lyd_free_tree(input_tree); + lyd_free_tree(output_tree); return grpc::Status::OK; } diff --git a/lib/northbound_sysrepo.c b/lib/northbound_sysrepo.c index 640334926dd8..0ec7610a9a7c 100644 --- a/lib/northbound_sysrepo.c +++ b/lib/northbound_sysrepo.c @@ -377,16 +377,11 @@ static int frr_sr_state_cb(sr_session_ctx_t *session, uint32_t sub_id, return SR_ERR_OK; } static int frr_sr_config_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id, - const char *xpath, const sr_val_t *sr_input, - const size_t input_cnt, sr_event_t sr_ev, - uint32_t request_id, sr_val_t **sr_output, - size_t *sr_output_cnt, void *private_ctx) + const char *xpath, const struct lyd_node *input, + sr_event_t sr_ev, uint32_t request_id, + struct lyd_node *output, void *private_ctx) { struct nb_node *nb_node; - struct list *input; - struct list *output; - struct yang_data *data; - size_t cb_output_cnt; int ret = SR_ERR_OK; char errmsg[BUFSIZ] = {0}; @@ -397,19 +392,6 @@ static int frr_sr_config_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id, return SR_ERR_INTERNAL; } - input = yang_data_list_new(); - output = yang_data_list_new(); - - /* Process input. */ - for (size_t i = 0; i < input_cnt; i++) { - char value_str[YANG_VALUE_MAXLEN]; - - sr_val_to_buff(&sr_input[i], value_str, sizeof(value_str)); - - data = yang_data_new(xpath, value_str); - listnode_add(input, data); - } - /* Execute callback registered for this XPath. */ if (nb_callback_rpc(nb_node, xpath, input, output, errmsg, sizeof(errmsg)) @@ -417,44 +399,8 @@ static int frr_sr_config_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id, flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", __func__, xpath); ret = SR_ERR_OPERATION_FAILED; - goto exit; } - /* Process output. */ - if (listcount(output) > 0) { - sr_val_t *values = NULL; - struct listnode *node; - int i = 0; - - cb_output_cnt = listcount(output); - ret = sr_new_values(cb_output_cnt, &values); - if (ret != SR_ERR_OK) { - flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", - __func__, sr_strerror(ret)); - goto exit; - } - - for (ALL_LIST_ELEMENTS_RO(output, node, data)) { - if (yang_data_frr2sr(data, &values[i++]) != 0) { - flog_err( - EC_LIB_SYSREPO_DATA_CONVERT, - "%s: failed to convert data to Sysrepo format", - __func__); - ret = SR_ERR_INTERNAL; - sr_free_values(values, cb_output_cnt); - goto exit; - } - } - - *sr_output = values; - *sr_output_cnt = cb_output_cnt; - } - -exit: - /* Release memory. */ - list_delete(&input); - list_delete(&output); - return ret; } @@ -579,8 +525,9 @@ static int frr_sr_subscribe_rpc(const struct lysc_node *snode, void *arg) DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: providing RPC to '%s'", nb_node->xpath); - ret = sr_rpc_subscribe(session, nb_node->xpath, frr_sr_config_rpc_cb, - NULL, 0, 0, &module->sr_subscription); + ret = sr_rpc_subscribe_tree(session, nb_node->xpath, + frr_sr_config_rpc_cb, NULL, 0, 0, + &module->sr_subscription); if (ret != SR_ERR_OK) flog_err(EC_LIB_LIBSYSREPO, "sr_rpc_subscribe(): %s", sr_strerror(ret)); diff --git a/lib/vty.c b/lib/vty.c index d69b5eebd270..0a2a7552f8e4 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -3843,6 +3843,26 @@ static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client, return 0; } +static int vty_mgmt_rpc_result_notified(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *result) +{ + struct vty *vty = (struct vty *)session_ctx; + + debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64 + " was successful", + client_id, req_id); + + if (result) + vty_out(vty, "%s\n", result); + + vty_mgmt_resume_response(vty, CMD_SUCCESS); + + return 0; +} + static int vty_mgmt_error_notified(struct mgmt_fe_client *client, uintptr_t user_data, uint64_t client_id, uint64_t session_id, uintptr_t session_ctx, @@ -3885,6 +3905,7 @@ static struct mgmt_fe_client_cbs mgmt_cbs = { .get_data_notify = vty_mgmt_get_data_result_notified, .get_tree_notify = vty_mgmt_get_tree_result_notified, .edit_notify = vty_mgmt_edit_result_notified, + .rpc_notify = vty_mgmt_rpc_result_notified, .error_notify = vty_mgmt_error_notified, }; @@ -4162,6 +4183,25 @@ int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, return 0; } +int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, + const char *xpath, const char *data) +{ + vty->mgmt_req_id++; + + if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, request_type, xpath, data)) { + zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64 + " req-id %" PRIu64 ".", + vty->mgmt_session_id, vty->mgmt_req_id); + vty_out(vty, "Failed to send RPC to MGMTD!\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ"; + + return 0; +} + /* Install vty's own commands like `who' command. */ void vty_init(struct event_loop *master_thread, bool do_command_logging) { diff --git a/lib/vty.h b/lib/vty.h index 0e0c92c33650..a9570ef048d5 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -122,6 +122,10 @@ struct vty { size_t num_cfg_changes; struct nb_cfg_change cfg_changes[VTY_MAXCFGCHANGES]; + /* Input parameters */ + size_t num_rpc_params; + struct nb_cfg_change rpc_params[VTY_MAXCFGCHANGES]; + /* XPath of the current node */ int xpath_index; char xpath[VTY_MAXDEPTH][XPATH_MAXLEN]; @@ -423,6 +427,8 @@ extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, LYD_FORMAT request_type, uint8_t flags, uint8_t operation, const char *xpath, const char *data); +extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, + const char *xpath, const char *data); extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, bool lock, bool scok); extern void vty_mgmt_resume_response(struct vty *vty, int ret); diff --git a/lib/yang.c b/lib/yang.c index 013a7628424a..06d29bb9c415 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -653,6 +653,16 @@ void yang_dnode_free(struct lyd_node *dnode) lyd_free_all(dnode); } +void yang_dnode_rpc_output_add(struct lyd_node *output, const char *xpath, + const char *value) +{ + LY_ERR err; + + err = lyd_new_path(output, ly_native_ctx, xpath, value, + LYD_NEW_PATH_OUTPUT | LYD_NEW_PATH_UPDATE, NULL); + assert(err == LY_SUCCESS); +} + struct yang_data *yang_data_new(const char *xpath, const char *value) { struct yang_data *data; @@ -764,6 +774,63 @@ LY_ERR yang_parse_notification(const char *xpath, LYD_FORMAT format, return LY_SUCCESS; } +LY_ERR yang_parse_rpc(const char *xpath, LYD_FORMAT format, const char *data, + bool reply, struct lyd_node **rpc) +{ + const struct lysc_node *snode; + struct lyd_node *parent = NULL; + struct ly_in *in = NULL; + LY_ERR err; + + snode = lys_find_path(ly_native_ctx, NULL, xpath, 0); + if (!snode) { + zlog_err("Failed to find RPC/action schema node: %s", xpath); + return LY_ENOTFOUND; + } + + /* If it's an action, create its parent */ + if (snode->nodetype == LYS_ACTION) { + char *parent_xpath = XSTRDUP(MTYPE_TMP, xpath); + + if (yang_xpath_pop_node(parent_xpath) != NB_OK) { + XFREE(MTYPE_TMP, parent_xpath); + zlog_err("Invalid action xpath: %s", xpath); + return LY_EINVAL; + } + + err = lyd_new_path2(NULL, ly_native_ctx, parent_xpath, NULL, 0, + 0, 0, NULL, &parent); + XFREE(MTYPE_TMP, parent_xpath); + if (err) { + zlog_err("Failed to create parent node for action: %s", + ly_last_errmsg()); + return err; + } + } else if (snode->nodetype != LYS_RPC) { + zlog_err("Schema node is not an RPC/action: %s", xpath); + return LY_EINVAL; + } + + err = ly_in_new_memory(data, &in); + if (err) { + lyd_free_all(parent); + zlog_err("Failed to initialize ly_in: %s", ly_last_errmsg()); + return err; + } + + err = lyd_parse_op(ly_native_ctx, parent, in, format, + reply ? LYD_TYPE_REPLY_YANG : LYD_TYPE_RPC_YANG, + NULL, rpc); + ly_in_free(in, 0); + if (err) { + lyd_free_all(parent); + zlog_err("Failed to parse RPC/action: %s", ly_last_errmsg()); + return err; + } + + return LY_SUCCESS; +} + static ssize_t yang_print_darr(void *arg, const void *buf, size_t count) { uint8_t *dst = darr_append_n(*(uint8_t **)arg, count); diff --git a/lib/yang.h b/lib/yang.h index 25703b187903..57131f478bac 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -535,6 +535,21 @@ extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode); */ extern void yang_dnode_free(struct lyd_node *dnode); +/* + * Add a libyang data node to an RPC/action output container. + * + * output + * RPC/action output container. + * + * xpath + * XPath of the data node to add, relative to the output container. + * + * value + * String representing the value of the data node. + */ +extern void yang_dnode_rpc_output_add(struct lyd_node *output, + const char *xpath, const char *value); + /* * Create a new yang_data structure. * @@ -619,6 +634,25 @@ extern void yang_debugging_set(bool enable); extern LY_ERR yang_parse_notification(const char *xpath, LYD_FORMAT format, const char *data, struct lyd_node **notif); +/* + * Parse a YANG RPC. + * + * Args: + * xpath: xpath of an RPC/action. + * format: LYD_FORMAT of input data. + * data: input data. + * reply: true if the data represents a reply to an RPC/action. + * rpc: pointer to the libyang data tree to store the parsed RPC/action. + * If data represents an action, the pointer to the action node is + * still returned, but it's part of the full data tree with all its + * parents. + * + * Returns: + * LY_ERR from underlying calls. + */ +LY_ERR yang_parse_rpc(const char *xpath, LYD_FORMAT format, const char *data, + bool reply, struct lyd_node **rpc); + /* * "Print" the yang tree in `root` into dynamic sized array. * diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c index 830c410676e7..fadeafa408e1 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -98,6 +98,10 @@ static const char *const ripd_oper_xpaths[] = { "/ietf-key-chain:key-chains", NULL, }; +static const char *const ripd_rpc_xpaths[] = { + "/frr-ripd", + NULL, +}; #endif #if HAVE_RIPNGD @@ -113,6 +117,10 @@ static const char *const ripngd_oper_xpaths[] = { "/frr-ripngd:ripngd", NULL, }; +static const char *const ripngd_rpc_xpaths[] = { + "/frr-ripngd", + NULL, +}; #endif #if HAVE_STATICD @@ -147,6 +155,15 @@ static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths, }; +static const char *const *be_client_rpc_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { +#ifdef HAVE_RIPD + [MGMTD_BE_CLIENT_ID_RIPD] = ripd_rpc_xpaths, +#endif +#ifdef HAVE_RIPNGD + [MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_rpc_xpaths, +#endif +}; + /* * We would like to have a better ADT than one with O(n) comparisons * @@ -159,6 +176,7 @@ static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { static struct mgmt_be_xpath_map *be_cfg_xpath_map; static struct mgmt_be_xpath_map *be_oper_xpath_map; static struct mgmt_be_xpath_map *be_notif_xpath_map; +static struct mgmt_be_xpath_map *be_rpc_xpath_map; static struct event_loop *mgmt_loop; static struct msg_server mgmt_be_server = {.fd = -1}; @@ -173,8 +191,8 @@ static struct mgmt_be_client_adapter static void mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter); -static bool be_is_client_interested(const char *xpath, - enum mgmt_be_client_id id, bool config); +static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, + enum mgmt_be_xpath_subscr_type type); const char *mgmt_be_client_id2name(enum mgmt_be_client_id id) { @@ -223,16 +241,25 @@ mgmt_be_find_adapter_by_name(const char *name) } static void mgmt_register_client_xpath(enum mgmt_be_client_id id, - const char *xpath, bool config, bool oper) + const char *xpath, + enum mgmt_be_xpath_subscr_type type) { struct mgmt_be_xpath_map **maps, *map; - if (config) + switch (type) { + case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: maps = &be_cfg_xpath_map; - else if (oper) + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: maps = &be_oper_xpath_map; - else + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: maps = &be_notif_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: + maps = &be_rpc_xpath_map; + break; + } darr_foreach_p (*maps, map) { if (!strcmp(xpath, map->xpath_prefix)) { @@ -260,18 +287,28 @@ static void mgmt_be_xpath_map_init(void) /* Initialize the common config init map */ for (init = be_client_config_xpaths[id]; init && *init; init++) { __dbg(" - CFG XPATH: '%s'", *init); - mgmt_register_client_xpath(id, *init, true, false); + mgmt_register_client_xpath(id, *init, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); } /* Initialize the common oper init map */ for (init = be_client_oper_xpaths[id]; init && *init; init++) { __dbg(" - OPER XPATH: '%s'", *init); - mgmt_register_client_xpath(id, *init, false, true); + mgmt_register_client_xpath(id, *init, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + } + + /* Initialize the common RPC init map */ + for (init = be_client_rpc_xpaths[id]; init && *init; init++) { + __dbg(" - RPC XPATH: '%s'", *init); + mgmt_register_client_xpath(id, *init, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); } } __dbg("Total Cfg XPath Maps: %u", darr_len(be_cfg_xpath_map)); __dbg("Total Oper XPath Maps: %u", darr_len(be_oper_xpath_map)); + __dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map)); } static void mgmt_be_xpath_map_cleanup(void) @@ -289,6 +326,10 @@ static void mgmt_be_xpath_map_cleanup(void) darr_foreach_p (be_notif_xpath_map, map) XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); darr_free(be_notif_xpath_map); + + darr_foreach_p (be_rpc_xpath_map, map) + XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); + darr_free(be_rpc_xpath_map); } @@ -405,11 +446,12 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter, */ switch ((int)be_msg->message_case) { case MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REQ: - __dbg("Got SUBSCR_REQ from '%s' to register xpaths config: %zu oper: %zu notif: %zu", + __dbg("Got SUBSCR_REQ from '%s' to register xpaths config: %zu oper: %zu notif: %zu rpc: %zu", be_msg->subscr_req->client_name, be_msg->subscr_req->n_config_xpaths, be_msg->subscr_req->n_oper_xpaths, - be_msg->subscr_req->n_notif_xpaths); + be_msg->subscr_req->n_notif_xpaths, + be_msg->subscr_req->n_rpc_xpaths); if (strlen(be_msg->subscr_req->client_name)) { strlcpy(adapter->name, be_msg->subscr_req->client_name, @@ -432,22 +474,29 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter, num = be_msg->subscr_req->n_config_xpaths; for (i = 0; i < num; i++) { xpath = be_msg->subscr_req->config_xpaths[i]; - mgmt_register_client_xpath(adapter->id, xpath, true, - false); + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); } num = be_msg->subscr_req->n_oper_xpaths; for (i = 0; i < num; i++) { xpath = be_msg->subscr_req->oper_xpaths[i]; - mgmt_register_client_xpath(adapter->id, xpath, false, - true); + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); } num = be_msg->subscr_req->n_notif_xpaths; for (i = 0; i < num; i++) { xpath = be_msg->subscr_req->notif_xpaths[i]; - mgmt_register_client_xpath(adapter->id, xpath, false, - false); + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); + } + + num = be_msg->subscr_req->n_rpc_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->rpc_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); } mgmt_be_send_subscr_reply(adapter, true); @@ -638,6 +687,7 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter, { struct mgmt_msg_notify_data *notify_msg; struct mgmt_msg_tree_data *tree_msg; + struct mgmt_msg_rpc_reply *rpc_msg; struct mgmt_msg_error *error_msg; /* get the transaction */ @@ -662,6 +712,15 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter, /* Forward the reply to the txn module */ mgmt_txn_notify_tree_data_reply(adapter, tree_msg, msg_len); break; + case MGMT_MSG_CODE_RPC_REPLY: + /* RPC reply from a backend client */ + rpc_msg = (typeof(rpc_msg))msg; + __dbg("Got RPC_REPLY from '%s' txn-id %" PRIx64, adapter->name, + msg->refer_id); + + /* Forward the reply to the txn module */ + mgmt_txn_notify_rpc_reply(adapter, rpc_msg, msg_len); + break; case MGMT_MSG_CODE_NOTIFY: notify_msg = (typeof(notify_msg))msg; __dbg("Got NOTIFY from '%s'", adapter->name); @@ -882,7 +941,8 @@ void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, goto walk_cont; xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0); - if (be_is_client_interested(xpath, adapter->id, true)) + if (be_is_client_interested(xpath, adapter->id, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG)) nb_config_diff_add_change(*changes, NB_CB_CREATE, &seq, dnode); else LYD_TREE_DFS_continue = 1; /* skip any subtree */ @@ -893,13 +953,27 @@ void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, } } -uint64_t mgmt_be_interested_clients(const char *xpath, bool config) +uint64_t mgmt_be_interested_clients(const char *xpath, + enum mgmt_be_xpath_subscr_type type) { - struct mgmt_be_xpath_map *maps, *map; + struct mgmt_be_xpath_map *maps = NULL, *map; enum mgmt_be_client_id id; uint64_t clients; - maps = config ? be_cfg_xpath_map : be_oper_xpath_map; + switch (type) { + case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: + maps = be_cfg_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: + maps = be_oper_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: + maps = be_notif_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: + maps = be_rpc_xpath_map; + break; + } clients = 0; @@ -928,8 +1002,8 @@ uint64_t mgmt_be_interested_clients(const char *xpath, bool config) * Returns: * Interested or not. */ -static bool be_is_client_interested(const char *xpath, - enum mgmt_be_client_id id, bool config) +static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, + enum mgmt_be_xpath_subscr_type type) { uint64_t clients; @@ -938,7 +1012,7 @@ static bool be_is_client_interested(const char *xpath, __dbg("Checking client: %s for xpath: '%s'", mgmt_be_client_id2name(id), xpath); - clients = mgmt_be_interested_clients(xpath, config); + clients = mgmt_be_interested_clients(xpath, type); if (IS_IDBIT_SET(clients, id)) { __dbg("client: %s: interested", mgmt_be_client_id2name(id)); return true; @@ -998,23 +1072,32 @@ void mgmt_be_xpath_register_write(struct vty *vty) darr_len(be_oper_xpath_map)); darr_foreach_p (be_oper_xpath_map, map) be_show_xpath_register(vty, map); + + vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n", + darr_len(be_rpc_xpath_map)); + darr_foreach_p (be_rpc_xpath_map, map) + be_show_xpath_register(vty, map); } void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath) { enum mgmt_be_client_id id; struct mgmt_be_client_adapter *adapter; - uint64_t cclients, oclients, combined; - - cclients = mgmt_be_interested_clients(xpath, true); - oclients = mgmt_be_interested_clients(xpath, false); + uint64_t cclients, oclients, rclients, combined; + + cclients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + oclients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + rclients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); combined = cclients | oclients; vty_out(vty, "XPath: '%s'\n", xpath); FOREACH_BE_CLIENT_BITS (id, combined) { - vty_out(vty, " -- Client: '%s'\tconfig:%d oper:%d\n", + vty_out(vty, " -- Client: '%s'\tconfig:%d oper:%d rpc:%d\n", mgmt_be_client_id2name(id), IS_IDBIT_SET(cclients, id), - IS_IDBIT_SET(oclients, id)); + IS_IDBIT_SET(oclients, id), IS_IDBIT_SET(rclients, id)); adapter = mgmt_be_get_adapter_by_id(id); if (adapter) vty_out(vty, " -- Adapter: %p\n", adapter); diff --git a/mgmtd/mgmt_be_adapter.h b/mgmtd/mgmt_be_adapter.h index 491410aa153b..c9f2ab1b7a88 100644 --- a/mgmtd/mgmt_be_adapter.h +++ b/mgmtd/mgmt_be_adapter.h @@ -235,15 +235,23 @@ extern void mgmt_be_xpath_register_write(struct vty *vty); */ extern int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg); +enum mgmt_be_xpath_subscr_type { + MGMT_BE_XPATH_SUBSCR_TYPE_CFG, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER, + MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC, +}; + /** * Lookup the clients which are subscribed to a given `xpath` * and the way they are subscribed. * * Args: * xpath - the xpath to check for subscription information. - * config - true for config interest false for oper interest. + * type - type of subscription to check for. */ -extern uint64_t mgmt_be_interested_clients(const char *xpath, bool config); +extern uint64_t mgmt_be_interested_clients(const char *xpath, + enum mgmt_be_xpath_subscr_type type); /** * mgmt_fe_adapter_send_notify() - notify FE clients of a notification. diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index ab0da64d8f88..b20e36ce0cd4 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -1101,6 +1101,47 @@ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, return ret; } +static int fe_adapter_send_rpc_reply(struct mgmt_fe_session_ctx *session, + uint64_t req_id, uint8_t result_type, + const struct lyd_node *result) +{ + struct mgmt_msg_rpc_reply *msg; + uint8_t **darrp = NULL; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, + MTYPE_MSG_NATIVE_RPC_REPLY); + msg->refer_id = session->session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_RPC_REPLY; + msg->result_type = result_type; + + if (result) { + darrp = mgmt_msg_native_get_darrp(msg); + ret = yang_print_tree_append(darrp, result, result_type, 0); + if (ret != LY_SUCCESS) { + __log_err("Error building rpc-reply result for client %s session-id %" PRIu64 + " req-id %" PRIu64 " result type %u", + session->adapter->name, session->session_id, + req_id, result_type); + goto done; + } + } + + __dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 + " req-id %" PRIu64 " len %u", + session->adapter->name, session->session_id, req_id, + mgmt_msg_native_get_msg_len(msg)); + + ret = fe_adapter_send_native_msg(session->adapter, msg, + mgmt_msg_native_get_msg_len(msg), + false); +done: + mgmt_msg_native_free_msg(msg); + + return ret; +} + static int fe_adapter_send_edit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, const char *xpath) { @@ -1215,7 +1256,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, } darr_free(snodes); - clients = mgmt_be_interested_clients(msg->xpath, false); + clients = mgmt_be_interested_clients(msg->xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); if (!clients && !CHECK_FLAG(msg->flags, GET_DATA_FLAG_CONFIG)) { __dbg("No backends provide xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64, @@ -1270,7 +1312,7 @@ static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session, } xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); - if (!xpath || !data) { + if (!xpath) { fe_adapter_send_error(session, msg->req_id, false, -EINVAL, "Invalid message"); return; @@ -1359,6 +1401,96 @@ static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session, } } +/** + * fe_adapter_handle_rpc() - Handle an RPC message from an FE client. + * @session: the client session. + * @msg_raw: the message data. + * @msg_len: the length of the message data. + */ +static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, + void *__msg, size_t msg_len) +{ + struct mgmt_msg_rpc *msg = __msg; + const struct lysc_node *snode; + const char *xpath, *data; + uint64_t req_id = msg->req_id; + uint64_t clients; + int ret; + + __dbg("Received RPC request from client %s for session-id %" PRIu64 + " req-id %" PRIu64, + session->adapter->name, session->session_id, msg->req_id); + + xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); + if (!xpath) { + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Invalid message"); + return; + } + + if (session->txn_id != MGMTD_TXN_ID_NONE) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "Transaction in progress txn-id: %" PRIu64 + " for session-id: %" PRIu64, + session->txn_id, session->session_id); + return; + } + + snode = lys_find_path(ly_native_ctx, NULL, xpath, 0); + if (!snode) { + fe_adapter_send_error(session, req_id, false, -ENOENT, + "No such path: %s", xpath); + return; + } + + if (snode->nodetype == LYS_RPC) + clients = + mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); + else if (snode->nodetype == LYS_ACTION) + clients = + mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + else { + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Not an RPC or action path: %s", xpath); + return; + } + + if (!clients) { + __dbg("No backends implement xpath: %s for txn-id: %" PRIu64 + " session-id: %" PRIu64, + xpath, session->txn_id, session->session_id); + + fe_adapter_send_error(session, req_id, false, -ENOENT, + "No backends implement xpath: %s", xpath); + return; + } + + /* Start a RPC Transaction */ + session->txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_RPC); + if (session->txn_id == MGMTD_SESSION_ID_NONE) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "Failed to create an RPC transaction"); + return; + } + + __dbg("Created new rpc txn-id: %" PRIu64 " for session-id: %" PRIu64, + session->txn_id, session->session_id); + + /* Create an RPC request under the transaction */ + ret = mgmt_txn_send_rpc(session->txn_id, req_id, clients, + msg->request_type, xpath, data, + mgmt_msg_native_data_len_decode(msg, msg_len)); + if (ret) { + /* destroy the just created txn */ + mgmt_destroy_txn(&session->txn_id); + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "Failed to create an RPC transaction"); + } +} + /** * Handle a native encoded message from the FE client. */ @@ -1383,6 +1515,9 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, case MGMT_MSG_CODE_EDIT: fe_adapter_handle_edit(session, msg, msg_len); break; + case MGMT_MSG_CODE_RPC: + fe_adapter_handle_rpc(session, msg, msg_len); + break; default: __log_err("unknown native message session-id %" PRIu64 " req-id %" PRIu64 " code %u to FE adapter %s", @@ -1622,6 +1757,24 @@ int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, return ret; } +int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, LYD_FORMAT result_type, + const struct lyd_node *result) +{ + struct mgmt_fe_session_ctx *session; + int ret; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->txn_id != txn_id) + return -1; + + ret = fe_adapter_send_rpc_reply(session, req_id, result_type, result); + + mgmt_destroy_txn(&session->txn_id); + + return ret; +} + int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, bool unlock, bool commit, const char *xpath, int16_t error, diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h index 8d61ffe9106b..61d6cfae13aa 100644 --- a/mgmtd/mgmt_fe_adapter.h +++ b/mgmtd/mgmt_fe_adapter.h @@ -162,6 +162,26 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint32_t wd_options, const struct lyd_node *tree, int partial_error, bool short_circuit_ok); +/** + * Send RPC reply back to client. + * + * This also cleans up and frees the transaction. + * + * Args: + * session_id: the session. + * txn_id: the txn_id this data pertains to + * req_id: the req id for the rpc message + * result_type: the format of the result data. + * result: the results. + * + * Return: + * the return value from the underlying send function. + */ +extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, + LYD_FORMAT result_type, + const struct lyd_node *result); + /** * Send edit reply back to client. If error is not 0, a native error is sent. * diff --git a/mgmtd/mgmt_memory.c b/mgmtd/mgmt_memory.c index 0fce61aa9792..72ccca062670 100644 --- a/mgmtd/mgmt_memory.c +++ b/mgmtd/mgmt_memory.c @@ -20,6 +20,7 @@ DEFINE_MGROUP(MGMTD, "mgmt"); DEFINE_MTYPE(MGMTD, MGMTD, "instance"); DEFINE_MTYPE(MGMTD, MGMTD_XPATH, "xpath regex"); +DEFINE_MTYPE(MGMTD, MGMTD_ERR, "error"); DEFINE_MTYPE(MGMTD, MGMTD_BE_ADPATER, "backend adapter"); DEFINE_MTYPE(MGMTD, MGMTD_FE_ADPATER, "frontend adapter"); DEFINE_MTYPE(MGMTD, MGMTD_FE_SESSION, "frontend session"); @@ -30,5 +31,6 @@ DEFINE_MTYPE(MGMTD, MGMTD_TXN_COMMCFG_REQ, "txn commit-config requests"); DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REQ, "txn get-data requests"); DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REPLY, "txn get-data replies"); DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETTREE_REQ, "txn get-tree requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_RPC_REQ, "txn rpc requests"); DEFINE_MTYPE(MGMTD, MGMTD_TXN_CFG_BATCH, "txn config batches"); DEFINE_MTYPE(MGMTD, MGMTD_CMT_INFO, "commit info"); diff --git a/mgmtd/mgmt_memory.h b/mgmtd/mgmt_memory.h index d5b6aa632ea3..e28586ed830d 100644 --- a/mgmtd/mgmt_memory.h +++ b/mgmtd/mgmt_memory.h @@ -14,6 +14,7 @@ DECLARE_MGROUP(MGMTD); DECLARE_MTYPE(MGMTD); DECLARE_MTYPE(MGMTD_XPATH); +DECLARE_MTYPE(MGMTD_ERR); DECLARE_MTYPE(MGMTD_BE_ADPATER); DECLARE_MTYPE(MGMTD_FE_ADPATER); DECLARE_MTYPE(MGMTD_FE_SESSION); @@ -24,6 +25,7 @@ DECLARE_MTYPE(MGMTD_TXN_COMMCFG_REQ); DECLARE_MTYPE(MGMTD_TXN_GETDATA_REQ); DECLARE_MTYPE(MGMTD_TXN_GETDATA_REPLY); DECLARE_MTYPE(MGMTD_TXN_GETTREE_REQ); +DECLARE_MTYPE(MGMTD_TXN_RPC_REQ); DECLARE_MTYPE(MGMTD_TXN_CFG_BATCH); DECLARE_MTYPE(MGMTD_BE_ADAPTER_MSG_BUF); DECLARE_MTYPE(MGMTD_CMT_INFO); diff --git a/mgmtd/mgmt_testc.c b/mgmtd/mgmt_testc.c index a33a55efcab3..8bb07ed06863 100644 --- a/mgmtd/mgmt_testc.c +++ b/mgmtd/mgmt_testc.c @@ -18,6 +18,7 @@ /* ---------------- */ static void async_notification(struct nb_cb_notify_args *args); +static int rpc_callback(struct nb_cb_rpc_args *args); static void sigusr1(void); static void sigint(void); @@ -86,6 +87,10 @@ static const struct frr_yang_module_info frr_ripd_info = { .xpath = "/frr-ripd:authentication-failure", .cbs.notify = async_notification, }, + { + .xpath = "/frr-ripd:clear-rip-route", + .cbs.rpc = rpc_callback, + }, { .xpath = NULL, } @@ -113,6 +118,7 @@ FRR_DAEMON_INFO(mgmtd_testc, MGMTD_TESTC, /* clang-format on */ const char **__notif_xpaths; +const char **__rpc_xpaths; struct mgmt_be_client_cbs __client_cbs = {}; struct event *event_timeout; @@ -134,6 +140,7 @@ static void quit(int exit_code) { EVENT_OFF(event_timeout); darr_free(__client_cbs.notif_xpaths); + darr_free(__client_cbs.rpc_xpaths); frr_fini(); @@ -152,6 +159,12 @@ static void timeout(struct event *event) quit(1); } +static void success(struct event *event) +{ + zlog_notice("Success, exiting"); + quit(0); +} + static void async_notification(struct nb_cb_notify_args *args) { zlog_notice("Received YANG notification"); @@ -163,6 +176,23 @@ static void async_notification(struct nb_cb_notify_args *args) quit(0); } +static int rpc_callback(struct nb_cb_rpc_args *args) +{ + const char *vrf = NULL; + + zlog_notice("Received YANG RPC"); + + if (yang_dnode_exists(args->input, "vrf")) + vrf = yang_dnode_get_string(args->input, "vrf"); + + printf("{\"frr-ripd:clear-rip-route\": {\"vrf\": \"%s\"}}\n", vrf); + + event_cancel(&event_timeout); + event_add_timer(master, success, NULL, 1, NULL); + + return 0; +} + int main(int argc, char **argv) { int f_listen = 0; @@ -217,6 +247,10 @@ int main(int argc, char **argv) __client_cbs.nnotif_xpaths = darr_len(__notif_xpaths); } + darr_push(__rpc_xpaths, "/frr-ripd:clear-rip-route"); + __client_cbs.rpc_xpaths = __rpc_xpaths; + __client_cbs.nrpc_xpaths = darr_len(__rpc_xpaths); + mgmt_be_client = mgmt_be_client_create("mgmtd-testc", &__client_cbs, 0, master); diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 901163c6e69a..25376c16576f 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -29,6 +29,7 @@ enum mgmt_txn_event { MGMTD_TXN_PROC_COMMITCFG, MGMTD_TXN_PROC_GETCFG, MGMTD_TXN_PROC_GETTREE, + MGMTD_TXN_PROC_RPC, MGMTD_TXN_COMMITCFG_TIMEOUT, }; @@ -188,6 +189,15 @@ struct txn_req_get_tree { struct lyd_node *client_results; /* result tree from clients */ }; +struct txn_req_rpc { + char *xpath; /* xpath of rpc/action to invoke */ + uint64_t sent_clients; /* Bitmask of clients sent req to */ + uint64_t recv_clients; /* Bitmask of clients recv reply from */ + uint8_t result_type; /* LYD_FORMAT for results */ + char *errstr; /* error string */ + struct lyd_node *client_results; /* result tree from clients */ +}; + struct mgmt_txn_req { struct mgmt_txn_ctx *txn; enum mgmt_txn_event req_event; @@ -196,6 +206,7 @@ struct mgmt_txn_req { struct mgmt_set_cfg_req *set_cfg; struct mgmt_get_data_req *get_data; struct txn_req_get_tree *get_tree; + struct txn_req_rpc *rpc; struct mgmt_commit_cfg_req commit_cfg; } req; @@ -221,6 +232,7 @@ struct mgmt_txn_ctx { struct event *proc_get_tree; struct event *comm_cfg_timeout; struct event *get_tree_timeout; + struct event *rpc_timeout; struct event *clnup; /* List of backend adapters involved in this transaction */ @@ -252,6 +264,10 @@ struct mgmt_txn_ctx { * List of pending get-tree requests. */ struct mgmt_txn_reqs_head get_tree_reqs; + /* + * List of pending rpc requests. + */ + struct mgmt_txn_reqs_head rpc_reqs; /* * There will always be one commit-config allowed for a given * transaction/session. No need to maintain lists for it. @@ -416,6 +432,15 @@ static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn, " session-id: %" PRIu64, txn_req->req_id, txn->txn_id, txn->session_id); break; + case MGMTD_TXN_PROC_RPC: + txn_req->req.rpc = XCALLOC(MTYPE_MGMTD_TXN_RPC_REQ, + sizeof(struct txn_req_rpc)); + assert(txn_req->req.rpc); + mgmt_txn_reqs_add_tail(&txn->rpc_reqs, txn_req); + __dbg("Added a new RPC req-id: %" PRIu64 " txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn_req->req_id, txn->txn_id, txn->session_id); + break; case MGMTD_TXN_COMMITCFG_TIMEOUT: break; } @@ -506,6 +531,15 @@ static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req) XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.get_tree->xpath); XFREE(MTYPE_MGMTD_TXN_GETTREE_REQ, (*txn_req)->req.get_tree); break; + case MGMTD_TXN_PROC_RPC: + __dbg("Deleting RPC req-id: %" PRIu64 " txn-id: %" PRIu64, + (*txn_req)->req_id, (*txn_req)->txn->txn_id); + req_list = &(*txn_req)->txn->rpc_reqs; + lyd_free_all((*txn_req)->req.rpc->client_results); + XFREE(MTYPE_MGMTD_ERR, (*txn_req)->req.rpc->errstr); + XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.rpc->xpath); + XFREE(MTYPE_MGMTD_TXN_RPC_REQ, (*txn_req)->req.rpc); + break; case MGMTD_TXN_COMMITCFG_TIMEOUT: break; } @@ -880,7 +914,9 @@ static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, __dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL"); - clients = mgmt_be_interested_clients(xpath, true); + clients = + mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); chg_clients = 0; @@ -1306,6 +1342,33 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, return ret; } +static int txn_rpc_done(struct mgmt_txn_ctx *txn, struct mgmt_txn_req *txn_req) +{ + struct txn_req_rpc *rpc = txn_req->req.rpc; + uint64_t req_id = txn_req->req_id; + + /* cancel timer and send reply onward */ + EVENT_OFF(txn->rpc_timeout); + + if (rpc->errstr) + mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -1, + rpc->errstr); + else if (mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id, + req_id, rpc->result_type, + rpc->client_results)) { + __log_err("Error sending the results of RPC for txn-id %" PRIu64 + " req_id %" PRIu64 " to requested type %u", + txn->txn_id, req_id, rpc->result_type); + + (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -1, + "Error converting results of RPC"); + } + + /* we're done with the request */ + mgmt_txn_req_free(&txn_req); + + return 0; +} static void txn_get_tree_timeout(struct event *thread) { @@ -1333,6 +1396,31 @@ static void txn_get_tree_timeout(struct event *thread) txn_get_tree_data_done(txn, txn_req); } +static void txn_rpc_timeout(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + + txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread); + txn = txn_req->txn; + + assert(txn); + assert(txn->type == MGMTD_TXN_TYPE_RPC); + + __log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id); + + /* + * Send a get-tree data reply. + * + * NOTE: The transaction cleanup will be triggered from Front-end + * adapter. + */ + + txn_req->req.rpc->errstr = + XSTRDUP(MTYPE_MGMTD_ERR, "Operation on the backend timed-out"); + txn_rpc_done(txn, txn_req); +} + /* * Send CFG_APPLY_REQs to all the backend client. * @@ -1516,6 +1604,7 @@ static void mgmt_txn_send_getcfg_reply_data(struct mgmt_txn_req *txn_req, case MGMTD_TXN_PROC_SETCFG: case MGMTD_TXN_PROC_COMMITCFG: case MGMTD_TXN_PROC_GETTREE: + case MGMTD_TXN_PROC_RPC: case MGMTD_TXN_COMMITCFG_TIMEOUT: __log_err("Invalid Txn-Req-Event %u", txn_req->req_event); break; @@ -1721,6 +1810,7 @@ static struct mgmt_txn_ctx *mgmt_txn_create_new(uint64_t session_id, mgmt_txn_reqs_init(&txn->set_cfg_reqs); mgmt_txn_reqs_init(&txn->get_cfg_reqs); mgmt_txn_reqs_init(&txn->get_tree_reqs); + mgmt_txn_reqs_init(&txn->rpc_reqs); txn->commit_cfg_req = NULL; txn->refcount = 0; if (!mgmt_txn_mm->next_txn_id) @@ -1890,6 +1980,7 @@ static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, &txn->comm_cfg_timeout); break; case MGMTD_TXN_PROC_GETTREE: + case MGMTD_TXN_PROC_RPC: assert(!"code bug do not register this event"); break; } @@ -2496,6 +2587,64 @@ int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, return 0; } +int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, + LYD_FORMAT result_type, const char *xpath, + const char *data, size_t data_len) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + struct mgmt_msg_rpc *msg; + struct txn_req_rpc *rpc; + uint64_t id; + int ret; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_RPC); + rpc = txn_req->req.rpc; + rpc->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); + rpc->result_type = result_type; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc, 0, + MTYPE_MSG_NATIVE_RPC); + msg->refer_id = txn_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_RPC; + msg->request_type = result_type; + + mgmt_msg_native_xpath_encode(msg, xpath); + if (data) + mgmt_msg_native_append(msg, data, data_len); + + assert(clients); + FOREACH_BE_CLIENT_BITS (id, clients) { + ret = mgmt_be_send_native(id, msg); + if (ret) { + __log_err("Could not send rpc message to backend client %s", + mgmt_be_client_id2name(id)); + continue; + } + + __dbg("Sent rpc req to backend client %s", + mgmt_be_client_id2name(id)); + + /* record that we sent the request to the client */ + rpc->sent_clients |= (1u << id); + } + + mgmt_msg_native_free_msg(msg); + + if (!rpc->sent_clients) + return txn_rpc_done(txn, txn_req); + + event_add_timer(mgmt_txn_tm, txn_rpc_timeout, txn_req, + MGMTD_TXN_RPC_MAX_DELAY_SEC, &txn->rpc_timeout); + + return 0; +} + /* * Error reply from the backend client. */ @@ -2506,6 +2655,7 @@ int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, enum mgmt_be_client_id id = adapter->id; struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); struct txn_req_get_tree *get_tree; + struct txn_req_rpc *rpc; struct mgmt_txn_req *txn_req; if (!txn) { @@ -2518,6 +2668,10 @@ int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req) if (txn_req->req_id == req_id) break; + if (!txn_req) + FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) + if (txn_req->req_id == req_id) + break; if (!txn_req) { __log_err("Error reply from %s for txn-id %" PRIu64 " cannot find req_id %" PRIu64, @@ -2538,6 +2692,15 @@ int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, if (get_tree->recv_clients != get_tree->sent_clients) return 0; return txn_get_tree_data_done(txn, txn_req); + case MGMTD_TXN_PROC_RPC: + rpc = txn_req->req.rpc; + rpc->recv_clients |= (1u << id); + rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, errstr); + + /* check if done yet */ + if (rpc->recv_clients != rpc->sent_clients) + return 0; + return txn_rpc_done(txn, txn_req); /* non-native message events */ case MGMTD_TXN_PROC_SETCFG: @@ -2625,6 +2788,64 @@ int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter, return txn_get_tree_data_done(txn, txn_req); } +int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, + struct mgmt_msg_rpc_reply *reply_msg, + size_t msg_len) +{ + uint64_t txn_id = reply_msg->refer_id; + uint64_t req_id = reply_msg->req_id; + enum mgmt_be_client_id id = adapter->id; + struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); + struct mgmt_txn_req *txn_req; + struct txn_req_rpc *rpc; + size_t data_len = msg_len - sizeof(*reply_msg); + LY_ERR err; + + if (!txn) { + __log_err("RPC reply from %s for a missing txn-id %" PRIu64, + adapter->name, txn_id); + return -1; + } + + /* Find the request. */ + FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) + if (txn_req->req_id == req_id) + break; + if (!txn_req) { + __log_err("RPC reply from %s for txn-id %" PRIu64 + " missing req_id %" PRIu64, + adapter->name, txn_id, req_id); + return -1; + } + + rpc = txn_req->req.rpc; + + /* we don't expect more than one daemon to provide output for an RPC */ + if (!rpc->client_results && data_len > 0) { + err = yang_parse_rpc(rpc->xpath, reply_msg->result_type, + reply_msg->data, true, + &rpc->client_results); + if (err) { + __log_err("RPC reply from %s for txn-id %" PRIu64 + " req_id %" PRIu64 + " error parsing result of type %u", + adapter->name, txn_id, req_id, + reply_msg->result_type); + rpc->errstr = + XSTRDUP(MTYPE_MGMTD_ERR, + "Cannot parse result from the backend"); + } + } + + rpc->recv_clients |= (1u << id); + + /* check if done yet */ + if (rpc->recv_clients != rpc->sent_clients) + return 0; + + return txn_rpc_done(txn, txn_req); +} + void mgmt_txn_status_write(struct vty *vty) { struct mgmt_txn_ctx *txn; diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h index aeb74469f141..b6ca288675b7 100644 --- a/mgmtd/mgmt_txn.h +++ b/mgmtd/mgmt_txn.h @@ -21,6 +21,7 @@ #define MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC 600 #define MGMTD_TXN_GET_TREE_MAX_DELAY_SEC 600 +#define MGMTD_TXN_RPC_MAX_DELAY_SEC 60 #define MGMTD_TXN_CLEANUP_DELAY_USEC 10 @@ -48,7 +49,8 @@ struct mgmt_edit_req; enum mgmt_txn_type { MGMTD_TXN_TYPE_NONE = 0, MGMTD_TXN_TYPE_CONFIG, - MGMTD_TXN_TYPE_SHOW + MGMTD_TXN_TYPE_SHOW, + MGMTD_TXN_TYPE_RPC, }; static inline const char *mgmt_txn_type2str(enum mgmt_txn_type type) @@ -60,6 +62,8 @@ static inline const char *mgmt_txn_type2str(enum mgmt_txn_type type) return "CONFIG"; case MGMTD_TXN_TYPE_SHOW: return "SHOW"; + case MGMTD_TXN_TYPE_RPC: + return "RPC"; } return "Unknown"; @@ -246,6 +250,25 @@ mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId ds_id, LYD_FORMAT request_type, uint8_t flags, uint8_t operation, const char *xpath, const char *data); +/** + * Send RPC request. + * + * Args: + * txn_id: Transaction identifier. + * req_id: FE client request identifier. + * clients: Bitmask of clients to send RPC to. + * result_type: LYD_FORMAT result format. + * xpath: The xpath of the RPC. + * data: The input parameters data tree. + * data_len: The length of the input parameters data. + * + * Return: + * 0 on success. + */ +extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, + LYD_FORMAT result_type, const char *xpath, + const char *data, size_t data_len); + /* * Notifiy backend adapter on connection. */ @@ -312,6 +335,18 @@ extern int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapte struct mgmt_msg_tree_data *data_msg, size_t msg_len); +/** + * Process a reply from a backend client to our RPC request + * + * Args: + * adapter: The adapter that received the result. + * reply_msg: The message from the backend. + * msg_len: Total length of the message. + */ +extern int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, + struct mgmt_msg_rpc_reply *reply_msg, + size_t msg_len); + /* * Dump transaction status to vty. */ diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 61d0760e05d6..8ccb463577d4 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -296,6 +296,21 @@ DEFPY(mgmt_edit, mgmt_edit_cmd, return CMD_SUCCESS; } +DEFPY(mgmt_rpc, mgmt_rpc_cmd, + "mgmt rpc XPATH [json|xml]$fmt [DATA]", + MGMTD_STR + "Invoke RPC\n" + "XPath expression specifying the YANG data path\n" + "JSON input format (default)\n" + "XML input format\n" + "Input data tree\n") +{ + LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; + + vty_mgmt_send_rpc_req(vty, format, xpath, data); + return CMD_SUCCESS; +} + DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd, "show mgmt get-config [candidate|operational|running]$dsname WORD$path", SHOW_STR MGMTD_STR @@ -702,6 +717,7 @@ void mgmt_vty_init(void) install_element(CONFIG_NODE, &mgmt_remove_config_data_cmd); install_element(CONFIG_NODE, &mgmt_replace_config_data_cmd); install_element(CONFIG_NODE, &mgmt_edit_cmd); + install_element(CONFIG_NODE, &mgmt_rpc_cmd); install_element(CONFIG_NODE, &mgmt_load_config_cmd); install_element(CONFIG_NODE, &mgmt_save_config_cmd); install_element(CONFIG_NODE, &mgmt_rollback_cmd); diff --git a/ripd/rip_cli.c b/ripd/rip_cli.c index 4d4349b81aff..7066485be065 100644 --- a/ripd/rip_cli.c +++ b/ripd/rip_cli.c @@ -1233,6 +1233,23 @@ DEFPY_YANG(no_rip_distribute_list_prefix, return nb_cli_apply_changes(vty, NULL); } +/* + * XPath: /frr-ripd:clear-rip-route + */ +DEFPY_YANG (clear_ip_rip, + clear_ip_rip_cmd, + "clear ip rip [vrf WORD]", + CLEAR_STR + IP_STR + "Clear IP RIP database\n" + VRF_CMD_HELP_STR) +{ + if (vrf) + nb_cli_rpc_enqueue(vty, "vrf", vrf); + + return nb_cli_rpc(vty, "/frr-ripd:clear-rip-route", NULL); +} + /* RIP node structure. */ static struct cmd_node rip_node = { .name = "rip", @@ -1295,6 +1312,8 @@ void rip_cli_init(void) install_element(INTERFACE_NODE, &ip_rip_bfd_profile_cmd); install_element(INTERFACE_NODE, &no_ip_rip_bfd_profile_cmd); + install_element(ENABLE_NODE, &clear_ip_rip_cmd); + if_rmap_init(RIP_NODE); } /* clang-format off */ diff --git a/ripd/rip_nb_rpcs.c b/ripd/rip_nb_rpcs.c index bbe3d0f0f834..5d3d7145b1d4 100644 --- a/ripd/rip_nb_rpcs.c +++ b/ripd/rip_nb_rpcs.c @@ -68,12 +68,11 @@ static void clear_rip_route(struct rip *rip) int clear_rip_route_rpc(struct nb_cb_rpc_args *args) { struct rip *rip; - struct yang_data *yang_vrf; - yang_vrf = yang_data_list_find(args->input, "%s/%s", args->xpath, - "input/vrf"); - if (yang_vrf) { - rip = rip_lookup_by_vrf_name(yang_vrf->value); + if (args->input && yang_dnode_exists(args->input, "vrf")) { + const char *name = yang_dnode_get_string(args->input, "vrf"); + + rip = rip_lookup_by_vrf_name(name); if (rip) clear_rip_route(rip); } else { diff --git a/ripd/ripd.c b/ripd/ripd.c index e3220a9267f3..b8a140c9ca5a 100644 --- a/ripd/ripd.c +++ b/ripd/ripd.c @@ -3254,38 +3254,6 @@ DEFUN (show_ip_rip_status, return CMD_SUCCESS; } -#include "ripd/ripd_clippy.c" - -/* - * XPath: /frr-ripd:clear-rip-route - */ -DEFPY_YANG (clear_ip_rip, - clear_ip_rip_cmd, - "clear ip rip [vrf WORD]", - CLEAR_STR - IP_STR - "Clear IP RIP database\n" - VRF_CMD_HELP_STR) -{ - struct list *input; - int ret; - - input = list_new(); - if (vrf) { - struct yang_data *yang_vrf; - - yang_vrf = yang_data_new("/frr-ripd:clear-rip-route/input/vrf", - vrf); - listnode_add(input, yang_vrf); - } - - ret = nb_cli_rpc(vty, "/frr-ripd:clear-rip-route", input, NULL); - - list_delete(&input); - - return ret; -} - /* Distribute-list update functions. */ static void rip_distribute_update(struct distribute_ctx *ctx, struct distribute *dist) @@ -3660,7 +3628,6 @@ void rip_init(void) /* Install rip commands. */ install_element(VIEW_NODE, &show_ip_rip_cmd); install_element(VIEW_NODE, &show_ip_rip_status_cmd); - install_element(ENABLE_NODE, &clear_ip_rip_cmd); /* Debug related init. */ rip_debug_init(); diff --git a/ripd/subdir.am b/ripd/subdir.am index 7fb3726077df..aed8d249fea2 100644 --- a/ripd/subdir.am +++ b/ripd/subdir.am @@ -33,7 +33,6 @@ ripd_ripd_SOURCES = \ clippy_scan += \ ripd/rip_bfd.c \ ripd/rip_cli.c \ - ripd/ripd.c \ # end noinst_HEADERS += \ diff --git a/ripngd/ripng_cli.c b/ripngd/ripng_cli.c index 4806861fe08d..99cb68ea3296 100644 --- a/ripngd/ripng_cli.c +++ b/ripngd/ripng_cli.c @@ -624,6 +624,23 @@ DEFPY_YANG(no_ripng_ipv6_distribute_list_prefix, return nb_cli_apply_changes(vty, NULL); } +/* + * XPath: /frr-ripngd:clear-ripng-route + */ +DEFPY_YANG (clear_ipv6_rip, + clear_ipv6_rip_cmd, + "clear ipv6 ripng [vrf WORD]", + CLEAR_STR + IPV6_STR + "Clear IPv6 RIP database\n" + VRF_CMD_HELP_STR) +{ + if (vrf) + nb_cli_rpc_enqueue(vty, "vrf", vrf); + + return nb_cli_rpc(vty, "/frr-ripngd:clear-ripng-route", NULL); +} + /* RIPng node structure. */ static struct cmd_node cmd_ripng_node = { .name = "ripng", @@ -663,6 +680,8 @@ void ripng_cli_init(void) install_element(INTERFACE_NODE, &ipv6_ripng_split_horizon_cmd); + install_element(ENABLE_NODE, &clear_ipv6_rip_cmd); + if_rmap_init(RIPNG_NODE); } diff --git a/ripngd/ripng_nb_rpcs.c b/ripngd/ripng_nb_rpcs.c index b23572d49282..5498bbfc8e38 100644 --- a/ripngd/ripng_nb_rpcs.c +++ b/ripngd/ripng_nb_rpcs.c @@ -70,12 +70,11 @@ static void clear_ripng_route(struct ripng *ripng) int clear_ripng_route_rpc(struct nb_cb_rpc_args *args) { struct ripng *ripng; - struct yang_data *yang_vrf; - yang_vrf = yang_data_list_find(args->input, "%s/%s", args->xpath, - "input/vrf"); - if (yang_vrf) { - ripng = ripng_lookup_by_vrf_name(yang_vrf->value); + if (args->input && yang_dnode_exists(args->input, "vrf")) { + const char *name = yang_dnode_get_string(args->input, "vrf"); + + ripng = ripng_lookup_by_vrf_name(name); if (ripng) clear_ripng_route(ripng); } else { diff --git a/ripngd/ripngd.c b/ripngd/ripngd.c index 80b9013e0f23..f4dadf377df2 100644 --- a/ripngd/ripngd.c +++ b/ripngd/ripngd.c @@ -2231,38 +2231,6 @@ DEFUN (show_ipv6_ripng_status, return CMD_SUCCESS; } -#include "ripngd/ripngd_clippy.c" - -/* - * XPath: /frr-ripngd:clear-ripng-route - */ -DEFPY_YANG (clear_ipv6_rip, - clear_ipv6_rip_cmd, - "clear ipv6 ripng [vrf WORD]", - CLEAR_STR - IPV6_STR - "Clear IPv6 RIP database\n" - VRF_CMD_HELP_STR) -{ - struct list *input; - int ret; - - input = list_new(); - if (vrf) { - struct yang_data *yang_vrf; - - yang_vrf = yang_data_new( - "/frr-ripngd:clear-ripng-route/input/vrf", vrf); - listnode_add(input, yang_vrf); - } - - ret = nb_cli_rpc(vty, "/frr-ripngd:clear-ripng-route", input, NULL); - - list_delete(&input); - - return ret; -} - /* Update ECMP routes to zebra when ECMP is disabled. */ void ripng_ecmp_disable(struct ripng *ripng) { @@ -2680,7 +2648,6 @@ void ripng_init(void) /* Install ripng commands. */ install_element(VIEW_NODE, &show_ipv6_ripng_cmd); install_element(VIEW_NODE, &show_ipv6_ripng_status_cmd); - install_element(ENABLE_NODE, &clear_ipv6_rip_cmd); ripng_if_init(); ripng_debug_init(); diff --git a/ripngd/subdir.am b/ripngd/subdir.am index a88114432df0..83e376b55530 100644 --- a/ripngd/subdir.am +++ b/ripngd/subdir.am @@ -27,7 +27,6 @@ ripngd_ripngd_SOURCES = \ clippy_scan += \ ripngd/ripng_cli.c \ - ripngd/ripngd.c \ # end noinst_HEADERS += \ diff --git a/tests/lib/northbound/test_oper_data.c b/tests/lib/northbound/test_oper_data.c index 8f7e7c5f8c71..321f158668e2 100644 --- a/tests/lib/northbound/test_oper_data.c +++ b/tests/lib/northbound/test_oper_data.c @@ -14,6 +14,7 @@ #include "lib_vty.h" #include "log.h" #include "northbound.h" +#include "northbound_cli.h" static struct event_loop *master; @@ -200,6 +201,19 @@ static struct yang_data *frr_test_module_vrfs_vrf_routes_route_active_get_elem( return NULL; } +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/ping + */ +static int frr_test_module_vrfs_vrf_ping(struct nb_cb_rpc_args *args) +{ + const char *vrf = yang_dnode_get_string(args->input, "../name"); + const char *data = yang_dnode_get_string(args->input, "data"); + + yang_dnode_rpc_output_add(args->output, "vrf", vrf); + yang_dnode_rpc_output_add(args->output, "data-out", data); + + return NB_OK; +} /* * XPath: /frr-test-module:frr-test-module/c1value @@ -262,6 +276,10 @@ const struct frr_yang_module_info frr_test_module_info = { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/active", .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_active_get_elem, }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/ping", + .cbs.rpc = frr_test_module_vrfs_vrf_ping, + }, { .xpath = "/frr-test-module:frr-test-module/c1value", .cbs.get_elem = frr_test_module_c1value_get_elem, @@ -277,6 +295,33 @@ const struct frr_yang_module_info frr_test_module_info = { }; /* clang-format on */ +DEFUN(test_rpc, test_rpc_cmd, "test rpc", + "Test\n" + "RPC\n") +{ + struct lyd_node *output = NULL; + char xpath[XPATH_MAXLEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-test-module:frr-test-module/vrfs/vrf[name='testname']/ping"); + + nb_cli_rpc_enqueue(vty, "data", "testdata"); + + ret = nb_cli_rpc(vty, xpath, &output); + if (ret != CMD_SUCCESS) { + vty_out(vty, "RPC failed\n"); + return ret; + } + + vty_out(vty, "vrf %s data %s\n", yang_dnode_get_string(output, "vrf"), + yang_dnode_get_string(output, "data-out")); + + yang_dnode_free(output); + + return CMD_SUCCESS; +} + static const struct frr_yang_module_info *const modules[] = { &frr_test_module_info, }; @@ -416,6 +461,8 @@ int main(int argc, char **argv) lib_cmd_init(); nb_init(master, modules, array_size(modules), false); + install_element(ENABLE_NODE, &test_rpc_cmd); + /* Create artificial data. */ create_data(num_vrfs, num_interfaces, num_routes); diff --git a/tests/lib/northbound/test_oper_data.in b/tests/lib/northbound/test_oper_data.in index a6c4f874f500..f7c44cad3154 100644 --- a/tests/lib/northbound/test_oper_data.in +++ b/tests/lib/northbound/test_oper_data.in @@ -1 +1,2 @@ show yang operational-data /frr-test-module:frr-test-module +test rpc diff --git a/tests/lib/northbound/test_oper_data.refout b/tests/lib/northbound/test_oper_data.refout index aa930fe127be..7c565641431c 100644 --- a/tests/lib/northbound/test_oper_data.refout +++ b/tests/lib/northbound/test_oper_data.refout @@ -119,5 +119,7 @@ test# show yang operational-data /frr-test-module:frr-test-module } } } +test# test rpc +vrf testname data testdata test# end. diff --git a/tests/topotests/mgmt_rpc/r1/frr.conf b/tests/topotests/mgmt_rpc/r1/frr.conf new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/mgmt_rpc/test_rpc.py b/tests/topotests/mgmt_rpc/test_rpc.py new file mode 100644 index 000000000000..618d9022cea0 --- /dev/null +++ b/tests/topotests/mgmt_rpc/test_rpc.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: ISC +# +# March 21 2024, Igor Ryzhov +# +# Copyright (c) 2024, NFWare Inc. +# + +""" +Test YANG Notifications +""" +import json +import os +import threading + +import pytest +from lib.topogen import Topogen +from lib.topotest import json_cmp + +pytestmark = [pytest.mark.ripd, pytest.mark.mgmtd] + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +@pytest.fixture(scope="module") +def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + topodef = {"s1": ("r1",)} + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_frr_config("frr.conf") + + tgen.start_router() + yield tgen + tgen.stop_topology() + + +def test_backend_rpc(tgen): + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + be_client_path = "/usr/lib/frr/mgmtd_testc" + rc, _, _ = r1.net.cmd_status(be_client_path + " --help") + + if rc: + pytest.skip("No mgmtd_testc") + + out = [] + + def run_testc(): + output = r1.net.cmd_raises( + be_client_path + " --timeout 10 --log file:mgmt_testc.log" + ) + out.append(json.loads(output)) + + t = threading.Thread(target=run_testc) + t.start() + + r1.vtysh_cmd("clear ip rip vrf testname") + + t.join() + + jsout = out[0] + + expected = {"frr-ripd:clear-rip-route": {"vrf": "testname"}} + result = json_cmp(jsout, expected) + assert result is None diff --git a/yang/frr-test-module.yang b/yang/frr-test-module.yang index 6cc60e866533..dcf204a95656 100644 --- a/yang/frr-test-module.yang +++ b/yang/frr-test-module.yang @@ -80,6 +80,23 @@ module frr-test-module { } } } + action ping { + input { + leaf data { + type string; + } + } + output { + leaf vrf { + type string; + } + // can't use the same name in input and output + // because of a bug in libyang < 2.1.148 + leaf data-out { + type string; + } + } + } } } choice achoice { diff --git a/zebra/zebra_nb_rpcs.c b/zebra/zebra_nb_rpcs.c index 083ab3fde69d..938193df2f2a 100644 --- a/zebra/zebra_nb_rpcs.c +++ b/zebra/zebra_nb_rpcs.c @@ -20,48 +20,30 @@ int clear_evpn_dup_addr_rpc(struct nb_cb_rpc_args *args) { struct zebra_vrf *zvrf; int ret = NB_OK; - struct yang_data *yang_dup_choice = NULL, *yang_dup_vni = NULL, - *yang_dup_ip = NULL, *yang_dup_mac = NULL; - - yang_dup_choice = yang_data_list_find(args->input, "%s/%s", args->xpath, - "input/clear-dup-choice"); zvrf = zebra_vrf_get_evpn(); - if (yang_dup_choice - && strcmp(yang_dup_choice->value, "all-case") == 0) { + if (yang_dnode_exists(args->input, "all-vnis")) { zebra_vxlan_clear_dup_detect_vni_all(zvrf); } else { - vni_t vni; + vni_t vni = yang_dnode_get_uint32(args->input, "vni-id"); struct ipaddr host_ip = {.ipa_type = IPADDR_NONE}; struct ethaddr mac; - yang_dup_vni = yang_data_list_find( - args->input, "%s/%s", args->xpath, - "input/clear-dup-choice/single-case/vni-id"); - if (yang_dup_vni) { - vni = yang_str2uint32(yang_dup_vni->value); - - yang_dup_mac = yang_data_list_find( - args->input, "%s/%s", args->xpath, - "input/clear-dup-choice/single-case/vni-id/mac-addr"); - yang_dup_ip = yang_data_list_find( - args->input, "%s/%s", args->xpath, - "input/clear-dup-choice/single-case/vni-id/vni-ipaddr"); - - if (yang_dup_mac) { - yang_str2mac(yang_dup_mac->value, &mac); - ret = zebra_vxlan_clear_dup_detect_vni_mac( - zvrf, vni, &mac, args->errmsg, - args->errmsg_len); - } else if (yang_dup_ip) { - yang_str2ip(yang_dup_ip->value, &host_ip); - ret = zebra_vxlan_clear_dup_detect_vni_ip( - zvrf, vni, &host_ip, args->errmsg, - args->errmsg_len); - } else - ret = zebra_vxlan_clear_dup_detect_vni(zvrf, - vni); + if (yang_dnode_exists(args->input, "mac-addr")) { + yang_dnode_get_mac(&mac, args->input, "mac-addr"); + ret = zebra_vxlan_clear_dup_detect_vni_mac(zvrf, vni, + &mac, + args->errmsg, + args->errmsg_len); + } else if (yang_dnode_exists(args->input, "vni-ipaddr")) { + yang_dnode_get_ip(&host_ip, args->input, "vni-ipaddr"); + ret = zebra_vxlan_clear_dup_detect_vni_ip(zvrf, vni, + &host_ip, + args->errmsg, + args->errmsg_len); + } else { + ret = zebra_vxlan_clear_dup_detect_vni(zvrf, vni); } } if (ret < 0) diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index 0b5362094eab..bdbb059e5619 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -3553,56 +3553,17 @@ DEFPY (clear_evpn_dup_addr, "IPv4 address\n" "IPv6 address\n") { - struct ipaddr host_ip = {.ipa_type = IPADDR_NONE }; - int ret = CMD_SUCCESS; - struct list *input; - struct yang_data *yang_dup = NULL, *yang_dup_ip = NULL, - *yang_dup_mac = NULL; - - input = list_new(); - if (!vni_str) { - yang_dup = yang_data_new( - "/frr-zebra:clear-evpn-dup-addr/input/clear-dup-choice", - "all-case"); + nb_cli_rpc_enqueue(vty, "all-vnis", NULL); } else { - yang_dup = yang_data_new_uint32( - "/frr-zebra:clear-evpn-dup-addr/input/clear-dup-choice/single-case/vni-id", - vni); - if (!is_zero_mac(&mac->eth_addr)) { - yang_dup_mac = yang_data_new_mac( - "/frr-zebra:clear-evpn-dup-addr/input/clear-dup-choice/single-case/vni-id/mac-addr", - &mac->eth_addr); - if (yang_dup_mac) - listnode_add(input, yang_dup_mac); - } else if (ip) { - if (sockunion_family(ip) == AF_INET) { - host_ip.ipa_type = IPADDR_V4; - host_ip.ipaddr_v4.s_addr = sockunion2ip(ip); - } else { - host_ip.ipa_type = IPADDR_V6; - memcpy(&host_ip.ipaddr_v6, &ip->sin6.sin6_addr, - sizeof(struct in6_addr)); - } - - yang_dup_ip = yang_data_new_ip( - "/frr-zebra:clear-evpn-dup-addr/input/clear-dup-choice/single-case/vni-id/vni-ipaddr", - &host_ip); - - if (yang_dup_ip) - listnode_add(input, yang_dup_ip); - } - } - - if (yang_dup) { - listnode_add(input, yang_dup); - ret = nb_cli_rpc(vty, "/frr-zebra:clear-evpn-dup-addr", input, - NULL); + nb_cli_rpc_enqueue(vty, "vni-id", vni_str); + if (mac_str) + nb_cli_rpc_enqueue(vty, "mac-addr", mac_str); + else if (ip_str) + nb_cli_rpc_enqueue(vty, "vni-ipaddr", ip_str); } - list_delete(&input); - - return ret; + return nb_cli_rpc(vty, "/frr-zebra:clear-evpn-dup-addr", NULL); } DEFPY_HIDDEN (evpn_accept_bgp_seq,