Map single-host port 10166 (instance 2) to host0 in BootTimeServiceImpl. In `GrpcBlobMain`, three gRPC instances (`numInstances = 3`) are spawned with offsets from `portNumber` (`10166`): - `instance = 0`: port `10167` (`offset = 1`) - `instance = 1`: port `10168` (`offset = 2`) - `instance = 2`: port `10166` (`offset = 0`, single-CN default) Because `gbmc-br` nftables rules (`50-gbmc-br-cn-redirect.rules` and `50-gbmc-br.rules`) drop direct traffic to ports `10167-10168` (`mark 0xff`) unless explicitly redirected for multi-host platforms, single-host platforms (such as Velorum) communicate over port `10166` (`instance = 2`). While `BaremetalServiceImpl` and `BiosSettingsServiceImpl` check `(instance + 1) == numInstances` to map `instance = 2` back to `0` (`host0`), `BootTimeServiceImpl` omitted this check and mapped port `10166` to `host2` (`/run/Boottime/host2` and `/var/google/boottime/host2`), isolating loader boot times from the rest of the BMC boot times stored under `host0`. Update `BootTimeServiceImpl` to normalize `instance_number_` to `0` when `(instance + 1) == numInstances`, matching `BaremetalServiceImpl` and `BiosSettingsServiceImpl`. Also move the H1 header to line 1 of `README.md` to satisfy `markdownlint` (`MD041`) in Kokoro presubmit and export `boottime_test.cc` in `copy.bara.sky`. PiperOrigin-RevId: 984494451 Change-Id: I4bff1bb94c7b7ad55e1dfd2ee815287fbe4e7fbb
diff --git a/README.md b/README.md index 5f68988..42f3b99 100644 --- a/README.md +++ b/README.md
@@ -1,7 +1,7 @@ -TODO(kkachana): Add developer workflow guide - # gRPC Blob Handler +TODO(kkachana): Add developer workflow guide + This is a general gRPC blob handler, receives blobs over gRPC and handles accordingly.
diff --git a/src/boottime_callback.cc b/src/boottime_callback.cc index aa2f411..f5c24ba 100644 --- a/src/boottime_callback.cc +++ b/src/boottime_callback.cc
@@ -14,6 +14,7 @@ #include "boottime_api/boottime_api_common.h" #include "boottime_api/node_config.h" #include "boottime_api/resource.h" +#include "grpcblob_defs.h" #include "grpcpp/server_context.h" #include "grpcpp/support/server_callback.h" #include "grpcpp/support/status.h" @@ -50,9 +51,9 @@ BootTimeServiceImpl::BootTimeServiceImpl(int instance) : phosphor::boottime::grpc_gen::BootTimeService::CallbackService(), - instance_number_(instance), + instance_number_((instance + 1 == numInstances) ? 0 : instance), base_path_(), - node_config_("host" + std::to_string(instance), + node_config_("host" + std::to_string(instance_number_), std::string(btm::kBootTimeTagHost)) { RegisterSelfToApi().IgnoreError(); } @@ -60,9 +61,9 @@ BootTimeServiceImpl::BootTimeServiceImpl(int instance, const std::string& basePath) : phosphor::boottime::grpc_gen::BootTimeService::CallbackService(), - instance_number_(instance), + instance_number_((instance + 1 == numInstances) ? 0 : instance), base_path_(basePath), - node_config_("host" + std::to_string(instance), + node_config_("host" + std::to_string(instance_number_), std::string(btm::kBootTimeTagHost)), boottime_api_(basePath + std::string(btm::def::kLockFile), absl::Seconds(5)) {
diff --git a/src/test/boottime_test.cc b/src/test/boottime_test.cc new file mode 100644 index 0000000..098055b --- /dev/null +++ b/src/test/boottime_test.cc
@@ -0,0 +1,349 @@ + +#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