Skip to content

Conversation

@benoit-nexthop
Copy link
Contributor

@benoit-nexthop benoit-nexthop commented Dec 18, 2025

Pre-submission checklist

  • I've ran the linters locally and fixed lint errors related to the files I modified in this PR. You can install the linters by running pip install -r requirements-dev.txt && pre-commit install
  • pre-commit run

Summary

This is a small helper class used to lookup ports and interfaces in a consistent fashion in CLI commands.

Note: this change is part of a series, the previous one is #754, the next one is #756.

Test Plan

Unit tests.

@meta-cla meta-cla bot added the CLA Signed label Dec 18, 2025
@benoit-nexthop benoit-nexthop marked this pull request as ready for review December 18, 2025 00:36
@benoit-nexthop benoit-nexthop force-pushed the fboss2-cli-prototype_part03 branch from 04bd218 to ab2a9fb Compare December 19, 2025 23:32
Copy link
Contributor

@joseph5wu joseph5wu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it looks pretty good to me. Please address the two main comments:

  • Removing vlog
  • Use fboss strong type for the ids

* @param portName The name of the port (e.g., "eth1/20/1")
* @return The interface ID if found, std::nullopt otherwise
*/
std::optional<int32_t> getInterfaceIdForPort(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use FBOSS_STRONG_TYPE from https://github.com/facebook/fboss/blob/main/fboss/agent/types.h#L74
like we do in
https://github.com/facebook/fboss/blob/main/fboss/agent/ApplyThriftConfig.cpp#L674

Please update all the int32_t to the corresponding PortID or InterfaceID from this types.h

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

VLOG(2) << "Built port maps with " << portNameToLogicalId_.size() << " ports";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will vlog dump this logging to the final output of your CLI?
We usually don't use vlog in fboss but xlog from folly.
If you don't need this logging, I'd suggest to remove it cause this is cli tool.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default it's not visible in the output of the CLI, no, unless you turn on verbose logging.

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
```
@benoit-nexthop benoit-nexthop force-pushed the fboss2-cli-prototype_part03 branch from 7a586bc to 4102813 Compare December 25, 2025 22:00
meta-codesync bot pushed a commit that referenced this pull request Dec 29, 2025
Summary:
**Pre-submission checklist**
- [x] I've ran the linters locally and fixed lint errors related to the files I modified in this PR. You can install the linters by running `pip install -r requirements-dev.txt && pre-commit install`
- [x] `pre-commit run`

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
```

Note: this change is part of a series, the previous one is #753, the next one is #755.

Pull Request resolved: #754

Test Plan: Unit tests.

Reviewed By: KevinYakar

Differential Revision: D89894234

Pulled By: joseph5wu

fbshipit-source-id: 97c71bc011882b0cb19b5e9d947c50d8bc3edef2
@meta-codesync
Copy link

meta-codesync bot commented Dec 29, 2025

@joseph5wu has imported this pull request. If you are a Meta employee, you can view this in D89903145.

Copy link
Contributor

@joseph5wu joseph5wu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please adjust the two main comments I added.
Besides, I noticed there're some missing dependency issues on buck:

❯❯❯ sl diff
diff --git a/fbcode/fboss/cli/fboss2/BUCK b/fbcode/fboss/cli/fboss2/BUCK
--- a/fbcode/fboss/cli/fboss2/BUCK
+++ b/fbcode/fboss/cli/fboss2/BUCK
@@ -776,8 +776,14 @@
     exported_deps = [
         ":cmd-handler",
         ":fboss2-lib",
+        "//fboss/agent:agent_config-cpp2-types",
+        "//fboss/agent:fboss-types",
+        "//fboss/agent/if:ctrl-cpp2-types",
         "//folly:conv",
     ],
+    exported_external_deps = [
+        "glog",
+    ],
 )

 cpp_binary(
diff --git a/fbcode/fboss/cli/fboss2/test/BUCK b/fbcode/fboss/cli/fboss2/test/BUCK
--- a/fbcode/fboss/cli/fboss2/test/BUCK
+++ b/fbcode/fboss/cli/fboss2/test/BUCK
@@ -89,6 +89,9 @@
         "CmdStopPcapTest.cpp",
         "PortMapTest.cpp",
     ],
