diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bfb3551..57b12f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,7 @@ file(GLOB SERVER_SOURCES "tateyama/configuration/*.cpp" ) if (ENABLE_ALTIMETER) - set(SERVER_SOURCES ${SERVER_SOURCES} "tateyama/altimeter/altimeter_helper.cpp") + list(APPEND SERVER_SOURCES "tateyama/altimeter/altimeter_helper.cpp") endif () add_executable(server @@ -96,7 +96,14 @@ set(ProtoFiles ${CMAKE_CURRENT_SOURCE_DIR}/tateyama/proto/session/response.proto ${CMAKE_CURRENT_SOURCE_DIR}/tateyama/proto/session/diagnostic.proto ) - +if (ENABLE_ALTIMETER) + list(APPEND ProtoFiles + ${CMAKE_CURRENT_SOURCE_DIR}/tateyama/proto/altimeter/request.proto + ${CMAKE_CURRENT_SOURCE_DIR}/tateyama/proto/altimeter/response.proto + ${CMAKE_CURRENT_SOURCE_DIR}/tateyama/proto/altimeter/common.proto + ) +endif() + # By default, PROTOBUF_GENERATE_CPP generates file path for .pb.cc as if they are in the same directory. # Work-around this with PROTOBUF_GENERATE_CPP_APPEND_PATH set(PROTOBUF_GENERATE_CPP_APPEND_PATH OFF) @@ -114,6 +121,9 @@ file(GLOB TGCTL_SOURCES "tateyama/session/*.cpp" "tateyama/metrics/*.cpp" ) +if (ENABLE_ALTIMETER) + list(APPEND TGCTL_SOURCES "tateyama/altimeter/altimeter.cpp") +endif () set_source_files_properties( ${GENERATED_PROTO_SRCS} diff --git a/src/tateyama/altimeter/altimeter.cpp b/src/tateyama/altimeter/altimeter.cpp new file mode 100644 index 0000000..6489484 --- /dev/null +++ b/src/tateyama/altimeter/altimeter.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2022-2024 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "tateyama/authentication/authentication.h" +#include +#include +#include + +#include +#include +#include "altimeter.h" + +DECLARE_string(monitor); + +namespace tateyama::altimeter { + +template +static monitor::reason post_processing(std::optional& response_opt, const std::string_view sub_command) { + if (response_opt) { + auto response = response_opt.value(); + + switch(response.result_case()) { + case T::ResultCase::kSuccess: + return monitor::reason::absent; + case T::ResultCase::kError: + std::cerr << "altimeter " << sub_command << " error: " << response.error().message() << std::endl; + return monitor::reason::server; + default: + std::cerr << "altimeter " << sub_command << " returns illegal response" << std::endl; + return monitor::reason::unknown; + } + } + std::cerr << "altimeter " << sub_command << " returns nullopt" << std::endl; + return monitor::reason::server; +} + +tgctl::return_code set_enabled(const std::string& type, bool enabled) { + std::unique_ptr monitor_output{}; + + if (!FLAGS_monitor.empty()) { + monitor_output = std::make_unique(FLAGS_monitor); + monitor_output->start(); + } + + auto reason = monitor::reason::absent; + authentication::auth_options(); + try { + auto transport = std::make_unique(tateyama::framework::service_id_altimeter); + ::tateyama::proto::altimeter::request::Request request{}; + auto* mutable_configure = request.mutable_configure(); + if (type == "event") { + mutable_configure->mutable_event_log()->set_enabled(enabled); + } else if(type == "audit") { + mutable_configure->mutable_audit_log()->set_enabled(enabled); + } else { + throw std::runtime_error("illegal type for altimeter set_enabled"); + } + auto response_opt = transport->send<::tateyama::proto::altimeter::response::Configure>(request); + request.clear_configure(); + + reason = post_processing<::tateyama::proto::altimeter::response::Configure>(response_opt, "set_enabled"); + } catch (std::runtime_error &ex) { + std::cerr << "could not connect to database with name '" << tateyama::bootstrap::wire::transport::database_name() << "'" << std::endl; + reason = monitor::reason::connection; + } + + if (monitor_output) { + monitor_output->finish(reason); + } + return (reason == monitor::reason::absent) ? tgctl::return_code::err : tgctl::return_code::err; // NOLINT(misc-redundant-expression) +} + +tgctl::return_code set_log_level(const std::string& type, const std::string& level) { + std::unique_ptr monitor_output{}; + + if (!FLAGS_monitor.empty()) { + monitor_output = std::make_unique(FLAGS_monitor); + monitor_output->start(); + } + + auto reason = monitor::reason::absent; + authentication::auth_options(); + try { + auto transport = std::make_unique(tateyama::framework::service_id_altimeter); + ::tateyama::proto::altimeter::request::Request request{}; + auto* mutable_configure = request.mutable_configure(); + std::uint64_t l = std::stoul(level); + if (type == "event") { + mutable_configure->mutable_event_log()->set_level(l); + } else if(type == "audit") { + mutable_configure->mutable_audit_log()->set_level(l); + } else { + throw std::runtime_error("illegal type for altimeter set_log_level"); + } + auto response_opt = transport->send<::tateyama::proto::altimeter::response::Configure>(request); + request.clear_configure(); + + reason = post_processing<::tateyama::proto::altimeter::response::Configure>(response_opt, "set_log_level"); + } catch (std::runtime_error &ex) { + std::cerr << "could not connect to database with name '" << tateyama::bootstrap::wire::transport::database_name() << "'" << std::endl; + reason = monitor::reason::connection; + } + + if (monitor_output) { + monitor_output->finish(reason); + } + return (reason == monitor::reason::absent) ? tgctl::return_code::err : tgctl::return_code::err; // NOLINT(misc-redundant-expression) +} + +tgctl::return_code set_statement_duration(const std::string& value) { + std::unique_ptr monitor_output{}; + + if (!FLAGS_monitor.empty()) { + monitor_output = std::make_unique(FLAGS_monitor); + monitor_output->start(); + } + + auto reason = monitor::reason::absent; + authentication::auth_options(); + try { + auto transport = std::make_unique(tateyama::framework::service_id_altimeter); + ::tateyama::proto::altimeter::request::Request request{}; + auto* mutable_configure = request.mutable_configure(); + std::uint64_t v = std::stoul(value); + mutable_configure->mutable_event_log()->set_statement_duration(v); + auto response_opt = transport->send<::tateyama::proto::altimeter::response::Configure>(request); + request.clear_configure(); + + reason = post_processing<::tateyama::proto::altimeter::response::Configure>(response_opt, "set_statement_duration"); + } catch (std::runtime_error &ex) { + std::cerr << "could not connect to database with name '" << tateyama::bootstrap::wire::transport::database_name() << "'" << std::endl; + reason = monitor::reason::connection; + } + + if (monitor_output) { + monitor_output->finish(reason); + } + return (reason == monitor::reason::absent) ? tgctl::return_code::err : tgctl::return_code::err; // NOLINT(misc-redundant-expression) +} + +tgctl::return_code rotate(const std::string& type) { + std::unique_ptr monitor_output{}; + + if (!FLAGS_monitor.empty()) { + monitor_output = std::make_unique(FLAGS_monitor); + monitor_output->start(); + } + + auto reason = monitor::reason::absent; + authentication::auth_options(); + try { + auto transport = std::make_unique(tateyama::framework::service_id_altimeter); + ::tateyama::proto::altimeter::request::Request request{}; + auto* mutable_log_rotate = request.mutable_log_rotate(); + ::tateyama::proto::altimeter::common::LogCategory category{}; + if (type == "event") { + category = ::tateyama::proto::altimeter::common::LogCategory::EVENT; + } else if(type == "audit") { + category = ::tateyama::proto::altimeter::common::LogCategory::AUDIT; + } else { + throw std::runtime_error("illegal type for altimeter rotate"); + } + mutable_log_rotate->set_category(category); + auto response_opt = transport->send<::tateyama::proto::altimeter::response::LogRotate>(request); + request.clear_log_rotate(); + + reason = post_processing<::tateyama::proto::altimeter::response::LogRotate>(response_opt, "rotete"); + } catch (std::runtime_error &ex) { + std::cerr << "could not connect to database with name '" << tateyama::bootstrap::wire::transport::database_name() << "'" << std::endl; + reason = monitor::reason::connection; + } + + if (monitor_output) { + monitor_output->finish(reason); + } + return (reason == monitor::reason::absent) ? tgctl::return_code::err : tgctl::return_code::err; // NOLINT(misc-redundant-expression) +} + +} // tateyama::bootstrap::backup diff --git a/src/tateyama/altimeter/altimeter.h b/src/tateyama/altimeter/altimeter.h new file mode 100644 index 0000000..fe06241 --- /dev/null +++ b/src/tateyama/altimeter/altimeter.h @@ -0,0 +1,31 @@ +/* + * Copyright 2022-2024 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include "tateyama/tgctl/tgctl.h" + +namespace tateyama::altimeter { + + tgctl::return_code set_enabled(const std::string&, bool); + tgctl::return_code set_log_level(const std::string&, const std::string&); + tgctl::return_code set_statement_duration(const std::string&); + tgctl::return_code rotate(const std::string&); + +} // tateyama::altimeter diff --git a/src/tateyama/altimeter/altimeter_helper.cpp b/src/tateyama/altimeter/altimeter_helper.cpp index 1fbc584..2e61f0e 100644 --- a/src/tateyama/altimeter/altimeter_helper.cpp +++ b/src/tateyama/altimeter/altimeter_helper.cpp @@ -84,7 +84,7 @@ void altimeter_helper::setup(::altimeter::configuration& configuration, tateyama std::cout << "Failed to flush or rotate event log files: " << error_message << "\n"; }); - ::altimeter::event::event_logger::set_stmt_duration_threshold(section->get("stmt_duration_threshold").value()); + ::altimeter::event::event_logger::set_stmt_duration_threshold(section->get("stmt_duration_threshold").value()); } else { configuration.error_handler([](std::string_view error_message) { std::cout << "Failed to flush or rotate audit log files: " @@ -115,7 +115,7 @@ void altimeter_helper::setup(::altimeter::configuration& configuration, tateyama LOG(INFO) << prefix << "flush_file_size = " << flush_file_size << ", file size to be flashed"; LOG(INFO) << prefix << "max_file_size = " << max_file_size << ", file size to rotate"; if (type == log_type::event_log) { - LOG(INFO) << prefix << "stmt_duration_threshold = " << section->get("stmt_duration_threshold").value() << ", duration threshold for statement log"; + LOG(INFO) << prefix << "stmt_duration_threshold = " << section->get("stmt_duration_threshold").value() << ", duration threshold for statement log"; } } diff --git a/src/tateyama/monitor/monitor.h b/src/tateyama/monitor/monitor.h index 5c17225..aef65d2 100644 --- a/src/tateyama/monitor/monitor.h +++ b/src/tateyama/monitor/monitor.h @@ -33,6 +33,19 @@ enum class status : std::int64_t { unknown = -1, }; +enum class reason : std::int64_t { + absent = 0, + connection = 1, + server = 2, + not_found = 3, + ambiguous = 4, + permission = 5, + variable_not_defined = 6, + variable_invalid_value = 7, + + unknown = -1, +}; + /** * @brief returns string representation of the value. * @param value the target value @@ -52,6 +65,27 @@ enum class status : std::int64_t { return "illegal state"sv; } +/** + * @brief returns string representation of the value. + * @param value the target value + * @return the corresponded string representation + */ +[[nodiscard]] constexpr inline std::string_view to_string_view(reason value) noexcept { + using namespace std::string_view_literals; + switch (value) { + case reason::absent:return "absent"sv; + case reason::connection:return "connection"sv; + case reason::server:return "server"sv; + case reason::not_found:return "not_found"sv; + case reason::ambiguous:return "ambiguous"sv; + case reason::permission:return "permission"sv; + case reason::variable_not_defined:return "variable_not_defined"sv; + case reason::variable_invalid_value:return "variable_invalid_value"sv; + case reason::unknown: return "unknown"sv; + } + return "illegal reason"sv; +} + class monitor { constexpr static std::string_view TIME_STAMP = R"("timestamp": )"; constexpr static std::string_view KIND_START = R"("kind": "start")"; @@ -62,6 +96,7 @@ class monitor { // status constexpr static std::string_view FORMAT_STATUS = R"("format": "status")"; constexpr static std::string_view STATUS = R"("status": ")"; + constexpr static std::string_view REASON = R"("reason": ")"; // session info constexpr static std::string_view FORMAT_SESSION_INFO = R"("format": "session-info")"; constexpr static std::string_view SESSION_ID = R"("session_id": ":)"; @@ -97,10 +132,21 @@ class monitor { void finish(bool status) { if (status) { strm_ << "{ " << TIME_STAMP << time(nullptr) << ", " - << KIND_FINISH << ", " << STATUS << "success\" }" << std::endl; + << KIND_FINISH << ", " << STATUS << R"(success" })" << std::endl; + } else { + strm_ << "{ " << TIME_STAMP << time(nullptr) << ", " + << KIND_FINISH << ", " << STATUS << R"(failure" })" << std::endl; + } + strm_.flush(); + } + void finish(reason rc) { + if (rc == reason::absent) { + strm_ << "{ " << TIME_STAMP << time(nullptr) << ", " + << KIND_FINISH << ", " << STATUS << R"(success" })" << std::endl; } else { strm_ << "{ " << TIME_STAMP << time(nullptr) << ", " - << KIND_FINISH << ", " << STATUS << "failure\" }" << std::endl; + << KIND_FINISH << ", " << STATUS << R"(failure", )" + << REASON << to_string_view(rc) << R"(" })" << std::endl; } strm_.flush(); } diff --git a/src/tateyama/proto/altimeter/common.proto b/src/tateyama/proto/altimeter/common.proto new file mode 100644 index 0000000..2c4b4b5 --- /dev/null +++ b/src/tateyama/proto/altimeter/common.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package tateyama.proto.altimeter.common; + +option java_multiple_files = false; +option java_package = "com.tsurugidb.altimeter.proto"; +option java_outer_classname = "AltimeterCommon"; + +// represents log category. +enum LogCategory { + + // log category is not set. + LOG_CATEGORY_NOT_SET = 0; + + // event log category. + EVENT = 1; + + // audit log category. + AUDIT = 2; +} diff --git a/src/tateyama/proto/altimeter/request.proto b/src/tateyama/proto/altimeter/request.proto new file mode 100644 index 0000000..70d6cfa --- /dev/null +++ b/src/tateyama/proto/altimeter/request.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package tateyama.proto.altimeter.request; + +option java_multiple_files = false; +option java_package = "com.tsurugidb.altimeter.proto"; +option java_outer_classname = "AltimeterRequest"; + +import "tateyama/proto/altimeter/common.proto"; + +// configuration for logging settings +message LogSettings { + + // whether or not enable logging, or keep the current settings. + oneof enabled_opt { + + // whether or not enable logging. + bool enabled = 1; + } + + // the threshold of log-level, or keep the current settings. + oneof level_opt { + + // the threshold of log-level, by numeric value. + uint64 level = 2; + } + + // the reporting threshold of statement duration, or keep the current settings. + oneof statement_duration_opt { + + // the reporting threshold of statement duration, by nanoseconds. + uint64 statement_duration = 3; + } +} + +// request message to configure the current log settings. +message Configure { + + // configure event log, or keep the current settings. + oneof event_log_opt { + + // configuration for event log. + LogSettings event_log = 1; + } + + // configure audit log, or keep the current settings. + oneof audit_log_opt { + + // configuration for audit log. + LogSettings audit_log = 2; + } +} + +// request message to rotate log files. +message LogRotate { + + // the target log category to rotate. + common.LogCategory category = 1; +} + +// the request message to altimeter logger service. +message Request { + // service message version (major) + uint64 service_message_version_major = 1; + + // service message version (minor) + uint64 service_message_version_minor = 2; + + // reserved for system use + reserved 3 to 10; + + // the request command. + oneof command { + // configure the log settings. + Configure configure = 11; + + // rotate the log files. + LogRotate log_rotate = 12; + } +} diff --git a/src/tateyama/proto/altimeter/response.proto b/src/tateyama/proto/altimeter/response.proto new file mode 100644 index 0000000..860010a --- /dev/null +++ b/src/tateyama/proto/altimeter/response.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package tateyama.proto.altimeter.response; + +option java_multiple_files = false; +option java_package = "com.tsurugidb.altimeter.proto"; +option java_outer_classname = "AltimeterResponse"; + +// empty message +message Void {} + +// represents kind of errors. +enum ErrorKind { + + // error kind is not set. + ERROR_KIND_NOT_SET = 0; + + // unknown error type. + UNKNOWN = 1; +} + +// represents an error message. +message Error { + + // the occurred error kind. + ErrorKind kind = 1; + + // the error message. + string message = 2; +} + +// response message for "Configure". +message Configure { + + // the response body. + oneof result { + // request is successfully completed. + Void success = 1; + + // request was failed by error. + Error error = 2; + } +} + +// response message for "LogRotate". +message LogRotate { + + // the response body. + oneof result { + // request is successfully completed. + Void success = 1; + + // request was failed by error. + Error error = 2; + } +} diff --git a/src/tateyama/tgctl/tgctl.cpp b/src/tateyama/tgctl/tgctl.cpp index 548f3cb..5fa9582 100644 --- a/src/tateyama/tgctl/tgctl.cpp +++ b/src/tateyama/tgctl/tgctl.cpp @@ -25,6 +25,9 @@ #include "tateyama/version/version.h" #include "tateyama/session/session.h" #include "tateyama/metrics/metrics.h" +#ifdef ENABLE_ALTIMETER +#include "tateyama/altimeter/altimeter.h" +#endif #include "help_text.h" @@ -208,6 +211,64 @@ int tgctl_main(const std::vector& args) { //NOLINT(readability-func std::cerr << "unknown dbstats-sub command '" << args.at(2) << "'" << std::endl; return tateyama::tgctl::return_code::err; } +#ifdef ENABLE_ALTIMETER + if (args.at(1) == "altimeter") { + if (args.size() < 3) { + std::cerr << "need to specify altimeter subcommand" << std::endl; + return tateyama::tgctl::return_code::err; + } + if (args.at(2) == "enable") { + if (args.size() < 4) { + std::cerr << "need to specify log type for altimeter enable" << std::endl; + return tateyama::tgctl::return_code::err; + } + return tateyama::altimeter::set_enabled(args.at(3), true); + } + if (args.at(2) == "disable") { + if (args.size() < 4) { + std::cerr << "need to specify log type for altimeter disable" << std::endl; + return tateyama::tgctl::return_code::err; + } + return tateyama::altimeter::set_enabled(args.at(3), false); + } + if (args.at(2) == "set") { + if (args.size() < 4) { + std::cerr << "need to specify parameter for altimeter set" << std::endl; + return tateyama::tgctl::return_code::err; + } + if (args.at(3) == "event_level") { + if (args.size() < 5) { + std::cerr << "need to specify parameter for altimeter set event_level" << std::endl; + return tateyama::tgctl::return_code::err; + } + return tateyama::altimeter::set_log_level("event", args.at(4)); + } + if (args.at(3) == "audit_level") { + if (args.size() < 5) { + std::cerr << "need to specify parameter for altimeter set audit_level" << std::endl; + return tateyama::tgctl::return_code::err; + } + return tateyama::altimeter::set_log_level("audit", args.at(4)); + } + if (args.at(3) == "statement_duration") { + if (args.size() < 5) { + std::cerr << "need to specify parameter for altimeter set statement_duration" << std::endl; + return tateyama::tgctl::return_code::err; + } + return tateyama::altimeter::set_statement_duration(args.at(4)); + } + } + if (args.at(2) == "rotate") { + if (args.size() < 4) { + std::cerr << "need to specify parameter for altimeter rotate" << std::endl; + return tateyama::tgctl::return_code::err; + } + return tateyama::altimeter::rotate(args.at(3)); + } + std::cerr << "unknown altimeter-sub command '" << args.at(2) << "'" << std::endl; + return tateyama::tgctl::return_code::err; + } +#endif std::cerr << "unknown command '" << args.at(1) << "'" << std::endl; return tateyama::tgctl::return_code::err; } diff --git a/src/tateyama/transport/transport.h b/src/tateyama/transport/transport.h index 3f11fd1..8aea9a6 100644 --- a/src/tateyama/transport/transport.h +++ b/src/tateyama/transport/transport.h @@ -39,7 +39,11 @@ #include #include #include - +#ifdef ENABLE_ALTIMETER +#include +#include +#include +#endif #include "tateyama/configuration/bootstrap_configuration.h" #include "client_wire.h" #include "timer.h" @@ -60,6 +64,10 @@ constexpr static std::size_t SESSION_MESSAGE_VERSION_MAJOR = 0; constexpr static std::size_t SESSION_MESSAGE_VERSION_MINOR = 0; constexpr static std::size_t METRICS_MESSAGE_VERSION_MAJOR = 0; constexpr static std::size_t METRICS_MESSAGE_VERSION_MINOR = 0; +#ifdef ENABLE_ALTIMETER +constexpr static std::size_t ALTIMETER_MESSAGE_VERSION_MAJOR = 0; +constexpr static std::size_t ALTIMETER_MESSAGE_VERSION_MINOR = 0; +#endif constexpr static std::int64_t EXPIRATION_SECONDS = 60; class transport { @@ -283,6 +291,41 @@ class transport { return response; } +#ifdef ENABLE_ALTIMETER + // altimeter + template + std::optional send(::tateyama::proto::altimeter::request::Request& request) { + std::stringstream sst{}; + if(auto res = tateyama::utils::SerializeDelimitedToOstream(header_, std::addressof(sst)); ! res) { + return std::nullopt; + } + request.set_service_message_version_major(ALTIMETER_MESSAGE_VERSION_MAJOR); + request.set_service_message_version_minor(ALTIMETER_MESSAGE_VERSION_MINOR); + if(auto res = tateyama::utils::SerializeDelimitedToOstream(request, std::addressof(sst)); ! res) { + return std::nullopt; + } + auto slot_index = wire_.search_slot(); + wire_.send(sst.str(), slot_index); + + std::string res_message{}; + wire_.receive(res_message, slot_index); + ::tateyama::proto::framework::response::Header header{}; + google::protobuf::io::ArrayInputStream ins{res_message.data(), static_cast(res_message.length())}; + if(auto res = tateyama::utils::ParseDelimitedFromZeroCopyStream(std::addressof(header), std::addressof(ins), nullptr); ! res) { + return std::nullopt; + } + std::string_view payload{}; + if (auto res = tateyama::utils::GetDelimitedBodyFromZeroCopyStream(std::addressof(ins), nullptr, payload); ! res) { + return std::nullopt; + } + T response{}; + if(auto res = response.ParseFromArray(payload.data(), payload.length()); ! res) { + return std::nullopt; + } + return response; + } +#endif + void close() { wire_.close(); closed_ = true; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8d67252..c4b4841 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,6 +45,12 @@ file(GLOB SRCS "tateyama/metrics/*_test.cpp" "tateyama/transport/*_test.cpp" ) +if (ENABLE_ALTIMETER) + file(GLOB ALTIMETER_SRCS + "tateyama/altimeter/*_test.cpp" + ) + list(APPEND SRCS ${ALTIMETER_SRCS}) +endif () foreach(file ${SRCS}) add_test_executable(${file}) diff --git a/test/tateyama/altimeter/altimeter_test.cpp b/test/tateyama/altimeter/altimeter_test.cpp new file mode 100644 index 0000000..42a7cfe --- /dev/null +++ b/test/tateyama/altimeter/altimeter_test.cpp @@ -0,0 +1,378 @@ +/* + * Copyright 2022-2024 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_root.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include "tateyama/configuration/bootstrap_configuration.h" +#include "tateyama/test_utils/server_mock.h" + +namespace tateyama::test_utils { + +template<> +inline void server_mock::request_message(tateyama::proto::altimeter::request::Configure& rq) { + tateyama::proto::altimeter::request::Request r{}; + auto s = current_request(); + EXPECT_TRUE(r.ParseFromString(s)); + EXPECT_EQ(r.command_case(), tateyama::proto::altimeter::request::Request::CommandCase::kConfigure); + rq = r.configure(); +} + +template<> +inline void server_mock::request_message(tateyama::proto::altimeter::request::LogRotate& rq) { + tateyama::proto::altimeter::request::Request r{}; + auto s = current_request(); + EXPECT_TRUE(r.ParseFromString(s)); + EXPECT_EQ(r.command_case(), tateyama::proto::altimeter::request::Request::CommandCase::kLogRotate); + rq = r.log_rotate(); +} + +} // tateyama::test_utils + + +namespace tateyama::altimeter { + +class altimeter_test : public ::testing::Test { +public: + virtual void SetUp() { + helper_ = std::make_unique("altimeter_test", 20301); + helper_->set_up(); + auto bst_conf = tateyama::configuration::bootstrap_configuration::create_bootstrap_configuration(helper_->conf_file_path()); + server_mock_ = std::make_unique("altimeter_test", bst_conf.digest(), sync_); + sync_.wait(); + } + + virtual void TearDown() { + helper_->tear_down(); + } + +protected: + std::unique_ptr helper_{}; + std::unique_ptr server_mock_{}; + boost::barrier sync_{2}; + + std::string read_pipe(FILE* fp) { + std::stringstream ss{}; + int c{}; + while ((c = std::fgetc(fp)) != EOF) { + ss << static_cast(c); + } + return ss.str(); + } +}; + +// enable +TEST_F(altimeter_test, enable_event) { + { + tateyama::proto::altimeter::response::Configure rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter enable event --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter enable event" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::Configure rq{}; + server_mock_->request_message(rq); + + EXPECT_TRUE(rq.has_event_log()); + EXPECT_FALSE(rq.has_audit_log()); + + auto& ls = rq.event_log(); + EXPECT_EQ(ls.enabled_opt_case(), tateyama::proto::altimeter::request::LogSettings::EnabledOptCase::kEnabled); + EXPECT_EQ(ls.level_opt_case(), tateyama::proto::altimeter::request::LogSettings::LevelOptCase::LEVEL_OPT_NOT_SET); + EXPECT_EQ(ls.statement_duration_opt_case(), tateyama::proto::altimeter::request::LogSettings::StatementDurationOptCase::STATEMENT_DURATION_OPT_NOT_SET); + EXPECT_EQ(ls.enabled(), true); +} + +TEST_F(altimeter_test, enable_audit) { + { + tateyama::proto::altimeter::response::Configure rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter enable audit --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter enable audit" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::Configure rq{}; + server_mock_->request_message(rq); + + EXPECT_FALSE(rq.has_event_log()); + EXPECT_TRUE(rq.has_audit_log()); + + auto& ls = rq.audit_log(); + EXPECT_EQ(ls.enabled_opt_case(), tateyama::proto::altimeter::request::LogSettings::EnabledOptCase::kEnabled); + EXPECT_EQ(ls.level_opt_case(), tateyama::proto::altimeter::request::LogSettings::LevelOptCase::LEVEL_OPT_NOT_SET); + EXPECT_EQ(ls.statement_duration_opt_case(), tateyama::proto::altimeter::request::LogSettings::StatementDurationOptCase::STATEMENT_DURATION_OPT_NOT_SET); + EXPECT_EQ(ls.enabled(), true); +} + +// disable +TEST_F(altimeter_test, disable_event) { + { + tateyama::proto::altimeter::response::Configure rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter disable event --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter disable event" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::Configure rq{}; + server_mock_->request_message(rq); + + EXPECT_TRUE(rq.has_event_log()); + EXPECT_FALSE(rq.has_audit_log()); + + auto& ls = rq.event_log(); + EXPECT_EQ(ls.enabled_opt_case(), tateyama::proto::altimeter::request::LogSettings::EnabledOptCase::kEnabled); + EXPECT_EQ(ls.level_opt_case(), tateyama::proto::altimeter::request::LogSettings::LevelOptCase::LEVEL_OPT_NOT_SET); + EXPECT_EQ(ls.statement_duration_opt_case(), tateyama::proto::altimeter::request::LogSettings::StatementDurationOptCase::STATEMENT_DURATION_OPT_NOT_SET); + EXPECT_EQ(ls.enabled(), false); +} + +TEST_F(altimeter_test, disable_audit) { + { + tateyama::proto::altimeter::response::Configure rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter disable audit --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter disable audit" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::Configure rq{}; + server_mock_->request_message(rq); + + EXPECT_FALSE(rq.has_event_log()); + EXPECT_TRUE(rq.has_audit_log()); + + auto& ls = rq.audit_log(); + EXPECT_EQ(ls.enabled_opt_case(), tateyama::proto::altimeter::request::LogSettings::EnabledOptCase::kEnabled); + EXPECT_EQ(ls.level_opt_case(), tateyama::proto::altimeter::request::LogSettings::LevelOptCase::LEVEL_OPT_NOT_SET); + EXPECT_EQ(ls.statement_duration_opt_case(), tateyama::proto::altimeter::request::LogSettings::StatementDurationOptCase::STATEMENT_DURATION_OPT_NOT_SET); + EXPECT_EQ(ls.enabled(), false); +} + + + +// set level +TEST_F(altimeter_test, level_event) { + { + tateyama::proto::altimeter::response::Configure rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter set event_level 12 --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter set event_level" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::Configure rq{}; + server_mock_->request_message(rq); + + EXPECT_TRUE(rq.has_event_log()); + EXPECT_FALSE(rq.has_audit_log()); + + auto& ls = rq.event_log(); + EXPECT_EQ(ls.enabled_opt_case(), tateyama::proto::altimeter::request::LogSettings::EnabledOptCase::ENABLED_OPT_NOT_SET); + EXPECT_EQ(ls.level_opt_case(), tateyama::proto::altimeter::request::LogSettings::LevelOptCase::kLevel); + EXPECT_EQ(ls.statement_duration_opt_case(), tateyama::proto::altimeter::request::LogSettings::StatementDurationOptCase::STATEMENT_DURATION_OPT_NOT_SET); + EXPECT_EQ(ls.level(), 12); +} + +TEST_F(altimeter_test, level_audit) { + { + tateyama::proto::altimeter::response::Configure rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter set audit_level 12 --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter set audit_level" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::Configure rq{}; + server_mock_->request_message(rq); + + EXPECT_FALSE(rq.has_event_log()); + EXPECT_TRUE(rq.has_audit_log()); + + auto& ls = rq.audit_log(); + EXPECT_EQ(ls.enabled_opt_case(), tateyama::proto::altimeter::request::LogSettings::EnabledOptCase::ENABLED_OPT_NOT_SET); + EXPECT_EQ(ls.level_opt_case(), tateyama::proto::altimeter::request::LogSettings::LevelOptCase::kLevel); + EXPECT_EQ(ls.statement_duration_opt_case(), tateyama::proto::altimeter::request::LogSettings::StatementDurationOptCase::STATEMENT_DURATION_OPT_NOT_SET); + EXPECT_EQ(ls.level(), 12); +} + +// set statement_duration +TEST_F(altimeter_test, statement_duration) { + { + tateyama::proto::altimeter::response::Configure rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter set statement_duration 12345 --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter set statement_duration" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::Configure rq{}; + server_mock_->request_message(rq); + + EXPECT_TRUE(rq.has_event_log()); + EXPECT_FALSE(rq.has_audit_log()); + + auto& ls = rq.event_log(); + EXPECT_EQ(ls.enabled_opt_case(), tateyama::proto::altimeter::request::LogSettings::EnabledOptCase::ENABLED_OPT_NOT_SET); + EXPECT_EQ(ls.level_opt_case(), tateyama::proto::altimeter::request::LogSettings::LevelOptCase::LEVEL_OPT_NOT_SET); + EXPECT_EQ(ls.statement_duration_opt_case(), tateyama::proto::altimeter::request::LogSettings::StatementDurationOptCase::kStatementDuration); + EXPECT_EQ(ls.statement_duration(), 12345); +} + +// rotate +TEST_F(altimeter_test, rotate_event) { + { + tateyama::proto::altimeter::response::LogRotate rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter rotate event --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter rotate event" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::LogRotate rq{}; + server_mock_->request_message(rq); + + EXPECT_EQ(rq.category(), tateyama::proto::altimeter::common::LogCategory::EVENT); +} + +TEST_F(altimeter_test, rotate_audit) { + { + tateyama::proto::altimeter::response::LogRotate rs{}; + (void) rs.mutable_success(); + server_mock_->push_response(rs.SerializeAsString()); + } + + std::string command; + FILE *fp; + + command = "tgctl altimeter rotate audit --conf "; + command += helper_->conf_file_path(); + std::cout << command << std::endl; + if((fp = popen(command.c_str(), "r")) == nullptr){ + std::cerr << "cannot tgctl altimeter rotate audit" << std::endl; + } + auto result = read_pipe(fp); + std::cout << result << std::flush; + + EXPECT_EQ(tateyama::framework::service_id_altimeter, server_mock_->component_id()); + tateyama::proto::altimeter::request::LogRotate rq{}; + server_mock_->request_message(rq); + + EXPECT_EQ(rq.category(), tateyama::proto::altimeter::common::LogCategory::AUDIT); +} + +} // namespace tateyama::altimeter