| 1 | /* |
| 2 | * rotation.c |
| 3 | * |
| 4 | * Tests suite for LTTng notification API (rotation notifications) |
| 5 | * |
| 6 | * Copyright (C) 2017 Jérémie Galarneau <jeremie.galarneau@efficios.com> |
| 7 | * |
| 8 | * SPDX-License-Identifier: MIT |
| 9 | * |
| 10 | */ |
| 11 | |
| 12 | #include <stdio.h> |
| 13 | #include <unistd.h> |
| 14 | #include <assert.h> |
| 15 | #include <tap/tap.h> |
| 16 | #include <stdint.h> |
| 17 | #include <string.h> |
| 18 | #include <lttng/lttng.h> |
| 19 | |
| 20 | #define TEST_COUNT 36 |
| 21 | |
| 22 | struct session { |
| 23 | const char *name; |
| 24 | const char *output_path; |
| 25 | }; |
| 26 | |
| 27 | uint64_t expected_rotation_id = UINT64_MAX; |
| 28 | |
| 29 | static |
| 30 | int test_condition(struct lttng_condition *condition, const char *type_name) |
| 31 | { |
| 32 | int ret = 0; |
| 33 | const char *out_session_name; |
| 34 | const char * const session_name = "test session name"; |
| 35 | enum lttng_condition_status status; |
| 36 | |
| 37 | status = lttng_condition_session_rotation_get_session_name(condition, |
| 38 | &out_session_name); |
| 39 | ok(status == LTTNG_CONDITION_STATUS_UNSET, |
| 40 | "Getting unset name of %s condition fails with LTTNG_CONDITION_STATUS_UNSET", |
| 41 | type_name); |
| 42 | |
| 43 | status = lttng_condition_session_rotation_set_session_name(condition, |
| 44 | session_name); |
| 45 | ok(status == LTTNG_CONDITION_STATUS_OK, |
| 46 | "Setting session name \"%s\" of %s condition succeeds", |
| 47 | session_name, type_name); |
| 48 | |
| 49 | status = lttng_condition_session_rotation_get_session_name(condition, |
| 50 | &out_session_name); |
| 51 | ok(status == LTTNG_CONDITION_STATUS_OK, |
| 52 | "Getting name of %s condition succeeds", |
| 53 | type_name); |
| 54 | |
| 55 | ok(out_session_name && !strcmp(session_name, out_session_name), |
| 56 | "Session name returned by %s condition matches the expected name", |
| 57 | type_name); |
| 58 | return ret; |
| 59 | } |
| 60 | |
| 61 | static |
| 62 | int setup_rotation_trigger(const struct session *session, |
| 63 | struct lttng_notification_channel *notification_channel) |
| 64 | { |
| 65 | int ret; |
| 66 | struct lttng_condition *rotation_ongoing_condition = NULL; |
| 67 | struct lttng_condition *rotation_completed_condition = NULL; |
| 68 | struct lttng_action *notify = NULL; |
| 69 | struct lttng_trigger *rotation_ongoing_trigger = NULL; |
| 70 | struct lttng_trigger *rotation_completed_trigger = NULL; |
| 71 | enum lttng_condition_status condition_status; |
| 72 | enum lttng_notification_channel_status notification_channel_status; |
| 73 | enum lttng_error_code ret_code; |
| 74 | |
| 75 | notify = lttng_action_notify_create(); |
| 76 | if (!notify) { |
| 77 | ret = -1; |
| 78 | goto end; |
| 79 | } |
| 80 | |
| 81 | /* Create rotation ongoing and completed conditions. */ |
| 82 | rotation_ongoing_condition = |
| 83 | lttng_condition_session_rotation_ongoing_create(); |
| 84 | ok(rotation_ongoing_condition, "Create session rotation ongoing condition"); |
| 85 | if (!rotation_ongoing_condition) { |
| 86 | ret = -1; |
| 87 | goto end; |
| 88 | } |
| 89 | ret = test_condition(rotation_ongoing_condition, "rotation ongoing"); |
| 90 | if (ret) { |
| 91 | goto end; |
| 92 | } |
| 93 | condition_status = lttng_condition_session_rotation_set_session_name( |
| 94 | rotation_ongoing_condition, session->name); |
| 95 | if (condition_status != LTTNG_CONDITION_STATUS_OK) { |
| 96 | ret = -1; |
| 97 | diag("Failed to set session name on session rotation ongoing condition"); |
| 98 | goto end; |
| 99 | } |
| 100 | |
| 101 | rotation_completed_condition = |
| 102 | lttng_condition_session_rotation_completed_create(); |
| 103 | ok(rotation_completed_condition, "Create session rotation completed condition"); |
| 104 | if (!rotation_completed_condition) { |
| 105 | ret = -1; |
| 106 | goto end; |
| 107 | } |
| 108 | ret = test_condition(rotation_completed_condition, "rotation completed"); |
| 109 | if (ret) { |
| 110 | diag("Failed to set session name on session rotation completed condition"); |
| 111 | goto end; |
| 112 | } |
| 113 | condition_status = lttng_condition_session_rotation_set_session_name( |
| 114 | rotation_completed_condition, session->name); |
| 115 | if (condition_status != LTTNG_CONDITION_STATUS_OK) { |
| 116 | ret = -1; |
| 117 | goto end; |
| 118 | } |
| 119 | |
| 120 | notification_channel_status = lttng_notification_channel_subscribe( |
| 121 | notification_channel, rotation_ongoing_condition); |
| 122 | ok(notification_channel_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, |
| 123 | "Subscribe to session rotation ongoing notifications"); |
| 124 | if (notification_channel_status != |
| 125 | LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { |
| 126 | ret = -1; |
| 127 | goto end; |
| 128 | } |
| 129 | notification_channel_status = lttng_notification_channel_subscribe( |
| 130 | notification_channel, rotation_completed_condition); |
| 131 | ok(notification_channel_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, |
| 132 | "Subscribe to session rotation completed notifications"); |
| 133 | if (notification_channel_status != |
| 134 | LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { |
| 135 | ret = -1; |
| 136 | goto end; |
| 137 | } |
| 138 | |
| 139 | /* Create rotation ongoing and completed triggers. */ |
| 140 | rotation_ongoing_trigger = lttng_trigger_create( |
| 141 | rotation_ongoing_condition, notify); |
| 142 | ok(rotation_ongoing_trigger, "Create a rotation ongoing notification trigger"); |
| 143 | if (!rotation_ongoing_trigger) { |
| 144 | ret = -1; |
| 145 | goto end; |
| 146 | } |
| 147 | |
| 148 | rotation_completed_trigger = lttng_trigger_create( |
| 149 | rotation_completed_condition, notify); |
| 150 | ok(rotation_completed_trigger, "Create a rotation completed notification trigger"); |
| 151 | if (!rotation_completed_trigger) { |
| 152 | ret = -1; |
| 153 | goto end; |
| 154 | } |
| 155 | |
| 156 | /* Register rotation ongoing and completed triggers. */ |
| 157 | ret_code = lttng_register_trigger_with_automatic_name( |
| 158 | rotation_ongoing_trigger); |
| 159 | ok(ret_code == LTTNG_OK, "Registered session rotation ongoing trigger"); |
| 160 | if (ret_code != LTTNG_OK) { |
| 161 | ret = -ret_code; |
| 162 | goto end; |
| 163 | } |
| 164 | |
| 165 | ret_code = lttng_register_trigger_with_automatic_name( |
| 166 | rotation_completed_trigger); |
| 167 | ok(ret_code == LTTNG_OK, |
| 168 | "Registered session rotation completed trigger"); |
| 169 | if (ret_code != LTTNG_OK) { |
| 170 | ret = -ret_code; |
| 171 | goto end; |
| 172 | } |
| 173 | |
| 174 | end: |
| 175 | lttng_trigger_destroy(rotation_ongoing_trigger); |
| 176 | lttng_trigger_destroy(rotation_completed_trigger); |
| 177 | lttng_condition_destroy(rotation_ongoing_condition); |
| 178 | lttng_condition_destroy(rotation_completed_condition); |
| 179 | lttng_action_destroy(notify); |
| 180 | return ret; |
| 181 | } |
| 182 | |
| 183 | static |
| 184 | int test_notification( |
| 185 | struct lttng_notification_channel *notification_channel, |
| 186 | const struct session *session, |
| 187 | const char *expected_notification_type_name, |
| 188 | enum lttng_condition_type expected_condition_type) |
| 189 | { |
| 190 | int ret = 0; |
| 191 | bool notification_pending; |
| 192 | enum lttng_notification_channel_status notification_channel_status; |
| 193 | enum lttng_condition_status condition_status; |
| 194 | enum lttng_evaluation_status evaluation_status; |
| 195 | enum lttng_trace_archive_location_status location_status; |
| 196 | enum lttng_condition_type condition_type; |
| 197 | struct lttng_notification *notification = NULL; |
| 198 | const struct lttng_condition *condition; |
| 199 | const struct lttng_evaluation *evaluation; |
| 200 | const char *session_name = NULL; |
| 201 | const struct lttng_trace_archive_location *location = NULL; |
| 202 | uint64_t rotation_id = UINT64_MAX; |
| 203 | const char *chunk_path = NULL; |
| 204 | |
| 205 | notification_channel_status = lttng_notification_channel_has_pending_notification( |
| 206 | notification_channel, ¬ification_pending); |
| 207 | ok(notification_channel_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, |
| 208 | "Check for %s notification pending on notification channel", |
| 209 | expected_notification_type_name); |
| 210 | if (notification_channel_status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { |
| 211 | ret = -1; |
| 212 | goto end; |
| 213 | } |
| 214 | |
| 215 | ok(notification_pending, |
| 216 | "Session %s notification is pending on notification channel", |
| 217 | expected_notification_type_name); |
| 218 | if (!notification_pending) { |
| 219 | ret = -1; |
| 220 | goto end; |
| 221 | } |
| 222 | |
| 223 | notification_channel_status = lttng_notification_channel_get_next_notification( |
| 224 | notification_channel, ¬ification); |
| 225 | ok(notification_channel_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification, |
| 226 | "Get %s notification from notification channel", |
| 227 | expected_notification_type_name); |
| 228 | if (notification_channel_status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK || !notification) { |
| 229 | ret = -1; |
| 230 | goto end; |
| 231 | } |
| 232 | |
| 233 | condition = lttng_notification_get_condition(notification); |
| 234 | if (!condition) { |
| 235 | diag("Failed to get notification condition"); |
| 236 | ret = -1; |
| 237 | goto end; |
| 238 | } |
| 239 | |
| 240 | condition_type = lttng_condition_get_type(condition); |
| 241 | ok(condition_type == expected_condition_type, |
| 242 | "Notification condition obtained from notification channel is of type \"%s\"", |
| 243 | expected_notification_type_name); |
| 244 | if (condition_type != expected_condition_type) { |
| 245 | ret = -1; |
| 246 | goto end; |
| 247 | } |
| 248 | |
| 249 | condition_status = lttng_condition_session_rotation_get_session_name( |
| 250 | condition, &session_name); |
| 251 | ok(condition_status == LTTNG_CONDITION_STATUS_OK && session_name && |
| 252 | !strcmp(session_name, session->name), |
| 253 | "Condition obtained from notification has the correct session name assigned"); |
| 254 | if (condition_status != LTTNG_CONDITION_STATUS_OK || !session_name) { |
| 255 | ret = -1; |
| 256 | goto end; |
| 257 | } |
| 258 | |
| 259 | evaluation = lttng_notification_get_evaluation(notification); |
| 260 | if (!evaluation) { |
| 261 | diag("Failed to get notification evaluation"); |
| 262 | ret = -1; |
| 263 | goto end; |
| 264 | } |
| 265 | condition_type = lttng_evaluation_get_type(evaluation); |
| 266 | ok(condition_type == expected_condition_type, |
| 267 | "Condition evaluation obtained from notification channel is of type \"%s\"", |
| 268 | expected_notification_type_name); |
| 269 | if (condition_type != expected_condition_type) { |
| 270 | ret = -1; |
| 271 | goto end; |
| 272 | } |
| 273 | |
| 274 | evaluation_status = lttng_evaluation_session_rotation_get_id(evaluation, |
| 275 | &rotation_id); |
| 276 | ok(evaluation_status == LTTNG_EVALUATION_STATUS_OK, |
| 277 | "Get %s id from notification evaluation", |
| 278 | expected_notification_type_name); |
| 279 | if (evaluation_status != LTTNG_EVALUATION_STATUS_OK) { |
| 280 | ret = -1; |
| 281 | goto end; |
| 282 | } |
| 283 | |
| 284 | if (expected_condition_type != LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED) { |
| 285 | /* |
| 286 | * Remaining tests only apply to "session rotation completed" |
| 287 | * notifications. |
| 288 | */ |
| 289 | goto end; |
| 290 | } |
| 291 | |
| 292 | evaluation_status = lttng_evaluation_session_rotation_completed_get_location( |
| 293 | evaluation, &location); |
| 294 | ok(evaluation_status == LTTNG_EVALUATION_STATUS_OK && location, |
| 295 | "Get session %s chunk location from evaluation", |
| 296 | expected_notification_type_name); |
| 297 | if (evaluation_status != LTTNG_EVALUATION_STATUS_OK || !location) { |
| 298 | ret = -1; |
| 299 | goto end; |
| 300 | } |
| 301 | |
| 302 | ok(lttng_trace_archive_location_get_type(location) == LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_LOCAL, |
| 303 | "Location returned from the session rotation completed notification is of type 'local'"); |
| 304 | |
| 305 | location_status = lttng_trace_archive_location_local_get_absolute_path( |
| 306 | location, &chunk_path); |
| 307 | ok(location_status == LTTNG_TRACE_ARCHIVE_LOCATION_STATUS_OK && chunk_path, |
| 308 | "Retrieved path from location returned by the session rotation completed notification"); |
| 309 | diag("Chunk available at %s", chunk_path ? chunk_path : "NULL"); |
| 310 | |
| 311 | ok(chunk_path && !strncmp(session->output_path, chunk_path, strlen(session->output_path)), |
| 312 | "Returned path from location starts with the output path"); |
| 313 | |
| 314 | end: |
| 315 | lttng_notification_destroy(notification); |
| 316 | return ret; |
| 317 | } |
| 318 | |
| 319 | static |
| 320 | int test_rotation_ongoing_notification( |
| 321 | struct lttng_notification_channel *notification_channel, |
| 322 | struct session *session) |
| 323 | { |
| 324 | return test_notification(notification_channel, session, |
| 325 | "rotation ongoing", |
| 326 | LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING); |
| 327 | } |
| 328 | |
| 329 | static |
| 330 | int test_rotation_completed_notification( |
| 331 | struct lttng_notification_channel *notification_channel, |
| 332 | struct session *session) |
| 333 | { |
| 334 | return test_notification(notification_channel, session, |
| 335 | "rotation completed", |
| 336 | LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED); |
| 337 | } |
| 338 | |
| 339 | int main(int argc, const char *argv[]) |
| 340 | { |
| 341 | int ret = 0; |
| 342 | struct session session = { 0 }; |
| 343 | struct lttng_notification_channel *notification_channel = NULL; |
| 344 | struct lttng_rotation_handle *rotation_handle = NULL; |
| 345 | enum lttng_rotation_status rotation_status; |
| 346 | enum lttng_rotation_state rotation_state = |
| 347 | LTTNG_ROTATION_STATE_NO_ROTATION; |
| 348 | |
| 349 | if (argc != 3) { |
| 350 | puts("Usage: rotation SESSION_NAME SESSION_OUTPUT_PATH"); |
| 351 | ret = 1; |
| 352 | goto error; |
| 353 | } |
| 354 | |
| 355 | session.name = argv[1]; |
| 356 | session.output_path = argv[2]; |
| 357 | |
| 358 | plan_tests(TEST_COUNT); |
| 359 | |
| 360 | notification_channel = lttng_notification_channel_create( |
| 361 | lttng_session_daemon_notification_endpoint); |
| 362 | if (!notification_channel) { |
| 363 | diag("Failed to create notification channel"); |
| 364 | ret = -1; |
| 365 | goto error; |
| 366 | } |
| 367 | |
| 368 | ret = setup_rotation_trigger(&session, notification_channel); |
| 369 | if (ret) { |
| 370 | goto error; |
| 371 | } |
| 372 | |
| 373 | /* Start rotation and wait for its completion. */ |
| 374 | ret = lttng_rotate_session(session.name, NULL, &rotation_handle); |
| 375 | ok(ret >= 0 && rotation_handle, "Start rotation of session \"%s\"", |
| 376 | session.name); |
| 377 | if (ret < 0 || !rotation_handle) { |
| 378 | goto error; |
| 379 | } |
| 380 | |
| 381 | do { |
| 382 | rotation_status = lttng_rotation_handle_get_state( |
| 383 | rotation_handle, &rotation_state); |
| 384 | } while (rotation_state == LTTNG_ROTATION_STATE_ONGOING && |
| 385 | rotation_status == LTTNG_ROTATION_STATUS_OK); |
| 386 | ok(rotation_status == LTTNG_ROTATION_STATUS_OK && |
| 387 | rotation_state == LTTNG_ROTATION_STATE_COMPLETED, |
| 388 | "Complete rotation of session \"%s\"", session.name); |
| 389 | |
| 390 | /* |
| 391 | * After a rotation has completed, we can expect two notifications to |
| 392 | * be queued: |
| 393 | * - Session rotation ongoing |
| 394 | * - Session rotation completed |
| 395 | */ |
| 396 | ret = test_rotation_ongoing_notification(notification_channel, |
| 397 | &session); |
| 398 | if (ret) { |
| 399 | goto error; |
| 400 | } |
| 401 | |
| 402 | ret = test_rotation_completed_notification(notification_channel, |
| 403 | &session); |
| 404 | if (ret) { |
| 405 | goto error; |
| 406 | } |
| 407 | error: |
| 408 | lttng_notification_channel_destroy(notification_channel); |
| 409 | lttng_rotation_handle_destroy(rotation_handle); |
| 410 | return exit_status(); |
| 411 | } |