Skip to content

Commit 82010c4

Browse files
Chet Powersmeta-codesync[bot]
authored andcommitted
Fix bypass module support, port filtering in show transceiver
Summary: In D84195662, I changed the code to print one line per transceiver entry, rather than one line per port entry. I forgot to update the port filtering logic, so we were filtering out all port entries, then still printing all transceiver entries. D85054733 partially fixed this, but it is still broken for multi-port modules. This diff also re-broke bypass module support As an example: P2010792781. Even though we only request fab1/59/1, we're still printing all entries for this transceiver. Since we already filtered out the port data, we think this transceiver has no agent port, so we mark it as a Bypass module, when it isn't. This diff fixes the port filtering (which also fixes incorrectly reporting transceivers as bypass modules) and adds a few unit tests to verify that port filtering and bypass module support work as expected Reviewed By: shiva-menta Differential Revision: D85720701 fbshipit-source-id: adc8c48ee994fe823987d6a8622fe161e5ec0157
1 parent 812712c commit 82010c4

File tree

2 files changed

+220
-45
lines changed

2 files changed

+220
-45
lines changed

fboss/cli/fboss2/commands/show/transceiver/CmdShowTransceiver.h

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ class CmdShowTransceiver
5050
utils::createClient<facebook::fboss::FbossCtrlAsyncClient>(hostInfo);
5151

5252
// TODO: explore performance improvement if we make all this parallel.
53-
auto portEntries = queryPortInfo(agent.get(), queriedPorts);
53+
auto portEntries = queryPortInfo(agent.get());
5454
auto portStatusEntries = queryPortStatus(agent.get(), portEntries);
55-
auto transceiverEntries =
56-
queryTransceiverInfo(qsfpService.get(), portStatusEntries);
55+
auto transceiverEntries = queryTransceiverInfo(qsfpService.get());
5756
auto transceiverValidationEntries =
5857
queryTransceiverValidationInfo(qsfpService.get(), portStatusEntries);
5958

6059
return createModel(
60+
std::set(queriedPorts.begin(), queriedPorts.end()),
6161
portStatusEntries,
6262
transceiverEntries,
6363
portEntries,
@@ -133,23 +133,11 @@ class CmdShowTransceiver
133133
const double LOW_SNR_WARN = 20.00;
134134

135135
std::map<int, PortInfoThrift> queryPortInfo(
136-
FbossCtrlAsyncClient* agent,
137-
const ObjectArgType& queriedPorts) const {
136+
FbossCtrlAsyncClient* agent) const {
137+
// Query all ports here. Any filtering should be done in createModel
138138
std::map<int, PortInfoThrift> portEntries;
139139
agent->sync_getAllPortInfo(portEntries);
140-
if (queriedPorts.size() == 0) {
141-
return portEntries;
142-
}
143-
144-
std::map<int, PortInfoThrift> filteredPortEntries;
145-
for (auto const& [portId, portInfo] : portEntries) {
146-
for (auto const& queriedPort : queriedPorts) {
147-
if (portInfo.name().value() == queriedPort) {
148-
filteredPortEntries.emplace(portId, portInfo);
149-
}
150-
}
151-
}
152-
return filteredPortEntries;
140+
return portEntries;
153141
}
154142

155143
Table::StyledCell statusToString(std::optional<bool> isUp) const {
@@ -213,19 +201,11 @@ class CmdShowTransceiver
213201
}
214202

215203
std::map<int, TransceiverInfo> queryTransceiverInfo(
216-
QsfpServiceAsyncClient* qsfpService,
217-
std::map<int, PortStatus> portStatusEntries) const {
204+
QsfpServiceAsyncClient* qsfpService) const {
218205
std::vector<int32_t> requiredTransceiverEntries;
219-
for (const auto& portStatusItr : portStatusEntries) {
220-
if (auto tidx = portStatusItr.second.transceiverIdx()) {
221-
requiredTransceiverEntries.push_back(
222-
folly::copy(tidx->transceiverId().value()));
223-
}
224-
}
225-
226206
std::map<int, TransceiverInfo> transceiverEntries;
227-
qsfpService->sync_getTransceiverInfo(
228-
transceiverEntries, requiredTransceiverEntries);
207+
// Query all transceivers here. Any filtering should be done in createModel
208+
qsfpService->sync_getTransceiverInfo(transceiverEntries, {});
229209
return transceiverEntries;
230210
}
231211

@@ -265,6 +245,7 @@ class CmdShowTransceiver
265245
}
266246

