| |
| #include <chrono> // NOLINT(build/c++11) |
| #include <cstdint> |
| #include <filesystem> // NOLINT(build/c++17) |
| #include <fstream> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| |
| #include "boottime.grpc.pb.h" |
| #include "boottime.pb.h" |
| #include <gtest/gtest.h> |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "grpcblob_server.h" |
| #include "grpcpp/client_context.h" |
| #include "grpcpp/create_channel.h" |
| #include "grpcpp/security/credentials.h" |
| #include "grpcpp/server_context.h" |
| #include "grpcpp/support/status.h" |
| |
| #ifndef EXPECT_OK |
| #define EXPECT_OK(x) EXPECT_TRUE(x.ok()) |
| #endif |
| |
| #ifndef TEMP_CLIENT_CONTEXT |
| #define TEMP_CLIENT_CONTEXT(x, y) \ |
| { \ |
| ClientContext x; \ |
| y; \ |
| } |
| #endif |
| |
| namespace blobs { |
| namespace { |
| |
| namespace fs = std::filesystem; |
| using ::grpc::Channel; |
| using ::grpc::ClientContext; |
| using ::phosphor::boottime::GetBootTimeRequest; |
| using ::phosphor::boottime::GetBootTimeResponse; |
| using ::phosphor::boottime::RebootCompleteRequest; |
| using ::phosphor::boottime::RebootCompleteResponse; |
| using ::phosphor::boottime::SetCheckpointRequest; |
| using ::phosphor::boottime::SetCheckpointResponse; |
| using ::phosphor::boottime::SetDurationRequest; |
| using ::phosphor::boottime::SetDurationResponse; |
| using ::phosphor::boottime::grpc_gen::BootTimeService; |
| |
| class BootTimeTest : public ::testing::Test { |
| protected: |
| BootTimeTest() { |
| if (fs::exists(test_dir_)) { |
| fs::remove_all(test_dir_); |
| } |
| fs::create_directories(test_dir_); |
| LockFilePermissions(fs::perm_options::add); |
| if (server_ == nullptr) { |
| SetUpServer(); |
| } |
| } |
| |
| ~BootTimeTest() { |
| if (fs::exists(test_dir_)) { |
| fs::remove_all(test_dir_); |
| } |
| } |
| |
| static int64_t GetMonoTimeMs() { |
| double monotime_tmp; |
| std::ifstream fin("/proc/uptime"); |
| fin >> monotime_tmp; |
| return static_cast<int64_t>(monotime_tmp * 1000); |
| } |
| |
| static void LockFilePermissions(fs::perm_options op) { |
| if (!fs::exists(test_lock_file_)) { |
| fs::create_directories(fs::path(test_lock_file_).remove_filename()); |
| std::ofstream{std::string(test_lock_file_)}; |
| } |
| fs::permissions(test_lock_file_, |
| fs::perms::owner_write | fs::perms::group_write | |
| fs::perms::others_write, |
| op); |
| } |
| |
| static void SetUpServer() { |
| server_ = std::make_unique<GrpcBlobServer>(std::string(test_dir_)); |
| server_->start(); |
| std::shared_ptr<Channel> channel = grpc::CreateChannel( |
| "localhost:10166", grpc::InsecureChannelCredentials()); |
| boottime_stub_ = BootTimeService::NewStub(channel); |
| } |
| |
| static void CloseServer() { |
| absl::SleepFor(absl::Seconds(1)); |
| server_->stop(); |
| server_.reset(); |
| boottime_stub_.reset(); |
| } |
| |
| static void SetUpTestSuite() { SetUpServer(); } |
| |
| static void TearDownTestSuite() { CloseServer(); } |
| |
| static constexpr std::string_view test_dir_ = "/tmp/boottime_grpc_test/"; |
| static constexpr std::string_view test_lock_file_ = |
| "/tmp/boottime_grpc_test/var/lock/boottime_api.lock"; |
| static std::unique_ptr<GrpcBlobServer> server_; |
| static std::unique_ptr<BootTimeService::Stub> boottime_stub_; |
| }; |
| |
| std::unique_ptr<GrpcBlobServer> BootTimeTest::server_; |
| std::unique_ptr<BootTimeService::Stub> BootTimeTest::boottime_stub_; |
| |
| TEST_F(BootTimeTest, BootTimeSetInvalidNameFormat) { |
| SetCheckpointRequest req_c; |
| SetCheckpointResponse resp_c; |
| SetDurationRequest req_d; |
| SetDurationResponse resp_d; |
| |
| req_c.set_name("_**#-aa"); |
| req_c.set_timestamp_ms(20000); |
| req_d.set_name("??_@@0002"); |
| req_d.set_duration_ms(1500); |
| TEMP_CLIENT_CONTEXT( |
| c, |
| EXPECT_EQ(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c).error_code(), |
| grpc::StatusCode::INVALID_ARGUMENT)); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_EQ(boottime_stub_->SetDuration(&c, req_d, &resp_d).error_code(), |
| grpc::StatusCode::INVALID_ARGUMENT)); |
| } |
| |
| TEST_F(BootTimeTest, BootTimeSetInvalidNameLength) { |
| SetCheckpointRequest req_c; |
| SetCheckpointResponse resp_c; |
| SetDurationRequest req_d; |
| SetDurationResponse resp_d; |
| |
| req_c.mutable_name()->clear(); |
| req_d.mutable_name()->clear(); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_EQ(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c), |
| grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "Invalid name length"))); |
| TEMP_CLIENT_CONTEXT(c, |
| EXPECT_EQ(boottime_stub_->SetDuration(&c, req_d, &resp_d), |
| grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "Invalid name length"))); |
| |
| req_c.mutable_name()->reserve(1024 + 32); |
| req_d.mutable_name()->reserve(1024 + 32); |
| for (int i = 0; i <= (1024 / 16); ++i) { |
| req_c.mutable_name()->append("aaaaaaaaaaaaaaaa"); |
| req_d.mutable_name()->append("aaaaaaaaaaaaaaaa"); |
| } |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_EQ(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c), |
| grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "Invalid name length"))); |
| TEMP_CLIENT_CONTEXT(c, |
| EXPECT_EQ(boottime_stub_->SetDuration(&c, req_d, &resp_d), |
| grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "Invalid name length"))); |
| } |
| |
| TEST_F(BootTimeTest, BootTimeSetAndGetData) { |
| SetCheckpointRequest req_c; |
| SetCheckpointResponse resp_c; |
| SetDurationRequest req_d; |
| SetDurationResponse resp_d; |
| RebootCompleteRequest req_r; |
| RebootCompleteResponse resp_r; |
| GetBootTimeRequest req_g; |
| GetBootTimeResponse resp_g; |
| int64_t monotime_ms_begin = 0; |
| int64_t monotime_ms_end = 0; |
| int64_t walltime_ms_begin = 0; |
| |
| monotime_ms_begin = GetMonoTimeMs(); |
| walltime_ms_begin = std::chrono::system_clock::now().time_since_epoch() / |
| std::chrono::milliseconds(1); |
| req_c.set_name("tc1"); |
| req_c.set_timestamp_ms(1000); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c))); |
| |
| req_c.set_name("tc2"); |
| req_c.set_timestamp_ms(2000); |
| req_c.set_duration_ms(300); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c))); |
| |
| // Use internal walltime |
| req_c.set_name("tc3"); |
| req_c.set_timestamp_ms(0); |
| req_c.set_duration_ms(0); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c))); |
| |
| req_d.set_name("td1"); |
| req_d.set_duration_ms(1600); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetDuration(&c, req_d, &resp_d))); |
| |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->GetBootTime(&c, req_g, &resp_g))); |
| monotime_ms_end = GetMonoTimeMs(); |
| |
| // We can't check everything via text proto as reboot_flow.timestamp_mono_ms |
| // is not determined by the caller. |
| EXPECT_EQ(resp_g.stat().is_rebooting(), true); |
| EXPECT_EQ(resp_g.reboot_flow(0).stage(), "tc1"); |
| EXPECT_EQ(resp_g.reboot_flow(0).timestamp_wall_ms(), 1000); |
| EXPECT_GE(resp_g.reboot_flow(0).timestamp_mono_ms(), monotime_ms_begin); |
| |
| EXPECT_EQ(resp_g.reboot_flow(1).stage(), "tc2:BEGIN"); |
| EXPECT_EQ(resp_g.reboot_flow(1).timestamp_wall_ms(), 2000 - 300); |
| |
| EXPECT_EQ(resp_g.reboot_flow(2).stage(), "tc2"); |
| EXPECT_EQ(resp_g.reboot_flow(2).timestamp_wall_ms(), 2000); |
| EXPECT_EQ(resp_g.reboot_flow(2).timestamp_mono_ms(), |
| resp_g.reboot_flow(1).timestamp_mono_ms() + 300); |
| EXPECT_GE(resp_g.reboot_flow(2).timestamp_mono_ms(), |
| resp_g.reboot_flow(0).timestamp_mono_ms()); |
| |
| EXPECT_EQ(resp_g.reboot_flow(3).stage(), "tc3"); |
| EXPECT_GE(resp_g.reboot_flow(3).timestamp_mono_ms(), |
| resp_g.reboot_flow(2).timestamp_mono_ms()); |
| EXPECT_LE(resp_g.reboot_flow(3).timestamp_mono_ms(), monotime_ms_end); |
| EXPECT_GE(resp_g.reboot_flow(3).timestamp_wall_ms(), walltime_ms_begin); |
| EXPECT_LE(resp_g.reboot_flow(3).timestamp_wall_ms(), |
| std::chrono::system_clock::now().time_since_epoch() / |
| std::chrono::milliseconds(1)); |
| |
| EXPECT_EQ(resp_g.durations(0).name(), "td1"); |
| EXPECT_EQ(resp_g.durations(0).duration_ms(), 1600); |
| |
| // Verify that single-host port 10166 (instance 2) writes to host0, matching |
| // instance 0 (port 10167). |
| std::shared_ptr<Channel> host0_channel = grpc::CreateChannel( |
| "localhost:10167", grpc::InsecureChannelCredentials()); |
| std::unique_ptr<BootTimeService::Stub> host0_stub = |
| BootTimeService::NewStub(host0_channel); |
| GetBootTimeResponse resp_host0; |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(host0_stub->GetBootTime(&c, req_g, &resp_host0))); |
| EXPECT_EQ(resp_host0.reboot_flow_size(), resp_g.reboot_flow_size()); |
| EXPECT_EQ(resp_host0.durations_size(), resp_g.durations_size()); |
| } |
| |
| TEST_F(BootTimeTest, BootTimeRebootBehavior) { |
| SetCheckpointRequest req_c; |
| SetCheckpointResponse resp_c; |
| SetDurationRequest req_d; |
| SetDurationResponse resp_d; |
| RebootCompleteRequest req_r; |
| RebootCompleteResponse resp_r; |
| GetBootTimeRequest req_g; |
| GetBootTimeResponse resp_g; |
| int64_t monotime_ms_begin = 0; |
| int64_t walltime_ms_begin = 0; |
| |
| monotime_ms_begin = GetMonoTimeMs(); |
| walltime_ms_begin = std::chrono::system_clock::now().time_since_epoch() / |
| std::chrono::milliseconds(1); |
| req_c.set_name("tc3"); |
| req_c.set_timestamp_ms(20000); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c))); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->RebootComplete(&c, req_r, &resp_r))); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->GetBootTime(&c, req_g, &resp_g))); |
| |
| EXPECT_EQ(resp_g.stat().is_rebooting(), false); |
| EXPECT_EQ(resp_g.reboot_flow(0).stage(), "tc3"); |
| EXPECT_EQ(resp_g.reboot_flow(0).timestamp_wall_ms(), 20000); |
| EXPECT_EQ(resp_g.reboot_flow(1).stage(), "RebootEnd"); |
| EXPECT_GE(resp_g.reboot_flow(1).timestamp_mono_ms(), monotime_ms_begin); |
| EXPECT_LE(resp_g.reboot_flow(1).timestamp_mono_ms(), GetMonoTimeMs()); |
| EXPECT_GE(resp_g.reboot_flow(1).timestamp_wall_ms(), walltime_ms_begin); |
| EXPECT_LE(resp_g.reboot_flow(1).timestamp_wall_ms(), |
| std::chrono::system_clock::now().time_since_epoch() / |
| std::chrono::milliseconds(1)); |
| |
| req_d.set_name("td2"); |
| req_d.set_duration_ms(1000); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetDuration(&c, req_d, &resp_d))); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->GetBootTime(&c, req_g, &resp_g))); |
| EXPECT_EQ(resp_g.stat().is_rebooting(), true); |
| EXPECT_EQ(resp_g.reboot_flow_size(), 0); |
| EXPECT_EQ(resp_g.durations(0).name(), "td2"); |
| EXPECT_EQ(resp_g.durations(0).duration_ms(), 1000); |
| } |
| |
| TEST_F(BootTimeTest, BootTimeApiRegisterFailure) { |
| SetCheckpointRequest req_c; |
| SetCheckpointResponse resp_c; |
| SetDurationRequest req_d; |
| SetDurationResponse resp_d; |
| RebootCompleteRequest req_r; |
| RebootCompleteResponse resp_r; |
| |
| CloseServer(); |
| LockFilePermissions(fs::perm_options::remove); |
| SetUpServer(); |
| |
| req_c.set_name("tc1"); |
| req_c.set_timestamp_ms(1000); |
| req_d.set_name("td1"); |
| req_d.set_duration_ms(1000); |
| |
| TEMP_CLIENT_CONTEXT( |
| c, |
| EXPECT_EQ(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c).error_code(), |
| grpc::StatusCode::INTERNAL)); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_EQ(boottime_stub_->SetDuration(&c, req_d, &resp_d).error_code(), |
| grpc::StatusCode::INTERNAL)); |
| TEMP_CLIENT_CONTEXT( |
| c, |
| EXPECT_EQ(boottime_stub_->RebootComplete(&c, req_r, &resp_r).error_code(), |
| grpc::StatusCode::INTERNAL)); |
| |
| LockFilePermissions(fs::perm_options::add); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetCheckpoint(&c, req_c, &resp_c))); |
| |
| CloseServer(); |
| LockFilePermissions(fs::perm_options::remove); |
| SetUpServer(); |
| LockFilePermissions(fs::perm_options::add); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->SetDuration(&c, req_d, &resp_d))); |
| |
| CloseServer(); |
| LockFilePermissions(fs::perm_options::remove); |
| SetUpServer(); |
| LockFilePermissions(fs::perm_options::add); |
| TEMP_CLIENT_CONTEXT( |
| c, EXPECT_OK(boottime_stub_->RebootComplete(&c, req_r, &resp_r))); |
| } |
| |
| } // namespace |
| } // namespace blobs |