CESNET / netopeer2

NETCONF toolset
BSD 3-Clause "New" or "Revised" License
301 stars 189 forks source link

Query: Create-subscription RPC and Notification handling at netconf server #1403

Closed ankit7gup closed 1 year ago

ankit7gup commented 1 year ago

Hi,

One query related to having RPC subscription for create-subscription in application and sending notifications for another session overlap, causing drop of notifications at Netconf client as message RPC Ok is not received yet, and further no notification is received further for Netconf client. Any suugestions, how can we handle such situations.

Logs from netopeer server:

Create subscription from Session 1 from Client 1.

 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 2 priority 5 for 1 subscribers published.
 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 2 priority 5 succeeded.

 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 2 priority 0 for 1 subscribers published.
 netopeer2-server[1735]: EV LISTEN: "/notifications:create-subscription" "rpc" ID 2 priority 0 processing (remaining 1 subscribers).
 netopeer2-server[1735]: EV LISTEN: "/notifications:create-subscription" "rpc" ID 2 priority 0 success (remaining 0 subscribers).
 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 2 priority 0 succeeded.

Create subscription from Session 2 from Client 1

 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 3 priority 5 for 1 subscribers published.
 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 3 priority 5 succeeded.

 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 3 priority 0 for 1 subscribers published.
 netopeer2-server[1735]: EV LISTEN: "/notifications:create-subscription" "rpc" ID 3 priority 0 processing (remaining 1 subscribers).

 Notification was sent from application in context of session 1 from Client 1

 netopeer2-server[1735]: EV LISTEN: "ietf-hardware" "notif" ID 1 processing.
 netopeer2-server[1735]: EV LISTEN: "ietf-hardware" "notif" ID 1 priority 0 success (remaining 0 subscribers).

 Create subscription from Session 3 from Client 1

 netopeer2-server[1735]: EV LISTEN: "/notifications:create-subscription" "rpc" ID 3 priority 0 success (remaining 0 subscribers).
 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 3 priority 0 succeeded.

 Create subscription from Session 4 from Client 1

 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 4 priority 5 for 1 subscribers published.
 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 4 priority 5 succeeded.

 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 4 priority 0 for 1 subscribers published.
 netopeer2-server[1735]: EV LISTEN: "/notifications:create-subscription" "rpc" ID 4 priority 0 processing (remaining 1 subscribers).
 netopeer2-server[1735]: EV LISTEN: "/notifications:create-subscription" "rpc" ID 4 priority 0 success (remaining 0 subscribers).
 netopeer2-server[1735]: EV ORIGIN: "/notifications:create-subscription" "rpc" ID 4 priority 0 succeeded.
michalvasko commented 1 year ago

Not sure if server logs are relevant here, libnetconf2 is able to handle overlapping rpc-replies and notifications, it just does not do so automatically (which allows for more customized behavior). When you call nc_recv_reply(), you can read a notification instead and you get the return value NC_MSG_NOTIF. As it is documented, you should then call the function again and it will normally read the rpc-reply, if it arrived. That is also what netopeer2-cli does and it should work reliably.

ankit7gup commented 1 year ago

We checked and code seemed to be simialr, still pasting the snippet, in case you can help pointing out if there is something wrong here.

Client Code:

=============

int ncClient::startNotifMon(std::string hostId, nc_session* session, const char* stream, const char* filter)
{
...
    /* create requests */
    rpc = nc_rpc_subscribe(stream, filter, NULL, NULL, NC_PARAMTYPE_CONST);
...
    int ret = rpc_send_recv_reply(hostId, session, rpc);
    nc_rpc_free(rpc);
    if (ret == FAILURE)       
        return FAILURE;

    ret = nc_recv_notif_dispatch(session, &ncClient::lnc2_notifClb);
    if (ret)
    {
        _error << hostId << ", session[" << nc_session_get_id(session) << "], msg: failed to create notif thread."<<std::endl;
        return FAILURE;
    }
    return ret;

}

int ncClient::rpc_send_recv_reply(std::string hostId, nc_session* sess, struct nc_rpc* rpc) 
{
...

msgtype = nc_send_rpc(sess, rpc, NCCLIENT_TIMEOUT, &msgid);

...
recv_reply:

    msgtype = nc_recv_reply(sess, rpc, msgid, NCCLIENT_TIMEOUT,
                            LYD_OPT_DESTRUCT | LYD_OPT_NOSIBLINGS, &reply);
    if (msgtype == NC_MSG_ERROR)
    {
        _error << hostId << ", session[" << sess_id << "], msg: failed to receive a reply for rpc[ " << rpc_type << "]."<<std::endl;
        return FAILURE;       ==> As we get NC_MSG_ERROR for nc_recv_reply
    }
    else if (msgtype == NC_MSG_WOULDBLOCK)
    {
        _error << hostId << ", session[" << sess_id << "], msg: timeout for receiving\
                             a reply expired for rpc[ "
               << nc_rpc_get_type(rpc) << "]."<<std::endl;
        return FAILURE;
    }
    else if (msgtype == NC_MSG_NOTIF)
    {
        /* read-action */
        goto recv_reply;            ==> in case of msgtype is NOTIF it is trying to receive reply again
    }

...

}

Libnetconf Code:

================

API NC_MSG_TYPE
nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, int timeout, int parseroptions, struct nc_reply **reply)
{
...
msgtype = get_msg(session, timeout, msgid, &xml);
...
return msgtype;
}

static NC_MSG_TYPE
get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_elem **msg)
{
...
    /* we read notif, want a rpc-reply */
    if (msgid && (msgtype == NC_MSG_NOTIF)) {
        if (!session->opts.client.ntf_tid) {
            ERR("Session %u: received a <notification> but session is not subscribed.", session->id);
            lyxml_free(session->ctx, xml);
            return NC_MSG_ERROR;   ==> this is causing out client code to fail and not to retry for recv_reply
        }
...
}

Thanks!

michalvasko commented 1 year ago

That is not what netopeer2-cli does. Looking at it, you would write your code like this

int ncClient::startNotifMon(std::string hostId, nc_session* session, const char* stream, const char* filter)
{
...
    /* create requests */
    rpc = nc_rpc_subscribe(stream, filter, NULL, NULL, NC_PARAMTYPE_CONST);
...
    ret = nc_recv_notif_dispatch(session, &ncClient::lnc2_notifClb);
    if (ret)
    {
        _error << hostId << ", session[" << nc_session_get_id(session) << "], msg: failed to create notif thread."<<std::endl;
        return FAILURE;
    }

    int ret = rpc_send_recv_reply(hostId, session, rpc);
    nc_rpc_free(rpc);

    return ret;

}

with some changes to avoid memory leaks. There is the short span of time when you subscribe but are not receiving notifications yet and when a notification arrives then, you get the error.