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