From 1db1e84c94c0c97c5fdab9f9d06dd2aa16179c19 Mon Sep 17 00:00:00 2001 From: Jeffrey Leung Date: Thu, 29 Aug 2024 15:29:52 -0700 Subject: [PATCH] Add RIF to CLI show interface Summary: Add RIF field to FBOSS2 CLI show interface command for remote device management on DSF switches. The field is not shown for non-VOQ switches. Compatibility has been added for non-DSF switches to prevent API breakage. Changes: - System port for `eth` and `evt`s added for VOQ sqitches - Reformatted to include borders - Unit tests updated Reviewed By: shri-khare Differential Revision: D61991255 fbshipit-source-id: 6881d94271cdd19385f564ae6cca2b9441d8d44b --- .../show/interface/CmdShowInterface.h | 141 ++++++++++++++---- .../commands/show/interface/model.thrift | 2 + .../cli/fboss2/test/CmdShowInterfaceTest.cpp | 31 ++-- 3 files changed, 138 insertions(+), 36 deletions(-) diff --git a/fboss/cli/fboss2/commands/show/interface/CmdShowInterface.h b/fboss/cli/fboss2/commands/show/interface/CmdShowInterface.h index e0ea01b4d975e..bc750b6694c61 100644 --- a/fboss/cli/fboss2/commands/show/interface/CmdShowInterface.h +++ b/fboss/cli/fboss2/commands/show/interface/CmdShowInterface.h @@ -14,6 +14,7 @@ #include "fboss/cli/fboss2/CmdHandler.h" #include "fboss/cli/fboss2/commands/show/interface/gen-cpp2/model_types.h" #include "fboss/cli/fboss2/utils/CmdClientUtils.h" +#include "fboss/cli/fboss2/utils/CmdUtils.h" #include "fboss/cli/fboss2/utils/Table.h" #include @@ -23,6 +24,17 @@ #include #include +namespace { + +std::string getRif(const std::string& name, int32_t port) { + if (port != 0 && (name.rfind("eth", 0) == 0 || name.rfind("evt", 0) == 0)) { + return "fboss" + std::to_string(port); + } + return ""; +} + +} // anonymous namespace + namespace facebook::fboss { using utils::Table; @@ -38,6 +50,9 @@ struct CmdShowInterfaceTraits : public BaseCommandTraits { class CmdShowInterface : public CmdHandler { + private: + bool isVoq = false; + public: using ObjectArgType = CmdShowInterfaceTraits::ObjectArgType; using RetType = CmdShowInterfaceTraits::RetType; @@ -63,12 +78,33 @@ class CmdShowInterface client->sync_getAllPortInfo(portEntries); client->sync_getAllInterfaces(intfDetails); - return createModel(portEntries, intfDetails, queriedIfs); + // Get DSF nodes data + std::map dsfNodes; + try { + std::map switchIdToSwitchInfo; + client->sync_getSwitchIdToSwitchInfo(switchIdToSwitchInfo); + isVoq = + (cfg::SwitchType::VOQ == + facebook::fboss::utils::getSwitchType(switchIdToSwitchInfo)); + if (isVoq) { + client->sync_getDsfNodes(dsfNodes); + } + } + // Thrift exception is expected to be thrown for switches which do not yet + // have the API getSwitchIdToSwitchInfo implemented. + catch (const std::exception& e) { + XLOG(DBG2) << e.what(); + } + + return createModel( + hostInfo, portEntries, intfDetails, dsfNodes, queriedIfs); } RetType createModel( + const HostInfo& hostInfo, std::map portEntries, std::map intfDetails, + std::map dsfNodes, const ObjectArgType& queriedIfs) { RetType model; std::unordered_set queriedSet( @@ -79,6 +115,14 @@ class CmdShowInterface populateVlanToMtu(vlanToMtu, intfDetails); populateVlanToPrefixes(vlanToPrefixes, intfDetails); + int32_t minSystemPort = 0; + for (const auto& idAndNode : dsfNodes) { + const auto& node = idAndNode.second; + if (hostInfo.getName() == *node.name()) { + minSystemPort = *node.systemPortRange()->minimum(); + } + } + for (const auto& [portId, portInfo] : portEntries) { if (queriedIfs.size() == 0 || queriedSet.count(*portInfo.name())) { cli::Interface ifModel; @@ -88,6 +132,7 @@ class CmdShowInterface ifModel.status() = (operState == facebook::fboss::PortOperState::UP) ? "up" : "down"; ifModel.speed() = std::to_string(*portInfo.speedMbps() / 1000) + "G"; + ifModel.prefixes() = {}; // We assume that there is a one-to-one association between // port, interface, and VLAN. @@ -102,6 +147,27 @@ class CmdShowInterface } } + if (dsfNodes.size() > 0) { + int systemPortId = minSystemPort + portId; + ifModel.systemPortId() = systemPortId; + + // Extract IP addresses for DSF switches + auto intf = intfDetails.find(systemPortId); + if (intf != intfDetails.end()) { + for (auto ipPrefix : *intf->second.address()) { + cli::IpPrefix prefix; + prefix.ip() = folly::IPAddress::fromBinary( + folly::ByteRange( + reinterpret_cast( + ipPrefix.ip()->addr()->data()), + ipPrefix.ip()->addr()->size())) + .str(); + prefix.prefixLength() = *ipPrefix.prefixLength(); + ifModel.prefixes()->push_back(std::move(prefix)); + } + } + } + model.interfaces()->push_back(std::move(ifModel)); } } @@ -183,36 +249,61 @@ class CmdShowInterface } void printOutput(const RetType& model, std::ostream& out = std::cout) { - Table outTable; - - outTable.setHeader({ - "Interface", - "Status", - "Speed", - "VLAN", - "MTU", - "Addresses", - "Description", - }); - - for (const auto& portAddress : *model.interfaces()) { + Table outTable{true}; + + if (isVoq) { + outTable.setHeader({ + "Interface", + "RIF", + "Status", + "Speed", + "VLAN", + "MTU", + "Addresses", + "Description", + }); + } else { + outTable.setHeader({ + "Interface", + "Status", + "Speed", + "VLAN", + "MTU", + "Addresses", + "Description", + }); + } + + for (const auto& interface : *model.interfaces()) { std::vector prefixes; - for (const auto& prefix : *portAddress.prefixes()) { + for (const auto& prefix : *interface.prefixes()) { prefixes.push_back( fmt::format("{}/{}", *prefix.ip(), *prefix.prefixLength())); } - outTable.addRow({ - *portAddress.name(), - colorStatusCell(*portAddress.status()), - *portAddress.speed(), - (portAddress.vlan() ? std::to_string(*portAddress.vlan()) : ""), - (portAddress.mtu() ? std::to_string(*portAddress.mtu()) : ""), - (prefixes.size() > 0 ? folly::join("\n", prefixes) : ""), - *portAddress.description(), - }); + std::string name = *interface.name(); + std::vector row; + if (isVoq) { + outTable.addRow( + {name, + getRif(name, *interface.systemPortId()), + colorStatusCell(*interface.status()), + *interface.speed(), + (interface.vlan() ? std::to_string(*interface.vlan()) : ""), + (interface.mtu() ? std::to_string(*interface.mtu()) : ""), + (prefixes.size() > 0 ? folly::join("\n", prefixes) : ""), + *interface.description()}); + } else { + outTable.addRow( + {name, + colorStatusCell(*interface.status()), + *interface.speed(), + (interface.vlan() ? std::to_string(*interface.vlan()) : ""), + (interface.mtu() ? std::to_string(*interface.mtu()) : ""), + (prefixes.size() > 0 ? folly::join("\n", prefixes) : ""), + *interface.description()}); + } } - out << outTable << std::endl; } diff --git a/fboss/cli/fboss2/commands/show/interface/model.thrift b/fboss/cli/fboss2/commands/show/interface/model.thrift index f910b5f68a2c7..93572fa242f93 100644 --- a/fboss/cli/fboss2/commands/show/interface/model.thrift +++ b/fboss/cli/fboss2/commands/show/interface/model.thrift @@ -12,6 +12,8 @@ struct Interface { 5: optional i32 vlan; 6: optional i32 mtu; 7: list prefixes; + 8: i32 systemPortId; + 9: list ips; } struct IpPrefix { diff --git a/fboss/cli/fboss2/test/CmdShowInterfaceTest.cpp b/fboss/cli/fboss2/test/CmdShowInterfaceTest.cpp index 8087181d08539..b4d095d9ef3f9 100644 --- a/fboss/cli/fboss2/test/CmdShowInterfaceTest.cpp +++ b/fboss/cli/fboss2/test/CmdShowInterfaceTest.cpp @@ -16,13 +16,16 @@ namespace facebook::fboss { class CmdShowInterfaceTestFixture : public CmdHandlerTestBase { public: + HostInfo hostInfo{"hostName"}; std::map portEntries; std::map intfDetails; + std::map dsfNodes; std::vector queriedEntries; void SetUp() override { CmdHandlerTestBase::SetUp(); portEntries = createPortEntries(); intfDetails = createInterfaceDetails(); + dsfNodes = {}; } std::map createPortEntries() { @@ -142,7 +145,8 @@ class CmdShowInterfaceTestFixture : public CmdHandlerTestBase { TEST_F(CmdShowInterfaceTestFixture, createModel) { auto cmd = CmdShowInterface(); - auto model = cmd.createModel(portEntries, intfDetails, queriedEntries); + auto model = cmd.createModel( + hostInfo, portEntries, intfDetails, dsfNodes, queriedEntries); auto intfAddressModel = *model.interfaces(); EXPECT_EQ(intfAddressModel.size(), 3); @@ -184,23 +188,28 @@ TEST_F(CmdShowInterfaceTestFixture, createModel) { TEST_F(CmdShowInterfaceTestFixture, printOutput) { auto cmd = CmdShowInterface(); - auto model = cmd.createModel(portEntries, intfDetails, queriedEntries); + auto model = cmd.createModel( + hostInfo, portEntries, intfDetails, dsfNodes, queriedEntries); std::stringstream ss; cmd.printOutput(model, ss); std::string output = ss.str(); std::string expectedOutput = - " Interface Status Speed VLAN MTU Addresses Description \n" + "+-----------+--------+-------+------+------+-----------------------+-------------------------------------+\n" + "| Interface | Status | Speed | VLAN | MTU | Addresses | Description |\n" "----------------------------------------------------------------------------------------------------------\n" - " eth1/1/1 up 100G 2001 1500 10.1.1.1/31 d-021: twshared54324.lol2 \n" - " 2401:db00::1:abcd/127 \n" - " fe80::be:face:b00c/64 \n" - " eth2/1/1 up 200G 2002 1500 10.1.2.1/31 d-022: gpuhost1234.lol2 \n" - " 2401:db00::2:abcd/127 \n" - " fe80::be:face:b00c/64 \n" - " eth3/1/1 down 400G 4001 9000 2401:db00::3:abcd/127 ctsw001.c081.f00.lol1:Ethernet5/4/1 \n" - " fe80::be:face:b00c/64 \n\n"; + "| eth1/1/1 | up | 100G | 2001 | 1500 | 10.1.1.1/31 | d-021: twshared54324.lol2 |\n" + "| | | | | | 2401:db00::1:abcd/127 | |\n" + "| | | | | | fe80::be:face:b00c/64 | |\n" + "+-----------+--------+-------+------+------+-----------------------+-------------------------------------+\n" + "| eth2/1/1 | up | 200G | 2002 | 1500 | 10.1.2.1/31 | d-022: gpuhost1234.lol2 |\n" + "| | | | | | 2401:db00::2:abcd/127 | |\n" + "| | | | | | fe80::be:face:b00c/64 | |\n" + "+-----------+--------+-------+------+------+-----------------------+-------------------------------------+\n" + "| eth3/1/1 | down | 400G | 4001 | 9000 | 2401:db00::3:abcd/127 | ctsw001.c081.f00.lol1:Ethernet5/4/1 |\n" + "| | | | | | fe80::be:face:b00c/64 | |\n" + "+-----------+--------+-------+------+------+-----------------------+-------------------------------------+\n"; EXPECT_EQ(output, expectedOutput); }