From 04fedcbaf3eb330bea80bb5a6dd932e9fd46755e Mon Sep 17 00:00:00 2001 From: Yura Sorokin Date: Fri, 3 Jul 2026 17:54:20 +0200 Subject: [PATCH] PBS-37 feature: Combine binlog_server with minimysql_server into a single executable (part 2) https://perconadev.atlassian.net/browse/PBS-37 Global refactoring in the main "binlog_server" top-level code ('app.cpp'): - Created an abstract interface for program execution modes ('operations::basic_operation'). - Logic of individual commands (fetch/pull, list, search_by_timestamp, search_by_gtid_set, purge_binlogs, and version) put into separate classes. - Created 'operations::operation_factory' class that auto-registers all concrete 'basic_operation' derived classes and simplifies construction by operation mode name. - A set of functions used to log info from various PBS entities (config sections, storage, reader_context, connection, etc.) put into a separate file 'operations/logger_helpers.hpp' - 'binsrv::operation_mode_type' moved into 'operations::mode_type' - A set of functions used to generate ROTATE / FORMAT_DESCRIPTION / PREVIOUS_GTIDS_LOG events in the rewrite mode put into a separate file 'operations/event_generation_helpers.hpp' --- CMakeLists.txt | 51 +- src/app.cpp | 1338 +---------------- src/binsrv/operation_mode_type.hpp | 101 -- src/binsrv/storage.hpp | 3 +- src/operations/basic_operation.cpp | 41 + src/operations/basic_operation.hpp | 56 + src/operations/basic_operation_fwd.hpp | 29 + src/operations/event_generation_helpers.cpp | 123 ++ src/operations/event_generation_helpers.hpp | 51 + src/operations/fetch_operation.hpp | 35 + src/operations/fetch_pull_operation.cpp | 706 +++++++++ src/operations/fetch_pull_operation.hpp | 46 + src/operations/generic_operation_fwd.hpp | 27 + src/operations/list_operation.cpp | 68 + src/operations/list_operation.hpp | 45 + src/operations/logger_helpers.cpp | 313 ++++ src/operations/logger_helpers.hpp | 72 + src/operations/mode_type.hpp | 96 ++ .../mode_type_fwd.hpp} | 18 +- src/operations/model_helpers.cpp | 34 + src/operations/model_helpers.hpp | 31 + src/operations/operation_factory.cpp | 84 ++ src/operations/operation_factory.hpp | 33 + src/operations/pull_operation.hpp | 35 + src/operations/purge_binlogs_operation.cpp | 91 ++ src/operations/purge_binlogs_operation.hpp | 49 + .../search_by_gtid_set_operation.cpp | 98 ++ .../search_by_gtid_set_operation.hpp | 50 + .../search_by_timestamp_operation.cpp | 88 ++ .../search_by_timestamp_operation.hpp | 50 + src/operations/version_operation.cpp | 38 + src/operations/version_operation.hpp | 39 + 32 files changed, 2494 insertions(+), 1445 deletions(-) delete mode 100644 src/binsrv/operation_mode_type.hpp create mode 100644 src/operations/basic_operation.cpp create mode 100644 src/operations/basic_operation.hpp create mode 100644 src/operations/basic_operation_fwd.hpp create mode 100644 src/operations/event_generation_helpers.cpp create mode 100644 src/operations/event_generation_helpers.hpp create mode 100644 src/operations/fetch_operation.hpp create mode 100644 src/operations/fetch_pull_operation.cpp create mode 100644 src/operations/fetch_pull_operation.hpp create mode 100644 src/operations/generic_operation_fwd.hpp create mode 100644 src/operations/list_operation.cpp create mode 100644 src/operations/list_operation.hpp create mode 100644 src/operations/logger_helpers.cpp create mode 100644 src/operations/logger_helpers.hpp create mode 100644 src/operations/mode_type.hpp rename src/{binsrv/operation_mode_type_fwd.hpp => operations/mode_type_fwd.hpp} (70%) create mode 100644 src/operations/model_helpers.cpp create mode 100644 src/operations/model_helpers.hpp create mode 100644 src/operations/operation_factory.cpp create mode 100644 src/operations/operation_factory.hpp create mode 100644 src/operations/pull_operation.hpp create mode 100644 src/operations/purge_binlogs_operation.cpp create mode 100644 src/operations/purge_binlogs_operation.hpp create mode 100644 src/operations/search_by_gtid_set_operation.cpp create mode 100644 src/operations/search_by_gtid_set_operation.hpp create mode 100644 src/operations/search_by_timestamp_operation.cpp create mode 100644 src/operations/search_by_timestamp_operation.hpp create mode 100644 src/operations/version_operation.cpp create mode 100644 src/operations/version_operation.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e39482a..a000112 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,6 +425,51 @@ set_target_properties(lib_models PROPERTIES ) add_library(binsrv::lib_models ALIAS lib_models) +set(operations_source_files + src/operations/basic_operation_fwd.hpp + src/operations/basic_operation.hpp + src/operations/basic_operation.cpp + + src/operations/event_generation_helpers.hpp + src/operations/event_generation_helpers.cpp + + src/operations/fetch_operation.hpp + + src/operations/fetch_pull_operation.hpp + src/operations/fetch_pull_operation.cpp + + src/operations/generic_operation_fwd.hpp + + src/operations/list_operation.hpp + src/operations/list_operation.cpp + + src/operations/logger_helpers.hpp + src/operations/logger_helpers.cpp + + src/operations/mode_type_fwd.hpp + src/operations/mode_type.hpp + + src/operations/model_helpers.hpp + src/operations/model_helpers.cpp + + src/operations/operation_factory.hpp + src/operations/operation_factory.cpp + + src/operations/pull_operation.hpp + + src/operations/purge_binlogs_operation.hpp + src/operations/purge_binlogs_operation.cpp + + src/operations/search_by_gtid_set_operation.hpp + src/operations/search_by_gtid_set_operation.cpp + + src/operations/search_by_timestamp_operation.hpp + src/operations/search_by_timestamp_operation.cpp + + src/operations/version_operation.hpp + src/operations/version_operation.cpp +) + set(binsrv_source_files # main application files src/app.cpp @@ -469,9 +514,6 @@ set(binsrv_source_files src/binsrv/main_config.hpp src/binsrv/main_config.cpp - src/binsrv/operation_mode_type_fwd.hpp - src/binsrv/operation_mode_type.hpp - src/binsrv/replication_config_fwd.hpp src/binsrv/replication_config.hpp src/binsrv/replication_config.cpp @@ -518,7 +560,7 @@ set(binsrv_source_files src/binsrv/time_unit.cpp ) -add_executable(binlog_server ${binsrv_source_files}) +add_executable(binlog_server ${binsrv_source_files} ${operations_source_files}) target_link_libraries(binlog_server PRIVATE binlog_server_compiler_flags @@ -571,6 +613,7 @@ set(minimysql_source_files src/minimysql/connection_context_fwd.hpp src/minimysql/connection_context.hpp src/minimysql/connection_context.cpp + src/minimysql/network_io_operations_fwd.hpp src/minimysql/network_io_operations.hpp src/minimysql/network_io_operations.cpp src/minimysql/network_service.hpp diff --git a/src/app.cpp b/src/app.cpp index 9d30f06..9809091 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -13,1205 +13,27 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -#include -#include -#include -#include -#include -#include -#include #include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "operations/basic_operation.hpp" +#include "operations/operation_factory.hpp" -#include "app_version.hpp" - -#include "binsrv/basic_logger.hpp" -#include "binsrv/exception_handling_helpers.hpp" -#include "binsrv/log_severity.hpp" -#include "binsrv/logger_factory.hpp" -#include "binsrv/main_config.hpp" -#include "binsrv/operation_mode_type.hpp" -#include "binsrv/replication_mode_type.hpp" -#include "binsrv/size_unit.hpp" -#include "binsrv/storage.hpp" -// needed for storage_backend_type's operator << -#include "binsrv/storage_backend_type.hpp" // IWYU pragma: keep -#include "binsrv/time_unit.hpp" - -#include "binsrv/gtids/common_types.hpp" -#include "binsrv/gtids/gtid_set.hpp" - -#include "binsrv/models/error_response.hpp" -#include "binsrv/models/response_status_type.hpp" -#include "binsrv/models/search_response.hpp" - -#include "binsrv/events/checksum_algorithm_type.hpp" -#include "binsrv/events/code_type.hpp" -#include "binsrv/events/common_header_flag_type.hpp" -#include "binsrv/events/event.hpp" -#include "binsrv/events/event_view.hpp" -#include "binsrv/events/protocol_traits_fwd.hpp" -#include "binsrv/events/reader_context.hpp" -#include "binsrv/events/rewriter.hpp" - -#include "easymysql/connection.hpp" -#include "easymysql/connection_config.hpp" -#include "easymysql/core_error.hpp" -#include "easymysql/library.hpp" -// Needed for ssl_mode_type's operator << -#include "easymysql/ssl_mode_type.hpp" // IWYU pragma: keep - -#include "util/byte_span_fwd.hpp" #include "util/command_line_helpers.hpp" -#include "util/common_optional_types.hpp" -#include "util/ct_string.hpp" -#include "util/ctime_timestamp.hpp" -#include "util/exception_location_helpers.hpp" -#include "util/nv_tuple.hpp" -#include "util/semantic_version.hpp" - -namespace { - -[[nodiscard]] bool -check_cmd_args(const util::command_line_arg_view &cmd_args, - binsrv::operation_mode_type &operation_mode, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - std::string_view &config_file_path, - std::string_view &subcommand_value) noexcept { - const auto number_of_cmd_args = std::size(cmd_args); - - static constexpr std::size_t expected_number_of_cmd_args_min{2U}; - - if (number_of_cmd_args < expected_number_of_cmd_args_min) { - return false; - } - - operation_mode = binsrv::operation_mode_type::delimiter; - - if (!boost::conversion::try_lexical_convert(cmd_args[1], operation_mode)) { - return false; - } - - if (operation_mode == binsrv::operation_mode_type::delimiter) { - return false; - } - - static constexpr std::size_t expected_number_of_cmd_args_with_config{3U}; - static constexpr std::size_t - expected_number_of_cmd_args_with_config_and_value{4U}; - - switch (operation_mode) { - case binsrv::operation_mode_type::fetch: - case binsrv::operation_mode_type::pull: - case binsrv::operation_mode_type::list: - if (number_of_cmd_args != expected_number_of_cmd_args_with_config) { - return false; - } - config_file_path = cmd_args[expected_number_of_cmd_args_with_config - 1U]; - return true; - case binsrv::operation_mode_type::search_by_timestamp: - case binsrv::operation_mode_type::search_by_gtid_set: - case binsrv::operation_mode_type::purge_binlogs: - if (number_of_cmd_args != - expected_number_of_cmd_args_with_config_and_value) { - return false; - } - config_file_path = cmd_args[expected_number_of_cmd_args_with_config - 1U]; - subcommand_value = - cmd_args[expected_number_of_cmd_args_with_config_and_value - 1U]; - return true; - case binsrv::operation_mode_type::version: - return number_of_cmd_args == expected_number_of_cmd_args_min; - break; - default: - // should never be here - assert(false); - return false; - } -} - -template util::optional_string to_log_string(const T &value) { - return boost::lexical_cast(value); -} - -util::optional_string to_log_string(const binsrv::size_unit &value) { - return value.get_description(); -} - -util::optional_string to_log_string(const binsrv::time_unit &value) { - return value.get_description(); -} - -util::optional_string to_log_string(bool value) { - return {value ? "true" : "false"}; -} - -template -util::optional_string to_log_string(const std::optional &value) { - if (!value.has_value()) { - return {}; - } - return to_log_string(*value); -} - -template -void log_config_param(binsrv::basic_logger &logger, const Config &config, - std::string_view label) { - const auto opt_log_string{to_log_string(config.template get())}; - if (opt_log_string.has_value()) { - std::string msg{label}; - msg += ": "; - msg += *opt_log_string; - logger.log(binsrv::log_severity::info, msg); - } -} - -void log_ssl_config_info(binsrv::basic_logger &logger, - const easymysql::ssl_config &ssl_config) { - log_config_param<"mode">(logger, ssl_config, "SSL mode"); - log_config_param<"ca">(logger, ssl_config, "SSL ca"); - log_config_param<"capath">(logger, ssl_config, "SSL capath"); - log_config_param<"crl">(logger, ssl_config, "SSL crl"); - log_config_param<"crlpath">(logger, ssl_config, "SSL crlpath"); - log_config_param<"cert">(logger, ssl_config, "SSL cert"); - log_config_param<"key">(logger, ssl_config, "SSL key"); - log_config_param<"cipher">(logger, ssl_config, "SSL cipher"); -} - -void log_tls_config_info(binsrv::basic_logger &logger, - const easymysql::tls_config &tls_config) { - log_config_param<"ciphersuites">(logger, tls_config, "TLS ciphersuites"); - log_config_param<"version">(logger, tls_config, "TLS version"); -} - -void log_connection_config_info( - binsrv::basic_logger &logger, - const easymysql::connection_config &connection_config) { - std::string msg; - msg = "mysql connection string: "; - msg += connection_config.get_connection_string(); - logger.log(binsrv::log_severity::info, msg); - - log_config_param<"connect_timeout">(logger, connection_config, - "mysql connect timeout (seconds)"); - log_config_param<"read_timeout">(logger, connection_config, - "mysql read timeout (seconds)"); - log_config_param<"write_timeout">(logger, connection_config, - "mysql write timeout (seconds)"); - - const auto &optional_ssl_config{connection_config.get<"ssl">()}; - if (optional_ssl_config.has_value()) { - log_ssl_config_info(logger, *optional_ssl_config); - } - const auto &optional_tls_config{connection_config.get<"tls">()}; - if (optional_tls_config.has_value()) { - log_tls_config_info(logger, *optional_tls_config); - } -} - -void log_rewrite_config_info(binsrv::basic_logger &logger, - const binsrv::rewrite_config &rewrite_config) { - log_config_param<"base_file_name">(logger, rewrite_config, - "rewrite base binlog file name"); - log_config_param<"file_size">(logger, rewrite_config, - "rewrite binlog file size"); -} -void log_replication_config_info( - binsrv::basic_logger &logger, - const binsrv::replication_config &replication_config) { - - log_config_param<"server_id">(logger, replication_config, - "mysql replication server id"); - log_config_param<"idle_time">(logger, replication_config, - "mysql replication idle time (seconds)"); - log_config_param<"verify_checksum">( - logger, replication_config, "mysql replication checksum verification"); - log_config_param<"mode">(logger, replication_config, - "mysql replication mode"); - const auto &optional_rewrite_config{replication_config.get<"rewrite">()}; - if (optional_rewrite_config.has_value()) { - log_rewrite_config_info(logger, *optional_rewrite_config); - } -} - -void log_storage_config_info(binsrv::basic_logger &logger, - const binsrv::storage_config &storage_config) { - - log_config_param<"backend">(logger, storage_config, - "binlog storage backend type"); - logger.log(binsrv::log_severity::info, - "binlog storage backend URI (masked): " + - storage_config.get_masked_uri()); - log_config_param<"fs_buffer_directory">( - logger, storage_config, - "binlog storage backend filesystem buffer directory"); - log_config_param<"checkpoint_size">( - logger, storage_config, "binlog storage backend checkpointing size"); - log_config_param<"checkpoint_interval">( - logger, storage_config, "binlog storage backend checkpointing interval"); -} - -void log_storage_info(binsrv::basic_logger &logger, - const binsrv::storage &storage) { - std::string msg{"created binlog storage with the following backend: "}; - msg += storage.get_backend_description(); - logger.log(binsrv::log_severity::info, msg); - - msg.clear(); - msg = "binlog storage initialized in "; - msg += boost::lexical_cast(storage.get_replication_mode()); - msg += " mode"; - logger.log(binsrv::log_severity::info, msg); - - msg.clear(); - if (storage.is_empty()) { - msg = "binlog storage initialized on an empty directory"; - } else { - msg = "binlog storage initialized at \""; - msg += storage.get_current_binlog_name().str(); - msg += "\":"; - msg += std::to_string(storage.get_current_position()); - } - logger.log(binsrv::log_severity::info, msg); -} - -void log_library_info(binsrv::basic_logger &logger, - const easymysql::library &mysql_lib) { - std::string msg{}; - msg = "mysql client version: "; - msg += mysql_lib.get_readable_client_version(); - logger.log(binsrv::log_severity::info, msg); -} - -void log_connection_info(binsrv::basic_logger &logger, - const easymysql::connection &connection) { - std::string msg{}; - msg = "mysql server version: "; - msg += connection.get_readable_server_version(); - logger.log(binsrv::log_severity::info, msg); - - logger.log(binsrv::log_severity::info, - "mysql protocol version: " + - std::to_string(connection.get_protocol_version())); - - msg = "mysql server connection info: "; - msg += connection.get_server_connection_info(); - logger.log(binsrv::log_severity::info, msg); - - msg = "mysql connection character set: "; - msg += connection.get_character_set_name(); - logger.log(binsrv::log_severity::info, msg); -} - -void log_replication_info( - binsrv::basic_logger &logger, std::uint32_t server_id, - const binsrv::storage &storage, bool verify_checksum, - easymysql::connection_replication_mode_type blocking_mode) { - const auto replication_mode{storage.get_replication_mode()}; - - std::string msg{"switched to replication (checksum "}; - msg += (verify_checksum ? "enabled" : "disabled"); - msg += ", "; - msg += boost::lexical_cast(replication_mode); - msg += +" mode)"; - logger.log(binsrv::log_severity::info, msg); - - msg = "replication info (server id "; - msg += std::to_string(server_id); - msg += ", "; - msg += (blocking_mode == easymysql::connection_replication_mode_type::blocking - ? "blocking" - : "non-blocking"); - msg += ", starting from "; - if (replication_mode == binsrv::replication_mode_type::position) { - if (storage.is_empty()) { - msg += "the very beginning"; - } else { - msg += storage.get_current_binlog_name().str(); - msg += ":"; - msg += std::to_string(storage.get_current_position()); - } - } else { - const auto >ids{storage.get_gtids()}; - if (gtids.is_empty()) { - msg += "an empty"; - } else { - msg += "the "; - msg += boost::lexical_cast(gtids); - } - msg += " GTID set"; - } - msg += ")"; - logger.log(binsrv::log_severity::info, msg); -} - -void log_span_dump(binsrv::basic_logger &logger, - util::const_byte_span portion) { - logger.log(binsrv::log_severity::debug, - "fetched " + std::to_string(std::size(portion)) + - "-byte(s) event from binlog"); - static constexpr std::size_t bytes_per_dump_line{16U}; - std::size_t offset{0U}; - while (offset < std::size(portion)) { - std::ostringstream oss; - oss << '['; - oss << std::setfill('0') << std::hex; - auto sub = portion.subspan( - offset, std::min(bytes_per_dump_line, std::size(portion) - offset)); - for (auto current_byte : sub) { - oss << ' ' << std::setw(2) - << std::to_integer(current_byte); - } - const std::size_t filler_length = - (bytes_per_dump_line - std::size(sub)) * 3U; - oss << std::setfill(' ') << std::setw(static_cast(filler_length)) - << ""; - oss << " ] "; - const auto &ctype_facet{ - std::use_facet>(std::locale::classic())}; - - for (auto current_byte : sub) { - auto current_char{std::to_integer(current_byte)}; - if (!ctype_facet.is(std::ctype_base::print, current_char)) { - current_char = '.'; - } - oss.put(current_char); - } - logger.log(binsrv::log_severity::trace, oss.str()); - offset += bytes_per_dump_line; - } -} - -void process_artificial_rotate_event( - const binsrv::events::event_view ¤t_event_v, - binsrv::basic_logger &logger, binsrv::storage &storage) { - assert(current_event_v.get_common_header_view().get_type_code() == - binsrv::events::code_type::rotate); - assert(current_event_v.get_common_header_view().get_flags().has_element( - binsrv::events::common_header_flag_type::artificial)); - - const binsrv::events::generic_body - current_rotate_body{current_event_v.get_body_raw()}; - - bool binlog_opening_needed{true}; - - if (storage.is_binlog_open()) { - // here we take a "shortcut" path - upon losing connection to the MySQL - // server, we do not close storage's binlog file immediately expecting - // that upon reconnection we will be able to continue writing to the - // same file - - // so, here we just need to make sure that (binlog name, position) pair - // in the artificial ROTATE event matches the current storage state - - // also, in case when the server was not shut down properly, it won't - // have ROTATE or STOP event as the last one in the binlog, so here we - // handle this case by closing the old binlog and opening a new one - - if (current_rotate_body.get_parsed_binlog() == - storage.get_current_binlog_name()) { - // in addition, in position-based replication mode we also need to check - // the position - if (storage.get_replication_mode() == - binsrv::replication_mode_type::position) { - const binsrv::events::generic_post_header< - binsrv::events::code_type::rotate> - current_rotate_post_header{current_event_v.get_post_header_raw()}; - - if (current_rotate_post_header.get_position_raw() != - storage.get_current_position()) { - util::exception_location().raise( - "unexpected binlog position in artificial rotate event"); - } - } - - binlog_opening_needed = false; - - const std::string current_binlog_name{ - storage.get_current_binlog_name().str()}; - logger.log(binsrv::log_severity::info, - "storage: reused already open binlog file: " + - current_binlog_name); - - } else { - // if names do not match, we need to close the currently open - // binlog and make sure that binlog_opening_needed is set to true, so - // that we will open a new one later - const std::string old_binlog_name{ - storage.get_current_binlog_name().str()}; - storage.close_binlog(); - logger.log(binsrv::log_severity::info, - "storage: closed binlog file left open: " + old_binlog_name); - // binlog_opening_needed remains true in this branch - assert(binlog_opening_needed); - } - } - if (binlog_opening_needed) { - const auto binlog_open_result{ - storage.open_binlog(current_rotate_body.get_parsed_binlog())}; - - std::string message{"storage: "}; - if (binlog_open_result == binsrv::open_binlog_status::created) { - message += "created a new"; - } else { - message += "opened an existing"; - if (binlog_open_result == binsrv::open_binlog_status::opened_empty) { - message += " (empty)"; - } else if (binlog_open_result == - binsrv::open_binlog_status::opened_at_magic_payload_offset) { - message += " (with magic payload only)"; - } - } - message += " binlog file: "; - message += current_rotate_body.get_readable_binlog(); - logger.log(binsrv::log_severity::info, message); - } -} - -void process_rotate_or_stop_event(binsrv::basic_logger &logger, - binsrv::storage &storage) { - const std::string old_binlog_name{storage.get_current_binlog_name().str()}; - storage.close_binlog(); - logger.log(binsrv::log_severity::info, - "storage: closed binlog file: " + old_binlog_name); -} - -void process_binlog_event(const binsrv::events::event_view ¤t_event_v, - binsrv::basic_logger &logger, - binsrv::events::reader_context &context, - binsrv::storage &storage) { - const auto current_common_header_v{current_event_v.get_common_header_view()}; - const auto readable_flags{current_common_header_v.get_readable_flags()}; - logger.log(binsrv::log_severity::info, - "event : " + - std::string{current_common_header_v.get_readable_type_code()} + - (readable_flags.empty() ? "" : " (" + readable_flags + ")")); - logger.log(binsrv::log_severity::debug, - "event : [parsed view] " + - boost::lexical_cast(current_event_v)); - - const bool info_only{context.process_event_view(current_event_v)}; - - if (info_only) { - logger.log( - binsrv::log_severity::info, - "event : [info_only] - will not be written to the binary log file"); - } - - if (context.is_at_transaction_boundary()) { - logger.log( - binsrv::log_severity::info, - "event : [end_of_transaction] " + - boost::lexical_cast(context.get_transaction_gtid())); - } - - // here we additionally check for log level because event materialization - // is not a trivial operation - if (binsrv::log_severity::debug >= logger.get_min_level()) { - const binsrv::events::event current_event{current_event_v}; - logger.log(binsrv::log_severity::debug, - "event : [parsed] " + - boost::lexical_cast(current_event)); - } - - const auto code = current_common_header_v.get_type_code(); - const auto is_artificial{current_common_header_v.get_flags().has_element( - binsrv::events::common_header_flag_type::artificial)}; - - // processing the very first event in the sequence - artificial ROTATE event - if (code == binsrv::events::code_type::rotate && is_artificial) { - process_artificial_rotate_event(current_event_v, logger, storage); - } - - // checking if the event needs to be written to the binlog - if (!info_only) { - storage.write_event( - current_event_v.get_portion(), context.is_at_transaction_boundary(), - context.get_transaction_gtid(), current_common_header_v.get_timestamp(), - context.get_transaction_sequence_number()); - } - - // processing the very last event in the sequence - either a non-artificial - // ROTATE event or a STOP event. This is the path that closes the local - // binlog file and (via storage::close_binlog -> flush_event_buffer) is - // what guarantees the terminator event itself lands on the backend - if ((code == binsrv::events::code_type::rotate && !is_artificial) || - code == binsrv::events::code_type::stop) { - process_rotate_or_stop_event(logger, storage); - } -} - -[[nodiscard]] binsrv::events::event_view generate_rotate_event( - binsrv::events::event_storage &event_buffer, - const binsrv::events::reader_context &context, std::uint32_t offset, - bool current_timestamp, std::uint32_t server_id, bool artificial, - const binsrv::events::composite_binlog_name &binlog_name) { - const binsrv::events::generic_post_header - post_header{binsrv::events::magic_binlog_offset}; - const binsrv::events::generic_body body{ - binlog_name}; - - util::ctime_timestamp timestamp{}; - if (current_timestamp) { - timestamp = util::ctime_timestamp::now(); - } - - binsrv::events::common_header_flag_set flags{}; - if (artificial) { - flags |= binsrv::events::common_header_flag_type::artificial; - } - - // the value of the 'include_checksum' parameters is taken from the - // 'reader_context': immediately after reconnection it will be equal to - // the '' configuration parameter and after - // that will be taken from the FORMAT_DESCRIPTION events, which in the - // rewrite mode will be generated by us and therefore will always include - // 'checksum_algorithm' set to 'crc32' - const auto generated_event{ - binsrv::events::event::create_event( - offset, timestamp, server_id, flags, post_header, body, - context.is_footer_expected(), event_buffer)}; - - return binsrv::events::event_view{context, - util::const_byte_span{event_buffer}}; -} - -[[nodiscard]] binsrv::events::event_view -generate_format_description_event(binsrv::events::event_storage &event_buffer, - const binsrv::events::reader_context &context, - std::uint32_t offset, - std::uint32_t server_id) { - const util::semantic_version server_version{ - context.get_current_encoded_server_version()}; - const binsrv::events::generic_post_header< - binsrv::events::code_type::format_description> - post_header{ - binsrv::events::default_binlog_version, server_version, - util::ctime_timestamp::now(), - binsrv::events::default_common_header_length, - binsrv::events::reader_context::get_hardcoded_post_header_lengths( - server_version.get_encoded())}; - const binsrv::events::generic_body< - binsrv::events::code_type::format_description> - body{binsrv::events::checksum_algorithm_type::crc32}; - - // enforcing checksums for all rewritten upcoming events - const auto generated_event{binsrv::events::event::create_event< - binsrv::events::code_type::format_description>( - offset, util::ctime_timestamp::now(), server_id, - binsrv::events::common_header_flag_set{}, post_header, body, - true /* include_checksum */, event_buffer)}; - - return binsrv::events::event_view{context, - util::const_byte_span{event_buffer}}; -} - -[[nodiscard]] binsrv::events::event_view -generate_previous_gtids_log_event(binsrv::events::event_storage &event_buffer, - const binsrv::events::reader_context &context, - std::uint32_t offset, std::uint32_t server_id, - const binsrv::gtids::gtid_set >ids) { - const binsrv::events::generic_post_header< - binsrv::events::code_type::previous_gtids_log> - post_header{}; - const binsrv::events::generic_body< - binsrv::events::code_type::previous_gtids_log> - body{gtids}; - const auto generated_previous_gtids_log_event{ - binsrv::events::event::create_event< - binsrv::events::code_type::previous_gtids_log>( - offset, util::ctime_timestamp::now(), server_id, - binsrv::events::common_header_flag_set{}, post_header, body, true, - event_buffer)}; - - return binsrv::events::event_view{context, - util::const_byte_span{event_buffer}}; -} - -void rewrite_and_process_binlog_event( - const binsrv::events::event_view ¤t_event_v, - binsrv::basic_logger &logger, binsrv::events::reader_context &context, - binsrv::storage &storage, std::uint32_t server_id, - std::string_view base_file_name, std::uint64_t file_size) { - assert(storage.is_in_gtid_replication_mode()); - const auto current_common_header_v = current_event_v.get_common_header_view(); - const auto code = current_common_header_v.get_type_code(); - - // for ROTATE (both artificial and non-artificial), FORMAT_DESCRIPTION, - // PREVIOUS_GTIDS_LOG, and STOP events we don't have to do anything - - // simply return early from this function - if (code == binsrv::events::code_type::format_description || - code == binsrv::events::code_type::previous_gtids_log || - code == binsrv::events::code_type::rotate || - code == binsrv::events::code_type::stop) { - // making sure that there will be no events without checksums in the rewrite - // mode because when recalculating the value of 'transaction_length' field - // in GTID events we rely on the fact that upcoming events from the same - // transaction will not change their size after rewriting (currently, to - // avoid scenarios when one binlog file with checksums enabled is followed - // by another file without checksums, and we have to combine them in the - // same storage binlog file, we enforce that all events in the rewrite mode - // must have checksums) - - // TODO: this restriction can be lifted if we implement whole transaction - // rewrite logic (we do not add incomplete transaction events into - // the storage buffer unless we receive all of them and perform - // necessary checksum addition/removal) - if (code == binsrv::events::code_type::format_description) { - const auto format_description_body{binsrv::events::generic_body< - binsrv::events::code_type::format_description>{ - current_event_v.get_body_raw()}}; - if (!format_description_body.has_checksum_algorithm()) { - util::exception_location().raise( - "rewrite is supported in gtid replication mode only when " - "all events received from the MySQL server have checksums"); - } - } - - const auto readable_flags{current_common_header_v.get_readable_flags()}; - logger.log( - binsrv::log_severity::info, - "rewrite: encountered " + - std::string{current_common_header_v.get_readable_type_code()} + - (readable_flags.empty() ? "" : " (" + readable_flags + ")") + - " event in the rewrite mode - skipping"); - return; - } - // the very first step is to check if we need to close the old binary log - // file and open a new one in case when we reached the file size specified - // in the 'rewrite_config' or this is the very first event we are going to - // write to an empty storage - - // in case of an empty storage we need to generate the following: - // 1. ROTATE(artificial ) .000001:4 - // 2. FORMAT_DESCRIPTION - // 3. PREVIOUS_GTIDS_LOG - - // in case when the storage is not empty, we are at transaction boundary, - // and current binlog file reached the file size specified in the - // 'rewrite_config', we need to generate the following: - // 0. ROTATE(non-artificial) .:4 - // 1. ROTATE(artificial ) .:4 - // 2. FORMAT_DESCRIPTION - // 3. PREVIOUS_GTIDS_LOG - - if (context.is_fresh() || (context.is_at_transaction_boundary() && - storage.get_current_position() >= file_size)) { - binsrv::events::event_storage event_buffer; - std::uint32_t offset{0U}; - - // generating next binlog file name based on base file name from the - // configuration file and current binlog file - // sequence number from the storage - - // please notice that if storage is empty, then the sequence number will be - // zero - binsrv::events::composite_binlog_name binlog_name{}; - if (storage.is_empty()) { - // the very first time we receive an event on an empty storage - binlog_name = binsrv::events::composite_binlog_name{base_file_name, 1U}; - } else if (context.is_fresh()) { - // this is the very first event we received after reconnection - // (the storage is not empty and we have an active binlog in it) - binlog_name = storage.get_current_binlog_name(); - } else { - // we are at transaction boundary and reached max binlog file size - binlog_name = storage.get_current_binlog_name().next(); - } - - if (!context.is_fresh()) { - // generate and process ROTATE(non-artificial) event - offset = static_cast(storage.get_current_position()); - const auto generated_rotate_event_v{generate_rotate_event( - event_buffer, context, offset, true /* current timestamp */, - server_id, false /* non-artificial */, binlog_name)}; - logger.log(binsrv::log_severity::info, - "rewrite: generated rotate event in the rewrite mode"); - process_binlog_event(generated_rotate_event_v, logger, context, storage); - } - - // generate and process ROTATE(artificial) event - offset = 0U; - // artificial ROTATE event must include zero timestamp - const auto generated_artificial_rotate_event_v{generate_rotate_event( - event_buffer, context, offset, false /* zero timestamp */, server_id, - true /* artificial */, binlog_name)}; - logger.log( - binsrv::log_severity::info, - "rewrite: generated artificial rotate event in the rewrite mode"); - process_binlog_event(generated_artificial_rotate_event_v, logger, context, - storage); - - // generate and process FORMAT_DESCRIPTION event - offset = binsrv::events::magic_binlog_offset; - const auto generated_format_description_event_v{ - generate_format_description_event(event_buffer, context, offset, - server_id)}; - logger.log( - binsrv::log_severity::info, - "rewrite: generated format description event in the rewrite mode"); - process_binlog_event(generated_format_description_event_v, logger, context, - storage); - - // generate and process PREVIOUS_GTIDS_LOG event - offset += static_cast( - generated_format_description_event_v.get_total_size()); - const auto generated_previous_gtids_log_event_v{ - generate_previous_gtids_log_event(event_buffer, context, offset, - server_id, storage.get_gtids())}; - logger.log( - binsrv::log_severity::info, - "rewrite: generated previous gtids log event in the rewrite mode"); - process_binlog_event(generated_previous_gtids_log_event_v, logger, context, - storage); - } - - // in rewrite mode we need to update next_event_position (and optional - // checksum in the footer) in the received event data portion - binsrv::events::event_storage buffer{}; - const auto event_copy_uv{binsrv::events::rewriter::rewrite( - storage.get_last_transaction_sequence_number(), current_event_v, buffer, - storage.get_current_position())}; - process_binlog_event(event_copy_uv, logger, context, storage); -} - -bool open_connection_and_switch_to_replication( - binsrv::operation_mode_type operation_mode, binsrv::basic_logger &logger, - const easymysql::library &mysql_lib, - const easymysql::connection_config &connection_config, - std::uint32_t server_id, bool verify_checksum, binsrv::storage &storage, - easymysql::connection &connection) { - try { - connection = mysql_lib.create_connection(connection_config); - } catch (const easymysql::core_error &) { - if (operation_mode == binsrv::operation_mode_type::fetch) { - throw; - } - logger.log(binsrv::log_severity::error, - "unable to establish connection to mysql server"); - return false; - } - - logger.log(binsrv::log_severity::info, - "established connection to mysql server"); - - log_connection_info(logger, connection); - - const auto blocking_mode{ - operation_mode == binsrv::operation_mode_type::fetch - ? easymysql::connection_replication_mode_type::non_blocking - : easymysql::connection_replication_mode_type::blocking}; - - try { - if (storage.is_in_gtid_replication_mode()) { - if (storage.is_empty()) { - static constexpr std::string_view select_gtid_purged_query{ - "SELECT @@GLOBAL.gtid_purged"}; - storage.set_purged_gtids(binsrv::gtids::gtid_set{ - connection.execute_select_query_string_result( - select_gtid_purged_query)}); - logger.log( - binsrv::log_severity::info, - "extracted purged GTIDs from the mysql server for an empty " - "storage: " + - boost::lexical_cast(storage.get_purged_gtids())); - } - - const auto gtids{storage.get_gtids()}; - const auto encoded_size{gtids.calculate_encoded_size()}; - - binsrv::gtids::gtid_set_storage encoded_gtids_buffer(encoded_size); - util::byte_span destination{encoded_gtids_buffer}; - gtids.encode_to(destination); - - connection.switch_to_gtid_replication( - server_id, util::const_byte_span{encoded_gtids_buffer}, - verify_checksum, blocking_mode); - } else { - if (storage.is_empty()) { - connection.switch_to_position_replication(server_id, verify_checksum, - blocking_mode); - } else { - connection.switch_to_position_replication( - server_id, storage.get_current_binlog_name().str(), - storage.get_current_position(), verify_checksum, blocking_mode); - } - } - } catch (const easymysql::core_error &) { - if (operation_mode == binsrv::operation_mode_type::fetch) { - throw; - } - logger.log(binsrv::log_severity::error, "unable to switch to replication"); - return false; - } - - log_replication_info(logger, server_id, storage, verify_checksum, - blocking_mode); - return true; -} - -void receive_binlog_events( - binsrv::operation_mode_type operation_mode, - const volatile std::atomic_flag &termination_flag, - binsrv::basic_logger &logger, const easymysql::library &mysql_lib, - const easymysql::connection_config &connection_config, - std::uint32_t server_id, bool verify_checksum, binsrv::storage &storage, - const binsrv::optional_rewrite_config &optional_rewrite_config) { - easymysql::connection connection{}; - if (!open_connection_and_switch_to_replication( - operation_mode, logger, mysql_lib, connection_config, server_id, - verify_checksum, storage, connection)) { - return; - } - - // Network streams are requested with COM_BINLOG_DUMP and - // each Binlog Event response is prepended with 00 OK-byte. - static constexpr std::byte expected_event_packet_prefix{'\0'}; - - util::const_byte_span portion; - - binsrv::events::reader_context context{ - connection.get_server_version(), verify_checksum, - storage.get_replication_mode(), storage.get_current_binlog_name().str(), - static_cast(storage.get_current_position())}; - - bool fetch_result{}; - - while (!termination_flag.test() && - (fetch_result = connection.fetch_binlog_event(portion)) && - !portion.empty()) { - if (portion[0] != expected_event_packet_prefix) { - util::exception_location().raise( - "unexpected event prefix"); - } - portion = portion.subspan(1U); - log_span_dump(logger, portion); - - const binsrv::events::event_view current_event_v{context, portion}; - - if (optional_rewrite_config.has_value()) { - // in rewrite mode we need to ignore ROTATE (artificial), - // FORMAT_DESCRIPTION, PREVIOUS_GTIDS_LOG, ROTATE (non-artificial), - // and STOP events - rewrite_and_process_binlog_event( - current_event_v, logger, context, storage, server_id, - optional_rewrite_config->get<"base_file_name">(), - optional_rewrite_config->get<"file_size">().get_value()); - } else { - process_binlog_event(current_event_v, logger, context, storage); - } - } - if (termination_flag.test()) { - logger.log(binsrv::log_severity::info, - "fetching binlog events loop terminated by signal"); - return; - } - if (fetch_result) { - logger.log(binsrv::log_severity::info, - "fetched everything and disconnected"); - return; - } - if (operation_mode == binsrv::operation_mode_type::fetch) { - util::exception_location().raise( - "fetch operation did not reach EOF reading binlog events"); - } - - // Truncate the in-memory event buffer to the last completed transaction so - // the persisted stream offset matches a transaction boundary. On reconnect, - // reader_context always expects the first logical event after the - // pseudo-preamble to be anonymous_gtid_log / gtid_log / gtid_tagged_log - storage.discard_incomplete_transaction_events(); - - // connection termination is a good place to flush any remaining data - // in the event buffer - this can be considered the third kind of - // checkpointing (in addition to size-based and time-based ones) - storage.flush_event_buffer(); - - logger.log(binsrv::log_severity::info, - "timed out waiting for events and disconnected"); -} - -bool wait_for_interruptable(std::uint32_t idle_time_seconds, - const volatile std::atomic_flag &termination_flag) { - // instead of - // 'std::this_thread::sleep_for(std::chrono::seconds(idle_time_seconds))' - // we do 'std::this_thread::sleep_for(1s)' '' times - // in a loop also checking for termination condition - - // standard pattern with declaring an instance of - // std::conditional_variable and waiting for it (for - // '' seconds) to be notified from the signal handler - // can be dangerous as the chances of signal handler being called on the - // same thread as this one ('main()') are pretty big. - for (std::uint32_t sleep_iteration{0U}; - sleep_iteration < idle_time_seconds && !termination_flag.test(); - ++sleep_iteration) { - std::this_thread::sleep_for(std::chrono::seconds(1U)); - } - return !termination_flag.test(); -} - -bool handle_version() { - std::cout << app_version.get_string() << '\n'; - return true; -} - -// shared by all 'handle_*' subcommands that build a 'search_response': -// translates a binlog record kept inside 'binsrv::storage' into -// a record of the response model -void append_record_to_response(binsrv::models::search_response &response, - const binsrv::storage &storage, - const auto &record) { - response.add_record(record.name.str(), record.size, - storage.get_binlog_uri(record.name), - record.previous_gtids, record.added_gtids, - record.timestamps.get_min_timestamp().get_value(), - record.timestamps.get_max_timestamp().get_value()); -} - -bool handle_list(std::string_view config_file_path) { - bool operation_successful{false}; - std::string result; - - try { - const binsrv::main_config config{config_file_path}; - const auto &storage_config = config.root().get<"storage">(); - const auto &replication_config = config.root().get<"replication">(); - const auto replication_mode{replication_config.get<"mode">()}; - - const binsrv::storage storage{ - storage_config, binsrv::storage_construction_mode_type::querying_only, - replication_mode}; - - binsrv::models::search_response response; - for (const auto &record : storage.get_binlog_records()) { - append_record_to_response(response, storage, record); - } - result = response.str(); - operation_successful = true; - } catch (const std::exception &e) { - const binsrv::models::error_response response{e.what()}; - result = response.str(); - } - std::cout << result << '\n'; - return operation_successful; -} - -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -bool handle_search_by_timestamp(std::string_view config_file_path, - std::string_view subcommand_value) { - bool operation_successful{false}; - std::string result; - - try { - util::ctime_timestamp timestamp; - if (!util::ctime_timestamp::try_parse(subcommand_value, timestamp)) { - throw std::runtime_error("Invalid timestamp format"); - } - - const binsrv::main_config config{config_file_path}; - const auto &storage_config = config.root().get<"storage">(); - const auto &replication_config = config.root().get<"replication">(); - const auto replication_mode{replication_config.get<"mode">()}; - - const binsrv::storage storage{ - storage_config, binsrv::storage_construction_mode_type::querying_only, - replication_mode}; - - binsrv::models::search_response response; - const auto &binlog_records{storage.get_binlog_records()}; - if (binlog_records.empty()) { - throw std::runtime_error("Binlog storage is empty"); - } - for (const auto &record : binlog_records) { - // break when we find a binlog file with min timestamp greater - // than the provided one - if (record.timestamps.get_min_timestamp() > timestamp) { - break; - } - append_record_to_response(response, storage, record); - } - if (response.root().get<"result">().empty()) { - throw std::runtime_error("Timestamp is too old"); - } - result = response.str(); - operation_successful = true; - } catch (const std::exception &e) { - const binsrv::models::error_response response{e.what()}; - result = response.str(); - } - std::cout << result << '\n'; - return operation_successful; -} - -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -bool handle_purge_binlogs(std::string_view config_file_path, - std::string_view subcommand_value) { - bool operation_successful{false}; - std::string result; - - try { - const auto target_name{ - binsrv::events::composite_binlog_name::parse(subcommand_value)}; - - const binsrv::main_config config{config_file_path}; - const auto &storage_config = config.root().get<"storage">(); - const auto &replication_config = config.root().get<"replication">(); - const auto replication_mode{replication_config.get<"mode">()}; - - binsrv::storage storage{storage_config, - binsrv::storage_construction_mode_type::purging, - replication_mode}; - - const auto [removed_records, cleanup_warning_message] = - storage.purge_binlogs(target_name); - - // The step-2 index rewrite has already committed by the time - // 'purge_binlogs' returns normally; if its best-effort step-3 - // cleanup left orphan payload/metadata objects on disk, the - // call reports it via a non-empty 'cleanup_warning_message' and - // we surface that as a 'warning' status (instead of plain - // 'success'), with the underlying error message attached, so the - // operator knows the storage will need attention before the next - // 'fetch' / 'pull' run. - binsrv::models::search_response response; - if (!cleanup_warning_message.empty()) { - response = binsrv::models::search_response{ - binsrv::models::response_status_type::warning, - cleanup_warning_message}; - } - for (const auto &record : removed_records) { - append_record_to_response(response, storage, record); - } - result = response.str(); - operation_successful = true; - } catch (const std::exception &e) { - const binsrv::models::error_response response{e.what()}; - result = response.str(); - } - std::cout << result << '\n'; - return operation_successful; -} - -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -bool handle_search_by_gtid_set(std::string_view config_file_path, - std::string_view subcommand_value) { - bool operation_successful{false}; - std::string result; +int main(int argc, char *argv[]) { + const auto cmd_args{util::to_command_line_agg_view(argc, argv)}; + operations::basic_operation_ptr operation{}; try { - binsrv::gtids::gtid_set remaining_gtids{subcommand_value}; - - const binsrv::main_config config{config_file_path}; - const auto &storage_config = config.root().get<"storage">(); - const auto &replication_config = config.root().get<"replication">(); - const auto replication_mode{replication_config.get<"mode">()}; - - const binsrv::storage storage{ - storage_config, binsrv::storage_construction_mode_type::querying_only, - replication_mode}; - - const auto &binlog_records{storage.get_binlog_records()}; - if (binlog_records.empty()) { - throw std::runtime_error("Binlog storage is empty"); - } - - binsrv::models::search_response response; - if (!storage.is_in_gtid_replication_mode()) { - throw std::runtime_error("GTID set search is not supported in storages " - "created in position-based replication mode"); - } - - for (const auto &record : binlog_records) { - if (remaining_gtids.is_empty()) { - break; - } - if (!record.added_gtids.has_value()) { - continue; - } - if (!binsrv::gtids::intersects(remaining_gtids, *record.added_gtids)) { - continue; - } - remaining_gtids.subtract(*record.added_gtids); - - append_record_to_response(response, storage, record); - } - if (!remaining_gtids.is_empty()) { - throw std::runtime_error("The specified GTID set cannot be covered"); - } - result = response.str(); - operation_successful = true; + operation = operations::operation_factory::create(cmd_args); } catch (const std::exception &e) { - const binsrv::models::error_response response{e.what()}; - result = response.str(); - } - std::cout << result << '\n'; - return operation_successful; -} - -// dispatcher for the read-only subcommands that do not need logger / signal -// handler / replication setup; returns std::nullopt for streaming modes -// ('fetch' and 'pull') and the handler's success flag otherwise -std::optional -dispatch_stateless_command(binsrv::operation_mode_type operation_mode, - std::string_view config_file_path, - std::string_view subcommand_value) { - switch (operation_mode) { - case binsrv::operation_mode_type::version: - return handle_version(); - case binsrv::operation_mode_type::list: - return handle_list(config_file_path); - case binsrv::operation_mode_type::search_by_timestamp: - return handle_search_by_timestamp(config_file_path, subcommand_value); - case binsrv::operation_mode_type::search_by_gtid_set: - return handle_search_by_gtid_set(config_file_path, subcommand_value); - case binsrv::operation_mode_type::purge_binlogs: - return handle_purge_binlogs(config_file_path, subcommand_value); - default: - return std::nullopt; + std::cerr << e.what() << '\n'; } -} - -// since c++20 it is no longer needed to initialize std::atomic_flag with -// ATOMIC_FLAG_INIT as this flag is modified from a signal handler it is marked -// as volatile to make sure optimizer do optimizations which will be unsafe for -// this scenario -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -volatile std::atomic_flag global_termination_flag{}; - -} // anonymous namespace -extern "C" void custom_signal_handler(int /*signo*/) { - global_termination_flag.test_and_set(); -} - -int main(int argc, char *argv[]) { - using namespace std::string_literals; - const auto cmd_args = util::to_command_line_agg_view(argc, argv); - const auto executable_name = util::extract_executable_name(cmd_args); - - binsrv::operation_mode_type operation_mode{ - binsrv::operation_mode_type::delimiter}; - std::string_view config_file_path; - std::string_view subcommand_value; - const auto cmd_args_checked{check_cmd_args( - cmd_args, operation_mode, config_file_path, subcommand_value)}; - if (!cmd_args_checked) { + if (!operation) { + const auto executable_name{util::extract_executable_name(cmd_args)}; std::cerr << "usage: " << executable_name << " (fetch|pull)) \n" << " " << executable_name << " list \n" @@ -1225,145 +47,5 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - // handling the read-only subcommands ('version', 'list', 'search_by_*') - if (const auto stateless_result{dispatch_stateless_command( - operation_mode, config_file_path, subcommand_value)}; - stateless_result.has_value()) { - return *stateless_result ? EXIT_SUCCESS : EXIT_FAILURE; - } - - int exit_code = EXIT_FAILURE; - - binsrv::basic_logger_ptr logger; - - try { - static constexpr auto default_log_level = binsrv::log_severity::trace; - - const binsrv::logger_config initial_logger_config{ - {{default_log_level}, {""}}}; - - logger = binsrv::logger_factory::create(initial_logger_config); - // logging with "delimiter" level has the highest priority and empty label - logger->log(binsrv::log_severity::delimiter, - '"' + executable_name + '"' + - " started with the following command line arguments:"); - logger->log(binsrv::log_severity::delimiter, - util::get_readable_command_line_arguments(cmd_args)); - - logger->log(binsrv::log_severity::delimiter, - "reading configuration from the JSON file."); - const binsrv::main_config config{config_file_path}; - - const auto &logger_config = config.root().get<"logger">(); - if (!logger_config.has_file()) { - logger->set_min_level(logger_config.get<"level">()); - } else { - logger->log(binsrv::log_severity::delimiter, - "redirecting logging to \"" + logger_config.get<"file">() + - "\""); - auto new_logger = binsrv::logger_factory::create(logger_config); - std::swap(logger, new_logger); - } - - const auto log_level_label = - binsrv::to_string_view(logger->get_min_level()); - logger->log(binsrv::log_severity::delimiter, - "logging level set to \""s + std::string{log_level_label} + - '"'); - - logger->log(binsrv::log_severity::delimiter, - "application version: " + app_version.get_string()); - - assert(operation_mode == binsrv::operation_mode_type::fetch || - operation_mode == binsrv::operation_mode_type::pull); - std::string msg; - msg = '\''; - msg += boost::lexical_cast(operation_mode); - msg += "' operation mode specified"; - logger->log(binsrv::log_severity::delimiter, msg); - - // setting custom SIGINT and SIGTERM signal handlers - if (std::signal(SIGTERM, &custom_signal_handler) == SIG_ERR) { - util::exception_location().raise( - "cannot set custom signal handler for SIGTERM"); - } - if (std::signal(SIGINT, &custom_signal_handler) == SIG_ERR) { - util::exception_location().raise( - "cannot set custom signal handler for SIGINT"); - } - - logger->log(binsrv::log_severity::info, - "set custom handlers for SIGINT and SIGTERM signals"); - const volatile std::atomic_flag &termination_flag{global_termination_flag}; - - const auto &storage_config = config.root().get<"storage">(); - log_storage_config_info(*logger, storage_config); - - const auto &connection_config = config.root().get<"connection">(); - log_connection_config_info(*logger, connection_config); - - const auto &replication_config = config.root().get<"replication">(); - log_replication_config_info(*logger, replication_config); - - const auto server_id{replication_config.get<"server_id">()}; - const auto idle_time_seconds{replication_config.get<"idle_time">()}; - const auto verify_checksum{replication_config.get<"verify_checksum">()}; - const auto replication_mode{replication_config.get<"mode">()}; - const auto optional_rewrite_config{replication_config.get<"rewrite">()}; - - binsrv::storage storage{storage_config, - binsrv::storage_construction_mode_type::streaming, - replication_mode}; - log_storage_info(*logger, storage); - - const easymysql::library mysql_lib; - logger->log(binsrv::log_severity::info, "initialized mysql client library"); - - log_library_info(*logger, mysql_lib); - - receive_binlog_events(operation_mode, termination_flag, *logger, mysql_lib, - connection_config, server_id, verify_checksum, - storage, optional_rewrite_config); - - if (operation_mode == binsrv::operation_mode_type::pull) { - std::size_t iteration_number{1U}; - while (!termination_flag.test()) { - msg = "entering idle mode for "; - msg += std::to_string(idle_time_seconds); - msg += " seconds"; - logger->log(binsrv::log_severity::info, msg); - - if (!wait_for_interruptable(idle_time_seconds, termination_flag)) { - break; - } - - msg = "awoke after sleeping and trying to reconnect (iteration "; - msg += std::to_string(iteration_number); - msg += ')'; - logger->log(binsrv::log_severity::info, msg); - - receive_binlog_events(operation_mode, termination_flag, *logger, - mysql_lib, connection_config, server_id, - verify_checksum, storage, - optional_rewrite_config); - ++iteration_number; - } - } - - if (termination_flag.test()) { - logger->log( - binsrv::log_severity::info, - "successfully shut down after receiving a termination signal"); - } else { - logger->log( - binsrv::log_severity::info, - "successfully shut down after finishing the requested operation"); - } - - exit_code = EXIT_SUCCESS; - } catch (...) { - handle_std_exception(logger); - } - - return exit_code; + return (operation->execute() ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/binsrv/operation_mode_type.hpp b/src/binsrv/operation_mode_type.hpp deleted file mode 100644 index aa1827e..0000000 --- a/src/binsrv/operation_mode_type.hpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2023-2024 Percona and/or its affiliates. -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 2.0, -// as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License, version 2.0, for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -#ifndef BINSRV_OPERATION_MODE_TYPE_HPP -#define BINSRV_OPERATION_MODE_TYPE_HPP - -#include "binsrv/operation_mode_type_fwd.hpp" // IWYU pragma: export - -#include -#include -#include -#include -#include -#include - -#include "util/conversion_helpers.hpp" - -namespace binsrv { - -// NOLINTBEGIN(cppcoreguidelines-macro-usage) -// clang-format off -#define BINSRV_OPERATION_MODE_TYPE_X_SEQUENCE() \ - BINSRV_OPERATION_MODE_TYPE_X_MACRO(fetch ), \ - BINSRV_OPERATION_MODE_TYPE_X_MACRO(pull ), \ - BINSRV_OPERATION_MODE_TYPE_X_MACRO(list ), \ - BINSRV_OPERATION_MODE_TYPE_X_MACRO(search_by_timestamp), \ - BINSRV_OPERATION_MODE_TYPE_X_MACRO(search_by_gtid_set ), \ - BINSRV_OPERATION_MODE_TYPE_X_MACRO(purge_binlogs ), \ - BINSRV_OPERATION_MODE_TYPE_X_MACRO(version ) -// clang-format on - -#define BINSRV_OPERATION_MODE_TYPE_X_MACRO(X) X -enum class operation_mode_type : std::uint8_t { - BINSRV_OPERATION_MODE_TYPE_X_SEQUENCE(), - delimiter -}; -#undef BINSRV_OPERATION_MODE_TYPE_X_MACRO - -inline std::string_view -to_string_view(operation_mode_type operation_mode) noexcept { - using namespace std::string_view_literals; -#define BINSRV_OPERATION_MODE_TYPE_X_MACRO(X) #X##sv - static constexpr std::array labels{BINSRV_OPERATION_MODE_TYPE_X_SEQUENCE(), - ""sv}; -#undef BINSRV_OPERATION_MODE_TYPE_X_MACRO - const auto index{util::enum_to_index( - std::min(operation_mode_type::delimiter, operation_mode))}; - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - return labels[index]; -} -#undef BINSRV_OPERATION_MODE_TYPE_X_SEQUENCE -// NOLINTEND(cppcoreguidelines-macro-usage) - -template - requires std::same_as -std::basic_ostream & -operator<<(std::basic_ostream &output, - operation_mode_type operation_mode) { - return output << to_string_view(operation_mode); -} - -template - requires std::same_as -std::basic_istream & -operator>>(std::basic_istream &input, - operation_mode_type &operation_mode) { - std::string operation_mode_str; - input >> operation_mode_str; - if (!input) { - return input; - } - std::size_t index{0U}; - const auto max_index = util::enum_to_index(operation_mode_type::delimiter); - while (index < max_index && - to_string_view(util::index_to_enum(index)) != - operation_mode_str) { - ++index; - } - if (index < max_index) { - operation_mode = util::index_to_enum(index); - } else { - input.setstate(std::ios_base::failbit); - } - return input; -} - -} // namespace binsrv - -#endif // BINSRV_OPERATION_MODE_TYPE_HPP diff --git a/src/binsrv/storage.hpp b/src/binsrv/storage.hpp index 192d41c..0722590 100644 --- a/src/binsrv/storage.hpp +++ b/src/binsrv/storage.hpp @@ -42,7 +42,7 @@ namespace binsrv { class [[nodiscard]] storage { -private: +public: struct binlog_record { // binlog file name events::composite_binlog_name name; @@ -60,7 +60,6 @@ class [[nodiscard]] storage { }; using binlog_record_container = std::vector; -public: static constexpr std::string_view default_binlog_index_name{"binlog.index"}; static constexpr std::string_view default_binlog_index_entry_path{"."}; static constexpr std::string_view metadata_name{"metadata.json"}; diff --git a/src/operations/basic_operation.cpp b/src/operations/basic_operation.cpp new file mode 100644 index 0000000..b1abccb --- /dev/null +++ b/src/operations/basic_operation.cpp @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/basic_operation.hpp" + +#include +#include +#include + +#include "operations/mode_type.hpp" + +#include "util/command_line_helpers_fwd.hpp" +#include "util/exception_location_helpers.hpp" + +namespace operations { + +basic_operation::basic_operation(mode_type mode, + util::command_line_arg_view cmd_args, + std::size_t expected_number_of_arguments) + : mode_{mode}, cmd_args_{cmd_args} { + // +2 is needed because 'cmd_args' always includes the name of the executable + // and it should also include the name of the operation + if (std::size(cmd_args) != expected_number_of_arguments + 2UZ) { + util::exception_location().raise( + "unexpected number of command line arguments for the '" + + std::string{to_string_view(mode)} + "' operation"); + } +} + +} // namespace operations diff --git a/src/operations/basic_operation.hpp b/src/operations/basic_operation.hpp new file mode 100644 index 0000000..0dbd2d4 --- /dev/null +++ b/src/operations/basic_operation.hpp @@ -0,0 +1,56 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_BASIC_OPERATION_HPP +#define OPERATIONS_BASIC_OPERATION_HPP + +#include "operations/basic_operation_fwd.hpp" // IWYU pragma: export + +#include + +#include "operations/mode_type_fwd.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +class basic_operation { +public: + basic_operation(const basic_operation &) = delete; + basic_operation &operator=(const basic_operation &) = delete; + basic_operation(basic_operation &&) = delete; + basic_operation &operator=(basic_operation &&) = delete; + + virtual ~basic_operation() = default; + + [[nodiscard]] virtual bool execute() const = 0; + +protected: + basic_operation(mode_type mode, util::command_line_arg_view cmd_args, + std::size_t expected_number_of_arguments); + + [[nodiscard]] util::command_line_arg_view get_cmd_args() const noexcept { + return cmd_args_; + } + [[nodiscard]] mode_type get_mode() const noexcept { return mode_; } + +private: + mode_type mode_; + util::command_line_arg_view cmd_args_; +}; + +} // namespace operations + +#endif // OPERATIONS_BASIC_OPERATION_HPP diff --git a/src/operations/basic_operation_fwd.hpp b/src/operations/basic_operation_fwd.hpp new file mode 100644 index 0000000..43d17a6 --- /dev/null +++ b/src/operations/basic_operation_fwd.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_BASIC_OPERATION_FWD_HPP +#define OPERATIONS_BASIC_OPERATION_FWD_HPP + +#include + +namespace operations { + +class basic_operation; + +using basic_operation_ptr = std::unique_ptr; + +} // namespace operations + +#endif // OPERATIONS_BASIC_OPERATION_FWD_HPP diff --git a/src/operations/event_generation_helpers.cpp b/src/operations/event_generation_helpers.cpp new file mode 100644 index 0000000..4c8e90d --- /dev/null +++ b/src/operations/event_generation_helpers.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "operations/event_generation_helpers.hpp" + +#include + +#include "binsrv/events/checksum_algorithm_type.hpp" +#include "binsrv/events/code_type.hpp" +#include "binsrv/events/common_header_flag_type.hpp" +#include "binsrv/events/composite_binlog_name_fwd.hpp" +#include "binsrv/events/event.hpp" +#include "binsrv/events/event_view.hpp" +#include "binsrv/events/protocol_traits_fwd.hpp" +#include "binsrv/events/reader_context.hpp" + +#include "binsrv/gtids/gtid_set_fwd.hpp" + +#include "util/ctime_timestamp.hpp" +#include "util/semantic_version.hpp" + +namespace operations { + +[[nodiscard]] binsrv::events::event_view generate_rotate_event( + binsrv::events::event_storage &event_buffer, + const binsrv::events::reader_context &context, std::uint32_t offset, + bool current_timestamp, std::uint32_t server_id, bool artificial, + const binsrv::events::composite_binlog_name &binlog_name) { + const binsrv::events::generic_post_header + post_header{binsrv::events::magic_binlog_offset}; + const binsrv::events::generic_body body{ + binlog_name}; + + util::ctime_timestamp timestamp{}; + if (current_timestamp) { + timestamp = util::ctime_timestamp::now(); + } + + binsrv::events::common_header_flag_set flags{}; + if (artificial) { + flags |= binsrv::events::common_header_flag_type::artificial; + } + + // the value of the 'include_checksum' parameters is taken from the + // 'reader_context': immediately after reconnection it will be equal to + // the '' configuration parameter and after + // that will be taken from the FORMAT_DESCRIPTION events, which in the + // rewrite mode will be generated by us and therefore will always include + // 'checksum_algorithm' set to 'crc32' + const auto generated_event{ + binsrv::events::event::create_event( + offset, timestamp, server_id, flags, post_header, body, + context.is_footer_expected(), event_buffer)}; + + return binsrv::events::event_view{context, + util::const_byte_span{event_buffer}}; +} + +[[nodiscard]] binsrv::events::event_view +generate_format_description_event(binsrv::events::event_storage &event_buffer, + const binsrv::events::reader_context &context, + std::uint32_t offset, + std::uint32_t server_id) { + const util::semantic_version server_version{ + context.get_current_encoded_server_version()}; + const binsrv::events::generic_post_header< + binsrv::events::code_type::format_description> + post_header{ + binsrv::events::default_binlog_version, server_version, + util::ctime_timestamp::now(), + binsrv::events::default_common_header_length, + binsrv::events::reader_context::get_hardcoded_post_header_lengths( + server_version.get_encoded())}; + const binsrv::events::generic_body< + binsrv::events::code_type::format_description> + body{binsrv::events::checksum_algorithm_type::crc32}; + + // enforcing checksums for all rewritten upcoming events + const auto generated_event{binsrv::events::event::create_event< + binsrv::events::code_type::format_description>( + offset, util::ctime_timestamp::now(), server_id, + binsrv::events::common_header_flag_set{}, post_header, body, + true /* include_checksum */, event_buffer)}; + + return binsrv::events::event_view{context, + util::const_byte_span{event_buffer}}; +} + +[[nodiscard]] binsrv::events::event_view +generate_previous_gtids_log_event(binsrv::events::event_storage &event_buffer, + const binsrv::events::reader_context &context, + std::uint32_t offset, std::uint32_t server_id, + const binsrv::gtids::gtid_set >ids) { + const binsrv::events::generic_post_header< + binsrv::events::code_type::previous_gtids_log> + post_header{}; + const binsrv::events::generic_body< + binsrv::events::code_type::previous_gtids_log> + body{gtids}; + const auto generated_previous_gtids_log_event{ + binsrv::events::event::create_event< + binsrv::events::code_type::previous_gtids_log>( + offset, util::ctime_timestamp::now(), server_id, + binsrv::events::common_header_flag_set{}, post_header, body, true, + event_buffer)}; + + return binsrv::events::event_view{context, + util::const_byte_span{event_buffer}}; +} + +} // namespace operations diff --git a/src/operations/event_generation_helpers.hpp b/src/operations/event_generation_helpers.hpp new file mode 100644 index 0000000..d8e952b --- /dev/null +++ b/src/operations/event_generation_helpers.hpp @@ -0,0 +1,51 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_EVENT_GENERATION_HELPERS_HPP +#define OPERATIONS_EVENT_GENERATION_HELPERS_HPP + +#include + +#include "binsrv/events/composite_binlog_name_fwd.hpp" +#include "binsrv/events/event_fwd.hpp" +#include "binsrv/events/event_view_fwd.hpp" +#include "binsrv/events/reader_context_fwd.hpp" + +#include "binsrv/gtids/gtid_set_fwd.hpp" + +namespace operations { + +[[nodiscard]] binsrv::events::event_view +generate_rotate_event(binsrv::events::event_storage &event_buffer, + const binsrv::events::reader_context &context, + std::uint32_t offset, bool current_timestamp, + std::uint32_t server_id, bool artificial, + const binsrv::events::composite_binlog_name &binlog_name); + +[[nodiscard]] binsrv::events::event_view +generate_format_description_event(binsrv::events::event_storage &event_buffer, + const binsrv::events::reader_context &context, + std::uint32_t offset, + std::uint32_t server_id); + +[[nodiscard]] binsrv::events::event_view +generate_previous_gtids_log_event(binsrv::events::event_storage &event_buffer, + const binsrv::events::reader_context &context, + std::uint32_t offset, std::uint32_t server_id, + const binsrv::gtids::gtid_set >ids); + +} // namespace operations + +#endif // OPERATIONS_EVENT_GENERATION_HELPERS_HPP diff --git a/src/operations/fetch_operation.hpp b/src/operations/fetch_operation.hpp new file mode 100644 index 0000000..b9861f5 --- /dev/null +++ b/src/operations/fetch_operation.hpp @@ -0,0 +1,35 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_FETCH_OPERATION_HPP +#define OPERATIONS_FETCH_OPERATION_HPP + +#include "operations/generic_operation_fwd.hpp" + +#include "operations/fetch_pull_operation.hpp" +#include "operations/mode_type.hpp" + +namespace operations { + +template <> +class generic_operation : public fetch_pull_operation { +public: + explicit generic_operation(util::command_line_arg_view cmd_args) + : fetch_pull_operation{mode_type::fetch, cmd_args} {} +}; + +} // namespace operations + +#endif // OPERATIONS_FETCH_OPERATION_HPP diff --git a/src/operations/fetch_pull_operation.cpp b/src/operations/fetch_pull_operation.cpp new file mode 100644 index 0000000..b2c048e --- /dev/null +++ b/src/operations/fetch_pull_operation.cpp @@ -0,0 +1,706 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/fetch_pull_operation.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "app_version.hpp" + +#include "binsrv/basic_logger.hpp" +#include "binsrv/exception_handling_helpers.hpp" +#include "binsrv/log_severity.hpp" +#include "binsrv/logger_factory.hpp" +#include "binsrv/main_config.hpp" +#include "binsrv/replication_mode_type.hpp" +#include "binsrv/storage.hpp" + +#include "binsrv/events/code_type.hpp" +#include "binsrv/events/common_header_flag_type.hpp" +#include "binsrv/events/event.hpp" +#include "binsrv/events/event_view.hpp" +#include "binsrv/events/protocol_traits_fwd.hpp" +#include "binsrv/events/reader_context.hpp" +#include "binsrv/events/rewriter.hpp" + +#include "binsrv/gtids/common_types.hpp" +#include "binsrv/gtids/gtid_set.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/event_generation_helpers.hpp" +#include "operations/logger_helpers.hpp" +#include "operations/mode_type.hpp" + +#include "easymysql/connection.hpp" +#include "easymysql/connection_config.hpp" +#include "easymysql/core_error.hpp" +#include "easymysql/library.hpp" + +#include "util/byte_span_fwd.hpp" +#include "util/command_line_helpers.hpp" +#include "util/exception_location_helpers.hpp" +#include "util/semantic_version.hpp" + +namespace operations { + +namespace { + +void process_artificial_rotate_event( + const binsrv::events::event_view ¤t_event_v, + binsrv::basic_logger &logger, binsrv::storage &storage) { + assert(current_event_v.get_common_header_view().get_type_code() == + binsrv::events::code_type::rotate); + assert(current_event_v.get_common_header_view().get_flags().has_element( + binsrv::events::common_header_flag_type::artificial)); + + const binsrv::events::generic_body + current_rotate_body{current_event_v.get_body_raw()}; + + bool binlog_opening_needed{true}; + + if (storage.is_binlog_open()) { + // here we take a "shortcut" path - upon losing connection to the MySQL + // server, we do not close storage's binlog file immediately expecting + // that upon reconnection we will be able to continue writing to the + // same file + + // so, here we just need to make sure that (binlog name, position) pair + // in the artificial ROTATE event matches the current storage state + + // also, in case when the server was not shut down properly, it won't + // have ROTATE or STOP event as the last one in the binlog, so here we + // handle this case by closing the old binlog and opening a new one + + if (current_rotate_body.get_parsed_binlog() == + storage.get_current_binlog_name()) { + // in addition, in position-based replication mode we also need to check + // the position + if (storage.get_replication_mode() == + binsrv::replication_mode_type::position) { + const binsrv::events::generic_post_header< + binsrv::events::code_type::rotate> + current_rotate_post_header{current_event_v.get_post_header_raw()}; + + if (current_rotate_post_header.get_position_raw() != + storage.get_current_position()) { + util::exception_location().raise( + "unexpected binlog position in artificial rotate event"); + } + } + + binlog_opening_needed = false; + + const std::string current_binlog_name{ + storage.get_current_binlog_name().str()}; + logger.log(binsrv::log_severity::info, + "storage: reused already open binlog file: " + + current_binlog_name); + + } else { + // if names do not match, we need to close the currently open + // binlog and make sure that binlog_opening_needed is set to true, so + // that we will open a new one later + const std::string old_binlog_name{ + storage.get_current_binlog_name().str()}; + storage.close_binlog(); + logger.log(binsrv::log_severity::info, + "storage: closed binlog file left open: " + old_binlog_name); + // binlog_opening_needed remains true in this branch + assert(binlog_opening_needed); + } + } + if (binlog_opening_needed) { + const auto binlog_open_result{ + storage.open_binlog(current_rotate_body.get_parsed_binlog())}; + + std::string message{"storage: "}; + if (binlog_open_result == binsrv::open_binlog_status::created) { + message += "created a new"; + } else { + message += "opened an existing"; + if (binlog_open_result == binsrv::open_binlog_status::opened_empty) { + message += " (empty)"; + } else if (binlog_open_result == + binsrv::open_binlog_status::opened_at_magic_payload_offset) { + message += " (with magic payload only)"; + } + } + message += " binlog file: "; + message += current_rotate_body.get_readable_binlog(); + logger.log(binsrv::log_severity::info, message); + } +} + +void process_rotate_or_stop_event(binsrv::basic_logger &logger, + binsrv::storage &storage) { + const std::string old_binlog_name{storage.get_current_binlog_name().str()}; + storage.close_binlog(); + logger.log(binsrv::log_severity::info, + "storage: closed binlog file: " + old_binlog_name); +} + +void process_binlog_event(const binsrv::events::event_view ¤t_event_v, + binsrv::basic_logger &logger, + binsrv::events::reader_context &context, + binsrv::storage &storage) { + const auto current_common_header_v{current_event_v.get_common_header_view()}; + const auto readable_flags{current_common_header_v.get_readable_flags()}; + logger.log(binsrv::log_severity::info, + "event : " + + std::string{current_common_header_v.get_readable_type_code()} + + (readable_flags.empty() ? "" : " (" + readable_flags + ")")); + logger.log(binsrv::log_severity::debug, + "event : [parsed view] " + + boost::lexical_cast(current_event_v)); + + const bool info_only{context.process_event_view(current_event_v)}; + + if (info_only) { + logger.log( + binsrv::log_severity::info, + "event : [info_only] - will not be written to the binary log file"); + } + + if (context.is_at_transaction_boundary()) { + logger.log( + binsrv::log_severity::info, + "event : [end_of_transaction] " + + boost::lexical_cast(context.get_transaction_gtid())); + } + + // here we additionally check for log level because event materialization + // is not a trivial operation + if (binsrv::log_severity::debug >= logger.get_min_level()) { + const binsrv::events::event current_event{current_event_v}; + logger.log(binsrv::log_severity::debug, + "event : [parsed] " + + boost::lexical_cast(current_event)); + } + + const auto code = current_common_header_v.get_type_code(); + const auto is_artificial{current_common_header_v.get_flags().has_element( + binsrv::events::common_header_flag_type::artificial)}; + + // processing the very first event in the sequence - artificial ROTATE event + if (code == binsrv::events::code_type::rotate && is_artificial) { + process_artificial_rotate_event(current_event_v, logger, storage); + } + + // checking if the event needs to be written to the binlog + if (!info_only) { + storage.write_event( + current_event_v.get_portion(), context.is_at_transaction_boundary(), + context.get_transaction_gtid(), current_common_header_v.get_timestamp(), + context.get_transaction_sequence_number()); + } + + // processing the very last event in the sequence - either a non-artificial + // ROTATE event or a STOP event. This is the path that closes the local + // binlog file and (via storage::close_binlog -> flush_event_buffer) is + // what guarantees the terminator event itself lands on the backend + if ((code == binsrv::events::code_type::rotate && !is_artificial) || + code == binsrv::events::code_type::stop) { + process_rotate_or_stop_event(logger, storage); + } +} + +void rewrite_and_process_binlog_event( + const binsrv::events::event_view ¤t_event_v, + binsrv::basic_logger &logger, binsrv::events::reader_context &context, + binsrv::storage &storage, std::uint32_t server_id, + std::string_view base_file_name, std::uint64_t file_size) { + assert(storage.is_in_gtid_replication_mode()); + const auto current_common_header_v = current_event_v.get_common_header_view(); + const auto code = current_common_header_v.get_type_code(); + + // for ROTATE (both artificial and non-artificial), FORMAT_DESCRIPTION, + // PREVIOUS_GTIDS_LOG, and STOP events we don't have to do anything - + // simply return early from this function + if (code == binsrv::events::code_type::format_description || + code == binsrv::events::code_type::previous_gtids_log || + code == binsrv::events::code_type::rotate || + code == binsrv::events::code_type::stop) { + // making sure that there will be no events without checksums in the rewrite + // mode because when recalculating the value of 'transaction_length' field + // in GTID events we rely on the fact that upcoming events from the same + // transaction will not change their size after rewriting (currently, to + // avoid scenarios when one binlog file with checksums enabled is followed + // by another file without checksums, and we have to combine them in the + // same storage binlog file, we enforce that all events in the rewrite mode + // must have checksums) + + // TODO: this restriction can be lifted if we implement whole transaction + // rewrite logic (we do not add incomplete transaction events into + // the storage buffer unless we receive all of them and perform + // necessary checksum addition/removal) + if (code == binsrv::events::code_type::format_description) { + const auto format_description_body{binsrv::events::generic_body< + binsrv::events::code_type::format_description>{ + current_event_v.get_body_raw()}}; + if (!format_description_body.has_checksum_algorithm()) { + util::exception_location().raise( + "rewrite is supported in gtid replication mode only when " + "all events received from the MySQL server have checksums"); + } + } + + const auto readable_flags{current_common_header_v.get_readable_flags()}; + logger.log( + binsrv::log_severity::info, + "rewrite: encountered " + + std::string{current_common_header_v.get_readable_type_code()} + + (readable_flags.empty() ? "" : " (" + readable_flags + ")") + + " event in the rewrite mode - skipping"); + return; + } + + // the very first step is to check if we need to close the old binary log + // file and open a new one in case when we reached the file size specified + // in the 'rewrite_config' or this is the very first event we are going to + // write to an empty storage + + // in case of an empty storage we need to generate the following: + // 1. ROTATE(artificial ) .000001:4 + // 2. FORMAT_DESCRIPTION + // 3. PREVIOUS_GTIDS_LOG + + // in case when the storage is not empty, we are at transaction boundary, + // and current binlog file reached the file size specified in the + // 'rewrite_config', we need to generate the following: + // 0. ROTATE(non-artificial) .:4 + // 1. ROTATE(artificial ) .:4 + // 2. FORMAT_DESCRIPTION + // 3. PREVIOUS_GTIDS_LOG + + if (context.is_fresh() || (context.is_at_transaction_boundary() && + storage.get_current_position() >= file_size)) { + binsrv::events::event_storage event_buffer; + std::uint32_t offset{0U}; + + // generating next binlog file name based on base file name from the + // configuration file and current binlog file + // sequence number from the storage + + // please notice that if storage is empty, then the sequence number will be + // zero + binsrv::events::composite_binlog_name binlog_name{}; + if (storage.is_empty()) { + // the very first time we receive an event on an empty storage + binlog_name = binsrv::events::composite_binlog_name{base_file_name, 1U}; + } else if (context.is_fresh()) { + // this is the very first event we received after reconnection + // (the storage is not empty and we have an active binlog in it) + binlog_name = storage.get_current_binlog_name(); + } else { + // we are at transaction boundary and reached max binlog file size + binlog_name = storage.get_current_binlog_name().next(); + } + + if (!context.is_fresh()) { + // generate and process ROTATE(non-artificial) event + offset = static_cast(storage.get_current_position()); + const auto generated_rotate_event_v{operations::generate_rotate_event( + event_buffer, context, offset, true /* current timestamp */, + server_id, false /* non-artificial */, binlog_name)}; + logger.log(binsrv::log_severity::info, + "rewrite: generated rotate event in the rewrite mode"); + process_binlog_event(generated_rotate_event_v, logger, context, storage); + } + + // generate and process ROTATE(artificial) event + offset = 0U; + // artificial ROTATE event must include zero timestamp + const auto generated_artificial_rotate_event_v{ + operations::generate_rotate_event(event_buffer, context, offset, + false /* zero timestamp */, server_id, + true /* artificial */, binlog_name)}; + logger.log( + binsrv::log_severity::info, + "rewrite: generated artificial rotate event in the rewrite mode"); + process_binlog_event(generated_artificial_rotate_event_v, logger, context, + storage); + + // generate and process FORMAT_DESCRIPTION event + offset = binsrv::events::magic_binlog_offset; + const auto generated_format_description_event_v{ + operations::generate_format_description_event(event_buffer, context, + offset, server_id)}; + logger.log( + binsrv::log_severity::info, + "rewrite: generated format description event in the rewrite mode"); + process_binlog_event(generated_format_description_event_v, logger, context, + storage); + + // generate and process PREVIOUS_GTIDS_LOG event + offset += static_cast( + generated_format_description_event_v.get_total_size()); + const auto generated_previous_gtids_log_event_v{ + operations::generate_previous_gtids_log_event( + event_buffer, context, offset, server_id, storage.get_gtids())}; + logger.log( + binsrv::log_severity::info, + "rewrite: generated previous gtids log event in the rewrite mode"); + process_binlog_event(generated_previous_gtids_log_event_v, logger, context, + storage); + } + + // in rewrite mode we need to update next_event_position (and optional + // checksum in the footer) in the received event data portion + binsrv::events::event_storage buffer{}; + const auto event_copy_uv{binsrv::events::rewriter::rewrite( + storage.get_last_transaction_sequence_number(), current_event_v, buffer, + storage.get_current_position())}; + process_binlog_event(event_copy_uv, logger, context, storage); +} + +bool open_connection_and_switch_to_replication( + operations::mode_type operation_mode, binsrv::basic_logger &logger, + const easymysql::library &mysql_lib, + const easymysql::connection_config &connection_config, + std::uint32_t server_id, bool verify_checksum, binsrv::storage &storage, + easymysql::connection &connection) { + try { + connection = mysql_lib.create_connection(connection_config); + } catch (const easymysql::core_error &) { + if (operation_mode == operations::mode_type::fetch) { + throw; + } + logger.log(binsrv::log_severity::error, + "unable to establish connection to mysql server"); + return false; + } + + logger.log(binsrv::log_severity::info, + "established connection to mysql server"); + + log_connection_info(logger, connection); + + const auto blocking_mode{ + operation_mode == operations::mode_type::fetch + ? easymysql::connection_replication_mode_type::non_blocking + : easymysql::connection_replication_mode_type::blocking}; + + try { + if (storage.is_in_gtid_replication_mode()) { + if (storage.is_empty()) { + static constexpr std::string_view select_gtid_purged_query{ + "SELECT @@GLOBAL.gtid_purged"}; + storage.set_purged_gtids(binsrv::gtids::gtid_set{ + connection.execute_select_query_string_result( + select_gtid_purged_query)}); + logger.log( + binsrv::log_severity::info, + "extracted purged GTIDs from the mysql server for an empty " + "storage: " + + boost::lexical_cast(storage.get_purged_gtids())); + } + + const auto gtids{storage.get_gtids()}; + const auto encoded_size{gtids.calculate_encoded_size()}; + + binsrv::gtids::gtid_set_storage encoded_gtids_buffer(encoded_size); + util::byte_span destination{encoded_gtids_buffer}; + gtids.encode_to(destination); + + connection.switch_to_gtid_replication( + server_id, util::const_byte_span{encoded_gtids_buffer}, + verify_checksum, blocking_mode); + } else { + if (storage.is_empty()) { + connection.switch_to_position_replication(server_id, verify_checksum, + blocking_mode); + } else { + connection.switch_to_position_replication( + server_id, storage.get_current_binlog_name().str(), + storage.get_current_position(), verify_checksum, blocking_mode); + } + } + } catch (const easymysql::core_error &) { + if (operation_mode == operations::mode_type::fetch) { + throw; + } + logger.log(binsrv::log_severity::error, "unable to switch to replication"); + return false; + } + + log_replication_info(logger, server_id, storage, verify_checksum, + blocking_mode); + return true; +} + +void receive_binlog_events( + operations::mode_type operation_mode, + const volatile std::atomic_flag &termination_flag, + binsrv::basic_logger &logger, const easymysql::library &mysql_lib, + const easymysql::connection_config &connection_config, + std::uint32_t server_id, bool verify_checksum, binsrv::storage &storage, + const binsrv::optional_rewrite_config &optional_rewrite_config) { + easymysql::connection connection{}; + if (!open_connection_and_switch_to_replication( + operation_mode, logger, mysql_lib, connection_config, server_id, + verify_checksum, storage, connection)) { + return; + } + + // Network streams are requested with COM_BINLOG_DUMP and + // each Binlog Event response is prepended with 00 OK-byte. + static constexpr std::byte expected_event_packet_prefix{'\0'}; + + util::const_byte_span portion; + + binsrv::events::reader_context context{ + connection.get_server_version(), verify_checksum, + storage.get_replication_mode(), storage.get_current_binlog_name().str(), + static_cast(storage.get_current_position())}; + + bool fetch_result{}; + + while (!termination_flag.test() && + (fetch_result = connection.fetch_binlog_event(portion)) && + !portion.empty()) { + if (portion[0] != expected_event_packet_prefix) { + util::exception_location().raise( + "unexpected event prefix"); + } + portion = portion.subspan(1U); + log_span_dump(logger, portion); + + const binsrv::events::event_view current_event_v{context, portion}; + + if (optional_rewrite_config.has_value()) { + // in rewrite mode we need to ignore ROTATE (artificial), + // FORMAT_DESCRIPTION, PREVIOUS_GTIDS_LOG, ROTATE (non-artificial), + // and STOP events + rewrite_and_process_binlog_event( + current_event_v, logger, context, storage, server_id, + optional_rewrite_config->get<"base_file_name">(), + optional_rewrite_config->get<"file_size">().get_value()); + } else { + process_binlog_event(current_event_v, logger, context, storage); + } + } + if (termination_flag.test()) { + logger.log(binsrv::log_severity::info, + "fetching binlog events loop terminated by signal"); + return; + } + if (fetch_result) { + logger.log(binsrv::log_severity::info, + "fetched everything and disconnected"); + return; + } + if (operation_mode == operations::mode_type::fetch) { + util::exception_location().raise( + "fetch operation did not reach EOF reading binlog events"); + } + + // Truncate the in-memory event buffer to the last completed transaction so + // the persisted stream offset matches a transaction boundary. On reconnect, + // reader_context always expects the first logical event after the + // pseudo-preamble to be anonymous_gtid_log / gtid_log / gtid_tagged_log + storage.discard_incomplete_transaction_events(); + + // connection termination is a good place to flush any remaining data + // in the event buffer - this can be considered the third kind of + // checkpointing (in addition to size-based and time-based ones) + storage.flush_event_buffer(); + + logger.log(binsrv::log_severity::info, + "timed out waiting for events and disconnected"); +} + +bool wait_for_interruptable(std::uint32_t idle_time_seconds, + const volatile std::atomic_flag &termination_flag) { + // instead of + // 'std::this_thread::sleep_for(std::chrono::seconds(idle_time_seconds))' + // we do 'std::this_thread::sleep_for(1s)' '' times + // in a loop also checking for termination condition + + // standard pattern with declaring an instance of + // std::conditional_variable and waiting for it (for + // '' seconds) to be notified from the signal handler + // can be dangerous as the chances of signal handler being called on the + // same thread as this one ('main()') are pretty big. + for (std::uint32_t sleep_iteration{0U}; + sleep_iteration < idle_time_seconds && !termination_flag.test(); + ++sleep_iteration) { + std::this_thread::sleep_for(std::chrono::seconds(1U)); + } + return !termination_flag.test(); +} + +// since c++20 it is no longer needed to initialize std::atomic_flag with +// ATOMIC_FLAG_INIT as this flag is modified from a signal handler it is marked +// as volatile to make sure optimizer do optimizations which will be unsafe for +// this scenario +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +volatile std::atomic_flag global_termination_flag{}; + +} // anonymous namespace + +extern "C" void custom_signal_handler(int /*signo*/) { + global_termination_flag.test_and_set(); +} + +fetch_pull_operation::fetch_pull_operation(mode_type mode, + util::command_line_arg_view cmd_args) + : basic_operation{mode, cmd_args, expected_number_of_arguments} {} + +[[nodiscard]] bool fetch_pull_operation::execute() const { + bool result{false}; + + binsrv::basic_logger_ptr logger; + + try { + static constexpr auto default_log_level = binsrv::log_severity::trace; + + const binsrv::logger_config initial_logger_config{ + {{default_log_level}, {""}}}; + + logger = binsrv::logger_factory::create(initial_logger_config); + // logging with "delimiter" level has the highest priority and empty label + const auto executable_name{util::extract_executable_name(get_cmd_args())}; + logger->log(binsrv::log_severity::delimiter, + '"' + executable_name + '"' + + " started with the following command line arguments:"); + logger->log(binsrv::log_severity::delimiter, + util::get_readable_command_line_arguments(get_cmd_args())); + + logger->log(binsrv::log_severity::delimiter, + "reading configuration from the JSON file."); + const binsrv::main_config config{get_config_file_path()}; + + const auto &logger_config = config.root().get<"logger">(); + if (!logger_config.has_file()) { + logger->set_min_level(logger_config.get<"level">()); + } else { + logger->log(binsrv::log_severity::delimiter, + "redirecting logging to \"" + logger_config.get<"file">() + + "\""); + auto new_logger = binsrv::logger_factory::create(logger_config); + std::swap(logger, new_logger); + } + + const auto log_level_label = + binsrv::to_string_view(logger->get_min_level()); + logger->log(binsrv::log_severity::delimiter, + "logging level set to \"" + std::string{log_level_label} + '"'); + + logger->log(binsrv::log_severity::delimiter, + "application version: " + app_version.get_string()); + + assert(get_mode() == operations::mode_type::fetch || + get_mode() == operations::mode_type::pull); + std::string msg; + msg = '\''; + msg += boost::lexical_cast(get_mode()); + msg += "' operation mode specified"; + logger->log(binsrv::log_severity::delimiter, msg); + + // setting custom SIGINT and SIGTERM signal handlers + if (std::signal(SIGTERM, &custom_signal_handler) == SIG_ERR) { + util::exception_location().raise( + "cannot set custom signal handler for SIGTERM"); + } + if (std::signal(SIGINT, &custom_signal_handler) == SIG_ERR) { + util::exception_location().raise( + "cannot set custom signal handler for SIGINT"); + } + + logger->log(binsrv::log_severity::info, + "set custom handlers for SIGINT and SIGTERM signals"); + const volatile std::atomic_flag &termination_flag{global_termination_flag}; + + const auto &storage_config = config.root().get<"storage">(); + log_storage_config_info(*logger, storage_config); + + const auto &connection_config = config.root().get<"connection">(); + log_connection_config_info(*logger, connection_config); + + const auto &replication_config = config.root().get<"replication">(); + log_replication_config_info(*logger, replication_config); + + const auto server_id{replication_config.get<"server_id">()}; + const auto idle_time_seconds{replication_config.get<"idle_time">()}; + const auto verify_checksum{replication_config.get<"verify_checksum">()}; + const auto replication_mode{replication_config.get<"mode">()}; + const auto optional_rewrite_config{replication_config.get<"rewrite">()}; + + binsrv::storage storage{storage_config, + binsrv::storage_construction_mode_type::streaming, + replication_mode}; + log_storage_info(*logger, storage); + + const easymysql::library mysql_lib; + logger->log(binsrv::log_severity::info, "initialized mysql client library"); + + log_library_info(*logger, mysql_lib); + + receive_binlog_events(get_mode(), termination_flag, *logger, mysql_lib, + connection_config, server_id, verify_checksum, + storage, optional_rewrite_config); + + if (get_mode() == operations::mode_type::pull) { + auto iteration_number{1UZ}; + while (!termination_flag.test()) { + msg = "entering idle mode for "; + msg += std::to_string(idle_time_seconds); + msg += " seconds"; + logger->log(binsrv::log_severity::info, msg); + + if (!wait_for_interruptable(idle_time_seconds, termination_flag)) { + break; + } + + msg = "awoke after sleeping and trying to reconnect (iteration "; + msg += std::to_string(iteration_number); + msg += ')'; + logger->log(binsrv::log_severity::info, msg); + + receive_binlog_events(get_mode(), termination_flag, *logger, mysql_lib, + connection_config, server_id, verify_checksum, + storage, optional_rewrite_config); + ++iteration_number; + } + } + + if (termination_flag.test()) { + logger->log( + binsrv::log_severity::info, + "successfully shut down after receiving a termination signal"); + } else { + logger->log( + binsrv::log_severity::info, + "successfully shut down after finishing the requested operation"); + } + + result = true; + } catch (...) { + handle_std_exception(logger); + } + return result; +} + +} // namespace operations diff --git a/src/operations/fetch_pull_operation.hpp b/src/operations/fetch_pull_operation.hpp new file mode 100644 index 0000000..fb5bdc0 --- /dev/null +++ b/src/operations/fetch_pull_operation.hpp @@ -0,0 +1,46 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_FETCH_PULL_OPERATION_HPP +#define OPERATIONS_FETCH_PULL_OPERATION_HPP + +#include + +#include "operations/basic_operation.hpp" +#include "operations/mode_type_fwd.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +class fetch_pull_operation : public basic_operation { +public: + static constexpr auto expected_number_of_arguments{1UZ}; + + [[nodiscard]] bool execute() const override; + +protected: + explicit fetch_pull_operation(mode_type mode, + util::command_line_arg_view cmd_args); + +private: + [[nodiscard]] std::string_view get_config_file_path() const noexcept { + return basic_operation::get_cmd_args()[2UZ]; + } +}; + +} // namespace operations + +#endif // OPERATIONS_FETCH_PULL_OPERATION_HPP diff --git a/src/operations/generic_operation_fwd.hpp b/src/operations/generic_operation_fwd.hpp new file mode 100644 index 0000000..790b13b --- /dev/null +++ b/src/operations/generic_operation_fwd.hpp @@ -0,0 +1,27 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_GENERIC_OPERATION_FWD_HPP +#define OPERATIONS_GENERIC_OPERATION_FWD_HPP + +#include "operations/mode_type_fwd.hpp" + +namespace operations { + +template class generic_operation; + +} // namespace operations + +#endif // OPERATIONS_GENERIC_OPERATION_FWD_HPP diff --git a/src/operations/list_operation.cpp b/src/operations/list_operation.cpp new file mode 100644 index 0000000..7e142fc --- /dev/null +++ b/src/operations/list_operation.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/list_operation.hpp" + +#include +#include +#include + +#include "binsrv/main_config.hpp" +#include "binsrv/storage.hpp" + +#include "binsrv/models/error_response.hpp" +#include "binsrv/models/search_response.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" +#include "operations/model_helpers.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +generic_operation::generic_operation( + util::command_line_arg_view cmd_args) + : basic_operation{mode_type::list, cmd_args, expected_number_of_arguments} { +} + +[[nodiscard]] bool generic_operation::execute() const { + bool operation_successful{false}; + std::string result; + + try { + const binsrv::main_config config{get_config_file_path()}; + const auto &storage_config = config.root().get<"storage">(); + const auto &replication_config = config.root().get<"replication">(); + const auto replication_mode{replication_config.get<"mode">()}; + + const binsrv::storage storage{ + storage_config, binsrv::storage_construction_mode_type::querying_only, + replication_mode}; + + binsrv::models::search_response response; + for (const auto &record : storage.get_binlog_records()) { + append_record_to_search_response(response, storage, record); + } + result = response.str(); + operation_successful = true; + } catch (const std::exception &e) { + const binsrv::models::error_response response{e.what()}; + result = response.str(); + } + std::cout << result << '\n'; + return operation_successful; +} + +} // namespace operations diff --git a/src/operations/list_operation.hpp b/src/operations/list_operation.hpp new file mode 100644 index 0000000..f91ff5c --- /dev/null +++ b/src/operations/list_operation.hpp @@ -0,0 +1,45 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_LIST_OPERATION_HPP +#define OPERATIONS_LIST_OPERATION_HPP + +#include + +#include "operations/generic_operation_fwd.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +template <> class generic_operation : public basic_operation { +public: + static constexpr auto expected_number_of_arguments{1UZ}; + + explicit generic_operation(util::command_line_arg_view cmd_args); + [[nodiscard]] bool execute() const override; + +private: + [[nodiscard]] std::string_view get_config_file_path() const noexcept { + return basic_operation::get_cmd_args()[2UZ]; + } +}; + +} // namespace operations + +#endif // OPERATIONS_LIST_OPERATION_HPP diff --git a/src/operations/logger_helpers.cpp b/src/operations/logger_helpers.cpp new file mode 100644 index 0000000..60357a3 --- /dev/null +++ b/src/operations/logger_helpers.cpp @@ -0,0 +1,313 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "operations/logger_helpers.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "binsrv/basic_logger.hpp" +#include "binsrv/log_severity.hpp" +#include "binsrv/replication_config.hpp" +#include "binsrv/replication_mode_type.hpp" +#include "binsrv/rewrite_config.hpp" +#include "binsrv/size_unit.hpp" +#include "binsrv/storage.hpp" +#include "binsrv/storage_backend_type.hpp" // IWYU pragma: keep +#include "binsrv/storage_config.hpp" +#include "binsrv/time_unit.hpp" + +#include "easymysql/connection.hpp" +#include "easymysql/connection_config.hpp" +#include "easymysql/library.hpp" +#include "easymysql/ssl_config.hpp" +#include "easymysql/ssl_mode_type.hpp" // IWYU pragma: keep +#include "easymysql/tls_config.hpp" + +#include "util/byte_span_fwd.hpp" +#include "util/common_optional_types.hpp" +#include "util/ct_string.hpp" +#include "util/nv_tuple_fwd.hpp" + +namespace operations { + +namespace { + +template util::optional_string to_log_string(const T &value) { + return boost::lexical_cast(value); +} + +util::optional_string to_log_string(const binsrv::size_unit &value) { + return value.get_description(); +} + +util::optional_string to_log_string(const binsrv::time_unit &value) { + return value.get_description(); +} + +util::optional_string to_log_string(bool value) { + return {value ? "true" : "false"}; +} + +template +util::optional_string to_log_string(const std::optional &value) { + if (!value.has_value()) { + return {}; + } + return to_log_string(*value); +} + +template +void log_config_param(binsrv::basic_logger &logger, const Config &config, + std::string_view label) { + const auto opt_log_string{to_log_string(config.template get())}; + if (opt_log_string.has_value()) { + std::string msg{label}; + msg += ": "; + msg += *opt_log_string; + logger.log(binsrv::log_severity::info, msg); + } +} + +} // anonymous namespace + +void log_ssl_config_info(binsrv::basic_logger &logger, + const easymysql::ssl_config &ssl_config) { + log_config_param<"mode">(logger, ssl_config, "SSL mode"); + log_config_param<"ca">(logger, ssl_config, "SSL ca"); + log_config_param<"capath">(logger, ssl_config, "SSL capath"); + log_config_param<"crl">(logger, ssl_config, "SSL crl"); + log_config_param<"crlpath">(logger, ssl_config, "SSL crlpath"); + log_config_param<"cert">(logger, ssl_config, "SSL cert"); + log_config_param<"key">(logger, ssl_config, "SSL key"); + log_config_param<"cipher">(logger, ssl_config, "SSL cipher"); +} + +void log_tls_config_info(binsrv::basic_logger &logger, + const easymysql::tls_config &tls_config) { + log_config_param<"ciphersuites">(logger, tls_config, "TLS ciphersuites"); + log_config_param<"version">(logger, tls_config, "TLS version"); +} + +void log_connection_config_info( + binsrv::basic_logger &logger, + const easymysql::connection_config &connection_config) { + std::string msg; + msg = "mysql connection string: "; + msg += connection_config.get_connection_string(); + logger.log(binsrv::log_severity::info, msg); + + log_config_param<"connect_timeout">(logger, connection_config, + "mysql connect timeout (seconds)"); + log_config_param<"read_timeout">(logger, connection_config, + "mysql read timeout (seconds)"); + log_config_param<"write_timeout">(logger, connection_config, + "mysql write timeout (seconds)"); + + const auto &optional_ssl_config{connection_config.get<"ssl">()}; + if (optional_ssl_config.has_value()) { + log_ssl_config_info(logger, *optional_ssl_config); + } + const auto &optional_tls_config{connection_config.get<"tls">()}; + if (optional_tls_config.has_value()) { + log_tls_config_info(logger, *optional_tls_config); + } +} + +void log_rewrite_config_info(binsrv::basic_logger &logger, + const binsrv::rewrite_config &rewrite_config) { + log_config_param<"base_file_name">(logger, rewrite_config, + "rewrite base binlog file name"); + log_config_param<"file_size">(logger, rewrite_config, + "rewrite binlog file size"); +} +void log_replication_config_info( + binsrv::basic_logger &logger, + const binsrv::replication_config &replication_config) { + + log_config_param<"server_id">(logger, replication_config, + "mysql replication server id"); + log_config_param<"idle_time">(logger, replication_config, + "mysql replication idle time (seconds)"); + log_config_param<"verify_checksum">( + logger, replication_config, "mysql replication checksum verification"); + log_config_param<"mode">(logger, replication_config, + "mysql replication mode"); + const auto &optional_rewrite_config{replication_config.get<"rewrite">()}; + if (optional_rewrite_config.has_value()) { + log_rewrite_config_info(logger, *optional_rewrite_config); + } +} + +void log_storage_config_info(binsrv::basic_logger &logger, + const binsrv::storage_config &storage_config) { + + log_config_param<"backend">(logger, storage_config, + "binlog storage backend type"); + logger.log(binsrv::log_severity::info, + "binlog storage backend URI (masked): " + + storage_config.get_masked_uri()); + log_config_param<"fs_buffer_directory">( + logger, storage_config, + "binlog storage backend filesystem buffer directory"); + log_config_param<"checkpoint_size">( + logger, storage_config, "binlog storage backend checkpointing size"); + log_config_param<"checkpoint_interval">( + logger, storage_config, "binlog storage backend checkpointing interval"); +} + +void log_storage_info(binsrv::basic_logger &logger, + const binsrv::storage &storage) { + std::string msg{"created binlog storage with the following backend: "}; + msg += storage.get_backend_description(); + logger.log(binsrv::log_severity::info, msg); + + msg.clear(); + msg = "binlog storage initialized in "; + msg += boost::lexical_cast(storage.get_replication_mode()); + msg += " mode"; + logger.log(binsrv::log_severity::info, msg); + + msg.clear(); + if (storage.is_empty()) { + msg = "binlog storage initialized on an empty directory"; + } else { + msg = "binlog storage initialized at \""; + msg += storage.get_current_binlog_name().str(); + msg += "\":"; + msg += std::to_string(storage.get_current_position()); + } + logger.log(binsrv::log_severity::info, msg); +} + +void log_library_info(binsrv::basic_logger &logger, + const easymysql::library &mysql_lib) { + std::string msg{}; + msg = "mysql client version: "; + msg += mysql_lib.get_readable_client_version(); + logger.log(binsrv::log_severity::info, msg); +} + +void log_connection_info(binsrv::basic_logger &logger, + const easymysql::connection &connection) { + std::string msg{}; + msg = "mysql server version: "; + msg += connection.get_readable_server_version(); + logger.log(binsrv::log_severity::info, msg); + + logger.log(binsrv::log_severity::info, + "mysql protocol version: " + + std::to_string(connection.get_protocol_version())); + + msg = "mysql server connection info: "; + msg += connection.get_server_connection_info(); + logger.log(binsrv::log_severity::info, msg); + + msg = "mysql connection character set: "; + msg += connection.get_character_set_name(); + logger.log(binsrv::log_severity::info, msg); +} + +void log_replication_info( + binsrv::basic_logger &logger, std::uint32_t server_id, + const binsrv::storage &storage, bool verify_checksum, + easymysql::connection_replication_mode_type blocking_mode) { + const auto replication_mode{storage.get_replication_mode()}; + + std::string msg{"switched to replication (checksum "}; + msg += (verify_checksum ? "enabled" : "disabled"); + msg += ", "; + msg += boost::lexical_cast(replication_mode); + msg += +" mode)"; + logger.log(binsrv::log_severity::info, msg); + + msg = "replication info (server id "; + msg += std::to_string(server_id); + msg += ", "; + msg += (blocking_mode == easymysql::connection_replication_mode_type::blocking + ? "blocking" + : "non-blocking"); + msg += ", starting from "; + if (replication_mode == binsrv::replication_mode_type::position) { + if (storage.is_empty()) { + msg += "the very beginning"; + } else { + msg += storage.get_current_binlog_name().str(); + msg += ":"; + msg += std::to_string(storage.get_current_position()); + } + } else { + const auto >ids{storage.get_gtids()}; + if (gtids.is_empty()) { + msg += "an empty"; + } else { + msg += "the "; + msg += boost::lexical_cast(gtids); + } + msg += " GTID set"; + } + msg += ")"; + logger.log(binsrv::log_severity::info, msg); +} + +void log_span_dump(binsrv::basic_logger &logger, + util::const_byte_span portion) { + logger.log(binsrv::log_severity::debug, + "fetched " + std::to_string(std::size(portion)) + + "-byte(s) event from binlog"); + static constexpr auto bytes_per_dump_line{16UZ}; + auto offset{0UZ}; + while (offset < std::size(portion)) { + std::ostringstream oss; + oss << '['; + oss << std::setfill('0') << std::hex; + auto sub = portion.subspan( + offset, std::min(bytes_per_dump_line, std::size(portion) - offset)); + for (auto current_byte : sub) { + oss << ' ' << std::setw(2) + << std::to_integer(current_byte); + } + const std::size_t filler_length = + (bytes_per_dump_line - std::size(sub)) * 3U; + oss << std::setfill(' ') << std::setw(static_cast(filler_length)) + << ""; + oss << " ] "; + const auto &ctype_facet{ + std::use_facet>(std::locale::classic())}; + + for (auto current_byte : sub) { + auto current_char{std::to_integer(current_byte)}; + if (!ctype_facet.is(std::ctype_base::print, current_char)) { + current_char = '.'; + } + oss.put(current_char); + } + logger.log(binsrv::log_severity::trace, oss.str()); + offset += bytes_per_dump_line; + } +} + +} // namespace operations diff --git a/src/operations/logger_helpers.hpp b/src/operations/logger_helpers.hpp new file mode 100644 index 0000000..d8a339f --- /dev/null +++ b/src/operations/logger_helpers.hpp @@ -0,0 +1,72 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_LOGGER_HELPERS_HPP +#define OPERATIONS_LOGGER_HELPERS_HPP + +#include + +#include "binsrv/basic_logger_fwd.hpp" +#include "binsrv/replication_config_fwd.hpp" +#include "binsrv/rewrite_config_fwd.hpp" +#include "binsrv/storage_config_fwd.hpp" +#include "binsrv/storage_fwd.hpp" + +#include "easymysql/connection_config_fwd.hpp" +#include "easymysql/connection_fwd.hpp" +#include "easymysql/library_fwd.hpp" +#include "easymysql/ssl_config_fwd.hpp" +#include "easymysql/tls_config_fwd.hpp" + +#include "util/byte_span_fwd.hpp" + +namespace operations { + +void log_ssl_config_info(binsrv::basic_logger &logger, + const easymysql::ssl_config &ssl_config); +void log_tls_config_info(binsrv::basic_logger &logger, + const easymysql::tls_config &tls_config); +void log_connection_config_info( + binsrv::basic_logger &logger, + const easymysql::connection_config &connection_config); + +void log_rewrite_config_info(binsrv::basic_logger &logger, + const binsrv::rewrite_config &rewrite_config); +void log_replication_config_info( + binsrv::basic_logger &logger, + const binsrv::replication_config &replication_config); + +void log_storage_config_info(binsrv::basic_logger &logger, + const binsrv::storage_config &storage_config); + +void log_storage_info(binsrv::basic_logger &logger, + const binsrv::storage &storage); + +void log_library_info(binsrv::basic_logger &logger, + const easymysql::library &mysql_lib); + +void log_connection_info(binsrv::basic_logger &logger, + const easymysql::connection &connection); + +void log_replication_info( + binsrv::basic_logger &logger, std::uint32_t server_id, + const binsrv::storage &storage, bool verify_checksum, + easymysql::connection_replication_mode_type blocking_mode); + +void log_span_dump(binsrv::basic_logger &logger, util::const_byte_span portion); + +} // namespace operations + +#endif // OPERATIONS_LOGGER_HELPERS_HPP diff --git a/src/operations/mode_type.hpp b/src/operations/mode_type.hpp new file mode 100644 index 0000000..0463d8c --- /dev/null +++ b/src/operations/mode_type.hpp @@ -0,0 +1,96 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_MODE_TYPE_HPP +#define OPERATIONS_MODE_TYPE_HPP + +#include "operations/mode_type_fwd.hpp" // IWYU pragma: export + +#include +#include +#include +#include +#include +#include + +#include "util/conversion_helpers.hpp" + +namespace operations { + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +// clang-format off +#define OPERATIONS_MODE_TYPE_X_SEQUENCE() \ + OPERATIONS_MODE_TYPE_X_MACRO(fetch ), \ + OPERATIONS_MODE_TYPE_X_MACRO(pull ), \ + OPERATIONS_MODE_TYPE_X_MACRO(list ), \ + OPERATIONS_MODE_TYPE_X_MACRO(search_by_timestamp), \ + OPERATIONS_MODE_TYPE_X_MACRO(search_by_gtid_set ), \ + OPERATIONS_MODE_TYPE_X_MACRO(purge_binlogs ), \ + OPERATIONS_MODE_TYPE_X_MACRO(version ) +// clang-format on + +#define OPERATIONS_MODE_TYPE_X_MACRO(X) X +enum class mode_type : std::uint8_t { + OPERATIONS_MODE_TYPE_X_SEQUENCE(), + delimiter +}; +#undef OPERATIONS_MODE_TYPE_X_MACRO + +inline std::string_view to_string_view(mode_type operation_mode) noexcept { + using namespace std::string_view_literals; +#define OPERATIONS_MODE_TYPE_X_MACRO(X) #X##sv + static constexpr std::array labels{OPERATIONS_MODE_TYPE_X_SEQUENCE(), ""sv}; +#undef OPERATIONS_MODE_TYPE_X_MACRO + const auto index{ + util::enum_to_index(std::min(mode_type::delimiter, operation_mode))}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + return labels[index]; +} +#undef OPERATIONS_MODE_TYPE_X_SEQUENCE +// NOLINTEND(cppcoreguidelines-macro-usage) + +template + requires std::same_as +std::basic_ostream & +operator<<(std::basic_ostream &output, mode_type operation_mode) { + return output << to_string_view(operation_mode); +} + +template + requires std::same_as +std::basic_istream & +operator>>(std::basic_istream &input, mode_type &operation_mode) { + std::string operation_mode_str; + input >> operation_mode_str; + if (!input) { + return input; + } + auto index{0UZ}; + const auto max_index = util::enum_to_index(mode_type::delimiter); + while (index < max_index && to_string_view(util::index_to_enum( + index)) != operation_mode_str) { + ++index; + } + if (index < max_index) { + operation_mode = util::index_to_enum(index); + } else { + input.setstate(std::ios_base::failbit); + } + return input; +} + +} // namespace operations + +#endif // OPERATIONS_MODE_TYPE_HPP diff --git a/src/binsrv/operation_mode_type_fwd.hpp b/src/operations/mode_type_fwd.hpp similarity index 70% rename from src/binsrv/operation_mode_type_fwd.hpp rename to src/operations/mode_type_fwd.hpp index b69f1fe..db5e656 100644 --- a/src/binsrv/operation_mode_type_fwd.hpp +++ b/src/operations/mode_type_fwd.hpp @@ -13,29 +13,27 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -#ifndef BINSRV_OPERATION_MODE_TYPE_FWD_HPP -#define BINSRV_OPERATION_MODE_TYPE_FWD_HPP +#ifndef OPERATIONS_MODE_TYPE_FWD_HPP +#define OPERATIONS_MODE_TYPE_FWD_HPP #include #include #include -namespace binsrv { +namespace operations { -enum class operation_mode_type : std::uint8_t; +enum class mode_type : std::uint8_t; template requires std::same_as std::basic_ostream & -operator<<(std::basic_ostream &output, - operation_mode_type operation_mode); +operator<<(std::basic_ostream &output, mode_type operation_mode); template requires std::same_as std::basic_istream & -operator>>(std::basic_istream &input, - operation_mode_type &operation_mode); +operator>>(std::basic_istream &input, mode_type &operation_mode); -} // namespace binsrv +} // namespace operations -#endif // BINSRV_OPERATION_MODE_TYPE_FWD_HPP +#endif // OPERATIONS_MODE_TYPE_FWD_HPP diff --git a/src/operations/model_helpers.cpp b/src/operations/model_helpers.cpp new file mode 100644 index 0000000..c32ac9e --- /dev/null +++ b/src/operations/model_helpers.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "operations/model_helpers.hpp" + +#include "binsrv/storage.hpp" + +#include "binsrv/models/search_response.hpp" + +namespace operations { + +void append_record_to_search_response( + binsrv::models::search_response &response, const binsrv::storage &storage, + const binsrv::storage::binlog_record &record) { + response.add_record(record.name.str(), record.size, + storage.get_binlog_uri(record.name), + record.previous_gtids, record.added_gtids, + record.timestamps.get_min_timestamp().get_value(), + record.timestamps.get_max_timestamp().get_value()); +} + +} // namespace operations diff --git a/src/operations/model_helpers.hpp b/src/operations/model_helpers.hpp new file mode 100644 index 0000000..cc6b47b --- /dev/null +++ b/src/operations/model_helpers.hpp @@ -0,0 +1,31 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_MODEL_HELPERS_HPP +#define OPERATIONS_MODEL_HELPERS_HPP + +#include "binsrv/storage.hpp" + +#include "binsrv/models/search_response_fwd.hpp" + +namespace operations { + +void append_record_to_search_response( + binsrv::models::search_response &response, const binsrv::storage &storage, + const binsrv::storage::binlog_record &record); + +} // namespace operations + +#endif // OPERATIONS_MODEL_HELPERS_HPP diff --git a/src/operations/operation_factory.cpp b/src/operations/operation_factory.cpp new file mode 100644 index 0000000..9393028 --- /dev/null +++ b/src/operations/operation_factory.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/operation_factory.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include "operations/basic_operation_fwd.hpp" +#include "operations/fetch_operation.hpp" // IWYU pragma: export +#include "operations/generic_operation_fwd.hpp" +#include "operations/list_operation.hpp" // IWYU pragma: export +#include "operations/mode_type.hpp" +#include "operations/pull_operation.hpp" // IWYU pragma: export +#include "operations/purge_binlogs_operation.hpp" // IWYU pragma: export +#include "operations/search_by_gtid_set_operation.hpp" // IWYU pragma: export +#include "operations/search_by_timestamp_operation.hpp" // IWYU pragma: export +#include "operations/version_operation.hpp" // IWYU pragma: export + +#include "util/command_line_helpers_fwd.hpp" +#include "util/conversion_helpers.hpp" +#include "util/exception_location_helpers.hpp" + +namespace operations { + +[[nodiscard]] basic_operation_ptr +operation_factory::create(util::command_line_arg_view cmd_args) { + // 'cmd_args' always includes the name of the executable + // and it should also include at least the name of the operation + static constexpr auto min_number_of_cmd_args{2UZ}; + + if (std::size(cmd_args) < min_number_of_cmd_args) { + util::exception_location().raise( + "insufficient number of command line arguments"); + } + + mode_type operation_mode{mode_type::delimiter}; + + if (!boost::conversion::try_lexical_convert(cmd_args[1UZ], operation_mode)) { + util::exception_location().raise( + "invalid operation mode specified"); + } + + if (operation_mode == mode_type::delimiter) { + util::exception_location().raise( + "invalid operation mode specified"); + } + + static constexpr auto number_of_operations{ + util::enum_to_index(mode_type::delimiter)}; + + static constexpr auto make_functions{ + []( + std::index_sequence) constexpr { + return std::array{+[](util::command_line_arg_view cmd_args_value) + -> basic_operation_ptr { + return std::make_unique< + generic_operation(IndexPack)>>( + cmd_args_value); + }...}; + }(std::make_index_sequence{})}; + + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + return make_functions[util::enum_to_index(operation_mode)](cmd_args); +} + +} // namespace operations diff --git a/src/operations/operation_factory.hpp b/src/operations/operation_factory.hpp new file mode 100644 index 0000000..dc919bc --- /dev/null +++ b/src/operations/operation_factory.hpp @@ -0,0 +1,33 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_OPERATION_FACTORY_HPP +#define OPERATIONS_OPERATION_FACTORY_HPP + +#include "operations/basic_operation_fwd.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +class operation_factory { +public: + [[nodiscard]] static basic_operation_ptr + create(util::command_line_arg_view cmd_args); +}; + +} // namespace operations + +#endif // OPERATIONS_OPERATION_FACTORY_HPP diff --git a/src/operations/pull_operation.hpp b/src/operations/pull_operation.hpp new file mode 100644 index 0000000..2e6bfb6 --- /dev/null +++ b/src/operations/pull_operation.hpp @@ -0,0 +1,35 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_PULL_OPERATION_HPP +#define OPERATIONS_PULL_OPERATION_HPP + +#include "operations/generic_operation_fwd.hpp" + +#include "operations/fetch_pull_operation.hpp" +#include "operations/mode_type.hpp" + +namespace operations { + +template <> +class generic_operation : public fetch_pull_operation { +public: + explicit generic_operation(util::command_line_arg_view cmd_args) + : fetch_pull_operation{mode_type::pull, cmd_args} {} +}; + +} // namespace operations + +#endif // OPERATIONS_PULL_OPERATION_HPP diff --git a/src/operations/purge_binlogs_operation.cpp b/src/operations/purge_binlogs_operation.cpp new file mode 100644 index 0000000..0136ca2 --- /dev/null +++ b/src/operations/purge_binlogs_operation.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/purge_binlogs_operation.hpp" + +#include +#include +#include + +#include "binsrv/main_config.hpp" +#include "binsrv/storage.hpp" + +#include "binsrv/events/composite_binlog_name.hpp" + +#include "binsrv/models/error_response.hpp" +#include "binsrv/models/response_status_type.hpp" +#include "binsrv/models/search_response.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" +#include "operations/model_helpers.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +generic_operation::generic_operation( + util::command_line_arg_view cmd_args) + : basic_operation{mode_type::purge_binlogs, cmd_args, + expected_number_of_arguments} {} + +[[nodiscard]] bool +generic_operation::execute() const { + bool operation_successful{false}; + std::string result; + + try { + const auto target_name{ + binsrv::events::composite_binlog_name::parse(get_binlog_name())}; + + const binsrv::main_config config{get_config_file_path()}; + const auto &storage_config = config.root().get<"storage">(); + const auto &replication_config = config.root().get<"replication">(); + const auto replication_mode{replication_config.get<"mode">()}; + + binsrv::storage storage{storage_config, + binsrv::storage_construction_mode_type::purging, + replication_mode}; + + const auto [removed_records, cleanup_warning_message] = + storage.purge_binlogs(target_name); + + // The step-2 index rewrite has already committed by the time + // 'purge_binlogs' returns normally; if its best-effort step-3 + // cleanup left orphan payload/metadata objects on disk, the + // call reports it via a non-empty 'cleanup_warning_message' and + // we surface that as a 'warning' status (instead of plain + // 'success'), with the underlying error message attached, so the + // operator knows the storage will need attention before the next + // 'fetch' / 'pull' run. + binsrv::models::search_response response; + if (!cleanup_warning_message.empty()) { + response = binsrv::models::search_response{ + binsrv::models::response_status_type::warning, + cleanup_warning_message}; + } + for (const auto &record : removed_records) { + append_record_to_search_response(response, storage, record); + } + result = response.str(); + operation_successful = true; + } catch (const std::exception &e) { + const binsrv::models::error_response response{e.what()}; + result = response.str(); + } + std::cout << result << '\n'; + return operation_successful; +} + +} // namespace operations diff --git a/src/operations/purge_binlogs_operation.hpp b/src/operations/purge_binlogs_operation.hpp new file mode 100644 index 0000000..eb09eb5 --- /dev/null +++ b/src/operations/purge_binlogs_operation.hpp @@ -0,0 +1,49 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_PURGE_BINLOGS_OPERATION_HPP +#define OPERATIONS_PURGE_BINLOGS_OPERATION_HPP + +#include + +#include "operations/generic_operation_fwd.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +template <> +class generic_operation : public basic_operation { +public: + static constexpr auto expected_number_of_arguments{2UZ}; + + explicit generic_operation(util::command_line_arg_view cmd_args); + [[nodiscard]] bool execute() const override; + +private: + [[nodiscard]] std::string_view get_config_file_path() const noexcept { + return basic_operation::get_cmd_args()[2UZ]; + } + [[nodiscard]] std::string_view get_binlog_name() const noexcept { + return basic_operation::get_cmd_args()[3UZ]; + } +}; + +} // namespace operations + +#endif // OPERATIONS_PURGE_BINLOGS_OPERATION_HPP diff --git a/src/operations/search_by_gtid_set_operation.cpp b/src/operations/search_by_gtid_set_operation.cpp new file mode 100644 index 0000000..2ca2ef9 --- /dev/null +++ b/src/operations/search_by_gtid_set_operation.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/search_by_gtid_set_operation.hpp" + +#include +#include +#include +#include + +#include "binsrv/main_config.hpp" +#include "binsrv/storage.hpp" + +#include "binsrv/models/error_response.hpp" +#include "binsrv/models/search_response.hpp" + +#include "binsrv/gtids/gtid_set.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" +#include "operations/model_helpers.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +generic_operation::generic_operation( + util::command_line_arg_view cmd_args) + : basic_operation{mode_type::search_by_gtid_set, cmd_args, + expected_number_of_arguments} {} + +[[nodiscard]] bool +generic_operation::execute() const { + bool operation_successful{false}; + std::string result; + + try { + binsrv::gtids::gtid_set remaining_gtids{get_gtid_set()}; + + const binsrv::main_config config{get_config_file_path()}; + const auto &storage_config = config.root().get<"storage">(); + const auto &replication_config = config.root().get<"replication">(); + const auto replication_mode{replication_config.get<"mode">()}; + + const binsrv::storage storage{ + storage_config, binsrv::storage_construction_mode_type::querying_only, + replication_mode}; + + const auto &binlog_records{storage.get_binlog_records()}; + if (binlog_records.empty()) { + throw std::runtime_error("Binlog storage is empty"); + } + + binsrv::models::search_response response; + if (!storage.is_in_gtid_replication_mode()) { + throw std::runtime_error("GTID set search is not supported in storages " + "created in position-based replication mode"); + } + + for (const auto &record : binlog_records) { + if (remaining_gtids.is_empty()) { + break; + } + if (!record.added_gtids.has_value()) { + continue; + } + if (!binsrv::gtids::intersects(remaining_gtids, *record.added_gtids)) { + continue; + } + remaining_gtids.subtract(*record.added_gtids); + + append_record_to_search_response(response, storage, record); + } + if (!remaining_gtids.is_empty()) { + throw std::runtime_error("The specified GTID set cannot be covered"); + } + result = response.str(); + operation_successful = true; + } catch (const std::exception &e) { + const binsrv::models::error_response response{e.what()}; + result = response.str(); + } + std::cout << result << '\n'; + return operation_successful; +} + +} // namespace operations diff --git a/src/operations/search_by_gtid_set_operation.hpp b/src/operations/search_by_gtid_set_operation.hpp new file mode 100644 index 0000000..f9737b5 --- /dev/null +++ b/src/operations/search_by_gtid_set_operation.hpp @@ -0,0 +1,50 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_SEARCH_BY_GTID_SET_OPERATION_HPP +#define OPERATIONS_SEARCH_BY_GTID_SET_OPERATION_HPP + +#include + +#include "operations/generic_operation_fwd.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +template <> +class generic_operation + : public basic_operation { +public: + static constexpr auto expected_number_of_arguments{2UZ}; + + explicit generic_operation(util::command_line_arg_view cmd_args); + [[nodiscard]] bool execute() const override; + +private: + [[nodiscard]] std::string_view get_config_file_path() const noexcept { + return basic_operation::get_cmd_args()[2UZ]; + } + [[nodiscard]] std::string_view get_gtid_set() const noexcept { + return basic_operation::get_cmd_args()[3UZ]; + } +}; + +} // namespace operations + +#endif // OPERATIONS_SEARCH_BY_GTID_SET_OPERATION_HPP diff --git a/src/operations/search_by_timestamp_operation.cpp b/src/operations/search_by_timestamp_operation.cpp new file mode 100644 index 0000000..9c85247 --- /dev/null +++ b/src/operations/search_by_timestamp_operation.cpp @@ -0,0 +1,88 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/search_by_timestamp_operation.hpp" + +#include +#include +#include +#include + +#include "binsrv/main_config.hpp" +#include "binsrv/storage.hpp" + +#include "binsrv/models/error_response.hpp" +#include "binsrv/models/search_response.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" +#include "operations/model_helpers.hpp" + +#include "util/command_line_helpers_fwd.hpp" +#include "util/ctime_timestamp.hpp" + +namespace operations { + +generic_operation::generic_operation( + util::command_line_arg_view cmd_args) + : basic_operation{mode_type::search_by_timestamp, cmd_args, + expected_number_of_arguments} {} + +[[nodiscard]] bool +generic_operation::execute() const { + bool operation_successful{false}; + std::string result; + + try { + util::ctime_timestamp timestamp; + if (!util::ctime_timestamp::try_parse(get_timestamp(), timestamp)) { + throw std::runtime_error("Invalid timestamp format"); + } + + const binsrv::main_config config{get_config_file_path()}; + const auto &storage_config = config.root().get<"storage">(); + const auto &replication_config = config.root().get<"replication">(); + const auto replication_mode{replication_config.get<"mode">()}; + + const binsrv::storage storage{ + storage_config, binsrv::storage_construction_mode_type::querying_only, + replication_mode}; + + binsrv::models::search_response response; + const auto &binlog_records{storage.get_binlog_records()}; + if (binlog_records.empty()) { + throw std::runtime_error("Binlog storage is empty"); + } + for (const auto &record : binlog_records) { + // break when we find a binlog file with min timestamp greater + // than the provided one + if (record.timestamps.get_min_timestamp() > timestamp) { + break; + } + append_record_to_search_response(response, storage, record); + } + if (response.root().get<"result">().empty()) { + throw std::runtime_error("Timestamp is too old"); + } + result = response.str(); + operation_successful = true; + } catch (const std::exception &e) { + const binsrv::models::error_response response{e.what()}; + result = response.str(); + } + std::cout << result << '\n'; + return operation_successful; +} + +} // namespace operations diff --git a/src/operations/search_by_timestamp_operation.hpp b/src/operations/search_by_timestamp_operation.hpp new file mode 100644 index 0000000..8177777 --- /dev/null +++ b/src/operations/search_by_timestamp_operation.hpp @@ -0,0 +1,50 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_SEARCH_BY_TIMESTAMP_OPERATION_HPP +#define OPERATIONS_SEARCH_BY_TIMESTAMP_OPERATION_HPP + +#include + +#include "operations/generic_operation_fwd.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +template <> +class generic_operation + : public basic_operation { +public: + static constexpr auto expected_number_of_arguments{2UZ}; + + explicit generic_operation(util::command_line_arg_view cmd_args); + [[nodiscard]] bool execute() const override; + +private: + [[nodiscard]] std::string_view get_config_file_path() const noexcept { + return basic_operation::get_cmd_args()[2UZ]; + } + [[nodiscard]] std::string_view get_timestamp() const noexcept { + return basic_operation::get_cmd_args()[3UZ]; + } +}; + +} // namespace operations + +#endif // OPERATIONS_SEARCH_BY_TIMESTAMP_OPERATION_HPP diff --git a/src/operations/version_operation.cpp b/src/operations/version_operation.cpp new file mode 100644 index 0000000..83ced0b --- /dev/null +++ b/src/operations/version_operation.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#include "operations/version_operation.hpp" + +#include + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" + +#include "app_version.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +generic_operation::generic_operation( + util::command_line_arg_view cmd_args) + : basic_operation{mode_type::version, cmd_args, + expected_number_of_arguments} {} + +[[nodiscard]] bool generic_operation::execute() const { + std::cout << app_version.get_string() << '\n'; + return true; +} + +} // namespace operations diff --git a/src/operations/version_operation.hpp b/src/operations/version_operation.hpp new file mode 100644 index 0000000..0b011bc --- /dev/null +++ b/src/operations/version_operation.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPERATIONS_VERSION_OPERATION_HPP +#define OPERATIONS_VERSION_OPERATION_HPP + +#include "operations/generic_operation_fwd.hpp" + +#include "operations/basic_operation.hpp" +#include "operations/mode_type.hpp" + +#include "util/command_line_helpers_fwd.hpp" + +namespace operations { + +template <> +class generic_operation : public basic_operation { +public: + static constexpr auto expected_number_of_arguments{0UZ}; + + explicit generic_operation(util::command_line_arg_view cmd_args); + [[nodiscard]] bool execute() const override; +}; + +} // namespace operations + +#endif // OPERATIONS_VERSION_OPERATION_HPP