267247
RetType createModel(
248+
const std::set<std::string>& queriedPorts,
268249
std::map<int, PortStatus> portStatusEntries,
269250
std::map<int, TransceiverInfo> transceiverEntries,
270251
std::map<int32_t, facebook::fboss::PortInfoThrift> portEntries,
@@ -277,9 +258,15 @@ class CmdShowTransceiver
277258
interfaceToPortId[portInfo.name().value()] = portId;
278259
}
279260

261+
// Iterate over transceiver entries, since there are some transceivers
262+
// (bypass modules) that won't have port entries
280263
for (const auto& tcvrEntry : transceiverEntries) {
281264
const auto& transceiver = tcvrEntry.second;
282265
for (const auto& intf : *transceiver.tcvrState()->interfaces()) {
266+
if (!queriedPorts.empty() &&
267+
queriedPorts.find(intf) == queriedPorts.end()) {
268+
continue;
269+
}
283270
cli::TransceiverDetail details;
284271
details.name() = intf;
285272

fboss/cli/fboss2/test/CmdShowTransceiverTest.cpp

Lines changed: 204 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,27 @@ TEST_F(CmdShowTransceiverTestFixture, queryClient) {
335335
auto model = cmd.queryClient(localhost(), queriedEntries);
336336

337337
EXPECT_THRIFT_EQ(normalizedModel, model);
338+
339+
// Verify that the model contains all transceivers including bypass modules
340+
EXPECT_EQ(model.transceivers()->size(), 8);
341+
342+
// Verify bypass module (transceiver 7) is present
343+
EXPECT_TRUE(
344+
model.transceivers()->find("eth1/7/1") != model.transceivers()->end());
345+
auto& bypassModule = model.transceivers()->at("eth1/7/1");
346+
EXPECT_EQ(bypassModule.name().value(), "eth1/7/1");
347+
EXPECT_FALSE(
348+
bypassModule.isUp().has_value()); // Bypass modules have no port status
349+
EXPECT_TRUE(bypassModule.isPresent().value());
350+
EXPECT_EQ(bypassModule.vendor().value(), "vendorBypass");
351+
352+
// Verify absent bypass module (transceiver 8) is present
353+
EXPECT_TRUE(
354+
model.transceivers()->find("eth1/8/1") != model.transceivers()->end());
355+
auto& absentBypassModule = model.transceivers()->at("eth1/8/1");
356+
EXPECT_EQ(absentBypassModule.name().value(), "eth1/8/1");
357+
EXPECT_FALSE(absentBypassModule.isUp().has_value());
358+
EXPECT_FALSE(absentBypassModule.isPresent().value());
338359
}
339360

340361
TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredByPort) {
@@ -350,8 +371,9 @@ TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredByPort) {
350371
// getPortStatus should be called with only the filtered port ID (port 1)
351372
EXPECT_CALL(getMockAgent(), getPortStatus(_, _))
352373
.WillOnce(Invoke([&](auto& entries, const auto& portIds) {
353-
// Verify that only port 1 is requested
354-
EXPECT_EQ(portIds->size(), 1);
374+
// Verify that all ports are requested, filtering is done later in
375+
// createModel
376+
EXPECT_EQ(portIds->size(), 6);
355377
EXPECT_EQ((*portIds)[0], 1);
356378

357379
// Return only the status for port 1
@@ -360,12 +382,12 @@ TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredByPort) {
360382
entries = filteredStatuses;
361383
}));
362384

363-
// getTransceiverInfo should be called with only the transceiver ID for port 1
385+
// getTransceiverInfo should query all transceivers, filtering is done later
386+
// in createModel
364387
EXPECT_CALL(getQsfpService(), getTransceiverInfo(_, _))
365388
.WillOnce(Invoke([&](auto& entries, const auto& transceiverIds) {
366-
// Verify that only transceiver 1 is requested
367-
EXPECT_EQ(transceiverIds->size(), 1);
368-
EXPECT_EQ((*transceiverIds)[0], 1);
389+
// Verify that all transceivers are requested
390+
EXPECT_EQ(transceiverIds->size(), 0);
369391

370392
// Return only the transceiver info for transceiver 1
371393
std::map<int32_t, TransceiverInfo> filteredTransceivers;
@@ -413,8 +435,8 @@ TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredByMultiplePorts) {
413435

414436
EXPECT_CALL(getMockAgent(), getPortStatus(_, _))
415437
.WillOnce(Invoke([&](auto& entries, const auto& portIds) {
416-
// Verify that only ports 1 and 6 are requested
417-
EXPECT_EQ(portIds->size(), 2);
438+
// We query all ports, filtering is done in createModel
439+
EXPECT_EQ(portIds->size(), 6);
418440
EXPECT_TRUE(
419441
std::find(portIds->begin(), portIds->end(), 1) != portIds->end());
420442
EXPECT_TRUE(
@@ -429,14 +451,8 @@ TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredByMultiplePorts) {
429451

430452
EXPECT_CALL(getQsfpService(), getTransceiverInfo(_, _))
431453
.WillOnce(Invoke([&](auto& entries, const auto& transceiverIds) {
432-
// Verify that only transceivers 1 and 6 are requested
433-
EXPECT_EQ(transceiverIds->size(), 2);
434-
EXPECT_TRUE(
435-
std::find(transceiverIds->begin(), transceiverIds->end(), 1) !=
436-
transceiverIds->end());
437-
EXPECT_TRUE(
438-
std::find(transceiverIds->begin(), transceiverIds->end(), 6) !=
439-
transceiverIds->end());
454+
// We query all transceivers, filtering is done later in createModel
455+
EXPECT_EQ(transceiverIds->size(), 0);
440456

441457
// Return only the transceiver info for transceivers 1 and 6
442458
std::map<int32_t, TransceiverInfo> filteredTransceivers;
@@ -475,6 +491,178 @@ TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredByMultiplePorts) {
475491
EXPECT_EQ(tcvr6.vendor().value(), "vendorThree");
476492
}
477493

494+
TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredMultiPortModule) {
495+
// Test filtering by a subset of ports for a multi-port module
496+
setupMockedAgentServer();
497+
498+
// Create a multi-port module (transceiver 10) with 8 interfaces
499+
std::map<int32_t, PortInfoThrift> multiPortPortEntries;
500+
std::map<int32_t, PortStatus> multiPortPortStatusEntries;
501+
for (int i = 1; i <= 8; i++) {
502+
PortInfoThrift portEntry;
503+
portEntry.portId() = i;
504+
portEntry.name() = folly::to<std::string>("fab1/1/", i);
505+
multiPortPortEntries[i] = std::move(portEntry);
506+
507+
PortStatus portStatus;
508+
TransceiverIdxThrift tcvr;
509+
tcvr.transceiverId() = 10;
510+
portStatus.transceiverIdx() = tcvr;
511+
portStatus.up() = true;
512+
multiPortPortStatusEntries[i] = std::move(portStatus);
513+
}
514+
515+
// Create a single transceiver with all 8 interfaces
516+
std::map<int32_t, TransceiverInfo> multiPortTransceiverEntries;
517+
TransceiverInfo multiPortTransceiver;
518+
multiPortTransceiver.tcvrState()->vendor() = []() {
519+
Vendor vendor;
520+
vendor.name() = "vendorMultiPort";
521+
vendor.serialNumber() = "mp123";
522+
vendor.partNumber() = "mp-part-1";
523+
return vendor;
524+
}();
525+
multiPortTransceiver.tcvrState()->present() = true;
526+
multiPortTransceiver.tcvrState()->moduleMediaInterface() =
527+
MediaInterfaceCode::FR8_800G;
528+
multiPortTransceiver.tcvrState()->interfaces() = {
529+
"fab1/1/1",
530+
"fab1/1/2",
531+
"fab1/1/3",
532+
"fab1/1/4",
533+
"fab1/1/5",
534+
"fab1/1/6",
535+
"fab1/1/7",
536+
"fab1/1/8"};
537+
multiPortTransceiver.tcvrState()->port() = 10;
538+
multiPortTransceiver.tcvrStats()->sensor() = []() {
539+
Sensor tempSensor;
540+
Sensor voltageSensor;
541+
tempSensor.value() = 45.0;
542+
voltageSensor.value() = 28.0;
543+
GlobalSensors sensors;
544+
sensors.temp() = tempSensor;
545+
sensors.vcc() = voltageSensor;
546+
return sensors;
547+
}();
548+
multiPortTransceiver.tcvrStats()->channels() = {};
549+
multiPortTransceiverEntries[10] = multiPortTransceiver;
550+
551+
std::map<int32_t, std::string> multiPortValidationEntries;
552+
multiPortValidationEntries[10] = "";
553+
554+
// Query for a subset of ports (fab1/1/2 and fab1/1/5)
555+
CmdShowTransceiverTraits::ObjectArgType queriedEntries = {
556+
"fab1/1/2", "fab1/1/5"};
557+
558+
EXPECT_CALL(getMockAgent(), getAllPortInfo(_))
559+
.WillOnce(Invoke([&](auto& entries) { entries = multiPortPortEntries; }));
560+
561+
EXPECT_CALL(getMockAgent(), getPortStatus(_, _))
562+
.WillOnce(Invoke([&](auto& entries, const auto& portIds) {
563+
// Verify all ports are requested
564+
EXPECT_EQ(portIds->size(), 8);
565+
566+
// Return status for all ports
567+
entries = multiPortPortStatusEntries;
568+
}));
569+
570+
EXPECT_CALL(getQsfpService(), getTransceiverInfo(_, _))
571+
.WillOnce(Invoke([&](auto& entries, const auto& transceiverIds) {
572+
// Verify all tcvrs are requested
573+
EXPECT_EQ(transceiverIds->size(), 0);
574+
575+
// Return the multi-port transceiver
576+
entries = multiPortTransceiverEntries;
577+
}));
578+
579+
EXPECT_CALL(getQsfpService(), getTransceiverConfigValidationInfo(_, _, _))
580+
.WillOnce(
581+
Invoke([&](auto& entries, const auto& /* transceiverIds */, auto) {
582+
entries = multiPortValidationEntries;
583+
}));
584+
585+
auto cmd = CmdShowTransceiver();
586+
auto model = cmd.queryClient(localhost(), queriedEntries);
587+
588+
// Verify that the model contains only the requested interfaces
589+
EXPECT_EQ(model.transceivers()->size(), 2);
590+
EXPECT_TRUE(
591+
model.transceivers()->find("fab1/1/2") != model.transceivers()->end());
592+
EXPECT_TRUE(
593+
model.transceivers()->find("fab1/1/5") != model.transceivers()->end());
594+
595+
// Verify the entries are correct
596+
auto& tcvr2 = model.transceivers()->at("fab1/1/2");
597+
EXPECT_EQ(tcvr2.name().value(), "fab1/1/2");
598+
EXPECT_TRUE(tcvr2.isUp().has_value());
599+
EXPECT_TRUE(tcvr2.isUp().value());
600+
EXPECT_EQ(tcvr2.vendor().value(), "vendorMultiPort");
601+
EXPECT_EQ(tcvr2.serial().value(), "mp123");
602+
603+
auto& tcvr5 = model.transceivers()->at("fab1/1/5");
604+
EXPECT_EQ(tcvr5.name().value(), "fab1/1/5");
605+
EXPECT_TRUE(tcvr5.isUp().has_value());
606+
EXPECT_TRUE(tcvr5.isUp().value());
607+
EXPECT_EQ(tcvr5.vendor().value(), "vendorMultiPort");
608+
EXPECT_EQ(tcvr5.serial().value(), "mp123");
609+
}
610+
611+
TEST_F(CmdShowTransceiverTestFixture, queryClientFilteredBypassModule) {
612+
// Test filtering for a single bypass module (module without port entry)
613+
setupMockedAgentServer();
614+
615+
// Query for a bypass module interface
616+
CmdShowTransceiverTraits::ObjectArgType queriedEntries = {"eth1/7/1"};
617+
618+
EXPECT_CALL(getMockAgent(), getAllPortInfo(_))
619+
.WillOnce(Invoke([&](auto& entries) { entries = mockPortEntries; }));
620+
621+
EXPECT_CALL(getMockAgent(), getPortStatus(_, _))
622+
.WillOnce(Invoke([&](auto& entries, const auto& portIds) {
623+
// All agent ports are queried
624+
EXPECT_EQ(portIds->size(), 6);
625+
626+
// Return all port statuses (bypass module won't have a port status)
627+
entries = mockPortStatusEntries;
628+
}));
629+
630+
EXPECT_CALL(getQsfpService(), getTransceiverInfo(_, _))
631+
.WillOnce(Invoke([&](auto& entries, const auto& transceiverIds) {
632+
// Verify that we query all tcvrs
633+
EXPECT_EQ(transceiverIds->size(), 0);
634+
635+
entries = mockTransceiverEntries;
636+
}));
637+
638+
EXPECT_CALL(getQsfpService(), getTransceiverConfigValidationInfo(_, _, _))
639+
.WillOnce(Invoke([&](auto& entries, const auto& transceiverIds, auto) {
640+
// Won't include the bypass modules, since there's no port status
641+
EXPECT_EQ(transceiverIds->size(), 6);
642+
643+
entries = mockTransceiverValidationEntries;
644+
}));
645+
646+
auto cmd = CmdShowTransceiver();
647+
auto model = cmd.queryClient(localhost(), queriedEntries);
648+
649+
// Check that only the bypass module is reported
650+
EXPECT_EQ(model.transceivers()->size(), 1);
651+
EXPECT_TRUE(
652+
model.transceivers()->find("eth1/7/1") != model.transceivers()->end());
653+
654+
// Check that the contents of the entry are correct (should be listed as
655+
// a bypass module)
656+
auto& bypassModule = model.transceivers()->at("eth1/7/1");
657+
EXPECT_EQ(bypassModule.name().value(), "eth1/7/1");
658+
EXPECT_FALSE(
659+
bypassModule.isUp().has_value()); // Bypass modules have no port status
660+
EXPECT_TRUE(bypassModule.isPresent().value());
661+
EXPECT_EQ(bypassModule.vendor().value(), "vendorBypass");
662+
EXPECT_EQ(bypassModule.serial().value(), "d");
663+
EXPECT_EQ(bypassModule.partNumber().value(), "5");
664+
}
665+
478666
TEST_F(CmdShowTransceiverTestFixture, printOutput) {
479667
std::stringstream ss;
480668
CmdShowTransceiver().printOutput(normalizedModel, ss);

0 commit comments

Comments
 (0)