From 92b18003707b83bb4ea20fd7239e674048e95043 Mon Sep 17 00:00:00 2001 From: benoit-nexthop Date: Fri, 14 Nov 2025 01:30:30 +0100 Subject: [PATCH] Add `fboss2 config applied-info`. Sample output: ``` Config Applied Information: =========================== Last Applied Time: 2025-10-11 09:29:36.589 Last Coldboot Applied Time: 2025-10-11 06:44:36.741 ``` --- cmake/CliFboss2.cmake | 2 + cmake/CliFboss2Test.cmake | 1 + fboss/cli/fboss2/BUCK | 2 + fboss/cli/fboss2/CmdHandlerImplConfig.cpp | 3 + fboss/cli/fboss2/CmdListConfig.cpp | 7 + .../commands/config/CmdConfigAppliedInfo.cpp | 69 ++++++++++ .../commands/config/CmdConfigAppliedInfo.h | 40 ++++++ fboss/cli/fboss2/test/BUCK | 1 + .../fboss2/test/CmdConfigAppliedInfoTest.cpp | 127 ++++++++++++++++++ fboss/cli/fboss2/test/MockClients.h | 1 + 10 files changed, 253 insertions(+) create mode 100644 fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.cpp create mode 100644 fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h create mode 100644 fboss/cli/fboss2/test/CmdConfigAppliedInfoTest.cpp diff --git a/cmake/CliFboss2.cmake b/cmake/CliFboss2.cmake index b11c987c579c7..b75dc5a2e60d4 100644 --- a/cmake/CliFboss2.cmake +++ b/cmake/CliFboss2.cmake @@ -570,6 +570,8 @@ install(TARGETS fboss2) # Config commands library for fboss2-dev add_library(fboss2_config_lib + fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h + fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.cpp fboss/cli/fboss2/commands/config/CmdConfigReload.h fboss/cli/fboss2/commands/config/CmdConfigReload.cpp fboss/cli/fboss2/CmdListConfig.cpp diff --git a/cmake/CliFboss2Test.cmake b/cmake/CliFboss2Test.cmake index b694f8971a219..81327d875fdc1 100644 --- a/cmake/CliFboss2Test.cmake +++ b/cmake/CliFboss2Test.cmake @@ -3,6 +3,7 @@ # cmd_test - Command tests from BUCK file add_executable(fboss2_cmd_test fboss/cli/fboss2/test/TestMain.cpp + fboss/cli/fboss2/test/CmdConfigAppliedInfoTest.cpp fboss/cli/fboss2/test/CmdConfigReloadTest.cpp fboss/cli/fboss2/test/CmdSetPortStateTest.cpp fboss/cli/fboss2/test/CmdShowAclTest.cpp diff --git a/fboss/cli/fboss2/BUCK b/fboss/cli/fboss2/BUCK index e386d23a8606d..05e6ade9a131e 100644 --- a/fboss/cli/fboss2/BUCK +++ b/fboss/cli/fboss2/BUCK @@ -764,9 +764,11 @@ cpp_library( srcs = [ "CmdHandlerImplConfig.cpp", "CmdListConfig.cpp", + "commands/config/CmdConfigAppliedInfo.cpp", "commands/config/CmdConfigReload.cpp", ], headers = [ + "commands/config/CmdConfigAppliedInfo.h", "commands/config/CmdConfigReload.h", ], exported_deps = [ diff --git a/fboss/cli/fboss2/CmdHandlerImplConfig.cpp b/fboss/cli/fboss2/CmdHandlerImplConfig.cpp index b17fafd022595..6df477995b8cc 100644 --- a/fboss/cli/fboss2/CmdHandlerImplConfig.cpp +++ b/fboss/cli/fboss2/CmdHandlerImplConfig.cpp @@ -10,10 +10,13 @@ #include "fboss/cli/fboss2/CmdHandler.cpp" +#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h" #include "fboss/cli/fboss2/commands/config/CmdConfigReload.h" namespace facebook::fboss { +template void +CmdHandler::run(); template void CmdHandler::run(); } // namespace facebook::fboss diff --git a/fboss/cli/fboss2/CmdListConfig.cpp b/fboss/cli/fboss2/CmdListConfig.cpp index 6e6e5f0854645..d59c576e5e576 100644 --- a/fboss/cli/fboss2/CmdListConfig.cpp +++ b/fboss/cli/fboss2/CmdListConfig.cpp @@ -11,12 +11,19 @@ #include "fboss/cli/fboss2/CmdList.h" #include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h" #include "fboss/cli/fboss2/commands/config/CmdConfigReload.h" namespace facebook::fboss { const CommandTree& kConfigCommandTree() { static CommandTree root = { + {"config", + "applied-info", + "Show config applied information", + commandHandler, + argTypeHandler}, + {"config", "reload", "Reload agent configuration", diff --git a/fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.cpp b/fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.cpp new file mode 100644 index 0000000000000..005aac8e86165 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h" +#include +#include +#include +#include + +namespace facebook::fboss { + +namespace { +std::string formatTimestamp(int64_t timestampMs) { + if (timestampMs == 0) { + return "Never"; + } + + // Convert milliseconds to seconds + std::time_t timeInSeconds = timestampMs / 1000; + int64_t milliseconds = timestampMs % 1000; + + // Convert to local time + std::tm* localTime = std::localtime(&timeInSeconds); + + // Format the timestamp + std::ostringstream oss; + oss << std::put_time(localTime, "%Y-%m-%d %H:%M:%S"); + oss << "." << std::setfill('0') << std::setw(3) << milliseconds; + + return oss.str(); +} +} // namespace + +CmdConfigAppliedInfoTraits::RetType CmdConfigAppliedInfo::queryClient( + const HostInfo& hostInfo) { + auto client = + utils::createClient(hostInfo); + + ConfigAppliedInfo configAppliedInfo; + client->sync_getConfigAppliedInfo(configAppliedInfo); + + return configAppliedInfo; +} + +void CmdConfigAppliedInfo::printOutput(const RetType& configAppliedInfo) { + std::cout << "Config Applied Information:" << std::endl; + std::cout << "===========================" << std::endl; + + std::cout << "Last Applied Time: " + << formatTimestamp(*configAppliedInfo.lastAppliedInMs()) + << std::endl; + + std::cout << "Last Coldboot Applied Time: "; + if (configAppliedInfo.lastColdbootAppliedInMs()) { + std::cout << formatTimestamp(*configAppliedInfo.lastColdbootAppliedInMs()) + << std::endl; + } else { + std::cout << "Not available (warmboot)" << std::endl; + } +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h b/fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h new file mode 100644 index 0000000000000..258236bd277e6 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#pragma once + +#include +#include +#include "fboss/agent/if/gen-cpp2/ctrl_types.h" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/utils/CmdClientUtils.h" +#include "fboss/cli/fboss2/utils/CmdUtils.h" + +namespace facebook::fboss { + +struct CmdConfigAppliedInfoTraits : public BaseCommandTraits { + static constexpr utils::ObjectArgTypeId ObjectArgTypeId = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_NONE; + using ObjectArgType = std::monostate; + using RetType = ConfigAppliedInfo; +}; + +class CmdConfigAppliedInfo + : public CmdHandler { + public: + using ObjectArgType = CmdConfigAppliedInfoTraits::ObjectArgType; + using RetType = CmdConfigAppliedInfoTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo); + + void printOutput(const RetType& configAppliedInfo); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/BUCK b/fboss/cli/fboss2/test/BUCK index 56c98a45a47fb..9d97fc24c6b66 100644 --- a/fboss/cli/fboss2/test/BUCK +++ b/fboss/cli/fboss2/test/BUCK @@ -53,6 +53,7 @@ cpp_unittest( cpp_unittest( name = "cmd_test", srcs = [ + "CmdConfigAppliedInfoTest.cpp", "CmdConfigReloadTest.cpp", "CmdGetPcapTest.cpp", "CmdSetPortStateTest.cpp", diff --git a/fboss/cli/fboss2/test/CmdConfigAppliedInfoTest.cpp b/fboss/cli/fboss2/test/CmdConfigAppliedInfoTest.cpp new file mode 100644 index 0000000000000..9725d3933441e --- /dev/null +++ b/fboss/cli/fboss2/test/CmdConfigAppliedInfoTest.cpp @@ -0,0 +1,127 @@ +// (c) Facebook, Inc. and its affiliates. Confidential and proprietary. + +#include +#include +#include + +#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h" +#include "fboss/cli/fboss2/test/CmdHandlerTestBase.h" + +using namespace ::testing; + +namespace facebook::fboss { + +ConfigAppliedInfo createMockConfigAppliedInfoWarmboot() { + ConfigAppliedInfo configAppliedInfo; + configAppliedInfo.lastAppliedInMs() = 1609459200000; + return configAppliedInfo; +} + +ConfigAppliedInfo createMockConfigAppliedInfo() { + auto configAppliedInfo = createMockConfigAppliedInfoWarmboot(); + configAppliedInfo.lastColdbootAppliedInMs() = 1609459100000; + return configAppliedInfo; +} + +class CmdConfigAppliedInfoTestFixture : public CmdHandlerTestBase { + public: + ConfigAppliedInfo mockConfigAppliedInfo; + ConfigAppliedInfo mockConfigAppliedInfoWarmboot; + + void SetUp() override { + CmdHandlerTestBase::SetUp(); + mockConfigAppliedInfo = createMockConfigAppliedInfo(); + mockConfigAppliedInfoWarmboot = createMockConfigAppliedInfoWarmboot(); + } +}; + +TEST_F(CmdConfigAppliedInfoTestFixture, queryClient) { + setupMockedAgentServer(); + EXPECT_CALL(getMockAgent(), getConfigAppliedInfo(_)) + .WillOnce(Invoke([&](auto& configAppliedInfo) { + configAppliedInfo = mockConfigAppliedInfo; + })); + + auto cmd = CmdConfigAppliedInfo(); + auto result = cmd.queryClient(localhost()); + + EXPECT_EQ( + *result.lastAppliedInMs(), *mockConfigAppliedInfo.lastAppliedInMs()); + EXPECT_EQ( + *result.lastColdbootAppliedInMs(), + *mockConfigAppliedInfo.lastColdbootAppliedInMs()); +} + +TEST_F(CmdConfigAppliedInfoTestFixture, queryClientWarmboot) { + setupMockedAgentServer(); + EXPECT_CALL(getMockAgent(), getConfigAppliedInfo(_)) + .WillOnce(Invoke([&](auto& configAppliedInfo) { + configAppliedInfo = mockConfigAppliedInfoWarmboot; + })); + + auto cmd = CmdConfigAppliedInfo(); + auto result = cmd.queryClient(localhost()); + + EXPECT_EQ( + *result.lastAppliedInMs(), + *mockConfigAppliedInfoWarmboot.lastAppliedInMs()); + EXPECT_FALSE(result.lastColdbootAppliedInMs().has_value()); +} + +TEST_F(CmdConfigAppliedInfoTestFixture, printOutputWithColdboot) { + auto cmd = CmdConfigAppliedInfo(); + + std::stringstream buffer; + std::streambuf* old = std::cout.rdbuf(buffer.rdbuf()); + + cmd.printOutput(mockConfigAppliedInfo); + + std::cout.rdbuf(old); + + std::string output = buffer.str(); + + EXPECT_NE(output.find("Config Applied Information:"), std::string::npos); + EXPECT_NE(output.find("==========================="), std::string::npos); + EXPECT_NE(output.find("Last Applied Time:"), std::string::npos); + EXPECT_NE(output.find("Last Coldboot Applied Time:"), std::string::npos); + EXPECT_EQ(output.find("Not available (warmboot)"), std::string::npos); +} + +TEST_F(CmdConfigAppliedInfoTestFixture, printOutputWarmboot) { + auto cmd = CmdConfigAppliedInfo(); + + std::stringstream buffer; + std::streambuf* old = std::cout.rdbuf(buffer.rdbuf()); + + cmd.printOutput(mockConfigAppliedInfoWarmboot); + + std::cout.rdbuf(old); + + std::string output = buffer.str(); + + EXPECT_NE(output.find("Config Applied Information:"), std::string::npos); + EXPECT_NE(output.find("==========================="), std::string::npos); + EXPECT_NE(output.find("Last Applied Time:"), std::string::npos); + EXPECT_NE(output.find("Last Coldboot Applied Time:"), std::string::npos); + EXPECT_NE(output.find("Not available (warmboot)"), std::string::npos); +} + +TEST_F(CmdConfigAppliedInfoTestFixture, printOutputZeroTimestamp) { + auto cmd = CmdConfigAppliedInfo(); + + std::stringstream buffer; + std::streambuf* old = std::cout.rdbuf(buffer.rdbuf()); + + cmd.printOutput(ConfigAppliedInfo()); // Zero value. + + std::cout.rdbuf(old); + + std::string output = buffer.str(); + + EXPECT_NE(output.find("Config Applied Information:"), std::string::npos); + EXPECT_NE(output.find("==========================="), std::string::npos); + EXPECT_NE(output.find("Last Applied Time:"), std::string::npos); + EXPECT_NE(output.find("Never"), std::string::npos); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/MockClients.h b/fboss/cli/fboss2/test/MockClients.h index 8a5de632dca57..df42b62aab978 100644 --- a/fboss/cli/fboss2/test/MockClients.h +++ b/fboss/cli/fboss2/test/MockClients.h @@ -117,6 +117,7 @@ class MockFbossCtrlAgent : public FbossCtrlSvIf { void, getAllEcmpDetails, (std::vector&)); + MOCK_METHOD(void, getConfigAppliedInfo, (ConfigAppliedInfo&)); }; class MockFbossQsfpService : public QsfpServiceSvIf {