+    # Required for parameterized tests (TEST_P) - static listing doesn't support them
+    # See: https://fburl.com/parameterized-test-not-supported
+    supports_static_listing = False,
     deps = [
         "fbsource//third-party/googletest:gmock",
         ":cmd_test_utils",
@@ -122,9 +125,11 @@
         "//fboss/cli/fboss2/commands/show/route:model-cpp2-types",
         "//fboss/cli/fboss2/commands/show/teflow:model-cpp2-types",
         "//fboss/cli/fboss2/commands/show/transceiver:model-cpp2-types",
+        "//folly:file_util",
         "//folly:network_address",
         "//folly/json:dynamic",
         "//neteng/fboss/bgp/if:bgp_thrift-cpp2-services",
+        "//thrift/lib/cpp2/protocol:protocol",
         "//thrift/lib/cpp2/reflection:testing",
     ],
     external_deps = [("boost", None, "boost_algorithm")],

Besides the missing dependency, supports_static_listing = False, is needed because you're using INSTANTIATE_TEST_SUITE_P in PortMapTest.cpp. Once you address the two major comments, please make sure to include the above buck fixes in the PR

for (auto& port : *switchConfig.ports()) {
// logicalID and name are required fields, so we can dereference directly
PortID logicalId(*port.logicalID());
const std::string& portName = *port.name();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because an older version of Port used to not have a PortName(https://github.com/facebook/fboss/blob/main/fboss/agent/switch_config.thrift#L1182),
port name is actually not a required field in our thrift struct.

Without specifically dealing with the potential optional field, our internal signal is not happy on this PR:

There may be an unchecked access to an optional Thrift field. Make sure you check if the field has a value before dereferencing it or calling `value()`. Otherwise these will throw an exception if the field doesn't have a value. You can suppress this warning by wrapping the expression in `apache::thrift::can_throw`, e.g. `can_throw(*obj.field())` or `can_throw(obj.field()->func())` 
`port.name` is unchecked.

You can consider to do something similar to https://github.com/facebook/fboss/blob/main/fboss/agent/ApplyThriftConfig.cpp#L191
to enforce port name has to exist, like throwing an exception if it doesn't

// Helper function to get all config files from the link_test_configs directory
std::vector<std::string> getAllConfigFiles() {
std::vector<std::string> configFiles;
std::string configDir = "/var/FBOSS/fboss/fboss/oss/link_test_configs/";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we can't guarantee the lint_test_configs in the exact directory when running the unit test.
Most of the time, we run our unit tests either on our dev servers or a remote machine just build the test binary and run it on a test virtual environment.
I'd suggest to not keep these tests as unit tests if we expect certain configs only exist in some fixed directory.
We can consider to have these tests in end-to-end tests, which we can setup test environment before running tests.

✗ Fail: fbcode//fboss/cli/fboss2/test:cmd_test - PortMapTest.MontblancVlanMapping (0.8s)
Note: Google Test filter = PortMapTest.MontblancVlanMapping
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from PortMapTest
[ RUN      ] PortMapTest.MontblancVlanMapping

Warning: Could not read config directory: filesystem error: directory iterator cannot open directory: No such file or directory [/var/FBOSS/fboss/fboss/oss/link_test_configs/]
terminate called after throwing an instance of 'std::runtime_error'
  what():  Failed to read config file: /var/FBOSS/fboss/fboss/oss/link_test_configs/montblanc.materialized_JSON
*** Aborted at 1767051882 (Unix time, try 'date -d @1767051882') ***
*** Signal 6 (SIGABRT) (0x1cc25001be8db) received by PID 1829083 (pthread TID 0x7fc95ad8e240) (linux TID 1829083) (maybe from PID 1829083, UID 117797) (code: -6), stack trace: ***
==1829083==WARNING: ASan doesn't fully support makecontext/swapcontext functions and may produce false positives in some cases!

Copy link
Contributor

@joseph5wu joseph5wu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some linter issues


cfg::AgentConfig agentConfig;
apache::thrift::SimpleJSONSerializer::deserialize<cfg::AgentConfig>(
configJson.c_str(), agentConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of our internal linter:

This constructs a StringPiece inefficiently.
This requires an extra call to strlen, which needs to traverse the entire string. Unless your string has embedded nulls, you should instead construct a StringPiece from the C++ string directly, as this would not require an extra call to strlen. (If your string has embedded nulls and you want this behavior, you can suppress this warning by using the StringPiece(const char *, size_t) constructor and passing strlen explicitly.)
Lint code: CLANGTIDY
Lint name: facebook-hte-StringPieceConstruction

-        configJson.c_str(), agentConfig);
+        configJson, agentConfig);


cfg::AgentConfig agentConfig;
apache::thrift::SimpleJSONSerializer::deserialize<cfg::AgentConfig>(
configJson.c_str(), agentConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same clang inter as below

}

void PortMap::buildVlanMaps(const cfg::SwitchConfig& switchConfig) {
if (!switchConfig.vlanPorts().has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

'has_value' is deprecated: Avoid using has_value() API for non-optional field since it's often used incorrectly. If this is a legit use-case, please migrate to apache::thrift::is_non_optional_field_set_manually_or_by_serializer(obj.field_ref()).
fbcode/thrift/lib/cpp2/FieldRef.h:136:5: note: 'has_value' has been explicitly marked deprecated here
  [[deprecated(
    ^
Lint code: CLANGTIDY
Lint name: clang-diagnostic-deprecated-declarations

}

void PortMap::buildInterfaceMaps(const cfg::SwitchConfig& switchConfig) {
if (!switchConfig.interfaces().has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

'has_value' is deprecated: Avoid using has_value() API for non-optional field since it's often used incorrectly. If this is a legit use-case, please migrate to apache::thrift::is_non_optional_field_set_manually_or_by_serializer(obj.field_ref()).
fbcode/thrift/lib/cpp2/FieldRef.h:136:5: note: 'has_value' has been explicitly marked deprecated here
  [[deprecated(
    ^
Lint code: CLANGTIDY
Lint name: clang-diagnostic-deprecated-declarations

interfaceIdToInterface_[intfId] = const_cast<cfg::Interface*>(&interface);

// vlanID is optional
if (interface.vlanID().has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

'has_value' is deprecated: Avoid using has_value() API for non-optional field since it's often used incorrectly. If this is a legit use-case, please migrate to apache::thrift::is_non_optional_field_set_manually_or_by_serializer(obj.field_ref()).
fbcode/thrift/lib/cpp2/FieldRef.h:136:5: note: 'has_value' has been explicitly marked deprecated here
  [[deprecated(
    ^
Lint code: CLANGTIDY
Lint name: clang-diagnostic-deprecated-declarations

portLogicalId = PortID(*interface.portID());
VLOG(3) << "Interface " << intfId
<< " mapped to port via portID: " << *portLogicalId;
} else if (interface.vlanID().has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

'has_value' is deprecated: Avoid using has_value() API for non-optional field since it's often used incorrectly. If this is a legit use-case, please migrate to apache::thrift::is_non_optional_field_set_manually_or_by_serializer(obj.field_ref()).
fbcode/thrift/lib/cpp2/FieldRef.h:136:5: note: 'has_value' has been explicitly marked deprecated here
  [[deprecated(
    ^
Lint code: CLANGTIDY
Lint name: clang-diagnostic-deprecated-declarations

}

std::optional<std::string> PortMap::getPortNameForInterface(
InterfaceID interfaceId) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

the parameter 'interfaceId' is copied for each invocation but only used as a const reference; consider making it a const reference
Lint code: CLANGTIDY
Lint name: performance-unnecessary-value-param

-    InterfaceID interfaceId) const {
+    const InterfaceID& interfaceId) const {

return portNameToLogicalId_.find(portName) != portNameToLogicalId_.end();
}

bool PortMap::hasInterface(InterfaceID interfaceId) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

the parameter 'interfaceId' is copied for each invocation but only used as a const reference; consider making it a const reference
Lint code: CLANGTIDY
Lint name: performance-unnecessary-value-param

-bool PortMap::hasInterface(InterfaceID interfaceId) const {
+bool PortMap::hasInterface(const InterfaceID& interfaceId) const {

return nullptr;
}

cfg::Interface* PortMap::getInterface(InterfaceID interfaceId) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

the parameter 'interfaceId' is copied for each invocation but only used as a const reference; consider making it a const reference
Lint code: CLANGTIDY
Lint name: performance-unnecessary-value-param

-cfg::Interface* PortMap::getInterface(InterfaceID interfaceId) const {
+cfg::Interface* PortMap::getInterface(const InterfaceID& interfaceId) const {

if (it != interfaceIdToInterface_.end()) {
return it->second;
}
return nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter:

This function may return a nullable value but is not marked nullable. Consider annotating your return type with FOLLY_NULLABLE defined in <folly/CppAttributes.h>. See https://fburl.com/nullability for more information.
Lint code: CLANGTIDY
Lint name: facebook-hte-NullableReturn

-cfg::Interface* PortMap::getInterface(InterfaceID interfaceId) const {
+cfg::Interface* FOLLY_NULLABLE PortMap::getInterface(const InterfaceID& interfaceId) const

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants