Skip to content

Commit

Permalink
Add RIF to CLI show interface
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jleung51 authored and facebook-github-bot committed Aug 29, 2024
1 parent 00742c7 commit 1db1e84
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 36 deletions.
141 changes: 116 additions & 25 deletions fboss/cli/fboss2/commands/show/interface/CmdShowInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <re2/re2.h>
Expand All @@ -23,6 +24,17 @@
#include <unordered_set>
#include <vector>

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;
Expand All @@ -38,6 +50,9 @@ struct CmdShowInterfaceTraits : public BaseCommandTraits {

class CmdShowInterface
: public CmdHandler<CmdShowInterface, CmdShowInterfaceTraits> {
private:
bool isVoq = false;

public:
using ObjectArgType = CmdShowInterfaceTraits::ObjectArgType;
using RetType = CmdShowInterfaceTraits::RetType;
Expand All @@ -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<int64_t, cfg::DsfNode> dsfNodes;
try {
std::map<int64_t, cfg::SwitchInfo> 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<int32_t, facebook::fboss::PortInfoThrift> portEntries,
std::map<int32_t, facebook::fboss::InterfaceDetail> intfDetails,
std::map<int64_t, cfg::DsfNode> dsfNodes,
const ObjectArgType& queriedIfs) {
RetType model;
std::unordered_set<std::string> queriedSet(
Expand All @@ -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;
Expand All @@ -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.
Expand All @@ -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<const unsigned char*>(
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));
}
}
Expand Down Expand Up @@ -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<std::string> 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<Table::RowData> 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;
}

Expand Down
2 changes: 2 additions & 0 deletions fboss/cli/fboss2/commands/show/interface/model.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ struct Interface {
5: optional i32 vlan;
6: optional i32 mtu;
7: list<IpPrefix> prefixes;
8: i32 systemPortId;
9: list<string> ips;
}

struct IpPrefix {
Expand Down
31 changes: 20 additions & 11 deletions fboss/cli/fboss2/test/CmdShowInterfaceTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ namespace facebook::fboss {

class CmdShowInterfaceTestFixture : public CmdHandlerTestBase {
public:
HostInfo hostInfo{"hostName"};
std::map<int32_t, facebook::fboss::PortInfoThrift> portEntries;
std::map<int32_t, facebook::fboss::InterfaceDetail> intfDetails;
std::map<int64_t, cfg::DsfNode> dsfNodes;
std::vector<std::string> queriedEntries;
void SetUp() override {
CmdHandlerTestBase::SetUp();
portEntries = createPortEntries();
intfDetails = createInterfaceDetails();
dsfNodes = {};
}

std::map<int32_t, facebook::fboss::PortInfoThrift> createPortEntries() {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 1db1e84

Please sign in to comment.