From 29305898f2d17c23321b61d411b2f515d64b225f Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Tue, 17 Oct 2023 11:48:16 -0700 Subject: [PATCH] Tests and API for discovering info or preconfigured info about the current running instance or compute environment. --- include/aws/s3/s3.h | 73 +--- include/aws/s3/s3_platform_info.h | 107 +++++ samples/s3/main.c | 11 +- samples/s3/s3-cp.c | 5 + samples/s3/s3-ls.c | 5 + samples/s3/s3_compute_platform_info.c | 119 +++++ source/s3.c | 302 +------------ source/s3_platform_info.c | 600 ++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/s3_compute_platform_info_test.c | 47 +- 10 files changed, 896 insertions(+), 374 deletions(-) create mode 100644 include/aws/s3/s3_platform_info.h create mode 100644 samples/s3/s3_compute_platform_info.c create mode 100644 source/s3_platform_info.c diff --git a/include/aws/s3/s3.h b/include/aws/s3/s3.h index c5a9716f6..43a057e25 100644 --- a/include/aws/s3/s3.h +++ b/include/aws/s3/s3.h @@ -53,37 +53,7 @@ enum aws_s3_subject { AWS_LS_S3_LAST = AWS_LOG_SUBJECT_END_RANGE(AWS_C_S3_PACKAGE_ID) }; -struct aws_s3_cpu_group_info { - /* group index, this usually refers to a particular numa node */ - uint16_t cpu_group; - /* array of network devices on this node */ - const struct aws_byte_cursor *nic_name_array; - /* length of network devices array */ - size_t nic_name_array_length; -}; - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4626) /* assignment operator was implicitly defined as deleted */ -# pragma warning(disable : 5027) /* move assignment operator was implicitly defined as deleted */ -#endif - -struct aws_s3_compute_platform_info { - /* name of the instance-type: example c5n.18xlarge */ - const struct aws_byte_cursor instance_type; - /* max throughput for this instance type */ - uint16_t max_throughput_gbps; - /* array of cpu group info. This will always have at least one entry. */ - const struct aws_s3_cpu_group_info *cpu_group_info_array; - /* length of cpu group info array */ - size_t cpu_group_info_array_length; -}; - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - -struct aws_system_environment; +struct aws_s3_compute_platform_info; AWS_EXTERN_C_BEGIN @@ -94,47 +64,8 @@ AWS_EXTERN_C_BEGIN AWS_S3_API void aws_s3_library_init(struct aws_allocator *allocator); -/** - * Retrieves the pre-configured metadata for an ec2 instance type. If no such pre-configuration exists, returns NULL. - */ -AWS_S3_API -struct aws_s3_compute_platform_info *aws_s3_get_compute_platform_info_for_instance_type( - struct aws_byte_cursor instance_type_name); - -/** - * Returns true if this build of this library has been pre-optimized for the environment this process is running in. - * - * Note: This does not mean this library has not been generically optimized the way most applications are. It means this - * build of the application was specifically optimized for the current environment with a known optimal configuration. - * - * @return true if it is optimized, false if it is not. - */ -AWS_S3_API -bool aws_is_build_optimized_for_environment(void); - -/** - * Returns true if the current process is running on an Amazon EC2 instance powered by Nitro. - */ -AWS_S3_API -bool aws_s3_is_running_on_ec2_nitro(void); - -/** - * Returns an EC2 instance type assuming this executable is running on Amazon EC2 powered by nitro. - * - * First this function will check it's running on EC2 via. attempting to read DMI info to avoid making IMDS calls. - * - * If the function detects it's on EC2, and it was able to detect the instance type without a call to IMDS - * it will return it. - * - * Finally, it will call IMDS and return the instance type from there. - * - * Note that in the case of the IMDS call, a new client stack is spun up using 1 background thread. The call is made - * synchronously with a 1 second timeout: It's not cheap. To make this easier, the underlying result is cached internally - * and will be freed when aws_s3_library_clean_up() is called. - * @return byte_cursor containing the instance type. If this is empty, the instance type could not be determined. - */ AWS_S3_API -struct aws_byte_cursor aws_s3_get_ec2_instance_type(void); +const struct aws_s3_compute_platform_info *aws_s3_current_compute_platform_info(void); /** * Shuts down the internal datastructures used by aws-c-s3. diff --git a/include/aws/s3/s3_platform_info.h b/include/aws/s3/s3_platform_info.h new file mode 100644 index 000000000..1f17be302 --- /dev/null +++ b/include/aws/s3/s3_platform_info.h @@ -0,0 +1,107 @@ +#ifndef AWS_S3_S3_PLATFORM_INFO_H +#define AWS_S3_S3_PLATFORM_INFO_H +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include + +struct aws_s3_cpu_group_info { + /* group index, this usually refers to a particular numa node */ + uint16_t cpu_group; + /* array of network devices on this node */ + const struct aws_byte_cursor *nic_name_array; + /* length of network devices array */ + size_t nic_name_array_length; + size_t cpus_in_group; +}; + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4626) /* assignment operator was implicitly defined as deleted */ +# pragma warning(disable : 5027) /* move assignment operator was implicitly defined as deleted */ +#endif + +struct aws_s3_compute_platform_info { + /* name of the instance-type: example c5n.18xlarge */ + struct aws_byte_cursor instance_type; + /* max throughput for this instance type */ + uint16_t max_throughput_gbps; + /* array of cpu group info. This will always have at least one entry. */ + struct aws_s3_cpu_group_info *cpu_group_info_array; + /* length of cpu group info array */ + size_t cpu_group_info_array_length; + + /* The current build of this library specifically knows an optimal configuration for this + * platform */ + bool has_recommended_configuration; +}; + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +struct aws_s3_compute_platform_info_loader; + +AWS_EXTERN_C_BEGIN + +/** + * Initializes and returns a loader for querying the compute platform for information needed for making configuration + * decisions. + * + * Returns NULL if an unrecoverable error occurs. + */ +AWS_S3_API +struct aws_s3_compute_platform_info_loader *aws_s3_compute_platform_info_loader_new(struct aws_allocator *allocator); + +AWS_S3_API +void aws_s3_compute_platform_info_loader_acquire(struct aws_s3_compute_platform_info_loader *loader); + +AWS_S3_API +void aws_s3_compute_platform_info_loader_release(struct aws_s3_compute_platform_info_loader *loader); + +/** + * Retrieves the pre-configured metadata for a given ec2 instance type. If no such pre-configuration exists, returns + * NULL. + */ +AWS_S3_API +const struct aws_s3_compute_platform_info *aws_s3_get_compute_platform_info_for_instance_type( + struct aws_s3_compute_platform_info_loader *loader, + struct aws_byte_cursor instance_type_name); + +/** + * Retrieves the metadata for the current environment. If EC2 instance type is unknown, or it is not an EC2 instance at + * all, this value will still include the information about the system that could be determined. This value will never + * be NULL. + */ +AWS_S3_API +const struct aws_s3_compute_platform_info *aws_s3_get_compute_platform_info_for_current_environment( + struct aws_s3_compute_platform_info_loader *loader); + +/** + * Returns true if the current process is running on an Amazon EC2 instance powered by Nitro. + */ +AWS_S3_API +bool aws_s3_is_running_on_ec2_nitro(struct aws_s3_compute_platform_info_loader *loader); + +/** + * Returns an EC2 instance type assuming this executable is running on Amazon EC2 powered by nitro. + * + * First this function will check it's running on EC2 via. attempting to read DMI info to avoid making IMDS calls. + * + * If the function detects it's on EC2, and it was able to detect the instance type without a call to IMDS + * it will return it. + * + * Finally, it will call IMDS and return the instance type from there. + * + * Note that in the case of the IMDS call, a new client stack is spun up using 1 background thread. The call is made + * synchronously with a 1 second timeout: It's not cheap. To make this easier, the underlying result is cached + * internally and will be freed when aws_s3_library_clean_up() is called. + * @return byte_cursor containing the instance type. If this is empty, the instance type could not be determined. + */ +AWS_S3_API +struct aws_byte_cursor aws_s3_get_ec2_instance_type(struct aws_s3_compute_platform_info_loader *loader); + +AWS_EXTERN_C_END + +#endif /* AWS_S3_S3_PLATFORM_INFO_H */ diff --git a/samples/s3/main.c b/samples/s3/main.c index c738ddd88..0ce43a6c4 100644 --- a/samples/s3/main.c +++ b/samples/s3/main.c @@ -19,6 +19,7 @@ int s3_ls_main(int argc, char *const argv[], const char *command_name, void *user_data); int s3_cp_main(int argc, char *const argv[], const char *command_name, void *user_data); +int s3_compute_platform_info_main(int argc, char *const argv[], const char *command_name, void *user_data); static struct aws_cli_subcommand_dispatch s_dispatch_table[] = { { @@ -29,7 +30,10 @@ static struct aws_cli_subcommand_dispatch s_dispatch_table[] = { .command_name = "cp", .subcommand_fn = s3_cp_main, }, -}; + { + .command_name = "platform-info", + .subcommand_fn = s3_compute_platform_info_main, + }}; static void s_usage(int exit_code) { @@ -102,11 +106,6 @@ static void s_parse_app_ctx(int argc, char *const argv[], struct app_ctx *app_ct } if (!app_ctx->help_requested) { - if (!app_ctx->region) { - fprintf(stderr, "region is a required argument\n"); - s_usage(1); - } - if (app_ctx->log_level != AWS_LOG_LEVEL_NONE) { s_setup_logger(app_ctx); } diff --git a/samples/s3/s3-cp.c b/samples/s3/s3-cp.c index 7cf79f6f0..5ab5e2b6b 100644 --- a/samples/s3/s3-cp.c +++ b/samples/s3/s3-cp.c @@ -122,6 +122,11 @@ int s3_cp_main(int argc, char *argv[], const char *command_name, void *user_data s_usage(0); } + if (!app_ctx->region) { + fprintf(stderr, "region is a required argument\n"); + s_usage(1); + } + struct cp_app_ctx cp_app_ctx = { .app_ctx = app_ctx, .mutex = AWS_MUTEX_INIT, diff --git a/samples/s3/s3-ls.c b/samples/s3/s3-ls.c index eb5fae1a4..c36fc3beb 100644 --- a/samples/s3/s3-ls.c +++ b/samples/s3/s3-ls.c @@ -138,6 +138,11 @@ int s3_ls_main(int argc, char *argv[], const char *command_name, void *user_data s_usage(0); } + if (!app_ctx->region) { + fprintf(stderr, "region is a required argument\n"); + s_usage(1); + } + struct s3_ls_app_data impl_data = { .app_ctx = app_ctx, .mutex = AWS_MUTEX_INIT, diff --git a/samples/s3/s3_compute_platform_info.c b/samples/s3/s3_compute_platform_info.c new file mode 100644 index 000000000..8e2abeeff --- /dev/null +++ b/samples/s3/s3_compute_platform_info.c @@ -0,0 +1,119 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include + +#include "app_ctx.h" + +struct s3_compute_platform_ctx { + struct app_ctx *app_ctx; + struct aws_byte_cursor instance_type; +}; + +static void s_usage(int exit_code) { + FILE *output = exit_code == 0 ? stdout : stderr; + fprintf(output, "usage: s3 platform-info [options]\n"); + fprintf( + output, + " -instance-type, (optional) Instance type to look up configuration for, if not set it will be the current " + "executing environment. \n"); + fprintf(output, " -h, --help\n"); + fprintf(output, " Display this message and quit.\n"); + exit(exit_code); +} + +static struct aws_cli_option s_long_options[] = { + {"instance-type", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'i'}, + /* Per getopt(3) the last element of the array has to be filled with all zeros */ + {NULL, AWS_CLI_OPTIONS_NO_ARGUMENT, NULL, 0}, +}; + +static void s_parse_options(int argc, char **argv, struct s3_compute_platform_ctx *ctx) { + int option_index = 0; + + int opt_val = 0; + do { + opt_val = aws_cli_getopt_long(argc, argv, "i:", s_long_options, &option_index); + /* START_OF_TEXT means our positional argument */ + if (opt_val == 'i') { + ctx->instance_type = aws_byte_cursor_from_c_str(aws_cli_optarg); + } + } while (opt_val != -1); +} + +int s3_compute_platform_info_main(int argc, char *argv[], const char *command_name, void *user_data) { + (void)command_name; + + struct app_ctx *app_ctx = user_data; + + if (app_ctx->help_requested) { + s_usage(0); + } + + struct s3_compute_platform_ctx compute_platform_app_ctx = { + .app_ctx = app_ctx, + }; + app_ctx->sub_command_data = &compute_platform_app_ctx; + + s_parse_options(argc, argv, &compute_platform_app_ctx); + + struct aws_s3_compute_platform_info_loader *loader = aws_s3_compute_platform_info_loader_new(app_ctx->allocator); + if (!loader) { + fprintf(stderr, "failed to load configuration info with error %s", aws_error_debug_str(aws_last_error())); + exit(-1); + } + + const struct aws_s3_compute_platform_info *platform_info = aws_s3_current_compute_platform_info(); + + if (compute_platform_app_ctx.instance_type.len) { + platform_info = + aws_s3_get_compute_platform_info_for_instance_type(loader, compute_platform_app_ctx.instance_type); + if (!platform_info) { + fprintf( + stderr, + "unknown instance type \"" PRInSTR "\"", + AWS_BYTE_CURSOR_PRI(compute_platform_app_ctx.instance_type)); + exit(-1); + } + } + fprintf(stdout, "{\n"); + fprintf(stdout, "\t'instance_type': '" PRInSTR "',\n", AWS_BYTE_CURSOR_PRI(platform_info->instance_type)); + fprintf(stdout, "\t'max_throughput_gbps': %d,\n", (int)platform_info->max_throughput_gbps); + fprintf( + stdout, + "\t'has_recommended_configuration': %s,\n", + platform_info->has_recommended_configuration ? "true" : "false"); + + fprintf(stdout, "\t'cpu_groups': [\n"); + + for (size_t i = 0; i < platform_info->cpu_group_info_array_length; ++i) { + fprintf(stdout, "\t{\n"); + fprintf(stdout, "\t\t'cpu_group_index': %d,\n", (int)platform_info->cpu_group_info_array[i].cpu_group); + fprintf(stdout, "\t\t'cpus_in_group': %d,\n", (int)platform_info->cpu_group_info_array[i].cpus_in_group); + fprintf(stdout, "\t\t'usable_network_devices': [\n"); + + for (size_t j = 0; j < platform_info->cpu_group_info_array[i].nic_name_array_length; j++) { + fprintf( + stdout, + "\t\t\t'" PRInSTR "'", + AWS_BYTE_CURSOR_PRI(platform_info->cpu_group_info_array[i].nic_name_array[j])); + if (j < platform_info->cpu_group_info_array[i].nic_name_array_length - 1) { + fprintf(stdout, ","); + } + fprintf(stdout, "\n"); + } + fprintf(stdout, "\t\t]\n"); + fprintf(stdout, "\t}"); + if (i < platform_info->cpu_group_info_array_length - 1) { + fprintf(stdout, ","); + } + fprintf(stdout, "\n"); + } + fprintf(stdout, "\t]\n"); + fprintf(stdout, "}"); + + return 0; +} diff --git a/source/s3.c b/source/s3.c index 584fc2cbc..07a960c42 100644 --- a/source/s3.c +++ b/source/s3.c @@ -5,17 +5,11 @@ #include +#include "aws/s3/s3_platform_info.h" #include -#include -#include -#include #include #include -#include -#include #include -#include -#include #define AWS_DEFINE_ERROR_INFO_S3(CODE, STR) AWS_DEFINE_ERROR_INFO(CODE, STR, "aws-c-s3") @@ -74,44 +68,9 @@ static struct aws_log_subject_info_list s_s3_log_subject_list = { .count = AWS_ARRAY_SIZE(s_s3_log_subject_infos), }; -/**** Configuration info for the c5n.18xlarge *****/ -static struct aws_byte_cursor s_c5n_18xlarge_nic_array[] = {AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth0")}; - -static struct aws_s3_cpu_group_info s_c5n_18xlarge_cpu_group_info_array[] = { - { - .cpu_group = 0u, - .nic_name_array = s_c5n_18xlarge_nic_array, - .nic_name_array_length = AWS_ARRAY_SIZE(s_c5n_18xlarge_nic_array), - }, - { - .cpu_group = 1u, - .nic_name_array = NULL, - .nic_name_array_length = 0u, - }, -}; - -static struct aws_s3_compute_platform_info s_c5n_18xlarge_platform_info = { - .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("c5n.18xlarge"), - .max_throughput_gbps = 100u, - .cpu_group_info_array = s_c5n_18xlarge_cpu_group_info_array, - .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_c5n_18xlarge_cpu_group_info_array), -}; -/****** End c5n.18xlarge *****/ - -static struct aws_hash_table s_compute_platform_info_table; - static bool s_library_initialized = false; static struct aws_allocator *s_library_allocator = NULL; - -static struct aws_string *s_detected_instance_type = NULL; -static struct aws_mutex s_detected_instance_type_mutex = AWS_MUTEX_INIT; -static struct aws_system_environment *s_env = NULL; - -static struct aws_byte_cursor s_instance_type_allow_list[] = { - AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("p4d"), - AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("p5"), - AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("trn1"), -}; +static struct aws_s3_compute_platform_info_loader *s_loader; void aws_s3_library_init(struct aws_allocator *allocator) { if (s_library_initialized) { @@ -129,271 +88,28 @@ void aws_s3_library_init(struct aws_allocator *allocator) { aws_register_error_info(&s_error_list); aws_register_log_subject_info_list(&s_s3_log_subject_list); - - AWS_FATAL_ASSERT( - !aws_hash_table_init( - &s_compute_platform_info_table, - allocator, - 32, - aws_hash_byte_cursor_ptr_ignore_case, - (bool (*)(const void *, const void *))aws_byte_cursor_eq_ignore_case, - NULL, - NULL) && - "Hash table init failed!"); - - AWS_FATAL_ASSERT( - !aws_hash_table_put( - &s_compute_platform_info_table, - &s_c5n_18xlarge_platform_info.instance_type, - &s_c5n_18xlarge_platform_info, - NULL) && - "hash table put failed!"); - - s_env = aws_system_environment_load(allocator); - AWS_FATAL_ASSERT(s_env && "Environment detection failed!"); + s_loader = aws_s3_compute_platform_info_loader_new(allocator); s_library_initialized = true; } +const struct aws_s3_compute_platform_info *aws_s3_current_compute_platform_info(void) { + return aws_s3_get_compute_platform_info_for_current_environment(s_loader); +} + void aws_s3_library_clean_up(void) { if (!s_library_initialized) { return; } s_library_initialized = false; - - if (s_detected_instance_type) { - aws_string_destroy(s_detected_instance_type); - s_detected_instance_type = NULL; - } - - aws_system_environment_destroy(s_env); + aws_s3_compute_platform_info_loader_release(s_loader); + s_loader = NULL; aws_thread_join_all_managed(); - aws_hash_table_clean_up(&s_compute_platform_info_table); aws_unregister_log_subject_info_list(&s_s3_log_subject_list); aws_unregister_error_info(&s_error_list); aws_http_library_clean_up(); aws_auth_library_clean_up(); s_library_allocator = NULL; } - -struct aws_s3_compute_platform_info *aws_s3_get_compute_platform_info_for_instance_type( - const struct aws_byte_cursor instance_type_name) { - AWS_LOGF_TRACE( - AWS_LS_S3_GENERAL, - "static: looking up compute platform info for instance type " PRInSTR, - AWS_BYTE_CURSOR_PRI(instance_type_name)); - - struct aws_hash_element *platform_info_element = NULL; - aws_hash_table_find(&s_compute_platform_info_table, &instance_type_name, &platform_info_element); - - if (platform_info_element) { - AWS_LOGF_INFO( - AWS_LS_S3_GENERAL, - "static: found compute platform info for instance type " PRInSTR, - AWS_BYTE_CURSOR_PRI(instance_type_name)); - return platform_info_element->value; - } - - AWS_LOGF_INFO( - AWS_LS_S3_GENERAL, - "static: compute platform info for instance type " PRInSTR " not found", - AWS_BYTE_CURSOR_PRI(instance_type_name)); - return NULL; -} - -bool aws_is_build_optimized_for_environment(void) { - struct aws_byte_cursor instance_type = aws_s3_get_ec2_instance_type(); - - for (size_t i = 0; i < AWS_ARRAY_SIZE(s_instance_type_allow_list); ++i) { - if (aws_byte_cursor_starts_with_ignore_case(&instance_type, &s_instance_type_allow_list[i])) { - return true; - } - } - - return false; -} - -struct imds_callback_info { - struct aws_allocator *allocator; - struct aws_string *instance_type; - struct aws_condition_variable c_var; - int error_code; - bool shutdown_completed; - struct aws_mutex mutex; -}; - -static void s_imds_client_shutdown_completed(void *user_data) { - struct imds_callback_info *info = user_data; - aws_mutex_lock(&info->mutex); - info->shutdown_completed = true; - aws_mutex_unlock(&info->mutex); - aws_condition_variable_notify_all(&info->c_var); -} - -static bool s_client_shutdown_predicate(void *arg) { - struct imds_callback_info *info = arg; - return info->shutdown_completed; -} - -static void s_imds_client_on_get_instance_info_callback( - const struct aws_imds_instance_info *instance_info, - int error_code, - void *user_data) { - struct imds_callback_info *info = user_data; - - aws_mutex_lock(&info->mutex); - if (error_code) { - info->error_code = error_code; - } else { - info->instance_type = aws_string_new_from_cursor(info->allocator, &instance_info->instance_type); - } - aws_mutex_unlock(&info->mutex); - aws_condition_variable_notify_all(&info->c_var); -} - -static bool s_completion_predicate(void *arg) { - struct imds_callback_info *info = arg; - return info->error_code != 0 || info->instance_type != NULL; -} - -struct aws_byte_cursor aws_s3_get_ec2_instance_type(void) { - - struct aws_byte_cursor return_cur; - AWS_ZERO_STRUCT(return_cur); - - aws_mutex_lock(&s_detected_instance_type_mutex); - if (s_detected_instance_type) { - AWS_LOGF_TRACE(AWS_LS_S3_CLIENT, - "static: Instance type has already been determined to be %s. Returning cached version.", - aws_string_bytes(s_detected_instance_type)); - goto return_instance_and_unlock; - } - - AWS_LOGF_TRACE(AWS_LS_S3_CLIENT, - "static: Instance type has not been determined, checking to see if running in EC2 nitro environment."); - - if (aws_s3_is_running_on_ec2_nitro()) { - AWS_LOGF_INFO(AWS_LS_S3_CLIENT, "static: Detected Amazon EC2 with nitro as the current environment."); - /* easy case not requiring any calls out to IMDS. If we detected we're running on ec2, then the dmi info is - * correct, and we can use it if we have it. Otherwise call out to IMDS. */ - struct aws_byte_cursor product_name = aws_system_environment_get_virtualization_product_name(s_env); - - if (product_name.len) { - s_detected_instance_type = aws_string_new_from_cursor(s_library_allocator, &product_name); - AWS_LOGF_INFO(AWS_LS_S3_CLIENT, "static: Determined instance type to be %s, from dmi info. Caching.", - aws_string_bytes(s_detected_instance_type)); - goto return_instance_and_unlock; - } - - AWS_LOGF_DEBUG(AWS_LS_S3_CLIENT, "static: DMI info was insufficient to determine instance type. Making call to IMDS to determine"); - struct imds_callback_info callback_info = { - .mutex = AWS_MUTEX_INIT, - .c_var = AWS_CONDITION_VARIABLE_INIT, - .allocator = s_library_allocator, - }; - - struct aws_event_loop_group *el_group = NULL; - struct aws_host_resolver *resolver = NULL; - struct aws_client_bootstrap *client_bootstrap = NULL; - /* now call IMDS */ - el_group = aws_event_loop_group_new_default(s_library_allocator, 1, NULL); - - if (!el_group) { - goto tear_down; - } - - struct aws_host_resolver_default_options resolver_options = { - .max_entries = 1, - .el_group = el_group, - }; - - resolver = aws_host_resolver_new_default(s_library_allocator, &resolver_options); - - if (!resolver) { - goto tear_down; - } - - struct aws_client_bootstrap_options bootstrap_options = { - .event_loop_group = el_group, - .host_resolver = resolver, - }; - - client_bootstrap = aws_client_bootstrap_new(s_library_allocator, &bootstrap_options); - - if (!client_bootstrap) { - goto tear_down; - } - - struct aws_imds_client_shutdown_options imds_shutdown_options = { - .shutdown_callback = s_imds_client_shutdown_completed, - .shutdown_user_data = &callback_info, - }; - - struct aws_imds_client_options imds_options = { - .bootstrap = client_bootstrap, - .imds_version = IMDS_PROTOCOL_V2, - .shutdown_options = imds_shutdown_options, - }; - - struct aws_imds_client *imds_client = aws_imds_client_new(s_library_allocator, &imds_options); - - if (!imds_client) { - goto tear_down; - } - - aws_mutex_lock(&callback_info.mutex); - aws_imds_client_get_instance_info(imds_client, s_imds_client_on_get_instance_info_callback, &callback_info); - aws_condition_variable_wait_for_pred( - &callback_info.c_var, &callback_info.mutex, AWS_TIMESTAMP_SECS, s_completion_predicate, &callback_info); - - aws_condition_variable_wait_pred( - &callback_info.c_var, &callback_info.mutex, s_client_shutdown_predicate, &callback_info); - aws_mutex_unlock(&callback_info.mutex); - aws_imds_client_release(imds_client); - - if (callback_info.error_code) { - aws_raise_error(callback_info.error_code); - AWS_LOGF_ERROR(AWS_LS_S3_CLIENT, "static: IMDS call failed with error %s.", - aws_error_debug_str(callback_info.error_code)); - } - - if (callback_info.instance_type) { - s_detected_instance_type = callback_info.instance_type; - AWS_LOGF_INFO(AWS_LS_S3_CLIENT, "static: Determined instance type to be %s, from IMDS. Caching.", - aws_string_bytes(s_detected_instance_type)); - } - - tear_down: - if (client_bootstrap) { - aws_client_bootstrap_release(client_bootstrap); - } - - if (resolver) { - aws_host_resolver_release(resolver); - } - - if (el_group) { - aws_event_loop_group_release(el_group); - } - } - -return_instance_and_unlock: - if (s_detected_instance_type) { - return_cur = aws_byte_cursor_from_string(s_detected_instance_type); - } - aws_mutex_unlock(&s_detected_instance_type_mutex); - - return return_cur; -} - -bool aws_s3_is_running_on_ec2_nitro(void) { - struct aws_byte_cursor system_virt_name = aws_system_environment_get_virtualization_vendor(s_env); - - if (aws_byte_cursor_eq_c_str_ignore_case(&system_virt_name, "amazon ec2")) { - return true; - } - - return false; -} diff --git a/source/s3_platform_info.c b/source/s3_platform_info.c new file mode 100644 index 000000000..21bc3f5c3 --- /dev/null +++ b/source/s3_platform_info.c @@ -0,0 +1,600 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/**** Configuration info for the c5n.18xlarge *****/ +static struct aws_byte_cursor s_c5n_nic_array[] = {AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth0")}; + +static struct aws_s3_cpu_group_info s_c5n_18xlarge_cpu_group_info_array[] = { + { + .cpu_group = 0u, + .nic_name_array = s_c5n_nic_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_c5n_nic_array), + .cpus_in_group = 36, + }, + { + .cpu_group = 1u, + .nic_name_array = NULL, + .nic_name_array_length = 0u, + .cpus_in_group = 36, + }, +}; + +static struct aws_s3_compute_platform_info s_c5n_18xlarge_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("c5n.18xlarge"), + .max_throughput_gbps = 100u, + .cpu_group_info_array = s_c5n_18xlarge_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_c5n_18xlarge_cpu_group_info_array), + /** not yet **/ + .has_recommended_configuration = false, +}; + +static struct aws_s3_compute_platform_info s_c5n_metal_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("c5n.metal"), + .max_throughput_gbps = 100u, + .cpu_group_info_array = s_c5n_18xlarge_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_c5n_18xlarge_cpu_group_info_array), + /** not yet **/ + .has_recommended_configuration = false, +}; + +/****** End c5n.18xlarge *****/ + +/****** Begin c5n.large ******/ +static struct aws_s3_cpu_group_info s_c5n_9xlarge_cpu_group_info_array[] = { + { + .cpu_group = 0u, + .nic_name_array = s_c5n_nic_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_c5n_nic_array), + .cpus_in_group = 36, + }, +}; + +static struct aws_s3_compute_platform_info s_c5n_9xlarge_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("c5n.9xlarge"), + .max_throughput_gbps = 50u, + .cpu_group_info_array = s_c5n_9xlarge_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_c5n_9xlarge_cpu_group_info_array), + /** not yet **/ + .has_recommended_configuration = false, +}; + +/****** End c5n.9large *****/ + +/***** Begin p4d.24xlarge and p4de.24xlarge ****/ +static struct aws_byte_cursor s_p4d_socket1_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth0"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth1")}; + +static struct aws_byte_cursor s_p4d_socket2_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth2"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth3")}; + +static struct aws_s3_cpu_group_info s_p4d_cpu_group_info_array[] = { + { + .cpu_group = 0u, + .nic_name_array = s_p4d_socket1_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_p4d_socket1_array), + .cpus_in_group = 48, + }, + { + .cpu_group = 1u, + .nic_name_array = s_p4d_socket2_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_p4d_socket1_array), + .cpus_in_group = 48, + }, +}; + +static struct aws_s3_compute_platform_info s_p4d_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("p4d.24xlarge"), + .max_throughput_gbps = 400u, + .cpu_group_info_array = s_p4d_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_p4d_cpu_group_info_array), + .has_recommended_configuration = true, +}; + +static struct aws_s3_compute_platform_info s_p4de_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("p4de.24xlarge"), + .max_throughput_gbps = 400u, + .cpu_group_info_array = s_p4d_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_p4d_cpu_group_info_array), + .has_recommended_configuration = true, +}; + +/***** End p4d.24xlarge and p4de.24xlarge ****/ + +/***** Begin p5.48xlarge ******/ + +/* note: the p5 is a stunningly massive instance type. + * While the specs have 3.2 TB/s for the network bandwidth + * not all of that is accessible from the CPU. From the CPU we'll + * be able to get around 400 Gbps. Also note, 3.2 TB/s + * with 2 sockets on a nitro instance inplies 16 NICs + * per node. However, practically, due to the topology of this instance + * as far as this client is concerned, there are two NICs per node, similar + * to the p4d. The rest is for other things on the machine to use. */ + +struct aws_byte_cursor s_p5_socket1_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth0"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth1"), +}; + +static struct aws_byte_cursor s_p5_socket2_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth2"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth3"), +}; + +static struct aws_s3_cpu_group_info s_p5_cpu_group_info_array[] = { + { + .cpu_group = 0u, + .nic_name_array = s_p5_socket1_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_p5_socket1_array), + .cpus_in_group = 96, + }, + { + .cpu_group = 1u, + .nic_name_array = s_p5_socket2_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_p5_socket2_array), + .cpus_in_group = 96, + }, +}; + +struct aws_s3_compute_platform_info s_p5_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("p5.48xlarge"), + .max_throughput_gbps = 400u, + .cpu_group_info_array = s_p5_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_p5_cpu_group_info_array), + .has_recommended_configuration = true, +}; + +/***** End p5.48xlarge *****/ + +/**** Begin trn1_32_large *****/ +struct aws_byte_cursor s_trn1_n_socket1_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth0"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth1"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth2"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth3"), + +}; + +static struct aws_byte_cursor s_trn1_n_socket2_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth4"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth5"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth6"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth7"), +}; + +static struct aws_s3_cpu_group_info s_trn1_n_cpu_group_info_array[] = { + { + .cpu_group = 0u, + .nic_name_array = s_trn1_n_socket1_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_trn1_n_socket1_array), + .cpus_in_group = 64, + }, + { + .cpu_group = 1u, + .nic_name_array = s_trn1_n_socket2_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_trn1_n_socket2_array), + .cpus_in_group = 64, + }, +}; + +static struct aws_s3_compute_platform_info s_trn1_n_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("trn1n.32xlarge"), + /* not all of the advertised 1600 Gbps bandwidth can be hit from the cpu in user-space */ + .max_throughput_gbps = 800, + .cpu_group_info_array = s_trn1_n_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_trn1_n_cpu_group_info_array), + .has_recommended_configuration = true, +}; + +struct aws_byte_cursor s_trn1_socket1_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth0"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth1"), +}; + +static struct aws_byte_cursor s_trn1_socket2_array[] = { + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth3"), + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("eth4"), +}; + +static struct aws_s3_cpu_group_info s_trn1_cpu_group_info_array[] = { + { + .cpu_group = 0u, + .nic_name_array = s_trn1_socket1_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_trn1_socket1_array), + .cpus_in_group = 64, + }, + { + .cpu_group = 1u, + .nic_name_array = s_trn1_socket2_array, + .nic_name_array_length = AWS_ARRAY_SIZE(s_trn1_socket2_array), + .cpus_in_group = 64, + }, +}; + +static struct aws_s3_compute_platform_info s_trn1_platform_info = { + .instance_type = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("trn1.32xlarge"), + /* not all of the advertised 800 Gbps bandwidth can be hit from the cpu in user-space */ + .max_throughput_gbps = 600, + .cpu_group_info_array = s_trn1_cpu_group_info_array, + .cpu_group_info_array_length = AWS_ARRAY_SIZE(s_trn1_cpu_group_info_array), + .has_recommended_configuration = true, +}; + +/**** End trn1.x32_large ******/ + +struct aws_s3_compute_platform_info_loader { + struct aws_allocator *allocator; + struct aws_ref_count ref_count; + struct { + struct aws_string *detected_instance_type; + struct aws_s3_compute_platform_info current_env_platform_info; + struct aws_hash_table compute_platform_info_table; + struct aws_mutex lock; + } lock_data; + struct aws_system_environment *current_env; +}; + +void s_add_platform_info_to_table( + struct aws_s3_compute_platform_info_loader *loader, + struct aws_s3_compute_platform_info *info) { + AWS_PRECONDITION(info->instance_type.len > 0); + AWS_LOGF_TRACE( + AWS_LS_S3_GENERAL, + "id=%p: adding platform entry for \"" PRInSTR "\".", + (void *)loader, + AWS_BYTE_CURSOR_PRI(info->instance_type)); + + struct aws_hash_element *platform_info_element = NULL; + aws_hash_table_find(&loader->lock_data.compute_platform_info_table, &info->instance_type, &platform_info_element); + if (platform_info_element) { + AWS_LOGF_TRACE( + AWS_LS_S3_GENERAL, + "id=%p: existing entry for \"" PRInSTR "\" found, syncing the values.", + (void *)loader, + AWS_BYTE_CURSOR_PRI(info->instance_type)); + + /* detected runtime NIC data is better than the pre-known config data but we don't always have it, + * so copy over any better info than we have. Assume if info has NIC data, it was discovered at runtime. + * The other data should be identical and we don't want to add complications to the memory model. + * You're guaranteed only one instance of an instance type's info, the initial load is static memory */ + struct aws_s3_compute_platform_info *existing = platform_info_element->value; + AWS_FATAL_ASSERT(info->cpu_group_info_array_length == existing->cpu_group_info_array_length); + + for (size_t i = 0; i < existing->cpu_group_info_array_length; ++i) { + if (info->cpu_group_info_array[i].nic_name_array_length) { + existing->cpu_group_info_array[i].nic_name_array = info->cpu_group_info_array[i].nic_name_array; + + /* intentionally not changing `existing->cpu_group_info_array[i].nic_name_array` value because we + * restrict the amount of known NICs to accommodate + * some hardware weirdness, so we don't want to override any such restrictions. We do... however, + * want to steal the names, and we only need them to be on that node: which they will be, and instead + * are mutating the input to reflect the explicit configuration. */ + info->cpu_group_info_array[i].nic_name_array_length = + existing->cpu_group_info_array[i].nic_name_array_length; + } + } + info->has_recommended_configuration = existing->has_recommended_configuration; + info->max_throughput_gbps = existing->max_throughput_gbps; + } else { + AWS_FATAL_ASSERT( + !aws_hash_table_put( + &loader->lock_data.compute_platform_info_table, &info->instance_type, (void *)info, NULL) && + "hash table put failed!"); + } +} + +static void s_destroy_loader(void *arg) { + struct aws_s3_compute_platform_info_loader *loader = arg; + + aws_hash_table_clean_up(&loader->lock_data.compute_platform_info_table); + aws_mutex_clean_up(&loader->lock_data.lock); + + /* clean up the memory we allocated in init() */ + aws_mem_release(loader->allocator, loader->lock_data.current_env_platform_info.cpu_group_info_array); + + if (loader->lock_data.detected_instance_type) { + aws_string_destroy(loader->lock_data.detected_instance_type); + } + + aws_system_environment_release(loader->current_env); + aws_mem_release(loader->allocator, loader); +} + +struct aws_s3_compute_platform_info_loader *aws_s3_compute_platform_info_loader_new(struct aws_allocator *allocator) { + struct aws_s3_compute_platform_info_loader *loader = + aws_mem_calloc(allocator, 1, sizeof(struct aws_s3_compute_platform_info_loader)); + + loader->allocator = allocator; + loader->current_env = aws_system_environment_load(allocator); + AWS_FATAL_ASSERT(loader->current_env && "Failed to load system environment"); + aws_mutex_init(&loader->lock_data.lock); + aws_ref_count_init(&loader->ref_count, loader, s_destroy_loader); + + /* we won't know an instance type, possibly ever, but it will be set if available before returning to the user. */ + loader->lock_data.current_env_platform_info.has_recommended_configuration = false; + loader->lock_data.current_env_platform_info.cpu_group_info_array_length = + aws_system_environment_get_cpu_group_count(loader->current_env); + loader->lock_data.current_env_platform_info.cpu_group_info_array = aws_mem_calloc( + allocator, + loader->lock_data.current_env_platform_info.cpu_group_info_array_length, + sizeof(struct aws_s3_cpu_group_info)); + + for (size_t i = 0; i < loader->lock_data.current_env_platform_info.cpu_group_info_array_length; ++i) { + struct aws_s3_cpu_group_info *group_info = &loader->lock_data.current_env_platform_info.cpu_group_info_array[i]; + group_info->cpu_group = i; + group_info->cpus_in_group = aws_get_cpu_count_for_group(i); + /* when we have the ability to detect NIC affinity add that here. */ + } + + AWS_FATAL_ASSERT( + !aws_hash_table_init( + &loader->lock_data.compute_platform_info_table, + allocator, + 32, + aws_hash_byte_cursor_ptr_ignore_case, + (bool (*)(const void *, const void *))aws_byte_cursor_eq_ignore_case, + NULL, + NULL) && + "Hash table init failed!"); + + s_add_platform_info_to_table(loader, &s_c5n_18xlarge_platform_info); + s_add_platform_info_to_table(loader, &s_c5n_9xlarge_platform_info); + s_add_platform_info_to_table(loader, &s_c5n_metal_platform_info); + s_add_platform_info_to_table(loader, &s_p4d_platform_info); + s_add_platform_info_to_table(loader, &s_p4de_platform_info); + s_add_platform_info_to_table(loader, &s_p5_platform_info); + s_add_platform_info_to_table(loader, &s_trn1_n_platform_info); + s_add_platform_info_to_table(loader, &s_trn1_platform_info); + + return loader; +} + +void aws_s3_compute_platform_info_loader_acquire(struct aws_s3_compute_platform_info_loader *loader) { + aws_ref_count_acquire(&loader->ref_count); +} + +void aws_s3_compute_platform_info_loader_release(struct aws_s3_compute_platform_info_loader *loader) { + if (loader) { + aws_ref_count_release(&loader->ref_count); + } +} + +struct imds_callback_info { + struct aws_allocator *allocator; + struct aws_string *instance_type; + struct aws_condition_variable c_var; + int error_code; + struct aws_s3_compute_platform_info_loader *loader; + bool shutdown_completed; + struct aws_mutex mutex; +}; + +static void s_imds_client_shutdown_completed(void *user_data) { + struct imds_callback_info *info = user_data; + aws_mutex_lock(&info->mutex); + info->shutdown_completed = true; + aws_mutex_unlock(&info->mutex); + aws_condition_variable_notify_all(&info->c_var); +} + +static bool s_client_shutdown_predicate(void *arg) { + struct imds_callback_info *info = arg; + return info->shutdown_completed; +} + +static void s_imds_client_on_get_instance_info_callback( + const struct aws_imds_instance_info *instance_info, + int error_code, + void *user_data) { + struct imds_callback_info *info = user_data; + + aws_mutex_lock(&info->mutex); + if (error_code) { + info->error_code = error_code; + } else { + info->instance_type = aws_string_new_from_cursor(info->allocator, &instance_info->instance_type); + info->loader->lock_data.current_env_platform_info.instance_type = + aws_byte_cursor_from_string(info->instance_type); + s_add_platform_info_to_table(info->loader, &info->loader->lock_data.current_env_platform_info); + } + aws_mutex_unlock(&info->mutex); + aws_condition_variable_notify_all(&info->c_var); +} + +static bool s_completion_predicate(void *arg) { + struct imds_callback_info *info = arg; + return info->error_code != 0 || info->instance_type != NULL; +} + +struct aws_byte_cursor aws_s3_get_ec2_instance_type(struct aws_s3_compute_platform_info_loader *loader) { + aws_mutex_lock(&loader->lock_data.lock); + if (loader->lock_data.detected_instance_type) { + AWS_LOGF_TRACE( + AWS_LS_S3_CLIENT, + "id=%p: Instance type has already been determined to be %s. Returning cached version.", + (void *)loader, + aws_string_bytes(loader->lock_data.detected_instance_type)); + goto return_instance_and_unlock; + } + + AWS_LOGF_TRACE( + AWS_LS_S3_CLIENT, + "id=%p: Instance type has not been determined, checking to see if running in EC2 nitro environment.", + (void *)loader); + + if (aws_s3_is_running_on_ec2_nitro(loader)) { + AWS_LOGF_INFO( + AWS_LS_S3_CLIENT, "id=%p: Detected Amazon EC2 with nitro as the current environment.", (void *)loader); + /* easy case not requiring any calls out to IMDS. If we detected we're running on ec2, then the dmi info is + * correct, and we can use it if we have it. Otherwise call out to IMDS. */ + struct aws_byte_cursor product_name = + aws_system_environment_get_virtualization_product_name(loader->current_env); + + if (product_name.len) { + loader->lock_data.detected_instance_type = aws_string_new_from_cursor(loader->allocator, &product_name); + AWS_LOGF_INFO( + AWS_LS_S3_CLIENT, + "id=%p: Determined instance type to be %s, from dmi info. Caching.", + (void *)loader, + aws_string_bytes(loader->lock_data.detected_instance_type)); + goto return_instance_and_unlock; + } + + AWS_LOGF_DEBUG( + AWS_LS_S3_CLIENT, + "static: DMI info was insufficient to determine instance type. Making call to IMDS to determine"); + struct imds_callback_info callback_info = { + .mutex = AWS_MUTEX_INIT, + .c_var = AWS_CONDITION_VARIABLE_INIT, + .allocator = loader->allocator, + .loader = loader, + }; + + struct aws_event_loop_group *el_group = NULL; + struct aws_host_resolver *resolver = NULL; + struct aws_client_bootstrap *client_bootstrap = NULL; + /* now call IMDS */ + el_group = aws_event_loop_group_new_default(loader->allocator, 1, NULL); + + if (!el_group) { + goto tear_down; + } + + struct aws_host_resolver_default_options resolver_options = { + .max_entries = 1, + .el_group = el_group, + }; + + resolver = aws_host_resolver_new_default(loader->allocator, &resolver_options); + + if (!resolver) { + goto tear_down; + } + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = el_group, + .host_resolver = resolver, + }; + + client_bootstrap = aws_client_bootstrap_new(loader->allocator, &bootstrap_options); + + if (!client_bootstrap) { + goto tear_down; + } + + struct aws_imds_client_shutdown_options imds_shutdown_options = { + .shutdown_callback = s_imds_client_shutdown_completed, + .shutdown_user_data = &callback_info, + }; + + struct aws_imds_client_options imds_options = { + .bootstrap = client_bootstrap, + .imds_version = IMDS_PROTOCOL_V2, + .shutdown_options = imds_shutdown_options, + }; + + struct aws_imds_client *imds_client = aws_imds_client_new(loader->allocator, &imds_options); + + if (!imds_client) { + goto tear_down; + } + + aws_mutex_lock(&callback_info.mutex); + aws_imds_client_get_instance_info(imds_client, s_imds_client_on_get_instance_info_callback, &callback_info); + aws_condition_variable_wait_for_pred( + &callback_info.c_var, &callback_info.mutex, AWS_TIMESTAMP_SECS, s_completion_predicate, &callback_info); + + aws_condition_variable_wait_pred( + &callback_info.c_var, &callback_info.mutex, s_client_shutdown_predicate, &callback_info); + aws_mutex_unlock(&callback_info.mutex); + aws_imds_client_release(imds_client); + + if (callback_info.error_code) { + aws_raise_error(callback_info.error_code); + AWS_LOGF_ERROR( + AWS_LS_S3_CLIENT, + "id=%p: IMDS call failed with error %s.", + (void *)loader, + aws_error_debug_str(callback_info.error_code)); + } + + if (callback_info.instance_type) { + loader->lock_data.detected_instance_type = callback_info.instance_type; + AWS_LOGF_INFO( + AWS_LS_S3_CLIENT, + "id=%p: Determined instance type to be %s, from IMDS. Caching.", + (void *)loader, + aws_string_bytes(loader->lock_data.detected_instance_type)); + } + + tear_down: + if (client_bootstrap) { + aws_client_bootstrap_release(client_bootstrap); + } + + if (resolver) { + aws_host_resolver_release(resolver); + } + + if (el_group) { + aws_event_loop_group_release(el_group); + } + } + + struct aws_byte_cursor return_cur; + AWS_ZERO_STRUCT(return_cur); + +return_instance_and_unlock: + return_cur = loader->lock_data.current_env_platform_info.instance_type; + aws_mutex_unlock(&loader->lock_data.lock); + + return return_cur; +} + +const struct aws_s3_compute_platform_info *aws_s3_get_compute_platform_info_for_current_environment( + struct aws_s3_compute_platform_info_loader *loader) { + /* getting the instance type will set it on the loader the first time if it can */ + aws_s3_get_ec2_instance_type(loader); + /* will never be mutated after the above call. */ + return &loader->lock_data.current_env_platform_info; +} + +const struct aws_s3_compute_platform_info *aws_s3_get_compute_platform_info_for_instance_type( + struct aws_s3_compute_platform_info_loader *loader, + struct aws_byte_cursor instance_type_name) { + aws_mutex_lock(&loader->lock_data.lock); + struct aws_hash_element *platform_info_element = NULL; + aws_hash_table_find(&loader->lock_data.compute_platform_info_table, &instance_type_name, &platform_info_element); + aws_mutex_unlock(&loader->lock_data.lock); + + if (platform_info_element) { + return platform_info_element->value; + } + + return NULL; +} + +bool aws_s3_is_running_on_ec2_nitro(struct aws_s3_compute_platform_info_loader *loader) { + struct aws_byte_cursor system_virt_name = aws_system_environment_get_virtualization_vendor(loader->current_env); + + if (aws_byte_cursor_eq_c_str_ignore_case(&system_virt_name, "amazon ec2")) { + return true; + } + + return false; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f455c0206..9e825fe9d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -184,6 +184,7 @@ add_test_case(test_add_user_agent_header) add_test_case(test_get_existing_compute_platform_info) add_test_case(test_get_nonexistent_compute_platform_info) +add_net_test_case(load_platform_info_from_global_state_sanity_test) add_net_test_case(sha1_nist_test_case_1) add_net_test_case(sha1_nist_test_case_2) diff --git a/tests/s3_compute_platform_info_test.c b/tests/s3_compute_platform_info_test.c index e971f5735..cce82fbb2 100644 --- a/tests/s3_compute_platform_info_test.c +++ b/tests/s3_compute_platform_info_test.c @@ -4,6 +4,7 @@ */ #include +#include #include @@ -13,8 +14,10 @@ static int s_test_get_existing_compute_platform_info(struct aws_allocator *alloc aws_s3_library_init(allocator); struct aws_byte_cursor instance_type = aws_byte_cursor_from_c_str("c5n.18xlarge"); - struct aws_s3_compute_platform_info *platform_info = - aws_s3_get_compute_platform_info_for_instance_type(instance_type); + struct aws_s3_compute_platform_info_loader *loader = aws_s3_compute_platform_info_loader_new(allocator); + + const struct aws_s3_compute_platform_info *platform_info = + aws_s3_get_compute_platform_info_for_instance_type(loader, instance_type); ASSERT_NOT_NULL(platform_info); ASSERT_BIN_ARRAYS_EQUALS( @@ -37,6 +40,7 @@ static int s_test_get_existing_compute_platform_info(struct aws_allocator *alloc ASSERT_NULL(platform_info->cpu_group_info_array[1].nic_name_array); ASSERT_UINT_EQUALS(0, platform_info->cpu_group_info_array[1].nic_name_array_length); + aws_s3_compute_platform_info_loader_release(loader); aws_s3_library_clean_up(); return AWS_OP_SUCCESS; } @@ -48,13 +52,48 @@ static int s_test_get_nonexistent_compute_platform_info(struct aws_allocator *al aws_s3_library_init(allocator); + struct aws_s3_compute_platform_info_loader *loader = aws_s3_compute_platform_info_loader_new(allocator); + struct aws_byte_cursor instance_type = aws_byte_cursor_from_c_str("non-existent"); - struct aws_s3_compute_platform_info *platform_info = - aws_s3_get_compute_platform_info_for_instance_type(instance_type); + const struct aws_s3_compute_platform_info *platform_info = + aws_s3_get_compute_platform_info_for_instance_type(loader, instance_type); ASSERT_NULL(platform_info); + aws_s3_compute_platform_info_loader_release(loader); aws_s3_library_clean_up(); return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_get_nonexistent_compute_platform_info, s_test_get_nonexistent_compute_platform_info) + +static int s_load_platform_info_from_global_state_sanity_test(struct aws_allocator *allocator, void *arg) { + (void)arg; + aws_s3_library_init(allocator); + + const struct aws_s3_compute_platform_info *platform_info = aws_s3_current_compute_platform_info(); + ASSERT_NOT_NULL(platform_info); + ASSERT_NOT_NULL(platform_info->cpu_group_info_array); + ASSERT_TRUE(platform_info->cpu_group_info_array_length > 0); + + if (platform_info->instance_type.len) { + struct aws_s3_compute_platform_info_loader *loader = aws_s3_compute_platform_info_loader_new(allocator); + const struct aws_s3_compute_platform_info *by_name_info = + aws_s3_get_compute_platform_info_for_instance_type(loader, platform_info->instance_type); + if (by_name_info) { + ASSERT_BIN_ARRAYS_EQUALS( + platform_info->instance_type.ptr, + platform_info->instance_type.len, + by_name_info->instance_type.ptr, + by_name_info->instance_type.len); + ASSERT_UINT_EQUALS(platform_info->cpu_group_info_array_length, by_name_info->cpu_group_info_array_length); + ASSERT_UINT_EQUALS(platform_info->max_throughput_gbps, by_name_info->max_throughput_gbps); + } + + aws_s3_compute_platform_info_loader_release(loader); + } + + aws_s3_library_clean_up(); + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(load_platform_info_from_global_state_sanity_test, s_load_platform_info_from_global_state_sanity_test)