From 80b8c089af3d0eeb08a2d14114bc1959dd522212 Mon Sep 17 00:00:00 2001 From: aPiecek Date: Fri, 15 Mar 2024 15:24:29 +0100 Subject: [PATCH 1/3] netconf REFACTOR of ietf-netconf-notifications Notifications are moved to common.c file. --- src/common.c | 138 +++++++++++++++++++++++++++++++++++++++------------ src/common.h | 20 ++++++++ src/main.c | 61 ++--------------------- 3 files changed, 130 insertions(+), 89 deletions(-) diff --git a/src/common.c b/src/common.c index 76ec3162..8e18b6e8 100644 --- a/src/common.c +++ b/src/common.c @@ -371,6 +371,111 @@ np_ntf_send(struct nc_session *ncs, const struct timespec *timestamp, struct lyd return rc; } +int +np_send_notif_session_start(const struct nc_session *new_session, sr_session_ctx_t *sr_session, uint32_t sr_timeout) +{ + int rc = 0; + char *host = NULL; + sr_val_t *event_data; + const struct ly_ctx *ly_ctx; + const struct lys_module *mod; + + ly_ctx = nc_session_get_ctx(new_session); + if ((mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"))) { + if (nc_session_get_ti(new_session) != NC_TI_UNIX) { + host = (char *)nc_session_get_host(new_session); + } + event_data = calloc(3, sizeof *event_data); + event_data[0].xpath = "/ietf-netconf-notifications:netconf-session-start/username"; + event_data[0].type = SR_STRING_T; + event_data[0].data.string_val = (char *)nc_session_get_username(new_session); + event_data[1].xpath = "/ietf-netconf-notifications:netconf-session-start/session-id"; + event_data[1].type = SR_UINT32_T; + event_data[1].data.uint32_val = nc_session_get_id(new_session); + if (host) { + event_data[2].xpath = "/ietf-netconf-notifications:netconf-session-start/source-host"; + event_data[2].type = SR_STRING_T; + event_data[2].data.string_val = host; + } + rc = sr_notif_send(sr_session, "/ietf-netconf-notifications:netconf-session-start", event_data, host ? 3 : 2, + sr_timeout, 0); + if (rc != SR_ERR_OK) { + WRN("Failed to send a notification (%s).", sr_strerror(rc)); + } else { + VRB("Generated new event (netconf-session-start)."); + } + free(event_data); + } + + return rc; +} + +int +np_send_notif_session_end(const struct nc_session *session, sr_session_ctx_t *sr_session, uint32_t sr_timeout) +{ + int rc = 0; + char *host = NULL; + sr_val_t *event_data; + const struct ly_ctx *ly_ctx; + const struct lys_module *mod; + uint32_t i; + + ly_ctx = nc_session_get_ctx(session); + if ((mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"))) { + if (nc_session_get_ti(session) != NC_TI_UNIX) { + host = (char *)nc_session_get_host(session); + } + event_data = calloc(5, sizeof *event_data); + i = 0; + + event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/username"; + event_data[i].type = SR_STRING_T; + event_data[i++].data.string_val = (char *)nc_session_get_username(session); + event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/session-id"; + event_data[i].type = SR_UINT32_T; + event_data[i++].data.uint32_val = nc_session_get_id(session); + if (host) { + event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/source-host"; + event_data[i].type = SR_STRING_T; + event_data[i++].data.string_val = host; + } + if (nc_session_get_killed_by(session)) { + event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/killed-by"; + event_data[i].type = SR_UINT32_T; + event_data[i++].data.uint32_val = nc_session_get_killed_by(session); + } + event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/termination-reason"; + event_data[i].type = SR_ENUM_T; + switch (nc_session_get_term_reason(session)) { + case NC_SESSION_TERM_CLOSED: + event_data[i++].data.enum_val = "closed"; + break; + case NC_SESSION_TERM_KILLED: + event_data[i++].data.enum_val = "killed"; + break; + case NC_SESSION_TERM_DROPPED: + event_data[i++].data.enum_val = "dropped"; + break; + case NC_SESSION_TERM_TIMEOUT: + event_data[i++].data.enum_val = "timeout"; + break; + default: + event_data[i++].data.enum_val = "other"; + break; + } + rc = sr_notif_send(sr_session, "/ietf-netconf-notifications:netconf-session-end", event_data, i, + sr_timeout, 0); + if (rc != SR_ERR_OK) { + WRN("Failed to send a notification (%s).", sr_strerror(rc)); + } else { + VRB("Generated new event (netconf-session-end)."); + } + free(event_data); + } + + return 0; +} + const struct ly_ctx * np2srv_acquire_ctx_cb(void *cb_data) { @@ -387,12 +492,8 @@ int np2srv_new_session_cb(const char *UNUSED(client_name), struct nc_session *new_session, void *UNUSED(user_data)) { int c; - sr_val_t *event_data; sr_session_ctx_t *sr_sess = NULL; struct np2_user_sess *user_sess = NULL; - const struct ly_ctx *ly_ctx; - const struct lys_module *mod; - char *host = NULL; uint32_t nc_id; const char *username; @@ -443,33 +544,8 @@ np2srv_new_session_cb(const char *UNUSED(client_name), struct nc_session *new_se goto error; } - ly_ctx = nc_session_get_ctx(new_session); - if ((mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"))) { - /* generate ietf-netconf-notification's netconf-session-start event for sysrepo */ - if (nc_session_get_ti(new_session) != NC_TI_UNIX) { - host = (char *)nc_session_get_host(new_session); - } - event_data = calloc(3, sizeof *event_data); - event_data[0].xpath = "/ietf-netconf-notifications:netconf-session-start/username"; - event_data[0].type = SR_STRING_T; - event_data[0].data.string_val = (char *)nc_session_get_username(new_session); - event_data[1].xpath = "/ietf-netconf-notifications:netconf-session-start/session-id"; - event_data[1].type = SR_UINT32_T; - event_data[1].data.uint32_val = nc_session_get_id(new_session); - if (host) { - event_data[2].xpath = "/ietf-netconf-notifications:netconf-session-start/source-host"; - event_data[2].type = SR_STRING_T; - event_data[2].data.string_val = host; - } - c = sr_notif_send(np2srv.sr_sess, "/ietf-netconf-notifications:netconf-session-start", event_data, host ? 3 : 2, - np2srv.sr_timeout, 0); - if (c != SR_ERR_OK) { - WRN("Failed to send a notification (%s).", sr_strerror(c)); - } else { - VRB("Generated new event (netconf-session-start)."); - } - free(event_data); - } + /* generate ietf-netconf-notification's netconf-session-start event for sysrepo */ + np_send_notif_session_start(new_session, np2srv.sr_sess, np2srv.sr_timeout); return 0; diff --git a/src/common.h b/src/common.h index fcc34b98..62240946 100644 --- a/src/common.h +++ b/src/common.h @@ -226,6 +226,26 @@ int np_ntf_add_dup(const struct lyd_node *notif, const struct timespec *timestam */ int np_ntf_send(struct nc_session *ncs, const struct timespec *timestamp, struct lyd_node **ly_ntf, int use_ntf); +/** + * @brief Send notification netconf-session-start. + * + * @param[in] new_session Created NC session. + * @param[in] sr_session Sysrepo server session. + * @param[in] sr_timeout Notification callback timeout in milliseconds. + * @return 0 on success. + */ +int np_send_notif_session_start(const struct nc_session *new_session, sr_session_ctx_t *sr_session, uint32_t sr_timeout); + +/** + * @brief Send notification netconf-session-end. + * + * @param[in] new_session NC session. + * @param[in] sr_session Sysrepo server session. + * @param[in] sr_timeout Notification callback timeout in milliseconds. + * @return 0 on success. + */ +int np_send_notif_session_end(const struct nc_session *session, sr_session_ctx_t *sr_session, uint32_t sr_timeout); + /** * @brief NP2 callback for acquiring context. */ diff --git a/src/main.c b/src/main.c index 85f07005..a4a9bbb1 100644 --- a/src/main.c +++ b/src/main.c @@ -93,12 +93,7 @@ signal_handler(int sig) static void np2srv_del_session_cb(struct nc_session *session) { - int rc; - char *host = NULL; - sr_val_t *event_data; struct np2_user_sess *user_sess; - const struct ly_ctx *ly_ctx; - const struct lys_module *mod; uint32_t i; /* terminate any subscriptions for the NETCONF session */ @@ -128,59 +123,9 @@ np2srv_del_session_cb(struct nc_session *session) free(user_sess); } - ly_ctx = nc_session_get_ctx(session); - if ((mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"))) { - /* generate ietf-netconf-notification's netconf-session-end event for sysrepo */ - if (nc_session_get_ti(session) != NC_TI_UNIX) { - host = (char *)nc_session_get_host(session); - } - event_data = calloc(5, sizeof *event_data); - i = 0; - - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/username"; - event_data[i].type = SR_STRING_T; - event_data[i++].data.string_val = (char *)nc_session_get_username(session); - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/session-id"; - event_data[i].type = SR_UINT32_T; - event_data[i++].data.uint32_val = nc_session_get_id(session); - if (host) { - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/source-host"; - event_data[i].type = SR_STRING_T; - event_data[i++].data.string_val = host; - } - if (nc_session_get_killed_by(session)) { - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/killed-by"; - event_data[i].type = SR_UINT32_T; - event_data[i++].data.uint32_val = nc_session_get_killed_by(session); - } - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/termination-reason"; - event_data[i].type = SR_ENUM_T; - switch (nc_session_get_term_reason(session)) { - case NC_SESSION_TERM_CLOSED: - event_data[i++].data.enum_val = "closed"; - break; - case NC_SESSION_TERM_KILLED: - event_data[i++].data.enum_val = "killed"; - break; - case NC_SESSION_TERM_DROPPED: - event_data[i++].data.enum_val = "dropped"; - break; - case NC_SESSION_TERM_TIMEOUT: - event_data[i++].data.enum_val = "timeout"; - break; - default: - event_data[i++].data.enum_val = "other"; - break; - } - rc = sr_notif_send(np2srv.sr_sess, "/ietf-netconf-notifications:netconf-session-end", event_data, i, - np2srv.sr_timeout, 0); - if (rc != SR_ERR_OK) { - WRN("Failed to send a notification (%s).", sr_strerror(rc)); - } else { - VRB("Generated new event (netconf-session-end)."); - } - free(event_data); - } + /* generate ietf-netconf-notification's netconf-session-end event for sysrepo */ + np_send_notif_session_end(session, np2srv.sr_sess, np2srv.sr_timeout); + /* stop monitoring and free NC session */ ncm_session_del(session); nc_session_free(session, NULL); From 326a47ff3ec8059c3c5d96e6edf0b4b8a5075966 Mon Sep 17 00:00:00 2001 From: aPiecek Date: Mon, 18 Mar 2024 09:09:59 +0100 Subject: [PATCH 2/3] common REFACTOR of ietf-netconf-notifications Creating a notification via the libyang API. --- src/common.c | 211 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 130 insertions(+), 81 deletions(-) diff --git a/src/common.c b/src/common.c index 8e18b6e8..e6d7d64f 100644 --- a/src/common.c +++ b/src/common.c @@ -371,109 +371,158 @@ np_ntf_send(struct nc_session *ncs, const struct timespec *timestamp, struct lyd return rc; } +/** + * @brief Create common session parameters to identify a management session. + * + * Detail description is in the common-session-parms grouping located in the ietf-netconf-notifications module. + * + * @param[in] new_session Created NC session. + * @param[in,out] notif Notification to which the session parameters are to be added. + * @return 0 on success. + */ +static LY_ERR +np_prepare_notif_common_session_parms(const struct nc_session *session, struct lyd_node *notif) +{ + char *value; + char num32[11]; /* max bytes for 32-bit unsigned number + \0 */ + + assert(session && notif); + + /* create 'username' node */ + value = (char *)nc_session_get_username(session); + if (lyd_new_term(notif, notif->schema->module, "username", value, 0, NULL)) { + return -1; + } + + /* create 'session-id' node */ + sprintf(num32, "%" PRIu32, nc_session_get_id(session)); + if (lyd_new_term(notif, notif->schema->module, "session-id", num32, 0, NULL)) { + return -1; + } + + /* create 'source-host' node */ + if (nc_session_get_ti(session) != NC_TI_UNIX) { + value = (char *)nc_session_get_host(session); + if (lyd_new_term(notif, notif->schema->module, "source-host", value, 0, NULL)) { + return -1; + } + } + + return 0; +} + int np_send_notif_session_start(const struct nc_session *new_session, sr_session_ctx_t *sr_session, uint32_t sr_timeout) { - int rc = 0; - char *host = NULL; - sr_val_t *event_data; + int rc = 0, r; const struct ly_ctx *ly_ctx; const struct lys_module *mod; + struct lyd_node *notif = NULL; + /* get module */ ly_ctx = nc_session_get_ctx(new_session); - if ((mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"))) { - if (nc_session_get_ti(new_session) != NC_TI_UNIX) { - host = (char *)nc_session_get_host(new_session); - } - event_data = calloc(3, sizeof *event_data); - event_data[0].xpath = "/ietf-netconf-notifications:netconf-session-start/username"; - event_data[0].type = SR_STRING_T; - event_data[0].data.string_val = (char *)nc_session_get_username(new_session); - event_data[1].xpath = "/ietf-netconf-notifications:netconf-session-start/session-id"; - event_data[1].type = SR_UINT32_T; - event_data[1].data.uint32_val = nc_session_get_id(new_session); - if (host) { - event_data[2].xpath = "/ietf-netconf-notifications:netconf-session-start/source-host"; - event_data[2].type = SR_STRING_T; - event_data[2].data.string_val = host; - } - rc = sr_notif_send(sr_session, "/ietf-netconf-notifications:netconf-session-start", event_data, host ? 3 : 2, - sr_timeout, 0); - if (rc != SR_ERR_OK) { - WRN("Failed to send a notification (%s).", sr_strerror(rc)); - } else { - VRB("Generated new event (netconf-session-start)."); - } - free(event_data); + mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"); + if (!mod) { + goto cleanup; + } + + /* create 'netconf-session-start' notification */ + if (lyd_new_inner(NULL, mod, "netconf-session-start", 0, ¬if)) { + rc = -1; + goto cleanup; + } + + /* create 'common-session-parms' grouping */ + if ((rc = np_prepare_notif_common_session_parms(new_session, notif))) { + goto cleanup; + } + + /* send notification */ + if ((r = sr_notif_send_tree(sr_session, notif, sr_timeout, 0))) { + WRN("Failed to send a notification (%s).", sr_strerror(r)); + rc = -1; + goto cleanup; } + VRB("Generated new event (netconf-session-start)."); + +cleanup: + lyd_free_tree(notif); return rc; } int np_send_notif_session_end(const struct nc_session *session, sr_session_ctx_t *sr_session, uint32_t sr_timeout) { - int rc = 0; - char *host = NULL; - sr_val_t *event_data; + int rc = 0, r; const struct ly_ctx *ly_ctx; const struct lys_module *mod; - uint32_t i; + struct lyd_node *notif = NULL; + char num32[11]; /* max bytes for 32-bit unsigned number + \0 */ + char *value; + /* get module */ ly_ctx = nc_session_get_ctx(session); - if ((mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"))) { - if (nc_session_get_ti(session) != NC_TI_UNIX) { - host = (char *)nc_session_get_host(session); - } - event_data = calloc(5, sizeof *event_data); - i = 0; - - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/username"; - event_data[i].type = SR_STRING_T; - event_data[i++].data.string_val = (char *)nc_session_get_username(session); - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/session-id"; - event_data[i].type = SR_UINT32_T; - event_data[i++].data.uint32_val = nc_session_get_id(session); - if (host) { - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/source-host"; - event_data[i].type = SR_STRING_T; - event_data[i++].data.string_val = host; - } - if (nc_session_get_killed_by(session)) { - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/killed-by"; - event_data[i].type = SR_UINT32_T; - event_data[i++].data.uint32_val = nc_session_get_killed_by(session); - } - event_data[i].xpath = "/ietf-netconf-notifications:netconf-session-end/termination-reason"; - event_data[i].type = SR_ENUM_T; - switch (nc_session_get_term_reason(session)) { - case NC_SESSION_TERM_CLOSED: - event_data[i++].data.enum_val = "closed"; - break; - case NC_SESSION_TERM_KILLED: - event_data[i++].data.enum_val = "killed"; - break; - case NC_SESSION_TERM_DROPPED: - event_data[i++].data.enum_val = "dropped"; - break; - case NC_SESSION_TERM_TIMEOUT: - event_data[i++].data.enum_val = "timeout"; - break; - default: - event_data[i++].data.enum_val = "other"; - break; - } - rc = sr_notif_send(sr_session, "/ietf-netconf-notifications:netconf-session-end", event_data, i, - sr_timeout, 0); - if (rc != SR_ERR_OK) { - WRN("Failed to send a notification (%s).", sr_strerror(rc)); - } else { - VRB("Generated new event (netconf-session-end)."); + mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"); + if (!mod) { + goto cleanup; + } + + /* create 'netconf-session-end' notification */ + if (lyd_new_inner(NULL, mod, "netconf-session-end", 0, ¬if)) { + rc = -1; + goto cleanup; + } + + /* create 'common-session-parms' grouping */ + if ((rc = np_prepare_notif_common_session_parms(session, notif))) { + goto cleanup; + } + + /* create 'killed-by' node */ + if (nc_session_get_killed_by(session)) { + sprintf(num32, "%" PRIu32, nc_session_get_killed_by(session)); + if (lyd_new_term(notif, notif->schema->module, "killed-by", num32, 0, NULL)) { + rc = -1; + goto cleanup; } - free(event_data); } - return 0; + /* create 'termination-reason' node */ + switch (nc_session_get_term_reason(session)) { + case NC_SESSION_TERM_CLOSED: + value = "closed"; + break; + case NC_SESSION_TERM_KILLED: + value = "killed"; + break; + case NC_SESSION_TERM_DROPPED: + value = "dropped"; + break; + case NC_SESSION_TERM_TIMEOUT: + value = "timeout"; + break; + default: + value = "other"; + break; + } + if (lyd_new_term(notif, notif->schema->module, "termination-reason", value, 0, NULL)) { + rc = -1; + goto cleanup; + } + + /* send notification */ + if ((r = sr_notif_send_tree(sr_session, notif, sr_timeout, 0))) { + WRN("Failed to send a notification (%s).", sr_strerror(r)); + rc = -1; + goto cleanup; + } + + VRB("Generated new event (netconf-session-end)."); + +cleanup: + lyd_free_tree(notif); + return rc; } const struct ly_ctx * From 0bc31d03cdd0487450abc04566b6ae29686ad329 Mon Sep 17 00:00:00 2001 From: aPiecek Date: Wed, 27 Mar 2024 15:57:45 +0100 Subject: [PATCH 3/3] netopeer FEATURE netconf-confirmed commit Refs #1548 --- src/common.c | 86 ++++++++++++ src/common.h | 23 ++++ src/main.c | 2 +- src/netconf_confirmed_commit.c | 24 +++- src/netconf_confirmed_commit.h | 3 +- tests/test_confirmed_commit.c | 243 +++++++++++++++++++++++++++++++++ 6 files changed, 378 insertions(+), 3 deletions(-) diff --git a/src/common.c b/src/common.c index e6d7d64f..6cb161d5 100644 --- a/src/common.c +++ b/src/common.c @@ -525,6 +525,92 @@ np_send_notif_session_end(const struct nc_session *session, sr_session_ctx_t *sr return rc; } +int +np_send_notif_confirmed_commit(const struct nc_session *session, sr_session_ctx_t *sr_session, enum np_cc_event event, + uint32_t cc_timeout, uint32_t sr_timeout) +{ + int rc = 0, r; + const struct ly_ctx *ly_ctx; + const struct lys_module *mod; + struct lyd_node *notif = NULL; + char num32[11]; /* max bytes for 32-bit unsigned number + \0 */ + char *value; + + /* get context */ + if (session) { + ly_ctx = nc_session_get_ctx(session); + } else { + assert(event == NP_CC_TIMEOUT); + ly_ctx = sr_session_acquire_context(sr_session); + } + + /* get module */ + mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-netconf-notifications"); + if (!mod) { + goto cleanup; + } + + /* create 'netconf-confirmed-commit' notification */ + if (lyd_new_inner(NULL, mod, "netconf-confirmed-commit", 0, ¬if)) { + rc = -1; + goto cleanup; + } + + /* create 'common-session-parms' grouping */ + if ((event != NP_CC_TIMEOUT) && (rc = np_prepare_notif_common_session_parms(session, notif))) { + goto cleanup; + } + + /* create 'confirm-event' node */ + switch (event) { + case NP_CC_START: + value = "start"; + break; + case NP_CC_CANCEL: + value = "cancel"; + break; + case NP_CC_TIMEOUT: + value = "timeout"; + break; + case NP_CC_EXTEND: + value = "extend"; + break; + case NP_CC_COMPLETE: + value = "complete"; + break; + default: + rc = -1; + goto cleanup; + } + if (lyd_new_term(notif, notif->schema->module, "confirm-event", value, 0, NULL)) { + rc = -1; + goto cleanup; + } + + /* create 'timeout' node */ + if (cc_timeout) { + assert((event == NP_CC_START) || (event == NP_CC_EXTEND)); + sprintf(num32, "%" PRIu32, cc_timeout); + if (lyd_new_term(notif, notif->schema->module, "timeout", num32, 0, NULL)) { + rc = -1; + goto cleanup; + } + } + + /* send notification */ + if ((r = sr_notif_send_tree(sr_session, notif, sr_timeout, 0))) { + WRN("Failed to send a notification (%s).", sr_strerror(r)); + rc = -1; + goto cleanup; + } + + VRB("Generated new event (netconf-confirmed-commit)."); + +cleanup: + lyd_free_tree(notif); + return rc; +} + const struct ly_ctx * np2srv_acquire_ctx_cb(void *cb_data) { diff --git a/src/common.h b/src/common.h index 62240946..93dedc5c 100644 --- a/src/common.h +++ b/src/common.h @@ -246,6 +246,29 @@ int np_send_notif_session_start(const struct nc_session *new_session, sr_session */ int np_send_notif_session_end(const struct nc_session *session, sr_session_ctx_t *sr_session, uint32_t sr_timeout); +enum np_cc_event { + NP_CC_START = 0, /**< The confirmed-commit has started. */ + NP_CC_CANCEL, /**< The confirmed-commit has been canceled, e.g., due to the session being terminated, + or an explicit operation. */ + NP_CC_TIMEOUT, /**< The confirmed-commit has been canceled due to the confirm-timeout interval expiring. */ + NP_CC_EXTEND, /**< The confirmed-commit timeout has been extended, e.g., by a new operation. */ + NP_CC_COMPLETE /**< The confirmed-commit has been completed. */ +}; + +/** + * @brief Send notification netconf-confirmed-commit. + * + * @param[in] new_session NC session. For :NP_CC_TIMEOUT can be NULL. + * @param[in] sr_session Sysrepo server session. + * @param[in] event Type of confirm-commit event. + * @param[in] cc_timout For event :NP_CC_START or :NP_CC_EXTEND. Number of seconds when the confirmed-commit 'timeout' + * event might occur. + * @param[in] sr_timeout Notification callback timeout in milliseconds. + * @return 0 on success. + */ +int np_send_notif_confirmed_commit(const struct nc_session *session, sr_session_ctx_t *sr_session, + enum np_cc_event event, uint32_t cc_timeout, uint32_t sr_timeout); + /** * @brief NP2 callback for acquiring context. */ diff --git a/src/main.c b/src/main.c index a4a9bbb1..f48d11ca 100644 --- a/src/main.c +++ b/src/main.c @@ -109,7 +109,7 @@ np2srv_del_session_cb(struct nc_session *session) sr_session_unsubscribe(user_sess->sess); /* revert any pending confirmed commits */ - ncc_del_session(session); + ncc_del_session(session, np2srv.sr_sess); /* free sysrepo session, if no callback is using it */ if (ATOMIC_DEC_RELAXED(user_sess->ref_count) == 1) { diff --git a/src/netconf_confirmed_commit.c b/src/netconf_confirmed_commit.c index a2e007b9..a5b45d87 100644 --- a/src/netconf_confirmed_commit.c +++ b/src/netconf_confirmed_commit.c @@ -510,6 +510,9 @@ ncc_changes_rollback_cb(union sigval sev) if (!sev.sival_int) { /* UNLOCK */ pthread_mutex_unlock(&commit_ctx.lock); + + /* send notification about timeout for confirmed-commits */ + np_send_notif_confirmed_commit(commit_ctx.nc_sess, sr_sess, NP_CC_TIMEOUT, 0, 0); } sr_release_context(np2srv.sr_conn); if (user_sess) { @@ -529,7 +532,7 @@ ncc_changes_rollback_cb(union sigval sev) } void -ncc_del_session(const struct nc_session *nc_sess) +ncc_del_session(const struct nc_session *nc_sess, sr_session_ctx_t *sr_sess) { /* LOCK */ pthread_mutex_lock(&commit_ctx.lock); @@ -538,6 +541,9 @@ ncc_del_session(const struct nc_session *nc_sess) /* rollback */ VRB("Performing confirmed commit rollback after the issuing session has terminated."); ncc_changes_rollback_cb((union sigval)1); + + /* send notification about canceling confirmed-commits */ + np_send_notif_confirmed_commit(nc_sess, sr_sess, NP_CC_CANCEL, 0, 0); } /* UNLOCK */ @@ -828,6 +834,7 @@ np2srv_confirmed_commit_cb(sr_session_ctx_t *session, const struct lyd_node *inp struct lyd_node *node = NULL; char *endptr = NULL; uint32_t timeout; + uint8_t timeout_changed = 0; /* get the user session */ if ((rc = np_find_user_sess(session, __func__, &nc_sess, &user_sess))) { @@ -884,6 +891,7 @@ np2srv_confirmed_commit_cb(sr_session_ctx_t *session, const struct lyd_node *inp /* there is already a pending confirmed commit, keep its backup, but the timeout will be reset */ timer_delete(commit_ctx.timer); commit_ctx.timer = 0; + timeout_changed = 1; } /* (re)set the meta file timeout */ @@ -903,6 +911,14 @@ np2srv_confirmed_commit_cb(sr_session_ctx_t *session, const struct lyd_node *inp goto cleanup; } + if (timeout_changed) { + /* send notification about extending timeout for confirmed-commits */ + np_send_notif_confirmed_commit(nc_sess, session, NP_CC_EXTEND, timeout, 0); + } else { + /* send notification about starting confirmed-commits */ + np_send_notif_confirmed_commit(nc_sess, session, NP_CC_START, timeout, 0); + } + /* sysrepo API */ rc = sr_copy_config(user_sess->sess, NULL, SR_DS_CANDIDATE, np2srv.sr_timeout); if (rc == SR_ERR_LOCKED) { @@ -971,6 +987,9 @@ np2srv_rpc_commit_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const c goto cleanup; } ncc_commit_confirmed(); + + /* send notification about complete confirmed-commits */ + np_send_notif_confirmed_commit(nc_sess, session, NP_CC_COMPLETE, 0, 0); } /* sysrepo API */ @@ -1065,6 +1084,9 @@ np2srv_rpc_cancel_commit_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), VRB("Performing confirmed commit rollback after receiving ."); ncc_changes_rollback_cb((union sigval)1); + /* send notification about canceling confirmed-commits */ + np_send_notif_confirmed_commit(nc_sess, session, NP_CC_CANCEL, 0, 0); + cleanup: /* UNLOCK */ pthread_mutex_unlock(&commit_ctx.lock); diff --git a/src/netconf_confirmed_commit.h b/src/netconf_confirmed_commit.h index 0ab36d02..4652a9f2 100644 --- a/src/netconf_confirmed_commit.h +++ b/src/netconf_confirmed_commit.h @@ -47,8 +47,9 @@ void ncc_try_restore(void); * @brief Revert current confirmed commit, if any, if not persistent and this session started it. * * @param[in] nc_sess Terminated NC session. + * @param[in] sr_sess Sysrepo server session. */ -void ncc_del_session(const struct nc_session *nc_sess); +void ncc_del_session(const struct nc_session *nc_sess, sr_session_ctx_t *sr_sess); int np2srv_rpc_commit_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *op_path, const struct lyd_node *input, sr_event_t event, uint32_t request_id, struct lyd_node *output, void *private_data); diff --git a/tests/test_confirmed_commit.c b/tests/test_confirmed_commit.c index d379eef0..311ba84b 100644 --- a/tests/test_confirmed_commit.c +++ b/tests/test_confirmed_commit.c @@ -33,6 +33,81 @@ #include "np_test.h" #include "np_test_config.h" +#define TCC_NOTIF_XMLNS "\"urn:ietf:params:xml:ns:yang:ietf-netconf-notifications\"" + +#define TCC_RECV_NOTIF_PARAM(nc_sess, timeout_ms, state) \ + do { \ + state->msgtype = nc_recv_notif(nc_sess, timeout_ms, &state->envp, &state->op); \ + } while (state->msgtype == NC_MSG_REPLY); \ + assert_int_equal(NC_MSG_NOTIF, state->msgtype); \ + while (state->op->parent) state->op = lyd_parent(state->op); \ + +#define TCC_RECV_NOTIF(state) \ + TCC_RECV_NOTIF_PARAM(state->nc_sess, 3000, state) + +#define TCC_ASSERT_NOTIF_EVENT(state, event, ssid) \ + { \ + assert_int_equal(lyd_print_mem(&state->str, state->op, LYD_XML, 0), LY_SUCCESS); \ + char *exp_cce = notif_cc_event(event, ssid); \ + assert_non_null(exp_cce); \ + assert_string_equal(exp_cce, state->str); \ + free(exp_cce); \ + free(state->str); \ + state->str = NULL; \ + } + +static char * +notif_cc_event(const char *event, uint32_t ssid) +{ + char *msg = NULL; + + ssid = (ssid == 0) ? 1 : ssid; + + /* Check data without 'timeout' leaf */ + if (!strcmp("timeout", event)) { + asprintf(&msg, + "\n" + " timeout\n" + "\n"); + } else { + asprintf(&msg, + "\n" + " %s\n" + " %" PRIu32 "\n" + " %s\n" + "\n", + np_get_user(), ssid, event); + } + + return msg; +} + +static int +notif_check_cc_timeout(struct np_test *st, uint32_t expected_timeout) +{ + struct lyd_node *timeout_node; + const uint32_t timeout_tolerance = 2; + uint32_t timeout; + + timeout_node = lyd_child(st->op)->prev; + if (!timeout_node || strcmp(timeout_node->schema->name, "timeout")) { + /* timeout node is missing */ + return 2; + } + timeout = ((struct lyd_node_term *)timeout_node)->value.uint32; + + if ((expected_timeout <= timeout_tolerance) || + ((timeout >= (expected_timeout - timeout_tolerance)) && + (timeout <= expected_timeout))) { + /* success, timeout node is checked so it can be removed */ + lyd_free_tree(timeout_node); + return 0; + } else { + /* timeout is out of range */ + return 1; + } +} + static int local_setup(void **state) { @@ -69,6 +144,15 @@ local_setup(void **state) rc = setup_nacm(state); assert_int_equal(rc, 0); + /* Enable replay support for ietf-netconf-notifications */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "ietf-netconf-notifications", 1)); + + /* Subscribe confirm-commit notification */ + SEND_RPC_ESTABSUB(st, "/ietf-netconf-notifications:netconf-confirmed-commit", + "ietf-netconf-notifications", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + return 0; } @@ -137,6 +221,12 @@ test_sameas_commit(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 600), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* Running should now be same as candidate, same as basic commit */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -156,6 +246,11 @@ test_sameas_commit(void **state) /* Check if received an OK reply */ ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + + /* Expect 'complete' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "complete", 0); + FREE_TEST_VARS(st); } static void @@ -185,6 +280,12 @@ test_timeout_runout(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification with 1s timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 1), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* Running should now be same as candidate */ GET_FILTER(st, "/edit1:first"); expected = @@ -201,6 +302,11 @@ test_timeout_runout(void **state) /* wait for the duration of the timeout */ sleep(2); + /* Expect 'timeout' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "timeout", 0); + FREE_TEST_VARS(st); + /* Running should have reverted back to it's original value */ ASSERT_EMPTY_CONFIG_FILTER(st, "/edit1:*"); @@ -212,6 +318,9 @@ test_timeout_runout(void **state) /* received an OK reply */ ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + + /* No notification should occur */ + ASSERT_NO_NOTIF(st); } static void @@ -232,6 +341,12 @@ test_timeout_confirm(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification with 1s timeout*/ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 1), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* Running should now be same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -252,12 +367,20 @@ test_timeout_confirm(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'complete' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "complete", 0); + FREE_TEST_VARS(st); + sleep(2); /* Data should remain unchanged */ GET_CONFIG_FILTER(st, "/edit1:*"); assert_string_equal(st->str, expected); FREE_TEST_VARS(st); + + /* No notification should occur */ + ASSERT_NO_NOTIF(st); } static void @@ -279,6 +402,12 @@ test_timeout_confirm_modify(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Send a confirmed-commit rpc with 1s timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 1), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* Running should now be same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -306,6 +435,11 @@ test_timeout_confirm_modify(void **state) sleep(2); + /* Expect 'complete' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "complete", 0); + FREE_TEST_VARS(st); + /* Data should change */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -334,6 +468,12 @@ test_timeout_followup(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification with 60s timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 60), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* modify candidate */ data = "Test2"; SR_EDIT_SESSION(st, st->sr_sess2, data); @@ -346,6 +486,12 @@ test_timeout_followup(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'extend' notification with 1s timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 1), 0); + TCC_ASSERT_NOTIF_EVENT(st, "extend", 0); + FREE_TEST_VARS(st); + /* running should now be same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -360,6 +506,11 @@ test_timeout_followup(void **state) /* wait for the rollback */ sleep(2); + /* Expect 'timeout' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "timeout", 0); + FREE_TEST_VARS(st); + /* data should remain unchanged, empty */ ASSERT_EMPTY_CONFIG_FILTER(st, "/edit1:*"); } @@ -382,6 +533,9 @@ test_cancel(void **state) ASSERT_ERROR_REPLY(st); FREE_TEST_VARS(st); + /* No notification should occur */ + ASSERT_NO_NOTIF(st); + /* edit running */ data = "val5"; SR_EDIT_SESSION(st, st->sr_sess, data); @@ -396,6 +550,12 @@ test_cancel(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification with 10m timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 600), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* running should now be same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -416,6 +576,11 @@ test_cancel(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'cancel' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "cancel", 0); + FREE_TEST_VARS(st); + /* running should now be back how it was */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -438,6 +603,7 @@ test_rollback_disconnect(void **state) struct np_test *st = *state; struct nc_session *ncs; const char *expected; + uint32_t sid; /* prior to the test running should be empty */ ASSERT_EMPTY_CONFIG_FILTER(st, "/edit1:*"); @@ -457,6 +623,12 @@ test_rollback_disconnect(void **state) assert_string_equal(LYD_NAME(lyd_child(st->envp)), "ok"); FREE_TEST_VARS(st); + /* Expect 'start' notification with 60s timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 60), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", nc_session_get_id(ncs)); + FREE_TEST_VARS(st); + /* running should now be same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -469,11 +641,17 @@ test_rollback_disconnect(void **state) FREE_TEST_VARS(st); /* disconnect session, commit is rolled back */ + sid = nc_session_get_id(ncs); nc_session_free(ncs, NULL); /* reply is sent before the server callback is called so give it a chance to perform the rollback */ usleep(100000); + /* Expect 'cancel' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "cancel", sid); + FREE_TEST_VARS(st); + /* data should remain unchanged, empty */ ASSERT_EMPTY_CONFIG_FILTER(st, "/edit1:*"); } @@ -501,6 +679,12 @@ test_rollback_locked(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification with 60s timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 60), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* running should now be the same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -519,6 +703,9 @@ test_rollback_locked(void **state) ASSERT_ERROR_REPLY_SESS2(st); FREE_TEST_VARS(st); + /* No notification should occur */ + ASSERT_NO_NOTIF(st); + /* cancel-commit on the same session */ st->rpc = nc_rpc_cancel("test-persist", NC_PARAMTYPE_CONST); st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); @@ -526,6 +713,11 @@ test_rollback_locked(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'cancel' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "cancel", 0); + FREE_TEST_VARS(st); + /* data should remain unchanged, empty */ ASSERT_EMPTY_CONFIG_FILTER(st, "/edit1:*"); @@ -535,6 +727,9 @@ test_rollback_locked(void **state) assert_int_equal(st->msgtype, NC_MSG_RPC); ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + + /* No notification should occur */ + ASSERT_NO_NOTIF(st); } static void @@ -555,6 +750,12 @@ test_confirm_persist(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 600), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* Running should now be same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -575,6 +776,11 @@ test_confirm_persist(void **state) ASSERT_OK_REPLY_SESS2(st); FREE_TEST_VARS(st); + /* Expect 'complete' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "complete", 2); + FREE_TEST_VARS(st); + /* Data should remain unchanged */ GET_CONFIG_FILTER(st, "/edit1:*"); assert_string_equal(st->str, expected); @@ -604,6 +810,12 @@ test_cancel_persist(void **state) ASSERT_OK_REPLY_PARAM(nc_sess, 3000, st) FREE_TEST_VARS(st); + /* Expect 'start' notification */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 600), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", nc_session_get_id(nc_sess)); + FREE_TEST_VARS(st); + /* running should now be same as candidate */ GET_CONFIG_FILTER(st, "/edit1:*"); expected = @@ -618,6 +830,9 @@ test_cancel_persist(void **state) /* disconnect NC session */ nc_session_free(nc_sess, NULL); + /* No notification should occur */ + ASSERT_NO_NOTIF(st); + /* send cancel-commit rpc on a different session */ st->rpc = nc_rpc_cancel(persist, NC_PARAMTYPE_CONST); st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); @@ -627,6 +842,11 @@ test_cancel_persist(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'cancel' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "cancel", 0); + FREE_TEST_VARS(st); + /* running should now be empty */ ASSERT_EMPTY_CONFIG_FILTER(st, "/edit1:*"); } @@ -642,6 +862,10 @@ test_wrong_session(void **state) assert_int_equal(st->msgtype, NC_MSG_RPC); ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 60), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); /* send another confirmed-commit rpc on a different NC session, invalid */ st->rpc = nc_rpc_commit(1, 1, NULL, NULL, NC_PARAMTYPE_CONST); @@ -650,6 +874,7 @@ test_wrong_session(void **state) ASSERT_ERROR_REPLY_SESS2(st); assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "operation-failed"); FREE_TEST_VARS(st); + ASSERT_NO_NOTIF(st); /* send confirming commit rpc on a different NC session, invalid */ st->rpc = nc_rpc_commit(0, 0, NULL, NULL, NC_PARAMTYPE_CONST); @@ -658,6 +883,7 @@ test_wrong_session(void **state) ASSERT_ERROR_REPLY_SESS2(st); assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "operation-failed"); FREE_TEST_VARS(st); + ASSERT_NO_NOTIF(st); /* send cancel commit rpc on a different NC session, invalid */ st->rpc = nc_rpc_cancel(NULL, NC_PARAMTYPE_CONST); @@ -666,6 +892,7 @@ test_wrong_session(void **state) ASSERT_ERROR_REPLY_SESS2(st); assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "operation-failed"); FREE_TEST_VARS(st); + ASSERT_NO_NOTIF(st); /* send running lock rpc on a different NC session, invalid */ st->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); @@ -674,6 +901,7 @@ test_wrong_session(void **state) ASSERT_ERROR_REPLY_SESS2(st); assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "lock-denied"); FREE_TEST_VARS(st); + ASSERT_NO_NOTIF(st); /* send cancel-commit rpc */ st->rpc = nc_rpc_cancel(NULL, NC_PARAMTYPE_CONST); @@ -681,6 +909,9 @@ test_wrong_session(void **state) assert_int_equal(st->msgtype, NC_MSG_RPC); ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "cancel", 0); + FREE_TEST_VARS(st); } static void @@ -698,6 +929,7 @@ test_wrong_persist_id(void **state) ASSERT_ERROR_REPLY(st); assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "invalid-value"); FREE_TEST_VARS(st); + ASSERT_NO_NOTIF(st); } static int @@ -750,9 +982,20 @@ test_failed_file(void **state) ASSERT_OK_REPLY(st); FREE_TEST_VARS(st); + /* Expect 'start' notification with 1s timeout */ + TCC_RECV_NOTIF(st); + assert_int_equal(notif_check_cc_timeout(st, 1), 0); + TCC_ASSERT_NOTIF_EVENT(st, "start", 0); + FREE_TEST_VARS(st); + /* Wait for the duration of the timeout */ sleep(2); + /* Expect 'timeout' notification */ + TCC_RECV_NOTIF(st); + TCC_ASSERT_NOTIF_EVENT(st, "timeout", 0); + FREE_TEST_VARS(st); + /* Try and find the .failed file, should be exactly one */ dir = opendir(st->path); assert_non_null(dir);