diff --git a/CMakeLists.txt b/CMakeLists.txt index d3a6eebae23c9..e27b0e537830b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,7 +291,7 @@ find_package(Glog REQUIRED) include_directories(${GLOG_INCLUDE_DIRS}) find_package(folly CONFIG REQUIRED) include_directories(${FOLLY_INCLUDE_DIR}) -find_package(Boost REQUIRED COMPONENTS container filesystem) +find_package(Boost REQUIRED COMPONENTS container filesystem regex) include_directories(${Boost_INCLUDE_DIR}) find_package(fb303 CONFIG REQUIRED) find_package(fizz CONFIG REQUIRED) diff --git a/cmake/CliFboss2.cmake b/cmake/CliFboss2.cmake index a32f3f39e92a3..c98292ff25603 100644 --- a/cmake/CliFboss2.cmake +++ b/cmake/CliFboss2.cmake @@ -938,122 +938,31 @@ add_library(fboss2_config_lib fboss/cli/fboss2/commands/config/mac/aging_time/CmdConfigMacAgingTime.h fboss/cli/fboss2/commands/config/protocol/CmdConfigProtocol.cpp fboss/cli/fboss2/commands/config/protocol/CmdConfigProtocol.h + fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h + fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h + fboss/cli/fboss2/commands/config/protocol/bgp/BgpPeerAddr.h fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.cpp fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.cpp fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.h fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.cpp fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.h - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.h + fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.cpp + fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.cpp fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.h - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.cpp - fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.h + fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.cpp + fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h + fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h + fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.cpp + fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h + fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.cpp + fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h + fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h + fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.cpp + fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h + fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.cpp + fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h fboss/cli/fboss2/commands/config/ptp/CmdConfigPtp.cpp fboss/cli/fboss2/commands/config/ptp/CmdConfigPtp.h fboss/cli/fboss2/commands/config/ptp/transparent_clock/CmdConfigPtpTransparentClock.cpp @@ -1166,6 +1075,18 @@ add_library(fboss2_config_lib fboss/cli/fboss2/commands/delete/interface/sflow/CmdDeleteInterfaceSflow.cpp fboss/cli/fboss2/commands/delete/interface/sflow/CmdDeleteInterfaceSflow.h fboss/cli/fboss2/commands/delete/protocol/CmdDeleteProtocol.cpp + fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.cpp + fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h + fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.cpp + fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h + fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.cpp + fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h + fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.cpp + fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h + fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.cpp + fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h + fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.cpp + fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h fboss/cli/fboss2/commands/delete/protocol/CmdDeleteProtocol.h fboss/cli/fboss2/commands/delete/protocol/static/CmdDeleteProtocolStatic.cpp fboss/cli/fboss2/commands/delete/protocol/static/CmdDeleteProtocolStatic.h @@ -1223,6 +1144,7 @@ target_link_libraries(fboss2_config_lib bgp_config_cpp2 switchinfo_utils platform_mapping + Boost::regex Folly::folly ) diff --git a/cmake/CliFboss2TestConfig.cmake b/cmake/CliFboss2TestConfig.cmake index 0b5d0cd23bd95..fcb126ab40057 100644 --- a/cmake/CliFboss2TestConfig.cmake +++ b/cmake/CliFboss2TestConfig.cmake @@ -9,7 +9,14 @@ add_executable(fboss2_cmd_config_test fboss/cli/fboss2/test/config/CmdConfigSrv6MySidTest.cpp fboss/cli/fboss2/test/config/CmdConfigAclRuleTest.cpp fboss/cli/fboss2/test/config/CmdConfigArpTest.cpp + fboss/cli/fboss2/test/config/CmdConfigBgpGlobalTest.cpp fboss/cli/fboss2/test/config/CmdConfigIcmpV4UnavailableSrcAddrTest.cpp + fboss/cli/fboss2/test/config/CmdConfigBgpNeighborTest.cpp + fboss/cli/fboss2/test/config/CmdConfigBgpPeerGroupTest.cpp + fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListTest.cpp + fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListEntryTest.cpp + fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListTest.cpp + fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListCommunityTest.cpp fboss/cli/fboss2/test/config/CmdConfigCoppTest.cpp fboss/cli/fboss2/test/config/CmdConfigDhcpTest.cpp fboss/cli/fboss2/test/config/CmdConfigHostnameTest.cpp @@ -42,6 +49,10 @@ add_executable(fboss2_cmd_config_test fboss/cli/fboss2/test/config/CmdConfigVlanTest.cpp fboss/cli/fboss2/test/config/CmdDeleteAclRuleTest.cpp fboss/cli/fboss2/test/config/CmdDeleteArpTest.cpp + fboss/cli/fboss2/test/config/CmdDeleteBgpNeighborTest.cpp + fboss/cli/fboss2/test/config/CmdDeleteBgpPeerGroupTest.cpp + fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyAsPathListTest.cpp + fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyCommunityListTest.cpp fboss/cli/fboss2/test/config/CmdDeleteConfigInterfaceTest.cpp fboss/cli/fboss2/test/config/CmdDeleteCoppQueueTest.cpp fboss/cli/fboss2/test/config/CmdDeleteCoppReasonTest.cpp diff --git a/cmake/CliFboss2TestIntegrationTest.cmake b/cmake/CliFboss2TestIntegrationTest.cmake index cf5df8c7539f3..8c52d5ac01ac0 100644 --- a/cmake/CliFboss2TestIntegrationTest.cmake +++ b/cmake/CliFboss2TestIntegrationTest.cmake @@ -13,6 +13,14 @@ add_executable(fboss2_integration_test fboss/cli/fboss2/test/integration_test/ConfigArpTest.cpp fboss/cli/fboss2/test/integration_test/ConfigSflowRateTest.cpp fboss/cli/fboss2/test/integration_test/ConfigSflowSampleDestTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpGlobalTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpNeighborTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpPeerGroupTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListEntryTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListCommunityTest.cpp + fboss/cli/fboss2/test/integration_test/ConfigBgpSessionTest.cpp fboss/cli/fboss2/test/integration_test/ConfigConcurrentSessionsTest.cpp fboss/cli/fboss2/test/integration_test/ConfigHostnameTest.cpp fboss/cli/fboss2/test/integration_test/ConfigIcmpV4UnavailableSrcAddrTest.cpp diff --git a/docs/docs/debugging/fboss_cli.md b/docs/docs/debugging/fboss_cli.md index cc3e3212d05e0..53dcc7e3ddfc8 100644 --- a/docs/docs/debugging/fboss_cli.md +++ b/docs/docs/debugging/fboss_cli.md @@ -1247,7 +1247,7 @@ BGP is configured through the same session model, but the changes are staged for The BGP command families are: - **`config protocol bgp global [ ...]`**: process-wide settings such as `router-id`, `local-asn`, `hold-time`, `confed-asn`, `cluster-id`, `network6` (advertised networks), and `switch-limit` overload-protection settings. -- **`config protocol bgp neighbor [ ...]`**: per-neighbor settings such as `remote-asn`, `local-asn`, `peer-group`, `description`, `bind-addr address`, `next-hop4`/`next-hop6`/`next-hop-self`, `connect-mode `, `afi disable-ipv4-afi`/`disable-ipv6-afi`, `rr-client`, `confed-peer`, `type`, `ingress-policy`/`egress-policy`, `add-path send|receive`, `link-bandwidth`, `graceful-restart restart-time`, `timers hold-time|keepalive|out-delay|withdraw-unprog-delay`, and `max-route pre-filter|post-filter` limits. A bare `neighbor ` creates the neighbor. +- **`config protocol bgp neighbor [ ...]`**: per-neighbor settings such as `remote-asn`, `local-asn`, `peer-group`, `description`, `bind-addr address`, `next-hop4`/`next-hop6`/`next-hop-self`, `passive `, `afi disable-ipv4-afi`/`disable-ipv6-afi`, `rr-client`, `confed-peer`, `type`, `ingress-policy`/`egress-policy`, `add-path send|receive`, `link-bandwidth`, `graceful-restart restart-time`, `timers hold-time|keepalive|out-delay|withdraw-unprog-delay`, and `max-route pre-filter|post-filter` limits. A bare `neighbor ` creates the neighbor. - **`config protocol bgp peer-group [ ...]`**: the same attribute family applied to a peer group, which neighbors can then reference via `peer-group`. - **`config protocol bgp policy as-path-list [entry ] [ ...]`**: BGP routing policy objects. `as-path-list` entries support `asn-regexp`, `description`, and `match-logic `. More policy object types (community-list, prefix-list, routing-policy) will follow the same pattern. diff --git a/fboss/cli/fboss2/BUCK b/fboss/cli/fboss2/BUCK index 25daa9463bed8..1e6be07a6ae39 100644 --- a/fboss/cli/fboss2/BUCK +++ b/fboss/cli/fboss2/BUCK @@ -1164,61 +1164,13 @@ cpp_library( "commands/config/protocol/bgp/BgpConfigSession.cpp", "commands/config/protocol/bgp/CmdConfigProtocolBgp.cpp", "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.cpp", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.cpp", + "commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.cpp", "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.cpp", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.cpp", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.cpp", + "commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.cpp", + "commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.cpp", + "commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.cpp", + "commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.cpp", + "commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.cpp", "commands/config/protocol/static/CmdConfigProtocolStatic.cpp", "commands/config/protocol/static/route/add/CmdConfigProtocolStaticRouteAdd.cpp", "commands/config/ptp/CmdConfigPtp.cpp", @@ -1274,6 +1226,12 @@ cpp_library( "commands/delete/interface/ipv6/ndp/CmdDeleteInterfaceIpv6Ndp.cpp", "commands/delete/interface/sflow/CmdDeleteInterfaceSflow.cpp", "commands/delete/protocol/CmdDeleteProtocol.cpp", + "commands/delete/protocol/bgp/CmdDeleteProtocolBgp.cpp", + "commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.cpp", + "commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.cpp", + "commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.cpp", + "commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.cpp", + "commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.cpp", "commands/delete/protocol/static/CmdDeleteProtocolStatic.cpp", "commands/delete/protocol/static/route/CmdDeleteProtocolStaticRoute.cpp", "commands/delete/qos/CmdDeleteQos.cpp", @@ -1333,47 +1291,21 @@ cpp_library( "commands/config/mac/CmdConfigMac.h", "commands/config/mac/aging_time/CmdConfigMacAgingTime.h", "commands/config/protocol/CmdConfigProtocol.h", + "commands/config/protocol/bgp/BgpCliAttrHandlers.h", + "commands/config/protocol/bgp/BgpCliValueParsers.h", + "commands/config/protocol/bgp/BgpPeerAddr.h", "commands/config/protocol/bgp/BgpConfigSession.h", "commands/config/protocol/bgp/CmdConfigProtocolBgp.h", "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.h", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.h", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.h", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.h", - "commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.h", + "commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h", "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.h", - "commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.h", - "commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.h", + "commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h", + "commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h", + "commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h", + "commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h", + "commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h", + "commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h", + "commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h", "commands/config/protocol/static/CmdConfigProtocolStatic.h", "commands/config/protocol/static/route/StaticRouteUtils.h", "commands/config/protocol/static/route/add/CmdConfigProtocolStaticRouteAdd.h", @@ -1430,6 +1362,12 @@ cpp_library( "commands/delete/interface/ipv6/ndp/CmdDeleteInterfaceIpv6Ndp.h", "commands/delete/interface/sflow/CmdDeleteInterfaceSflow.h", "commands/delete/protocol/CmdDeleteProtocol.h", + "commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h", + "commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h", + "commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h", + "commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h", + "commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h", + "commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h", "commands/delete/protocol/static/CmdDeleteProtocolStatic.h", "commands/delete/protocol/static/route/CmdDeleteProtocolStaticRoute.h", "commands/delete/qos/CmdDeleteQos.h", @@ -1498,6 +1436,9 @@ cpp_library( "//folly/json:dynamic", "//thrift/lib/cpp2:field_ref", ], + external_deps = [ + ("boost", None, "boost_regex"), + ], ) export_file( diff --git a/fboss/cli/fboss2/CmdListConfig.cpp b/fboss/cli/fboss2/CmdListConfig.cpp index ecf98edf2716a..c7481dd1d94fa 100644 --- a/fboss/cli/fboss2/CmdListConfig.cpp +++ b/fboss/cli/fboss2/CmdListConfig.cpp @@ -39,61 +39,13 @@ #include "fboss/cli/fboss2/commands/config/protocol/CmdConfigProtocol.h" #include "fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.h" #include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h" #include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h" #include "fboss/cli/fboss2/commands/config/protocol/static/CmdConfigProtocolStatic.h" #include "fboss/cli/fboss2/commands/config/protocol/static/route/add/CmdConfigProtocolStaticRouteAdd.h" #include "fboss/cli/fboss2/commands/config/ptp/CmdConfigPtp.h" @@ -146,6 +98,12 @@ #include "fboss/cli/fboss2/commands/delete/interface/ipv6/ndp/CmdDeleteInterfaceIpv6Ndp.h" #include "fboss/cli/fboss2/commands/delete/interface/sflow/CmdDeleteInterfaceSflow.h" #include "fboss/cli/fboss2/commands/delete/protocol/CmdDeleteProtocol.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h" #include "fboss/cli/fboss2/commands/delete/protocol/static/CmdDeleteProtocolStatic.h" #include "fboss/cli/fboss2/commands/delete/protocol/static/route/CmdDeleteProtocolStaticRoute.h" #include "fboss/cli/fboss2/commands/delete/qos/CmdDeleteQos.h" @@ -401,454 +359,87 @@ const CommandTree& kConfigCommandTree() { { { "global", - "Configure BGP global settings", + "Configure BGP global settings: " + "(router-id, local-asn, hold-time, confed-asn, " + "count-confeds-in-as-path-len, " + "graceful-restart-time, rib-allocated-path-ids, " + "network6, switch-limit[-total-path|" + "-max-golden-vips|-overload-protection-mode])", commandHandler, argRegistrar, - { - { - "router-id", - "Set BGP router identifier", - commandHandler< - CmdConfigProtocolBgpGlobalRouterId>, - argRegistrar< - CmdConfigProtocolBgpGlobalRouterIdTraits>, - }, - { - "local-asn", - "Set local AS number", - commandHandler< - CmdConfigProtocolBgpGlobalLocalAsn>, - argRegistrar< - CmdConfigProtocolBgpGlobalLocalAsnTraits>, - }, - { - "hold-time", - "Set BGP hold time in seconds", - commandHandler< - CmdConfigProtocolBgpGlobalHoldTime>, - argRegistrar< - CmdConfigProtocolBgpGlobalHoldTimeTraits>, - }, - { - "confed-asn", - "Set BGP confederation AS number", - commandHandler< - CmdConfigProtocolBgpGlobalConfedAsn>, - argRegistrar< - CmdConfigProtocolBgpGlobalConfedAsnTraits>, - }, - { - "cluster-id", - "Set route reflector cluster ID", - commandHandler< - CmdConfigProtocolBgpGlobalClusterId>, - argRegistrar< - CmdConfigProtocolBgpGlobalClusterIdTraits>, - }, - { - "network6", - "Add IPv6 network to advertise", - commandHandler< - CmdConfigProtocolBgpGlobalNetwork6Add>, - argRegistrar< - CmdConfigProtocolBgpGlobalNetwork6AddTraits>, - }, - { - "switch-limit", - "Set switch limit prefix-limit", - commandHandler< - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit>, - argRegistrar< - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimitTraits>, - }, - { - "switch-limit-total-path", - "Set switch limit total-path-limit", - commandHandler< - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit>, - argRegistrar< - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimitTraits>, - }, - { - "switch-limit-max-golden-vips", - "Set switch limit max-golden-vips", - commandHandler< - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips>, - argRegistrar< - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVipsTraits>, - }, - { - "switch-limit-overload-protection-mode", - "Set switch limit overload-protection-mode", - commandHandler< - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode>, - argRegistrar< - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionModeTraits>, - }, - }, }, { "peer-group", - "Configure BGP peer group", + "Configure BGP peer-group: " + "[ ...] (remote-asn, local-asn, " + "description, peer-tag, ingress-policy, " + "egress-policy, rr-client, confed-peer, " + "redistribute-peer, enhanced-route-refresh, " + "passive, next-hop-self, add-path send|receive, " + "afi disable-ipv4-afi|disable-ipv6-afi|" + "ipv4-over-ipv6-nh, " + "graceful-restart restart-time|stateful-ha, " + "max-route pre-filter|post-filter|" + "pre-warning-threshold|post-warning-threshold|" + "pre-warning-only|post-warning-only, " + "timers hold-time|keepalive|out-delay|" + "withdraw-unprog-delay)", commandHandler, argRegistrar, - { - { - "description", - "Set peer group description", - commandHandler< - CmdConfigProtocolBgpPeerGroupDescription>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupDescriptionTraits>, - }, - { - "remote-asn", - "Set remote AS number", - commandHandler< - CmdConfigProtocolBgpPeerGroupRemoteAsn>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupRemoteAsnTraits>, - }, - { - "next-hop-self", - "Enable/disable next-hop-self", - commandHandler< - CmdConfigProtocolBgpPeerGroupNextHopSelf>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupNextHopSelfTraits>, - }, - { - "confed-peer", - "Enable/disable confederation peer", - commandHandler< - CmdConfigProtocolBgpPeerGroupConfedPeer>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupConfedPeerTraits>, - }, - { - "disable-ipv4-afi", - "Enable/disable IPv4 AFI", - commandHandler< - CmdConfigProtocolBgpPeerGroupDisableIpv4Afi>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupDisableIpv4AfiTraits>, - }, - { - "v4-over-v6-nh", - "Enable/disable v4-over-v6 next-hop", - commandHandler< - CmdConfigProtocolBgpPeerGroupV4OverV6Nh>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupV4OverV6NhTraits>, - }, - { - "rr-client", - "Enable/disable route reflector client", - commandHandler< - CmdConfigProtocolBgpPeerGroupRrClient>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupRrClientTraits>, - }, - { - "ingress-policy", - "Set ingress policy", - commandHandler< - CmdConfigProtocolBgpPeerGroupIngressPolicy>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupIngressPolicyTraits>, - }, - { - "egress-policy", - "Set egress policy", - commandHandler< - CmdConfigProtocolBgpPeerGroupEgressPolicy>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupEgressPolicyTraits>, - }, - { - "max-routes", - "Set maximum routes", - commandHandler< - CmdConfigProtocolBgpPeerGroupMaxRoutes>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupMaxRoutesTraits>, - }, - { - "peer-tag", - "Set peer tag", - commandHandler< - CmdConfigProtocolBgpPeerGroupPeerTag>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupPeerTagTraits>, - }, - { - "warning-only", - "Enable/disable warning-only mode", - commandHandler< - CmdConfigProtocolBgpPeerGroupWarningOnly>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupWarningOnlyTraits>, - }, - { - "warning-limit", - "Set warning limit percentage", - commandHandler< - CmdConfigProtocolBgpPeerGroupWarningLimit>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupWarningLimitTraits>, - }, - { - "timers", - "Configure BGP timers", - commandHandler< - CmdConfigProtocolBgpPeerGroupTimers>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupTimersTraits>, - { - { - "hold-time", - "Set hold time", - commandHandler< - CmdConfigProtocolBgpPeerGroupTimersHoldTime>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupTimersHoldTimeTraits>, - }, - { - "keepalive", - "Set keepalive interval", - commandHandler< - CmdConfigProtocolBgpPeerGroupTimersKeepalive>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupTimersKeepaliveTraits>, - }, - { - "out-delay", - "Set output delay", - commandHandler< - CmdConfigProtocolBgpPeerGroupTimersOutDelay>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupTimersOutDelayTraits>, - }, - { - "withdraw-unprog-delay", - "Set withdraw unprogrammed delay", - commandHandler< - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay>, - argRegistrar< - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelayTraits>, - }, - }, - }, - }, }, { - "peer", - "Configure BGP peer", - commandHandler, - argRegistrar, - { - { - "peer-group", - "Set peer group", - commandHandler< - CmdConfigProtocolBgpPeerPeerGroup>, - argRegistrar< - CmdConfigProtocolBgpPeerPeerGroupTraits>, - }, - { - "description", - "Set peer description", - commandHandler< - CmdConfigProtocolBgpPeerDescription>, - argRegistrar< - CmdConfigProtocolBgpPeerDescriptionTraits>, - }, - { - "remote-asn", - "Set remote AS number", - commandHandler< - CmdConfigProtocolBgpPeerRemoteAsn>, - argRegistrar< - CmdConfigProtocolBgpPeerRemoteAsnTraits>, - }, - { - "local-addr", - "Set local address", - commandHandler< - CmdConfigProtocolBgpPeerLocalAddr>, - argRegistrar< - CmdConfigProtocolBgpPeerLocalAddrTraits>, - }, - { - "next-hop4", - "Set IPv4 next-hop", - commandHandler< - CmdConfigProtocolBgpPeerNextHop4>, - argRegistrar< - CmdConfigProtocolBgpPeerNextHop4Traits>, - }, - { - "next-hop6", - "Set IPv6 next-hop", - commandHandler< - CmdConfigProtocolBgpPeerNextHop6>, - argRegistrar< - CmdConfigProtocolBgpPeerNextHop6Traits>, - }, - { - "next-hop-self", - "Enable/disable next-hop-self", - commandHandler< - CmdConfigProtocolBgpPeerNextHopSelf>, - argRegistrar< - CmdConfigProtocolBgpPeerNextHopSelfTraits>, - }, - { - "disable-ipv4-afi", - "Enable/disable IPv4 AFI", - commandHandler< - CmdConfigProtocolBgpPeerDisableIpv4Afi>, - argRegistrar< - CmdConfigProtocolBgpPeerDisableIpv4AfiTraits>, - }, - { - "v4-over-v6-nh", - "Enable/disable v4-over-v6 next-hop", - commandHandler< - CmdConfigProtocolBgpPeerV4OverV6Nh>, - argRegistrar< - CmdConfigProtocolBgpPeerV4OverV6NhTraits>, - }, - { - "rr-client", - "Enable/disable route reflector client", - commandHandler< - CmdConfigProtocolBgpPeerRrClient>, - argRegistrar< - CmdConfigProtocolBgpPeerRrClientTraits>, - }, - { - "passive", - "Enable/disable passive mode", - commandHandler< - CmdConfigProtocolBgpPeerPassive>, - argRegistrar< - CmdConfigProtocolBgpPeerPassiveTraits>, - }, - { - "type", - "Set peer type", - commandHandler, - argRegistrar< - CmdConfigProtocolBgpPeerTypeTraits>, - }, - { - "ingress-policy", - "Set ingress policy", - commandHandler< - CmdConfigProtocolBgpPeerIngressPolicy>, - argRegistrar< - CmdConfigProtocolBgpPeerIngressPolicyTraits>, - }, - { - "egress-policy", - "Set egress policy", - commandHandler< - CmdConfigProtocolBgpPeerEgressPolicy>, - argRegistrar< - CmdConfigProtocolBgpPeerEgressPolicyTraits>, - }, - { - "max-routes", - "Set maximum routes", - commandHandler< - CmdConfigProtocolBgpPeerMaxRoutes>, - argRegistrar< - CmdConfigProtocolBgpPeerMaxRoutesTraits>, - }, - { - "peer-id", - "Set peer identifier", - commandHandler< - CmdConfigProtocolBgpPeerPeerId>, - argRegistrar< - CmdConfigProtocolBgpPeerPeerIdTraits>, - }, - { - "advertise-lbw", - "Enable/disable link bandwidth advertisement", - commandHandler< - CmdConfigProtocolBgpPeerAdvertiseLbw>, - argRegistrar< - CmdConfigProtocolBgpPeerAdvertiseLbwTraits>, - }, - { - "link-bandwidth", - "Set link bandwidth", - commandHandler< - CmdConfigProtocolBgpPeerLinkBandwidth>, - argRegistrar< - CmdConfigProtocolBgpPeerLinkBandwidthTraits>, - }, - { - "hold-time", - "Set hold time", - commandHandler< - CmdConfigProtocolBgpPeerHoldTime>, - argRegistrar< - CmdConfigProtocolBgpPeerHoldTimeTraits>, - }, - { - "warning-only", - "Enable/disable warning-only mode", - commandHandler< - CmdConfigProtocolBgpPeerWarningOnly>, - argRegistrar< - CmdConfigProtocolBgpPeerWarningOnlyTraits>, - }, - { - "warning-limit", - "Set warning limit percentage", - commandHandler< - CmdConfigProtocolBgpPeerWarningLimit>, - argRegistrar< - CmdConfigProtocolBgpPeerWarningLimitTraits>, - }, - { - "timers", - "Configure BGP timers", - commandHandler< - CmdConfigProtocolBgpPeerTimers>, - argRegistrar< - CmdConfigProtocolBgpPeerTimersTraits>, - { - { - "keepalive", - "Set keepalive interval", - commandHandler< - CmdConfigProtocolBgpPeerTimersKeepalive>, - argRegistrar< - CmdConfigProtocolBgpPeerTimersKeepaliveTraits>, - }, - { - "out-delay", - "Set output delay", - commandHandler< - CmdConfigProtocolBgpPeerTimersOutDelay>, - argRegistrar< - CmdConfigProtocolBgpPeerTimersOutDelayTraits>, - }, - { - "withdraw-unprog-delay", - "Set withdraw unprogrammed delay", - commandHandler< - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay>, - argRegistrar< - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelayTraits>, - }, - }, - }, - }, + "neighbor", + "Configure BGP neighbor: " + "[ ...]; `neighbor --help` " + "lists the attributes", + commandHandler, + argRegistrar, + }, + { + "policy", + "Configure BGP policy objects", + commandHandler, + argRegistrar, + {{ + "as-path-list", + "Configure BGP AS-path list: " + "[ ...] (description, " + "regex, boolean-operator)", + commandHandler< + CmdConfigProtocolBgpPolicyAsPathList>, + argRegistrar< + CmdConfigProtocolBgpPolicyAsPathListTraits>, + {{ + "entry", + "Configure an AS-path list entry: " + " [ ...] " + "(asn-regexp, description, match-logic)", + commandHandler< + CmdConfigProtocolBgpPolicyAsPathListEntry>, + argRegistrar< + CmdConfigProtocolBgpPolicyAsPathListEntryTraits>, + }}, + }, + { + "community-list", + "Configure BGP community-list: " + "[ ...] " + "(boolean-operator, description, exact-match)", + commandHandler< + CmdConfigProtocolBgpPolicyCommunityList>, + argRegistrar< + CmdConfigProtocolBgpPolicyCommunityListTraits>, + {{ + "community", + "Configure a community-list community: " + " [ ...] " + "(description, type, value)", + commandHandler< + CmdConfigProtocolBgpPolicyCommunityListCommunity>, + argRegistrar< + CmdConfigProtocolBgpPolicyCommunityListCommunityTraits>, + }}, + }}, }, }, }, @@ -1176,31 +767,71 @@ const CommandTree& kConfigCommandTree() { commandHandler, argTypeHandler, {{ - "static", - "Delete static routing configuration", - commandHandler, - argTypeHandler, - {{ - "ip", - "Delete IPv4 static routes", - {{ - "route", - "Delete IPv4 static route", - commandHandler, - argRegistrar, - }}, - }, - { - "ipv6", - "Delete IPv6 static routes", - {{ - "route", - "Delete IPv6 static route", - commandHandler, - argRegistrar, - }}, - }}, - }}, + "bgp", + "Delete BGP configuration objects", + commandHandler, + argTypeHandler, + {{ + "neighbor", + "Delete a BGP neighbor: ", + commandHandler, + argRegistrar, + }, + { + "peer-group", + "Delete a BGP peer-group: ", + commandHandler, + argRegistrar, + }, + { + "policy", + "Delete BGP policy objects", + commandHandler, + argTypeHandler, + {{ + "as-path-list", + "Delete a BGP AS-path list, or one of its regexes: " + " [regex ]", + commandHandler, + argRegistrar< + CmdDeleteProtocolBgpPolicyAsPathListTraits>, + }, + { + "community-list", + "Delete a BGP community-list: ", + commandHandler< + CmdDeleteProtocolBgpPolicyCommunityList>, + argRegistrar< + CmdDeleteProtocolBgpPolicyCommunityListTraits>, + }}, + }}, + }, + { + "static", + "Delete static routing configuration", + commandHandler, + argTypeHandler, + {{ + "ip", + "Delete IPv4 static routes", + {{ + "route", + "Delete IPv4 static route", + commandHandler, + argRegistrar, + }}, + }, + { + "ipv6", + "Delete IPv6 static routes", + {{ + "route", + "Delete IPv6 static route", + commandHandler, + argRegistrar, + }}, + }}, + }}, }, { diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h new file mode 100644 index 0000000000000..c1195994f47a1 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h @@ -0,0 +1,446 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h" +#include "fmt/format.h" +#include "thrift/lib/cpp2/gen/module_types_h.h" + +/** + * Generic, target-typed per-attribute handler factories shared by every + * `config protocol bgp ...` dispatcher. + * + * Each factory owns one value SHAPE (a bool, a bounded int, a 4-byte ASN, an + * IP address, a seconds-valued timer, ...): it validates the CLI tokens, + * produces the usage and rejection text for that shape, and on success calls + * the caller-supplied setter with an already-parsed value. They are templated + * on the target type T so the same logic mutates any BGP thrift object + * (BgpPeer, PeerGroup, AsPathList, PrefixListEntry, BgpPolicyTerm, ...). + * + * A dispatcher's registry is therefore one line per attribute -- key, shape, + * setter -- with no handler bodies inline. If an attribute seems to need a + * hand-written handler, that means its value shape has no factory yet, and the + * fix is to add the factory here. + */ +namespace facebook::fboss::bgpcli { + +using Tokens = std::vector; + +// A per-attribute handler mutating a typed thrift target T in place. +template +using AttrHandler = std::function; + +// A single-token string value (names, regex patterns, policy names). +template +AttrHandler stringAttr( + std::string_view name, + std::string_view valueName, + std::function set) { + return [name, valueName, set = std::move(set)]( + T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires <{}>", name, valueName)); + } + set(target, values[0]); + return ok(fmt::format("Successfully set {} to: {}", name, values[0])); + }; +} + +// A free-text string value that may span multiple CLI tokens (descriptions); +// the tokens are re-joined with single spaces. +template +AttrHandler joinedStringAttr( + std::string_view name, + std::function set) { + return + [name, set = std::move(set)](T& target, const Tokens& values) -> Result { + if (values.empty()) { + return err(fmt::format("Error: {} requires ", name)); + } + std::string joined = values[0]; + for (size_t i = 1; i < values.size(); ++i) { + joined += " " + values[i]; + } + set(target, joined); + return ok(fmt::format("Successfully set {} to: {}", name, joined)); + }; +} + +// A true/false value (accepts the parseBool spellings). +template +AttrHandler boolAttr( + std::string_view name, + std::function set) { + return + [name, set = std::move(set)](T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + auto enable = parseBool(values[0]); + if (!enable) { + return err( + fmt::format( + "Error: Invalid {} value '{}'; expected true or false", + name, + values[0])); + } + set(target, *enable); + return ok( + fmt::format( + "Successfully {} {}", *enable ? "enabled" : "disabled", name)); + }; +} + +// A bounded integer value in [minValue, maxValue]. `valueDesc` documents the +// accepted range in both the usage and the rejection message; it is taken by +// value for the same dangling-temporary reason as enumAttr's. +template +AttrHandler intAttr( + std::string_view name, + std::string valueDesc, + int32_t minValue, + int32_t maxValue, + std::function set) { + return [name, + valueDesc = std::move(valueDesc), + minValue, + maxValue, + set = std::move(set)](T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires <{}>", name, valueDesc)); + } + auto parsed = parseInt(values[0]); + if (!parsed || *parsed < minValue || *parsed > maxValue) { + return err( + fmt::format( + "Error: Invalid {} value '{}'; expected {}", + name, + values[0], + valueDesc)); + } + set(target, static_cast(*parsed)); + return ok(fmt::format("Successfully set {} to: {}", name, values[0])); + }; +} + +// A non-negative integer value in [0, maxValue], for the thrift fields that +// hold an unsigned quantity in a signed i64 (local-pref, med, ...). Unlike +// intAttr the accepted range is DERIVED from maxValue for both the usage and +// the rejection message, so the two cannot drift apart. The setter takes +// int64_t to match those fields directly, with no cast at the call site. +template +AttrHandler uintAttr( + std::string_view name, + int64_t maxValue, + std::function set) { + return [name, maxValue, set = std::move(set)]( + T& target, const Tokens& values) -> Result { + auto parsed = values.size() == 1 ? parseInt(values[0]) + : std::optional(); + if (!parsed || *parsed < 0 || *parsed > maxValue) { + return err(fmt::format("Error: {} requires <0-{}>", name, maxValue)); + } + set(target, *parsed); + return ok(fmt::format("Successfully set {} to: {}", name, values[0])); + }; +} + +// A 4-byte ASN value (RFC 6793). The thrift fields are i64, so an unchecked +// parse would let an out-of-range ASN wrap or exceed the protocol range. +template +AttrHandler asnAttr( + std::string_view name, + std::function set) { + return + [name, set = std::move(set)](T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + auto asn = parseAsn4Byte(values[0]); + if (!asn) { + return err( + fmt::format( + "Error: Invalid {} value '{}'; expected an unsigned 4-byte " + "ASN", + name, + values[0])); + } + set(target, *asn); + return ok(fmt::format("Successfully set {} to: {}", name, *asn)); + }; +} + +// A validated IP address value, optionally restricted to one family, stored +// normalized. +template +AttrHandler ipAttr( + std::string_view name, + std::function set, + std::optional requireV6 = std::nullopt) { + return [name, set = std::move(set), requireV6]( + T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + auto addr = folly::IPAddress::tryFromString(values[0]); + if (!addr.hasValue()) { + return err( + fmt::format("Error: Invalid {} address '{}'", name, values[0])); + } + if (requireV6.has_value() && addr->isV6() != *requireV6) { + return err( + fmt::format( + "Error: {} requires an IPv{} address, got '{}'", + name, + *requireV6 ? 6 : 4, + values[0])); + } + set(target, addr->str()); + return ok(fmt::format("Successfully set {} to: {}", name, addr->str())); + }; +} + +// The protocol range of a second-valued timer: 0 (disabled) is always +// accepted, any other value must lie in [min, max]. The default is "anything +// non-negative that fits in int32"; callers narrow it where the wire format or +// the daemon does (a 16-bit BGP hold time, a 12-bit graceful-restart time). +struct SecondsRange { + int32_t min = 0; + int32_t max = std::numeric_limits::max(); +}; + +// A second-valued timer: 0 or a value within `range`. +template +AttrHandler secondsAttr( + std::string_view name, + std::function set, + SecondsRange range = {}) { + return [name, set = std::move(set), range]( + T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + auto seconds = parseNonNegInt32(values[0]); + if (!seconds) { + return err( + fmt::format( + "Error: {} must be a non-negative integer that fits in " + "int32, got '{}'", + name, + values[0])); + } + if (*seconds != 0 && (*seconds < range.min || *seconds > range.max)) { + return err( + fmt::format( + "Error: {} must be 0 or between {} and {} seconds, got {}", + name, + range.min, + range.max, + *seconds)); + } + set(target, *seconds); + return ok( + fmt::format("Successfully set {} to: {} seconds", name, *seconds)); + }; +} + +// A non-negative int64 route-count value (RouteLimit fields). +template +AttrHandler routeCountAttr( + std::string_view name, + std::function set) { + return + [name, set = std::move(set)](T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + auto limit = parseNonNegInt64(values[0]); + if (!limit) { + return err( + fmt::format( + "Error: {} must be a non-negative integer, got '{}'", + name, + values[0])); + } + set(target, *limit); + return ok(fmt::format("Successfully set {} to: {}", name, *limit)); + }; +} + +// An attribute the CLI must refuse: either the thrift has no field for it +// (persisting it would stage dead config the daemon ignores) or the knob is +// not settable at this level. The reason is surfaced to the user instead. +template +AttrHandler rejectedAttr(std::string_view name, std::string_view reason) { + return [name, reason](T& /* target */, const Tokens& /* values */) -> Result { + return err(fmt::format("Error: {} is not supported: {}", name, reason)); + }; +} + +// The spelling bgpd's link-bandwidth parser accepts (parseLinkBandwidthBps in +// its policy utils): a decimal number with at most one '.', an optional +// case-insensitive K/M/G multiplier as the last character, or the literal +// "auto" (bgpd derives the value from the port speed). nullopt if bgpd would +// reject it. +inline std::optional parseLinkBandwidth(const std::string& raw) { + std::string v = folly::trimWhitespace(raw).str(); + if (v == "auto") { + return v; + } + if (std::count(v.begin(), v.end(), '.') > 1) { + return std::nullopt; + } + std::string number = v; + const size_t suffixAt = v.find_first_not_of(".0123456789"); + if (suffixAt != std::string::npos) { + const char suffix = + static_cast(std::toupper(static_cast(v.back()))); + if (suffixAt != v.size() - 1 || + (suffix != 'K' && suffix != 'M' && suffix != 'G')) { + return std::nullopt; + } + number.pop_back(); + } + if (number.empty() || number == ".") { + return std::nullopt; + } + try { + std::stod(number); + } catch (const std::exception&) { + return std::nullopt; + } + return v; +} + +// A link-bandwidth value in the spelling bgpd accepts (see +// parseLinkBandwidth). The thrift field is a string that bgpd re-parses, so +// the CLI validates with the same rules rather than a stricter subset -- an +// existing config's "1.5G", "150g" or "auto" must round-trip through the CLI. +template +AttrHandler bitRateAttr( + std::string_view name, + std::function set) { + return + [name, set = std::move(set)](T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err( + fmt::format( + "Error: {} requires [K|M|G] or auto", name)); + } + auto v = parseLinkBandwidth(values[0]); + if (!v) { + return err( + fmt::format( + "Error: Invalid {} value '{}'; expected a number with an " + "optional K/M/G suffix (e.g. 100G, 1.5G) or auto", + name, + values[0])); + } + set(target, *v); + return ok(fmt::format("Successfully set {} to: {}", name, *v)); + }; +} + +// A value drawn from a fixed set of names, mapped to an enum by `lookup` +// (returns std::nullopt for an unrecognized name). `valueDesc` documents the +// accepted names in both the usage and the rejection message; it is taken by +// value because call sites build it with fmt::format, and a string_view +// capture would dangle once that temporary dies. +template +AttrHandler enumAttr( + std::string_view name, + std::string valueDesc, + std::function(const std::string&)> lookup, + std::function set) { + return [name, + valueDesc = std::move(valueDesc), + lookup = std::move(lookup), + set = std::move(set)](T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires <{}>", name, valueDesc)); + } + auto parsed = lookup(values[0]); + if (!parsed) { + return err( + fmt::format( + "Error: Invalid {} value '{}'; expected {}", + name, + values[0], + valueDesc)); + } + set(target, *parsed); + return ok(fmt::format("Successfully set {} to: {}", name, values[0])); + }; +} + +// A thrift enum attribute identified by its generated metadata rather than a +// hand-written lookup: accepts the enum value name (e.g. BEST_PATH), the +// integer value, or true/false as aliases for 1/0 (the per-attribute commands +// this replaced took booleans, so existing usage keeps working). Prefer +// enumAttr when only a documented subset of the enum is valid; use this when +// the whole enum is. +template +AttrHandler thriftEnumAttr( + std::string_view name, + std::function set) { + return [name, set = std::move(set)]( + T& target, const Tokens& values) -> Result { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + EnumT mode; + bool valid = false; + if (auto parsed = parseInt(values[0])) { + mode = static_cast(*parsed); + valid = apache::thrift::TEnumTraits::findName(mode) != nullptr; + } else if (auto enable = parseBool(values[0])) { + mode = static_cast(*enable ? 1 : 0); + valid = true; + } else { + valid = apache::thrift::TEnumTraits::findValue(values[0], &mode); + } + if (!valid) { + std::string names; + for (auto n : apache::thrift::TEnumTraits::names) { + names += names.empty() ? "" : ", "; + names += std::string(n); + } + return err( + fmt::format( + "Error: {} value '{}' is not a valid mode; expected one of: " + "{}", + name, + values[0], + names)); + } + set(target, mode); + return ok( + fmt::format( + "Successfully set {} to: {}", name, static_cast(mode))); + }; +} + +} // namespace facebook::fboss::bgpcli diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h new file mode 100644 index 0000000000000..5d610e328cae5 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h @@ -0,0 +1,148 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Value parsing helpers shared by the BGP config dispatchers + * (`config protocol bgp global` / `config protocol bgp neighbor`). + * + * All parsers return std::nullopt on invalid input instead of throwing so a + * handler can reject the value with a user-facing message and leave the + * session unpersisted. + */ +namespace facebook::fboss::bgpcli { + +// Outcome of an attribute handler: on failure the message is returned to the +// user and the session is NOT persisted, so a rejected value never lands on +// disk. +struct Result { + bool ok; + std::string message; +}; + +inline Result ok(std::string message) { + return Result{true, std::move(message)}; +} + +inline Result err(std::string message) { + return Result{false, std::move(message)}; +} + +inline std::optional parseBool(const std::string& value) { + if (value == "true" || value == "1" || value == "yes") { + return true; + } + if (value == "false" || value == "0" || value == "no") { + return false; + } + return std::nullopt; +} + +template +std::optional parseInt(const std::string& value) { + try { + return folly::to(value); + } catch (const std::exception&) { + return std::nullopt; + } +} + +// Parse a non-negative value that must fit in int32 (used for second-valued +// timers and min-routes). +inline std::optional parseNonNegInt32(const std::string& value) { + auto parsed = parseInt(value); + if (!parsed || *parsed < 0 || *parsed > std::numeric_limits::max()) { + return std::nullopt; + } + return static_cast(*parsed); +} + +// Parse a non-negative int64 (used for route limits). +inline std::optional parseNonNegInt64(const std::string& value) { + auto parsed = parseInt(value); + if (!parsed || *parsed < 0) { + return std::nullopt; + } + return parsed; +} + +// Parse a 4-byte ASN (RFC 6793): an unsigned value in [0, 2^32-1]. The thrift +// fields are i64, so an unchecked uint64 parse would let an out-of-range ASN +// wrap or exceed the protocol range and be persisted. +inline std::optional parseAsn4Byte(const std::string& value) { + auto parsed = parseInt(value); + if (!parsed || *parsed > std::numeric_limits::max()) { + return std::nullopt; + } + return static_cast(*parsed); +} + +// ---- constructor-time token parsing ---------------------------------------- +// Unlike the value parsers above, this runs while constructing a command's +// ObjectArgType, where throwing std::invalid_argument IS the framework's +// error channel — the message is surfaced to the user as the parse error. + +// The ` [ ]` prefix shared by the policy +// list commands (config and delete): the list name, the optional named +// member selected by `keyword`, and where any remaining tokens begin. +struct ListMemberSelector { + std::string listName; + std::optional memberName; + // Index of the first token after the parsed prefix (== tokens.size() when + // nothing follows). The config dispatcher reads an ... + // tail from here; the delete dispatcher rejects any tail. + size_t restStart; +}; + +// Parse the shared prefix. `objectName` is the list flavor for messages +// (e.g. "community-list"), `memberKeyword` selects the nested member (e.g. +// `community`), and `usage` is the whole-command usage line thrown when no +// tokens were given. A second token other than `memberKeyword` is left to +// the caller (restStart == 1): the config grammar treats it as a list-level +// attribute, delete as an unexpected token. +inline ListMemberSelector parseListMemberSelector( + const std::vector& tokens, + std::string_view objectName, + std::string_view memberKeyword, + std::string_view usage) { + if (tokens.empty()) { + throw std::invalid_argument(std::string(usage)); + } + if (tokens[0].empty()) { + throw std::invalid_argument( + fmt::format("Error: {} name must not be empty", objectName)); + } + ListMemberSelector selector{tokens[0], std::nullopt, 1}; + if (tokens.size() > 1 && tokens[1] == memberKeyword) { + if (tokens.size() < 3 || tokens[2].empty()) { + throw std::invalid_argument( + fmt::format("Error: `{}` requires a ", memberKeyword)); + } + selector.memberName = tokens[2]; + selector.restStart = 3; + } + return selector; +} + +} // namespace facebook::fboss::bgpcli diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.cpp index f5378e3609638..637d5aa16337f 100644 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.cpp +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.cpp @@ -13,9 +13,16 @@ #include #include #include +#include #include +#include #include #include +#include +#include +#include +#include +#include "folly/json/dynamic.h" namespace fs = std::filesystem; @@ -42,6 +49,7 @@ folly::dynamic getDefaultBgpConfig() { } // namespace BgpConfigSession::BgpConfigSession() { + // NOLINTNEXTLINE(concurrency-mt-unsafe): single-threaded CLI const char* homeDir = std::getenv("HOME"); if (homeDir == nullptr) { throw std::runtime_error("HOME environment variable not set"); @@ -56,6 +64,7 @@ BgpConfigSession::BgpConfigSession() { bgpConfig_ = getDefaultBgpConfig(); // Check for stub mode via environment variable + // NOLINTNEXTLINE(concurrency-mt-unsafe): single-threaded CLI const char* stubEnv = std::getenv("FBOSS_BGP_STUB_MODE"); if (stubEnv != nullptr && std::string(stubEnv) == "1") { stubMode_ = true; @@ -208,20 +217,6 @@ std::optional BgpConfigSession::getConfedAsn() const { return std::nullopt; } -void BgpConfigSession::setClusterId(const std::string& clusterId) { - ensureConfigLoaded(); - bgpConfig_["cluster_id"] = clusterId; -} - -std::optional BgpConfigSession::getClusterId() const { - const_cast(this)->ensureConfigLoaded(); - if (bgpConfig_.count("cluster_id") && - !bgpConfig_["cluster_id"].asString().empty()) { - return bgpConfig_["cluster_id"].asString(); - } - return std::nullopt; -} - void BgpConfigSession::setListenAddress(const std::string& listenAddr) { ensureConfigLoaded(); bgpConfig_["listen_addr"] = listenAddr; @@ -462,284 +457,6 @@ std::optional BgpConfigSession::getUcmpWidth() const { return std::nullopt; } -// ==================== Peer Configuration ==================== - -folly::dynamic& BgpConfigSession::findOrCreatePeer( - const std::string& peerAddr) { - ensureConfigLoaded(); - auto& peers = bgpConfig_["peers"]; - - // Find existing peer - for (auto& peer : peers) { - if (peer["peer_addr"].asString() == peerAddr) { - return peer; - } - } - - // Create new peer with required fields - folly::dynamic newPeer = folly::dynamic::object("peer_addr", peerAddr)( - "local_addr", "")("next_hop4", "")("next_hop6", ""); - - peers.push_back(std::move(newPeer)); - return peers[peers.size() - 1]; -} - -void BgpConfigSession::createPeer(const std::string& peerAddr) { - findOrCreatePeer(peerAddr); -} - -void BgpConfigSession::setPeerRemoteAsn( - const std::string& peerAddr, - uint64_t remoteAsn) { - findOrCreatePeer(peerAddr)["remote_as_4_byte"] = - static_cast(remoteAsn); -} - -void BgpConfigSession::setPeerLocalAsn( - const std::string& peerAddr, - uint64_t localAsn) { - findOrCreatePeer(peerAddr)["local_as"] = static_cast(localAsn); -} - -void BgpConfigSession::setPeerDescription( - const std::string& peerAddr, - const std::string& description) { - findOrCreatePeer(peerAddr)["description"] = description; -} - -void BgpConfigSession::setPeerLocalAddr( - const std::string& peerAddr, - const std::string& localAddr) { - findOrCreatePeer(peerAddr)["local_addr"] = localAddr; -} - -void BgpConfigSession::setPeerNextHop4( - const std::string& peerAddr, - const std::string& nextHop4) { - findOrCreatePeer(peerAddr)["next_hop4"] = nextHop4; -} - -void BgpConfigSession::setPeerNextHop6( - const std::string& peerAddr, - const std::string& nextHop6) { - findOrCreatePeer(peerAddr)["next_hop6"] = nextHop6; -} - -void BgpConfigSession::setPeerGroupName( - const std::string& peerAddr, - const std::string& peerGroupName) { - findOrCreatePeer(peerAddr)["peer_group_name"] = peerGroupName; -} - -void BgpConfigSession::setPeerPassive( - const std::string& peerAddr, - bool isPassive) { - findOrCreatePeer(peerAddr)["is_passive"] = isPassive; -} - -void BgpConfigSession::setPeerRrClient( - const std::string& peerAddr, - bool isRrClient) { - findOrCreatePeer(peerAddr)["is_rr_client"] = isRrClient; -} - -void BgpConfigSession::setPeerNextHopSelf( - const std::string& peerAddr, - bool nextHopSelf) { - findOrCreatePeer(peerAddr)["next_hop_self"] = nextHopSelf; -} - -void BgpConfigSession::setPeerConfedPeer( - const std::string& peerAddr, - bool isConfedPeer) { - findOrCreatePeer(peerAddr)["is_confed_peer"] = isConfedPeer; -} - -void BgpConfigSession::setPeerPort(const std::string& peerAddr, int32_t port) { - findOrCreatePeer(peerAddr)["peer_port"] = port; -} - -void BgpConfigSession::setPeerLocalPort( - const std::string& peerAddr, - int32_t port) { - findOrCreatePeer(peerAddr)["local_port"] = port; -} - -void BgpConfigSession::setPeerConnectMode( - const std::string& peerAddr, - const std::string& connectMode) { - findOrCreatePeer(peerAddr)["connect_mode"] = connectMode; -} - -// Peer timers helper -folly::dynamic& BgpConfigSession::ensurePeerTimers( - const std::string& peerAddr) { - auto& peer = findOrCreatePeer(peerAddr); - if (!peer.count("bgp_peer_timers")) { - peer["bgp_peer_timers"] = folly::dynamic::object; - } - return peer["bgp_peer_timers"]; -} - -void BgpConfigSession::setPeerHoldTime( - const std::string& peerAddr, - int32_t holdTime) { - ensurePeerTimers(peerAddr)["hold_time_seconds"] = holdTime; -} - -void BgpConfigSession::setPeerKeepalive( - const std::string& peerAddr, - int32_t keepalive) { - ensurePeerTimers(peerAddr)["keep_alive_seconds"] = keepalive; -} - -void BgpConfigSession::setPeerOutDelay( - const std::string& peerAddr, - int32_t outDelay) { - ensurePeerTimers(peerAddr)["out_delay_seconds"] = outDelay; -} - -void BgpConfigSession::setPeerGracefulRestartTime( - const std::string& peerAddr, - int32_t seconds) { - findOrCreatePeer(peerAddr)["graceful_restart_seconds"] = seconds; -} - -void BgpConfigSession::setPeerStatefulHa( - const std::string& peerAddr, - bool enable) { - findOrCreatePeer(peerAddr)["enable_stateful_ha"] = enable; -} - -// Peer AFI/SAFI -void BgpConfigSession::setPeerDisableIpv4Afi( - const std::string& peerAddr, - bool disable) { - findOrCreatePeer(peerAddr)["disable_ipv4_afi"] = disable; -} - -void BgpConfigSession::setPeerDisableIpv6Afi( - const std::string& peerAddr, - bool disable) { - findOrCreatePeer(peerAddr)["disable_ipv6_afi"] = disable; -} - -void BgpConfigSession::setPeerIpv4LabeledUnicast( - const std::string& peerAddr, - bool enable) { - findOrCreatePeer(peerAddr)["is_afi_ipv4_lu_configured"] = enable; -} - -void BgpConfigSession::setPeerIpv6LabeledUnicast( - const std::string& peerAddr, - bool enable) { - findOrCreatePeer(peerAddr)["is_afi_ipv6_lu_configured"] = enable; -} - -void BgpConfigSession::setPeerIpv4OverIpv6Nh( - const std::string& peerAddr, - bool enable) { - findOrCreatePeer(peerAddr)["v4_over_v6_nexthop"] = enable; -} - -// Peer add-path helper -folly::dynamic& BgpConfigSession::ensurePeerAddPath( - const std::string& peerAddr) { - auto& peer = findOrCreatePeer(peerAddr); - if (!peer.count("add_path")) { - peer["add_path"] = folly::dynamic::object; - } - return peer["add_path"]; -} - -void BgpConfigSession::setPeerAddPathSend( - const std::string& peerAddr, - bool enable) { - ensurePeerAddPath(peerAddr)["send"] = enable; -} - -void BgpConfigSession::setPeerAddPathReceive( - const std::string& peerAddr, - bool enable) { - ensurePeerAddPath(peerAddr)["receive"] = enable; -} - -// Peer AS-path settings -void BgpConfigSession::setPeerRemovePrivateAs( - const std::string& peerAddr, - bool remove) { - findOrCreatePeer(peerAddr)["remove_private_as"] = remove; -} - -void BgpConfigSession::setPeerEnforceFirstAs( - const std::string& peerAddr, - bool enforce) { - findOrCreatePeer(peerAddr)["enforce_first_as"] = enforce; -} - -// Peer policies -void BgpConfigSession::setPeerIngressPolicy( - const std::string& peerAddr, - const std::string& policy) { - findOrCreatePeer(peerAddr)["ingress_policy_name"] = policy; -} - -void BgpConfigSession::setPeerEgressPolicy( - const std::string& peerAddr, - const std::string& policy) { - findOrCreatePeer(peerAddr)["egress_policy_name"] = policy; -} - -// Peer route limits helpers -folly::dynamic& BgpConfigSession::ensurePeerPreFilter( - const std::string& peerAddr) { - auto& peer = findOrCreatePeer(peerAddr); - if (!peer.count("pre_filter")) { - peer["pre_filter"] = folly::dynamic::object; - } - return peer["pre_filter"]; -} - -folly::dynamic& BgpConfigSession::ensurePeerPostFilter( - const std::string& peerAddr) { - auto& peer = findOrCreatePeer(peerAddr); - if (!peer.count("post_filter")) { - peer["post_filter"] = folly::dynamic::object; - } - return peer["post_filter"]; -} - -void BgpConfigSession::setPeerPreFilterMaxRoutes( - const std::string& peerAddr, - int64_t maxRoutes) { - ensurePeerPreFilter(peerAddr)["max_routes"] = maxRoutes; -} - -void BgpConfigSession::setPeerPostFilterMaxRoutes( - const std::string& peerAddr, - int64_t maxRoutes) { - ensurePeerPostFilter(peerAddr)["max_routes"] = maxRoutes; -} - -// Peer link bandwidth -void BgpConfigSession::setPeerAdvertiseLbw( - const std::string& peerAddr, - bool advertise) { - findOrCreatePeer(peerAddr)["advertise_link_bandwidth"] = advertise; -} - -void BgpConfigSession::setPeerReceiveLbw( - const std::string& peerAddr, - bool receive) { - findOrCreatePeer(peerAddr)["receive_link_bandwidth"] = receive; -} - -void BgpConfigSession::setPeerLbwValue( - const std::string& peerAddr, - int64_t bps) { - findOrCreatePeer(peerAddr)["link_bandwidth_bps"] = bps; -} - // ==================== Peer Group Configuration ==================== folly::dynamic& BgpConfigSession::findOrCreatePeerGroup( @@ -1038,47 +755,6 @@ void BgpConfigSession::setPeerGroupWithdrawUnprogDelay( ensurePeerGroupTimers(groupName)["withdraw_unprog_delay_seconds"] = seconds; } -// ==================== Peer Identification (RSW compatibility) -// ==================== - -void BgpConfigSession::setPeerPeerId( - const std::string& peerAddr, - const std::string& peerId) { - findOrCreatePeer(peerAddr)["peer_id"] = peerId; -} - -void BgpConfigSession::setPeerType( - const std::string& peerAddr, - const std::string& type) { - findOrCreatePeer(peerAddr)["type"] = type; -} - -void BgpConfigSession::setPeerPeerTag( - const std::string& peerAddr, - const std::string& peerTag) { - findOrCreatePeer(peerAddr)["peer_tag"] = peerTag; -} - -// Peer pre_filter warning settings -void BgpConfigSession::setPeerPreFilterWarningLimit( - const std::string& peerAddr, - int64_t limit) { - ensurePeerPreFilter(peerAddr)["warning_limit"] = limit; -} - -void BgpConfigSession::setPeerPreFilterWarningOnly( - const std::string& peerAddr, - bool warningOnly) { - ensurePeerPreFilter(peerAddr)["warning_only"] = warningOnly; -} - -// Peer timer: withdraw_unprog_delay_seconds -void BgpConfigSession::setPeerWithdrawUnprogDelay( - const std::string& peerAddr, - int32_t seconds) { - ensurePeerTimers(peerAddr)["withdraw_unprog_delay_seconds"] = seconds; -} - // ==================== Networks Configuration ==================== void BgpConfigSession::addNetwork6( diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h index 30569f5a59bde..1ff564e7f70ad 100644 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h @@ -11,6 +11,7 @@ #pragma once #include +#include #include #include @@ -42,7 +43,6 @@ namespace facebook::fboss { * - local_as_4_byte: i64 - Local AS number (RFC 6793) * - hold_time: i32 - Hold time in seconds (default 30) * - local_confed_as_4_byte: i64 - Confederation AS number - * - cluster_id: string - Route reflector cluster ID * - peers: list - List of BGP peers * - peer_groups: list - List of peer groups * @@ -103,10 +103,6 @@ class BgpConfigSession { void setConfedAsn(uint64_t asn); std::optional getConfedAsn() const; - // cluster_id: string - Route reflector cluster ID - void setClusterId(const std::string& clusterId); - std::optional getClusterId() const; - // listen_addr: string - Listen address for BGP sessions void setListenAddress(const std::string& listenAddr); std::optional getListenAddress() const; @@ -182,125 +178,6 @@ class BgpConfigSession { void setUcmpWidth(int32_t width); std::optional getUcmpWidth() const; - // ==================== Peer Configuration (Level: P) ==================== - // These map to BgpPeer thrift struct fields - - // Create/get peer by peer_addr - void createPeer(const std::string& peerAddr); - - // remote_as_4_byte: i64 - Peer's ASN - void setPeerRemoteAsn(const std::string& peerAddr, uint64_t remoteAsn); - - // local_as: i64 - Local ASN override for this peer - void setPeerLocalAsn(const std::string& peerAddr, uint64_t localAsn); - - // description: string - Peer description - void setPeerDescription( - const std::string& peerAddr, - const std::string& description); - - // local_addr: string - Local address to connect/listen on - void setPeerLocalAddr( - const std::string& peerAddr, - const std::string& localAddr); - - // next_hop4: string - IPv4 next-hop for outgoing updates - void setPeerNextHop4( - const std::string& peerAddr, - const std::string& nextHop4); - - // next_hop6: string - IPv6 next-hop for outgoing updates - void setPeerNextHop6( - const std::string& peerAddr, - const std::string& nextHop6); - - // peer_group_name: string - Reference to peer group - void setPeerGroupName( - const std::string& peerAddr, - const std::string& peerGroupName); - - // is_passive: bool - Listen for incoming connections - void setPeerPassive(const std::string& peerAddr, bool isPassive); - - // is_rr_client: bool - Route reflector client - void setPeerRrClient(const std::string& peerAddr, bool isRrClient); - - // next_hop_self: bool - Set next-hop to self - void setPeerNextHopSelf(const std::string& peerAddr, bool nextHopSelf); - - // is_confed_peer: bool - Confederation peer - void setPeerConfedPeer(const std::string& peerAddr, bool isConfedPeer); - - // peer_port: i32 - Remote peer port - void setPeerPort(const std::string& peerAddr, int32_t port); - - // local_port: i32 - Local source port - void setPeerLocalPort(const std::string& peerAddr, int32_t port); - - // connect_mode: string - PASSIVE_ACTIVE, PASSIVE_ONLY, ACTIVE_ONLY - void setPeerConnectMode( - const std::string& peerAddr, - const std::string& connectMode); - - // Peer timers - void setPeerHoldTime(const std::string& peerAddr, int32_t holdTime); - void setPeerKeepalive(const std::string& peerAddr, int32_t keepalive); - void setPeerOutDelay(const std::string& peerAddr, int32_t outDelay); - - // Peer graceful restart - void setPeerGracefulRestartTime(const std::string& peerAddr, int32_t seconds); - void setPeerStatefulHa(const std::string& peerAddr, bool enable); - - // Peer AFI/SAFI - void setPeerDisableIpv4Afi(const std::string& peerAddr, bool disable); - void setPeerDisableIpv6Afi(const std::string& peerAddr, bool disable); - void setPeerIpv4LabeledUnicast(const std::string& peerAddr, bool enable); - void setPeerIpv6LabeledUnicast(const std::string& peerAddr, bool enable); - void setPeerIpv4OverIpv6Nh(const std::string& peerAddr, bool enable); - - // Peer add-path - void setPeerAddPathSend(const std::string& peerAddr, bool enable); - void setPeerAddPathReceive(const std::string& peerAddr, bool enable); - - // Peer AS-path settings - void setPeerRemovePrivateAs(const std::string& peerAddr, bool remove); - void setPeerEnforceFirstAs(const std::string& peerAddr, bool enforce); - - // Peer policies - void setPeerIngressPolicy( - const std::string& peerAddr, - const std::string& policy); - void setPeerEgressPolicy( - const std::string& peerAddr, - const std::string& policy); - - // Peer route limits - void setPeerPreFilterMaxRoutes( - const std::string& peerAddr, - int64_t maxRoutes); - void setPeerPostFilterMaxRoutes( - const std::string& peerAddr, - int64_t maxRoutes); - - // Peer link bandwidth - void setPeerAdvertiseLbw(const std::string& peerAddr, bool advertise); - void setPeerReceiveLbw(const std::string& peerAddr, bool receive); - void setPeerLbwValue(const std::string& peerAddr, int64_t bps); - - // Peer identification (for RSW config compatibility) - void setPeerPeerId(const std::string& peerAddr, const std::string& peerId); - void setPeerType(const std::string& peerAddr, const std::string& type); - void setPeerPeerTag(const std::string& peerAddr, const std::string& peerTag); - - // Peer pre_filter warning settings - void setPeerPreFilterWarningLimit(const std::string& peerAddr, int64_t limit); - void setPeerPreFilterWarningOnly( - const std::string& peerAddr, - bool warningOnly); - - // Peer timer: withdraw_unprog_delay_seconds - void setPeerWithdrawUnprogDelay(const std::string& peerAddr, int32_t seconds); - // ==================== Peer Group Configuration (Level: PG) // ==================== These map to PeerGroup thrift struct fields @@ -468,23 +345,16 @@ class BgpConfigSession { void loadConfig(); void ensureConfigLoaded(); - // Helper to find or create a peer in the peers list - folly::dynamic& findOrCreatePeer(const std::string& peerAddr); - // Helper to find or create a peer group in the peer_groups list folly::dynamic& findOrCreatePeerGroup(const std::string& groupName); - // Helper to ensure peer timers exist - folly::dynamic& ensurePeerTimers(const std::string& peerAddr); + // Helper to ensure peer group timers exist folly::dynamic& ensurePeerGroupTimers(const std::string& groupName); // Helper to ensure add_path config exists - folly::dynamic& ensurePeerAddPath(const std::string& peerAddr); folly::dynamic& ensurePeerGroupAddPath(const std::string& groupName); // Helper to ensure pre_filter/post_filter config exists - folly::dynamic& ensurePeerPreFilter(const std::string& peerAddr); - folly::dynamic& ensurePeerPostFilter(const std::string& peerAddr); folly::dynamic& ensurePeerGroupPreFilter(const std::string& groupName); folly::dynamic& ensurePeerGroupPostFilter(const std::string& groupName); }; diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/BgpPeerAddr.h b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpPeerAddr.h new file mode 100644 index 0000000000000..63ee0225f9a18 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpPeerAddr.h @@ -0,0 +1,66 @@ +/* + * 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 +#include +#include + +/** + * Peer-address helpers shared by the BGP neighbor commands (`config protocol + * bgp neighbor` / `delete protocol bgp neighbor`). + * + * peer_addr is the identity of a BgpPeer, but the same address can be spelled + * many ways (2001:DB8::1 vs 2001:db8::1), and passive listening sessions may + * use prefix notation (2001:db8::/64, per bgp_config.thrift). Both commands + * normalize the CLI-supplied address once and compare stored entries in + * normalized form so every spelling addresses the same peer. + * + * Prefixes are masked (10.0.0.1/24 -> 10.0.0.0/24) because bgpd keys dynamic + * peers by the masked network (IPAddress::createNetwork in its config loader), + * so two spellings that differ only in host bits are the same neighbor to it. + */ +namespace facebook::fboss::bgpcli { + +// Normalize an address (or prefix, for passive listening sessions) to its +// canonical string form; a prefix has its host bits cleared. nullopt if the +// input parses as neither. +inline std::optional normalizeBgpPeerAddr(const std::string& s) { + if (auto addr = folly::IPAddress::tryFromString(s)) { + return addr->str(); + } + if (auto network = folly::IPAddress::tryCreateNetwork( + s, /* defaultCidr */ -1, /* applyMask */ true)) { + return folly::IPAddress::networkToString(*network); + } + return std::nullopt; +} + +// Find the peer whose (normalized) peer_addr matches the given normalized +// address; peers().end() if absent. Stored entries may predate normalization, +// so their normalized form is compared when possible. +inline std::vector::iterator findBgpPeer( + bgp::thrift::BgpConfig& cfg, + const std::string& normalizedAddr) { + auto& peers = *cfg.peers(); + for (auto it = peers.begin(); it != peers.end(); ++it) { + const std::string key = + normalizeBgpPeerAddr(*it->peer_addr()).value_or(*it->peer_addr()); + if (key == normalizedAddr) { + return it; + } + } + return peers.end(); +} + +} // namespace facebook::fboss::bgpcli diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.cpp index ff97042a344b6..a1660ae69ea6c 100644 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.cpp +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.cpp @@ -12,11 +12,431 @@ #include "fboss/cli/fboss2/CmdHandler.cpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" +#include "thrift/lib/cpp/Thrift.h" + namespace facebook::fboss { +namespace { + +// CLI attribute names. Kept here (not as raw string literals at the dispatch +// sites) so the valid-attribute set and the handler table stay in sync. +constexpr std::string_view kRouterId = "router-id"; +constexpr std::string_view kLocalAsn = "local-asn"; +constexpr std::string_view kHoldTime = "hold-time"; +constexpr std::string_view kConfedAsn = "confed-asn"; +constexpr std::string_view kNetwork6 = "network6"; +constexpr std::string_view kSwitchLimit = "switch-limit"; +constexpr std::string_view kSwitchLimitTotalPath = "switch-limit-total-path"; +constexpr std::string_view kSwitchLimitMaxGoldenVips = + "switch-limit-max-golden-vips"; +constexpr std::string_view kSwitchLimitOverloadProtectionMode = + "switch-limit-overload-protection-mode"; +constexpr std::string_view kCountConfedsInAsPathLen = + "count-confeds-in-as-path-len"; +constexpr std::string_view kGracefulRestartTime = "graceful-restart-time"; +constexpr std::string_view kRibAllocatedPathIds = "rib-allocated-path-ids"; + +using Tokens = std::vector; +using BgpConfig = bgp::thrift::BgpConfig; +// Value parsers and the handler Result type are shared with the neighbor +// dispatcher (see BgpCliValueParsers.h). +using bgpcli::err; +using bgpcli::ok; +using bgpcli::parseBool; +using bgpcli::parseInt; +using bgpcli::parseNonNegInt32; +using bgpcli::Result; + +// ---- per-attribute handlers ------------------------------------------------ +// Each handler mutates the typed bgp::thrift::BgpConfig directly. Field names +// map 1:1 to bgp_config.thrift, so the compiler validates them. + +// bgpd constructs an IPAddress from router_id at config load (a bad string +// fails the load) and later calls asV4() on it, so only an IPv4 address is +// safe to persist. Stored normalized. +Result applyRouterId(BgpConfig& cfg, const Tokens& values) { + if (values.size() != 1) { + return err("Error: router-id requires "); + } + auto addr = folly::IPAddress::tryFromString(values[0]); + if (!addr.hasValue()) { + return err(fmt::format("Error: Invalid router-id address '{}'", values[0])); + } + if (!addr->isV4()) { + return err( + fmt::format( + "Error: router-id requires an IPv4 address, got '{}'", values[0])); + } + cfg.router_id() = addr->str(); + return ok(fmt::format("Successfully set BGP router-id to: {}", addr->str())); +} + +Result applyLocalAsn(BgpConfig& cfg, const Tokens& values) { + if (values.size() != 1) { + return err("Error: local-asn requires "); + } + auto asn = bgpcli::parseAsn4Byte(values[0]); + if (!asn) { + return err( + fmt::format( + "Error: Invalid local-asn value '{}'; expected an unsigned " + "4-byte ASN", + values[0])); + } + cfg.local_as_4_byte() = *asn; + return ok(fmt::format("Successfully set BGP local-asn to: {}", *asn)); +} + +Result applyConfedAsn(BgpConfig& cfg, const Tokens& values) { + if (values.size() != 1) { + return err("Error: confed-asn requires "); + } + auto asn = bgpcli::parseAsn4Byte(values[0]); + if (!asn) { + return err( + fmt::format( + "Error: Invalid confed-asn value '{}'; expected an unsigned " + "4-byte ASN", + values[0])); + } + cfg.local_confed_as_4_byte() = *asn; + return ok( + fmt::format("Successfully set BGP confederation AS number to: {}", *asn)); +} + +Result applyHoldTime(BgpConfig& cfg, const Tokens& values) { + if (values.size() != 1) { + return err("Error: hold-time requires "); + } + auto seconds = parseNonNegInt32(values[0]); + if (!seconds) { + return err(fmt::format("Error: Invalid hold-time value '{}'", values[0])); + } + cfg.hold_time() = *seconds; + return ok( + fmt::format("Successfully set BGP hold-time to: {} seconds", *seconds)); +} + +Result applyGracefulRestartTime(BgpConfig& cfg, const Tokens& values) { + if (values.size() != 1) { + return err("Error: graceful-restart-time requires "); + } + auto seconds = parseNonNegInt32(values[0]); + if (!seconds) { + return err( + fmt::format( + "Error: graceful-restart-time must be a non-negative integer " + "that fits in int32, got '{}'", + values[0])); + } + cfg.graceful_restart_convergence_seconds() = *seconds; + return ok( + fmt::format( + "Successfully set BGP graceful-restart-time to: {} seconds", + *seconds)); +} + +Result applyCountConfedsInAsPathLen(BgpConfig& cfg, const Tokens& values) { + if (values.size() != 1) { + return err("Error: count-confeds-in-as-path-len requires "); + } + auto enable = parseBool(values[0]); + if (!enable) { + return err( + fmt::format( + "Error: Invalid value '{}'; expected true or false", values[0])); + } + cfg.count_confeds_in_as_path_len() = *enable; + return ok( + fmt::format( + "Successfully {} count-confeds-in-as-path-len", + *enable ? "enabled" : "disabled")); +} + +Result applyRibAllocatedPathIds(BgpConfig& cfg, const Tokens& values) { + if (values.size() != 1) { + return err("Error: rib-allocated-path-ids requires "); + } + auto enable = parseBool(values[0]); + if (!enable) { + return err( + fmt::format( + "Error: Invalid value '{}'; expected true or false", values[0])); + } + // enable_rib_allocated_path_id lives inside the nested bgp_setting_config. + cfg.bgp_setting_config().ensure().enable_rib_allocated_path_id() = *enable; + return ok( + fmt::format( + "Successfully {} rib-allocated-path-ids", + *enable ? "enabled" : "disabled")); +} + +// Shared implementation for the switch-limit attributes, which differ only in +// the field setter, the field name in the success message, and the upper bound +// (i64 fields use INT64_MAX; the i32 max_golden_vips field uses INT32_MAX so an +// out-of-range value is rejected rather than silently truncated). All limits +// must be non-negative. +Result applyInt64Limit( + BgpConfig& cfg, + const Tokens& values, + std::string_view attrName, + std::string_view fieldName, + int64_t maxValue, + const std::function& setter) { + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", attrName)); + } + auto limit = parseInt(values[0]); + if (!limit) { + return err( + fmt::format("Error: Invalid {} value '{}'", attrName, values[0])); + } + if (*limit < 0 || *limit > maxValue) { + return err( + fmt::format( + "Error: {} value '{}' is out of range [0, {}]", + attrName, + values[0], + maxValue)); + } + setter(cfg, *limit); + return ok( + fmt::format( + "Successfully set switch_limit_config {} to: {}", fieldName, *limit)); +} + +// Accepts the enum value name or its integer; anything outside the enum is +// rejected (bgpd would otherwise treat an unknown value as +// DROP_EXCESS_PREFIXES without complaint). Validated before ensure() so a +// rejected value leaves switch_limit_config untouched. The success message +// prints the integer so the j2c round-trip is stable. +Result applyOverloadProtectionMode(BgpConfig& cfg, const Tokens& values) { + using OverloadProtectionMode = bgp::thrift::OverloadProtectionMode; + if (values.size() != 1) { + return err("Error: switch-limit-overload-protection-mode requires "); + } + OverloadProtectionMode mode; + bool valid = false; + if (auto parsed = parseInt(values[0])) { + mode = static_cast(*parsed); + valid = apache::thrift::TEnumTraits::findName( + mode) != nullptr; + } else { + valid = apache::thrift::TEnumTraits::findValue( + values[0], &mode); + } + if (!valid) { + std::string names; + for (auto n : apache::thrift::TEnumTraits::names) { + names += names.empty() ? "" : ", "; + names += std::string(n); + } + return err( + fmt::format( + "Error: switch-limit-overload-protection-mode value '{}' is not a " + "valid mode; expected one of: {}", + values[0], + names)); + } + cfg.switch_limit_config().ensure().overload_protection_mode() = mode; + return ok( + fmt::format( + "Successfully set switch_limit_config overload_protection_mode to: {}", + static_cast(mode))); +} + +Result applyNetwork6(BgpConfig& cfg, const Tokens& values) { + // Usage: network6 add [policy ] [install-to-fib ] + // [min-routes ] + if (values.size() < 2 || values[0] != "add") { + return err( + "Error: usage: network6 add [policy ] " + "[install-to-fib ] [min-routes ]"); + } + std::string prefix = values[1]; + std::string policyName; + bool installToFib = false; + int32_t minSupportingRoutes = 0; + + for (size_t i = 2; i < values.size(); i++) { + if (values[i] == "policy" && i + 1 < values.size()) { + policyName = values[++i]; + } else if (values[i] == "install-to-fib" && i + 1 < values.size()) { + auto parsed = parseBool(values[++i]); + if (!parsed) { + return err( + fmt::format( + "Error: Invalid install-to-fib value '{}'; expected true or false", + values[i])); + } + installToFib = *parsed; + } else if (values[i] == "min-routes" && i + 1 < values.size()) { + auto parsed = parseNonNegInt32(values[++i]); + if (!parsed) { + return err( + fmt::format("Error: Invalid min-routes value '{}'", values[i])); + } + minSupportingRoutes = *parsed; + } else { + return err( + fmt::format("Error: unexpected network6 token '{}'", values[i])); + } + } + + // Find-or-update the networks6 entry keyed by prefix. + auto& networks = *cfg.networks6(); + bgp::thrift::BgpNetwork* entry = nullptr; + for (auto& n : networks) { + if (*n.prefix() == prefix) { + entry = &n; + break; + } + } + if (entry == nullptr) { + networks.emplace_back(); + entry = &networks.back(); + } + entry->prefix() = prefix; + entry->policy_name() = policyName; + entry->install_to_fib() = installToFib; + entry->minimum_supporting_routes() = minSupportingRoutes; + + return ok( + fmt::format( + "Successfully added network6 prefix: {} (policy: {}, install_to_fib: {}, " + "min_routes: {})", + prefix, + policyName.empty() ? "(none)" : policyName, + installToFib ? "true" : "false", + minSupportingRoutes)); +} + +using AttrHandler = std::function; + +const std::map& attrHandlers() { + static const std::map kHandlers = { + {kRouterId, applyRouterId}, + {kLocalAsn, applyLocalAsn}, + {kConfedAsn, applyConfedAsn}, + {kHoldTime, applyHoldTime}, + {kGracefulRestartTime, applyGracefulRestartTime}, + {kCountConfedsInAsPathLen, applyCountConfedsInAsPathLen}, + {kRibAllocatedPathIds, applyRibAllocatedPathIds}, + {kNetwork6, applyNetwork6}, + {kSwitchLimit, + [](BgpConfig& cfg, const Tokens& v) { + return applyInt64Limit( + cfg, + v, + kSwitchLimit, + "prefix_limit", + std::numeric_limits::max(), + [](BgpConfig& c, int64_t l) { + c.switch_limit_config().ensure().prefix_limit() = l; + }); + }}, + {kSwitchLimitTotalPath, + [](BgpConfig& cfg, const Tokens& v) { + return applyInt64Limit( + cfg, + v, + kSwitchLimitTotalPath, + "total_path_limit", + std::numeric_limits::max(), + [](BgpConfig& c, int64_t l) { + c.switch_limit_config().ensure().total_path_limit() = l; + }); + }}, + {kSwitchLimitMaxGoldenVips, + [](BgpConfig& cfg, const Tokens& v) { + // max_golden_vips is an i32 field; bound it so an out-of-range value + // is rejected rather than silently truncated by the static_cast. + return applyInt64Limit( + cfg, + v, + kSwitchLimitMaxGoldenVips, + "max_golden_vips", + std::numeric_limits::max(), + [](BgpConfig& c, int64_t l) { + c.switch_limit_config().ensure().max_golden_vips() = + static_cast(l); + }); + }}, + {kSwitchLimitOverloadProtectionMode, applyOverloadProtectionMode}, + }; + return kHandlers; +} + +std::string validAttrList() { + std::string out; + for (const auto& [name, _] : attrHandlers()) { + if (!out.empty()) { + out += ", "; + } + out += std::string(name); + } + return out; +} + +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as InterfacesConfig / parseTokens). +BgpGlobalConfig::BgpGlobalConfig(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty()) { + throw std::invalid_argument( + fmt::format( + "Error: attribute is required. Valid attributes: {}", + validAttrList())); + } + if (attrHandlers().find(v[0]) == attrHandlers().end()) { + throw std::invalid_argument( + fmt::format( + "Error: unknown global attribute '{}'. Valid attributes: {}", + v[0], + validAttrList())); + } + attr_ = v[0]; + values_.assign(v.begin() + 1, v.end()); +} + CmdConfigProtocolBgpGlobalTraits::RetType -CmdConfigProtocolBgpGlobal::queryClient(const HostInfo& /* hostInfo */) { - return "BGP global configuration. Use subcommands: router-id, local-asn, hold-time, graceful-restart, best-path, link-bw, nh-tracking, network-originate"; +CmdConfigProtocolBgpGlobal::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + + // The attribute is guaranteed valid: BgpGlobalConfig's constructor rejects + // an empty/unknown attribute before we get here. + auto handler = attrHandlers().find(args.attr()); + Result result = handler->second(session.getBgpConfig(), args.values()); + if (result.ok) { + session.saveBgpConfig(); + result.message += + fmt::format("\nConfig saved to: {}", session.getBgpSessionConfigPath()); + } + return result.message; } void CmdConfigProtocolBgpGlobal::printOutput(const RetType& output) { diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h index 0a6faa7a09fb6..2c64bc0d4d537 100644 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h @@ -10,14 +10,50 @@ #pragma once +#include +#include +#include "CLI/App.hpp" #include "fboss/cli/fboss2/CmdHandler.h" #include "fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" namespace facebook::fboss { +// Parsed `config protocol bgp global [value ...]`, validated at +// construction. values() is greedy to keep network6's multi-token tail. +class BgpGlobalConfig : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpGlobalConfig(std::vector v); + const std::string& attr() const { + return attr_; + } + const std::vector& values() const { + return values_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string attr_; + std::vector values_; +}; + +// Single handler for the whole `config protocol bgp global` family. The +// attribute name is the first positional token and the remaining tokens are +// its value(s); dispatch and per-attribute parsing live in the .cpp so adding +// a new global tunable is a one-entry change rather than a new command class. struct CmdConfigProtocolBgpGlobalTraits : public WriteCommandTraits { using ParentCmd = CmdConfigProtocolBgp; - using ObjectArgType = std::monostate; + static void addCliArg(CLI::App& cmd, std::vector& args) { + // Stop CLI11's parent-chain subcommand fallthrough from stealing value + // tokens that happen to match a sibling command name (e.g. a policy + // named "peer-group"); see CmdConfigProtocolBgpNeighborTraits. + cmd.positionals_at_end(); + cmd.add_option("args", args, " [value ...]"); + } + using ObjectArgType = BgpGlobalConfig; using RetType = std::string; }; @@ -28,7 +64,7 @@ class CmdConfigProtocolBgpGlobal : public CmdHandler< using ObjectArgType = CmdConfigProtocolBgpGlobalTraits::ObjectArgType; using RetType = CmdConfigProtocolBgpGlobalTraits::RetType; - RetType queryClient(const HostInfo& hostInfo); + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); void printOutput(const RetType& output); }; diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.cpp deleted file mode 100644 index ed1b541d0298f..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalClusterIdTraits::RetType -CmdConfigProtocolBgpGlobalClusterId::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& clusterIdList) { - auto& session = BgpConfigSession::getInstance(); - - if (clusterIdList.data().empty()) { - return "Error: cluster-id is required"; - } - - std::string clusterId = clusterIdList.data()[0]; - session.setClusterId(clusterId); - session.saveConfig(); - - return fmt::format( - "Successfully set BGP cluster-id to: {}\nConfig saved to: {}", - clusterId, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalClusterId::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalClusterId, - CmdConfigProtocolBgpGlobalClusterIdTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.h deleted file mode 100644 index 8345a5b485888..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalClusterId.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalClusterIdTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("ipAddrs", args, "IPv4 or IPv6 addr(s)"); - } - using ObjectArgType = utils::IPList; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalClusterId - : public CmdHandler< - CmdConfigProtocolBgpGlobalClusterId, - CmdConfigProtocolBgpGlobalClusterIdTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpGlobalClusterIdTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpGlobalClusterIdTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& clusterId); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.cpp deleted file mode 100644 index bcc6512e89a38..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalConfedAsnTraits::RetType -CmdConfigProtocolBgpGlobalConfedAsn::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& confedAsnMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (confedAsnMsg.data().empty()) { - return "Error: confed-asn value is required"; - } - - std::string asnStr = confedAsnMsg.data()[0]; - uint64_t confedAsn; - try { - confedAsn = folly::to(asnStr); - } catch (const std::exception& e) { - return fmt::format("Error: Invalid confed-asn '{}': {}", asnStr, e.what()); - } - - session.setConfedAsn(confedAsn); - session.saveConfig(); - - return fmt::format( - "Successfully set BGP confederation AS number to: {}\nConfig saved to: {}", - confedAsn, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalConfedAsn::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalConfedAsn, - CmdConfigProtocolBgpGlobalConfedAsnTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.h deleted file mode 100644 index 2020e1b4cb1f6..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalConfedAsn.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalConfedAsnTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalConfedAsn - : public CmdHandler< - CmdConfigProtocolBgpGlobalConfedAsn, - CmdConfigProtocolBgpGlobalConfedAsnTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpGlobalConfedAsnTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpGlobalConfedAsnTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& confedAsn); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.cpp deleted file mode 100644 index c526e68c97028..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalHoldTimeTraits::RetType -CmdConfigProtocolBgpGlobalHoldTime::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& holdTimeMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (holdTimeMsg.data().empty()) { - return "Error: hold-time value is required"; - } - - // Extract string and parse to uint32_t - std::string holdTimeStr = holdTimeMsg.data()[0]; - uint32_t holdTime; - try { - holdTime = folly::to(holdTimeStr); - } catch (const std::exception& e) { - return fmt::format( - "Error: Invalid hold-time value '{}': {}", holdTimeStr, e.what()); - } - - // Set the hold-time in the BGP config session - session.setHoldTime(holdTime); - session.saveConfig(); - - return fmt::format( - "Successfully set BGP hold-time to: {} seconds\nConfig saved to: {}", - holdTime, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalHoldTime::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalHoldTime, - CmdConfigProtocolBgpGlobalHoldTimeTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.h deleted file mode 100644 index 1db1004d320b3..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalHoldTime.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalHoldTimeTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalHoldTime - : public CmdHandler< - CmdConfigProtocolBgpGlobalHoldTime, - CmdConfigProtocolBgpGlobalHoldTimeTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpGlobalHoldTimeTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpGlobalHoldTimeTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& holdTime); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.cpp deleted file mode 100644 index 81a8571197a79..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalLocalAsnTraits::RetType -CmdConfigProtocolBgpGlobalLocalAsn::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& localAsnMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (localAsnMsg.data().empty()) { - return "Error: local-asn value is required"; - } - - // Extract string and parse to uint32_t - std::string asnStr = localAsnMsg.data()[0]; - uint32_t localAsn; - try { - localAsn = folly::to(asnStr); - } catch (const std::exception& e) { - return fmt::format( - "Error: Invalid local-asn value '{}': {}", asnStr, e.what()); - } - - // Set the local-asn in the BGP config session - session.setLocalAsn(localAsn); - session.saveConfig(); - - return fmt::format( - "Successfully set BGP local-asn to: {}\nConfig saved to: {}", - localAsn, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalLocalAsn::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalLocalAsn, - CmdConfigProtocolBgpGlobalLocalAsnTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.h deleted file mode 100644 index d39cc07e490bf..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalLocalAsn.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalLocalAsnTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalLocalAsn - : public CmdHandler< - CmdConfigProtocolBgpGlobalLocalAsn, - CmdConfigProtocolBgpGlobalLocalAsnTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpGlobalLocalAsnTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpGlobalLocalAsnTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& localAsn); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.cpp deleted file mode 100644 index 336f5f50f6f70..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalNetwork6AddTraits::RetType -CmdConfigProtocolBgpGlobalNetwork6Add::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& args) { - auto& session = BgpConfigSession::getInstance(); - - // args.data() returns a vector of strings - // Command: "config protocol bgp global network6 add [policy ] - // [install-to-fib ] [min-routes ]" - // After "network6", args contains: ["add", "", ...] - // So we need to skip tokens[0] which is "add" - const auto& tokens = args.data(); - - if (tokens.size() < 2) { - return "Error: prefix is required. Usage: network6 add [policy " - "] [install-to-fib ] [min-routes ]"; - } - - // tokens[0] is "add", tokens[1] is the actual prefix - std::string prefixStr = tokens[1]; - std::string policyName; - bool installToFib = false; - int32_t minSupportingRoutes = 0; - - // Start parsing optional args from index 2 - for (size_t i = 2; i < tokens.size(); i++) { - if (tokens[i] == "policy" && i + 1 < tokens.size()) { - policyName = tokens[++i]; - } else if (tokens[i] == "install-to-fib" && i + 1 < tokens.size()) { - std::string val = tokens[++i]; - installToFib = (val == "true" || val == "1"); - } else if (tokens[i] == "min-routes" && i + 1 < tokens.size()) { - std::string minRoutesStr = tokens[++i]; - try { - minSupportingRoutes = folly::to(minRoutesStr); - } catch (const std::exception& e) { - return fmt::format( - "Error: Invalid min-routes value '{}': {}", minRoutesStr, e.what()); - } - } - } - - session.addNetwork6(prefixStr, policyName, installToFib, minSupportingRoutes); - session.saveConfig(); - - return fmt::format( - "Successfully added network6 prefix: {} (policy: {}, install_to_fib: {}, min_routes: {})\nConfig saved to: {}", - prefixStr, - policyName.empty() ? "(none)" : policyName, - installToFib ? "true" : "false", - minSupportingRoutes, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalNetwork6Add::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalNetwork6Add, - CmdConfigProtocolBgpGlobalNetwork6AddTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.h deleted file mode 100644 index 885e7b3e2b63a..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalNetwork6Add.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalNetwork6AddTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalNetwork6Add - : public CmdHandler< - CmdConfigProtocolBgpGlobalNetwork6Add, - CmdConfigProtocolBgpGlobalNetwork6AddTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpGlobalNetwork6AddTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpGlobalNetwork6AddTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.cpp deleted file mode 100644 index c7093026cd1e7..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalRouterIdTraits::RetType -CmdConfigProtocolBgpGlobalRouterId::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& routerIdList) { - auto& session = BgpConfigSession::getInstance(); - - if (routerIdList.data().empty()) { - return "Error: router-id is required"; - } - - // Extract the first IP from the list as router-id - std::string routerId = routerIdList.data()[0]; - - // Set the router-id in the BGP config session - session.setRouterId(routerId); - session.saveConfig(); - - return fmt::format( - "Successfully set BGP router-id to: {}\nConfig saved to: {}", - routerId, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalRouterId::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalRouterId, - CmdConfigProtocolBgpGlobalRouterIdTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.h deleted file mode 100644 index dd2f2871144c5..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalRouterId.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalRouterIdTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("ipAddrs", args, "IPv4 or IPv6 addr(s)"); - } - using ObjectArgType = utils::IPList; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalRouterId - : public CmdHandler< - CmdConfigProtocolBgpGlobalRouterId, - CmdConfigProtocolBgpGlobalRouterIdTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpGlobalRouterIdTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpGlobalRouterIdTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& routerId); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.cpp deleted file mode 100644 index 6ef8c4fd536f5..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVipsTraits::RetType -CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& limitStr) { - auto& session = BgpConfigSession::getInstance(); - - if (limitStr.data().empty()) { - return "Error: max-golden-vips value is required"; - } - - int64_t limit = std::stoll(limitStr.data()[0]); - - session.setSwitchLimitMaxGoldenVips(limit); - session.saveConfig(); - - return fmt::format( - "Successfully set switch_limit_config max_golden_vips to: {}\nConfig saved to: {}", - limit, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips, - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVipsTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.h deleted file mode 100644 index c28eab45fc3f9..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVipsTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips - : public CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVips, - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVipsTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVipsTraits::ObjectArgType; - using RetType = - CmdConfigProtocolBgpGlobalSwitchLimitMaxGoldenVipsTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& limit); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.cpp deleted file mode 100644 index 78c634bf4bcd2..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionModeTraits::RetType -CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& modeStr) { - auto& session = BgpConfigSession::getInstance(); - - if (modeStr.data().empty()) { - return "Error: overload-protection-mode value is required"; - } - - int32_t mode = std::stoi(modeStr.data()[0]); - - session.setSwitchLimitOverloadProtectionMode(mode); - session.saveConfig(); - - return fmt::format( - "Successfully set switch_limit_config overload_protection_mode to: {}\nConfig saved to: {}", - mode, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode, - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionModeTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.h deleted file mode 100644 index 2a155cf1460a4..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionModeTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode - : public CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionMode, - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionModeTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionModeTraits:: - ObjectArgType; - using RetType = - CmdConfigProtocolBgpGlobalSwitchLimitOverloadProtectionModeTraits:: - RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& mode); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.cpp deleted file mode 100644 index 0241e189f6f1c..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimitTraits::RetType -CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& limitStr) { - auto& session = BgpConfigSession::getInstance(); - - // Command: "config protocol bgp global switch-limit " - // After "switch-limit", args contains just [""] - const auto& tokens = limitStr.data(); - if (tokens.empty()) { - return "Error: prefix-limit value is required. Usage: switch-limit "; - } - - int64_t limit = std::stoll(tokens[0]); - - session.setSwitchLimitPrefixLimit(limit); - session.saveConfig(); - - return fmt::format( - "Successfully set switch_limit_config prefix_limit to: {}\nConfig saved to: {}", - limit, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit, - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimitTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.h deleted file mode 100644 index d6496f037c749..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimitTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit - : public CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimit, - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimitTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimitTraits::ObjectArgType; - using RetType = - CmdConfigProtocolBgpGlobalSwitchLimitPrefixLimitTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& limit); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.cpp deleted file mode 100644 index b0d67f57ec616..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimitTraits::RetType -CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& limitStr) { - auto& session = BgpConfigSession::getInstance(); - - if (limitStr.data().empty()) { - return "Error: total-path-limit value is required"; - } - - int64_t limit = std::stoll(limitStr.data()[0]); - - session.setSwitchLimitTotalPathLimit(limit); - session.saveConfig(); - - return fmt::format( - "Successfully set switch_limit_config total_path_limit to: {}\nConfig saved to: {}", - limit, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit, - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimitTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.h b/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.h deleted file mode 100644 index 23e702fa743d7..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimitTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpGlobal; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit - : public CmdHandler< - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimit, - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimitTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimitTraits::ObjectArgType; - using RetType = - CmdConfigProtocolBgpGlobalSwitchLimitTotalPathLimitTraits::RetType; - - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& limit); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.cpp new file mode 100644 index 0000000000000..4a5799148cf0a --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.cpp @@ -0,0 +1,855 @@ +/* + * 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/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "configerator/structs/neteng/fboss/bgp/if/gen-cpp2/bgp_attr_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpPeerAddr.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" +#include "thrift/lib/cpp/Thrift.h" + +namespace facebook::fboss { + +namespace { + +// CLI attribute names, exactly as documented (two-token attributes keep their +// space). Kept here (not as raw string literals at the dispatch sites) so the +// valid-attribute set and the handler table stay in sync. +constexpr std::string_view kRemoteAsn = "remote-asn"; +constexpr std::string_view kLocalAsn = "local-asn"; +constexpr std::string_view kDescription = "description"; +constexpr std::string_view kPeerTag = "peer-tag"; +constexpr std::string_view kPeerGroup = "peer-group"; +constexpr std::string_view kIngressPolicy = "ingress-policy"; +constexpr std::string_view kEgressPolicy = "egress-policy"; +constexpr std::string_view kRrClient = "rr-client"; +constexpr std::string_view kConfedPeer = "confed-peer"; +constexpr std::string_view kRedistributePeer = "redistribute-peer"; +constexpr std::string_view kEnhancedRouteRefresh = "enhanced-route-refresh"; +constexpr std::string_view kRouteRefresh = "route-refresh"; +constexpr std::string_view kRemovePrivateAs = "remove-private-as"; +constexpr std::string_view kEnforceFirstAs = "enforce-first-as"; +constexpr std::string_view kTtlSecurityHops = "ttl-security-hops"; +constexpr std::string_view kPassive = "passive"; +constexpr std::string_view kPeerPort = "peer-port"; +constexpr std::string_view kAddPathSend = "add-path send"; +constexpr std::string_view kAddPathReceive = "add-path receive"; +constexpr std::string_view kAfiDisableIpv4Afi = "afi disable-ipv4-afi"; +constexpr std::string_view kAfiDisableIpv6Afi = "afi disable-ipv6-afi"; +constexpr std::string_view kAfiIpv4OverIpv6Nh = "afi ipv4-over-ipv6-nh"; +constexpr std::string_view kAfiIpv4LabeledUnicast = "afi ipv4-labeled-unicast"; +constexpr std::string_view kAfiIpv6LabeledUnicast = "afi ipv6-labeled-unicast"; +constexpr std::string_view kBindAddrAddress = "bind-addr address"; +constexpr std::string_view kBindAddrPort = "bind-addr port"; +constexpr std::string_view kGracefulRestartTime = + "graceful-restart restart-time"; +constexpr std::string_view kGracefulRestartStatefulHa = + "graceful-restart stateful-ha"; +constexpr std::string_view kMaxRoutePreFilter = "max-route pre-filter"; +constexpr std::string_view kMaxRoutePostFilter = "max-route post-filter"; +constexpr std::string_view kMaxRoutePostWarningThreshold = + "max-route post-warning-threshold"; +constexpr std::string_view kTimersHoldTime = "timers hold-time"; +constexpr std::string_view kTimersKeepalive = "timers keepalive"; +constexpr std::string_view kTimersOutDelay = "timers out-delay"; +// Parity attributes: not in the documented neighbor grammar, but supported by +// the per-attribute peer commands this dispatcher replaces (and by real +// BgpPeer thrift fields), so dropping them would regress existing usage. +constexpr std::string_view kNextHop4 = "next-hop4"; +constexpr std::string_view kNextHop6 = "next-hop6"; +constexpr std::string_view kNextHopSelf = "next-hop-self"; +constexpr std::string_view kPeerId = "peer-id"; +constexpr std::string_view kPeerType = "type"; +constexpr std::string_view kLinkBandwidth = "link-bandwidth"; +constexpr std::string_view kAdvertiseLbw = "advertise-lbw"; +constexpr std::string_view kReceiveLbw = "receive-lbw"; +constexpr std::string_view kTimersWithdrawUnprogDelay = + "timers withdraw-unprog-delay"; +constexpr std::string_view kMaxRoutePreWarningThreshold = + "max-route pre-warning-threshold"; +constexpr std::string_view kMaxRoutePreWarningOnly = + "max-route pre-warning-only"; +constexpr std::string_view kMaxRoutePostWarningOnly = + "max-route post-warning-only"; +// BgpPeer fields deliberately NOT exposed: `enabled` and `router_port_id` +// (nothing in bgpd reads them) and +// bgp_peer_timers.graceful_restart_end_of_rib_seconds (same). Staging them +// would persist config the daemon ignores. + +// Protocol ranges bgpd enforces (or the wire format silently truncates to): +// the OPEN hold time is 16 bits and bgpd refuses 1-2s; the graceful-restart +// restart time is the 12-bit field of RFC 4724; TTL security hops are +// bgpd's kMin/kMaxTtlSecurityHops (it throws on anything else at load). +constexpr int32_t kHoldTimeMinSeconds = 3; +constexpr int32_t kHoldTimeMaxSeconds = 65535; +constexpr int32_t kGracefulRestartMaxSeconds = 4095; +constexpr int32_t kTtlSecurityHopsMin = 1; +constexpr int32_t kTtlSecurityHopsMax = 255; + +// Value placeholders for the generated help. One per value shape, so the +// wording matches the factory that validates that shape. +constexpr std::string_view kUsageBool = ""; +constexpr std::string_view kUsageAsn = ""; +constexpr std::string_view kUsageName = ""; +constexpr std::string_view kUsageString = ""; +constexpr std::string_view kUsageText = ""; +constexpr std::string_view kUsagePolicy = ""; +constexpr std::string_view kUsageIp = ""; +constexpr std::string_view kUsageIpv4 = ""; +constexpr std::string_view kUsageIpv6 = ""; +constexpr std::string_view kUsageSeconds = ""; +constexpr std::string_view kUsageHoldTime = ""; +constexpr std::string_view kUsageGracefulRestart = ""; +constexpr std::string_view kUsageRouteCount = ""; +constexpr std::string_view kUsageBitRate = "[K|M|G] | auto"; +constexpr std::string_view kUsageTtlHops = ""; + +using bgpcli::Tokens; +using BgpConfig = bgp::thrift::BgpConfig; +using BgpPeer = bgp::thrift::BgpPeer; +using PeerGroup = bgp::thrift::PeerGroup; +using bgpcli::asnAttr; +using bgpcli::AttrHandler; +using bgpcli::bitRateAttr; +using bgpcli::boolAttr; +using bgpcli::err; +using bgpcli::intAttr; +using bgpcli::ipAttr; +using bgpcli::joinedStringAttr; +using bgpcli::ok; +using bgpcli::parseBool; +using bgpcli::rejectedAttr; +using bgpcli::Result; +using bgpcli::routeCountAttr; +using bgpcli::secondsAttr; +using bgpcli::SecondsRange; +using bgpcli::stringAttr; +using bgpcli::thriftEnumAttr; +using facebook::neteng::fboss::bgp_attr::AddPath; +using facebook::neteng::fboss::bgp_attr::AdvertiseLinkBandwidth; +using facebook::neteng::fboss::bgp_attr::ReceiveLinkBandwidth; + +// ---- per-attribute setters ------------------------------------------------- +// Pure thrift assignment: no validation, no messages. Parsing, bounds and the +// user-facing text all live in the shared factories in BgpCliAttrHandlers.h, +// so a setter is just the field this attribute writes. Field names map 1:1 to +// bgp_config.thrift, so the compiler validates them. + +void setRemoteAsn(BgpPeer& p, int64_t v) { + p.remote_as_4_byte() = v; +} + +void setLocalAsn(BgpPeer& p, int64_t v) { + p.local_as_4_byte() = v; +} + +void setDescription(BgpPeer& p, const std::string& v) { + p.description() = v; +} + +void setPeerTag(BgpPeer& p, const std::string& v) { + p.peer_tag() = v; +} + +void setPeerGroup(BgpPeer& p, const std::string& v) { + p.peer_group_name() = v; +} + +void setIngressPolicy(BgpPeer& p, const std::string& v) { + p.ingress_policy_name() = v; +} + +void setEgressPolicy(BgpPeer& p, const std::string& v) { + p.egress_policy_name() = v; +} + +void setRrClient(BgpPeer& p, bool v) { + p.is_rr_client() = v; +} + +void setConfedPeer(BgpPeer& p, bool v) { + p.is_confed_peer() = v; +} + +void setRedistributePeer(BgpPeer& p, bool v) { + p.is_redistribute_peer() = v; +} + +void setEnhancedRouteRefresh(BgpPeer& p, bool v) { + p.enhanced_route_refresh() = v; +} + +void setRouteRefresh(BgpPeer& p, bool v) { + p.route_refresh() = v; +} + +void setRemovePrivateAs(BgpPeer& p, bool v) { + p.remove_private_as() = v; +} + +void setEnforceFirstAs(BgpPeer& p, bool v) { + p.enforce_first_as() = v; +} + +void setTtlSecurityHops(BgpPeer& p, int32_t v) { + p.ttl_security_hops() = v; +} + +// bgpd reads is_passive=true as PASSIVE_ONLY (listen) and false as +// PASSIVE_ACTIVE (listen and connect). Its session code also knows an +// ACTIVE_ONLY mode, but the config model has no way to request it (the mode +// is derived from is_passive alone), so the CLI exposes the flag itself +// rather than inventing connect-mode names. +void setPassive(BgpPeer& p, bool v) { + p.is_passive() = v; +} + +void setDisableIpv4Afi(BgpPeer& p, bool v) { + p.disable_ipv4_afi() = v; +} + +void setDisableIpv6Afi(BgpPeer& p, bool v) { + p.disable_ipv6_afi() = v; +} + +void setIpv4OverIpv6Nh(BgpPeer& p, bool v) { + p.v4_over_v6_nexthop() = v; +} + +void setBindAddrAddress(BgpPeer& p, const std::string& v) { + p.local_addr() = v; +} + +void setGracefulRestartTime(BgpPeer& p, int32_t v) { + p.bgp_peer_timers().ensure().graceful_restart_seconds() = v; +} + +void setGracefulRestartStatefulHa(BgpPeer& p, bool v) { + p.enable_stateful_ha() = v; +} + +void setMaxRoutePreFilter(BgpPeer& p, int64_t v) { + p.pre_filter().ensure().max_routes() = v; +} + +void setMaxRoutePostFilter(BgpPeer& p, int64_t v) { + p.post_filter().ensure().max_routes() = v; +} + +// RouteLimit.warning_limit is an absolute route count in the thrift schema, +// not a percentage. +void setMaxRoutePreWarningThreshold(BgpPeer& p, int64_t v) { + p.pre_filter().ensure().warning_limit() = v; +} + +void setMaxRoutePostWarningThreshold(BgpPeer& p, int64_t v) { + p.post_filter().ensure().warning_limit() = v; +} + +void setMaxRoutePreWarningOnly(BgpPeer& p, bool v) { + p.pre_filter().ensure().warning_only() = v; +} + +void setMaxRoutePostWarningOnly(BgpPeer& p, bool v) { + p.post_filter().ensure().warning_only() = v; +} + +void setTimersHoldTime(BgpPeer& p, int32_t v) { + p.bgp_peer_timers().ensure().hold_time_seconds() = v; +} + +void setTimersKeepalive(BgpPeer& p, int32_t v) { + p.bgp_peer_timers().ensure().keep_alive_seconds() = v; +} + +void setTimersOutDelay(BgpPeer& p, int32_t v) { + p.bgp_peer_timers().ensure().out_delay_seconds() = v; +} + +void setTimersWithdrawUnprogDelay(BgpPeer& p, int32_t v) { + p.bgp_peer_timers().ensure().withdraw_unprog_delay_seconds() = v; +} + +void setNextHop4(BgpPeer& p, const std::string& v) { + p.next_hop4() = v; +} + +void setNextHop6(BgpPeer& p, const std::string& v) { + p.next_hop6() = v; +} + +void setNextHopSelf(BgpPeer& p, bool v) { + p.next_hop_self() = v; +} + +void setPeerId(BgpPeer& p, const std::string& v) { + p.peer_id() = v; +} + +void setPeerType(BgpPeer& p, const std::string& v) { + p.type() = v; +} + +void setLinkBandwidth(BgpPeer& p, const std::string& v) { + p.link_bandwidth_bps() = v; +} + +void setAdvertiseLbw(BgpPeer& p, AdvertiseLinkBandwidth v) { + p.advertise_link_bandwidth() = v; +} + +void setReceiveLbw(BgpPeer& p, ReceiveLinkBandwidth v) { + p.receive_link_bandwidth() = v; +} + +// ---- inherited values ------------------------------------------------------ +// bgpd resolves a peer against its peer group at STRUCT granularity: if the +// peer carries bgp_peer_timers (or pre_filter / post_filter) at all, every +// field of that struct comes from the peer and the group's copy is ignored; +// a timers struct whose other fields are thrift defaults therefore runs the +// session with hold time 0 (no keepalives). So before the first attribute of +// such a struct is set on a peer, the struct is seeded from what the peer +// inherits today -- the group's struct, else the global defaults -- and the +// one requested field is then changed on top of it. + +const PeerGroup* peerGroupOf(const BgpConfig& cfg, const BgpPeer& peer) { + if (!peer.peer_group_name() || !cfg.peer_groups()) { + return nullptr; + } + for (const auto& group : *cfg.peer_groups()) { + if (*group.name() == *peer.peer_group_name()) { + return &group; + } + } + return nullptr; +} + +void seedTimers(const BgpConfig& cfg, BgpPeer& peer) { + if (peer.bgp_peer_timers()) { + return; + } + if (const auto* group = peerGroupOf(cfg, peer); + group && group->bgp_peer_timers()) { + peer.bgp_peer_timers() = *group->bgp_peer_timers(); + return; + } + bgp::thrift::BgpPeerTimers timers; + timers.hold_time_seconds() = *cfg.hold_time(); + timers.keep_alive_seconds() = *cfg.hold_time() / 3; + peer.bgp_peer_timers() = std::move(timers); +} + +void seedRouteLimit(const BgpConfig& cfg, BgpPeer& peer, bool pre) { + auto field = [pre](auto& p) -> decltype(auto) { + return pre ? p.pre_filter() : p.post_filter(); + }; + if (field(peer)) { + return; + } + if (const auto* group = peerGroupOf(cfg, peer); group && field(*group)) { + field(peer) = *field(*group); + return; + } + // No limit anywhere above: bgpd applies none to a peer without a struct + // (capRoutesPerPeer returns early) and reads max_routes == 0 as unlimited, + // so seed that. The thrift default the setter's ensure() would leave behind + // is a 12000-route hard cap with session teardown -- a warning-only or + // threshold edit must not introduce one. + bgp::thrift::RouteLimit unlimited; + unlimited.max_routes() = 0; + unlimited.warning_only() = false; + unlimited.warning_limit() = 0; + field(peer) = std::move(unlimited); +} + +// A neighbor handler sees the whole config (for the peer group lookup) plus +// the peer being edited. The shared factories only know the peer, so they are +// adapted; the adapters for struct-valued attributes seed first. +using NeighborHandler = + std::function; + +// One dispatch-table row: how the value is spelled in help, and the handler. +struct NeighborAttr { + std::string usage; + NeighborHandler handle; +}; + +// "" from a thrift enum's value names, for the generated help. +template +std::string enumUsage() { + std::string out = "<"; + for (auto n : apache::thrift::TEnumTraits::names) { + out += out.size() == 1 ? "" : "|"; + out += std::string(n); + } + return out + ">"; +} + +NeighborHandler peerOnly(AttrHandler handler) { + return [handler = std::move(handler)]( + const BgpConfig&, BgpPeer& peer, const Tokens& values) { + return handler(peer, values); + }; +} + +NeighborHandler withTimers(AttrHandler handler) { + return [handler = std::move(handler)]( + const BgpConfig& cfg, BgpPeer& peer, const Tokens& values) { + seedTimers(cfg, peer); + return handler(peer, values); + }; +} + +NeighborHandler withRouteLimit(bool pre, AttrHandler handler) { + return [pre, handler = std::move(handler)]( + const BgpConfig& cfg, BgpPeer& peer, const Tokens& values) { + seedRouteLimit(cfg, peer, pre); + return handler(peer, values); + }; +} + +// ---- hand-written handlers ------------------------------------------------- +// Only for value shapes no factory covers, because the shape is unique to this +// attribute rather than reusable. + +// add-path send/receive: a boolean at the CLI, but the two attributes share +// one thrift field -- add_path is a single enum whose values form a bitmask by +// design (RECEIVE=1, SEND=2, BOTH=3). Each takes the boolAttr text but merges +// its direction into the value in effect for the peer (its own, else the peer +// group's) instead of overwriting it. The enum has no "disabled" member and +// an unset peer field means "inherit", so a direction the group enables +// cannot be switched off per peer; that is refused rather than staged as a +// no-op the CLI would report as success. +Result applyAddPath( + const BgpConfig& cfg, + BgpPeer& peer, + bool send, + const Tokens& values) { + const std::string_view name = send ? kAddPathSend : kAddPathReceive; + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + auto enable = parseBool(values[0]); + if (!enable) { + return err( + fmt::format( + "Error: Invalid {} value '{}'; expected true or false", + name, + values[0])); + } + const auto* group = peerGroupOf(cfg, peer); + const std::optional inherited = group && group->add_path() + ? std::optional(*group->add_path()) + : std::nullopt; + const std::optional effective = + peer.add_path() ? std::optional(*peer.add_path()) : inherited; + int bits = effective ? static_cast(*effective) : 0; + const int bit = send ? static_cast(AddPath::SEND) + : static_cast(AddPath::RECEIVE); + bits = *enable ? (bits | bit) : (bits & ~bit); + if (bits == 0) { + if (inherited) { + return err( + fmt::format( + "Error: cannot disable {} on this neighbor: peer group {} " + "enables add-path {} and BgpPeer.add_path has no disabled " + "value to override it (an unset field inherits the group); " + "change it on the peer group instead", + name, + *group->name(), + apache::thrift::TEnumTraits::findName(*inherited))); + } + peer.add_path().reset(); + } else { + peer.add_path() = static_cast(bits); + } + return ok( + fmt::format( + "Successfully {} {}", *enable ? "enabled" : "disabled", name)); +} + +Result addPathSend(const BgpConfig& cfg, BgpPeer& peer, const Tokens& values) { + return applyAddPath(cfg, peer, /* send */ true, values); +} + +Result +addPathReceive(const BgpConfig& cfg, BgpPeer& peer, const Tokens& values) { + return applyAddPath(cfg, peer, /* send */ false, values); +} + +// ---- the dispatch table ---------------------------------------------------- +// One line per attribute: dispatch key, value shape, setter. There are no +// handler bodies here by design — if an attribute appears to need one, its +// value shape is missing a factory and the fix is to add the factory. + +constexpr std::string_view kNoPeerPortField = + "BgpPeer has no per-neighbor port field; the daemon listens on the global " + "listen_port"; +constexpr std::string_view kNoLabeledUnicastField = + "bgp_config.thrift has no labeled-unicast field on BgpPeer"; + +const std::map>& attrHandlers() { + auto plain = [](std::string_view usage, AttrHandler h) { + return NeighborAttr{std::string(usage), peerOnly(std::move(h))}; + }; + auto timers = [](std::string_view usage, AttrHandler h) { + return NeighborAttr{std::string(usage), withTimers(std::move(h))}; + }; + auto preLimit = [](std::string_view usage, AttrHandler h) { + return NeighborAttr{ + std::string(usage), withRouteLimit(/* pre */ true, std::move(h))}; + }; + auto postLimit = [](std::string_view usage, AttrHandler h) { + return NeighborAttr{ + std::string(usage), withRouteLimit(/* pre */ false, std::move(h))}; + }; + auto rejected = [](std::string_view name, std::string_view reason) { + return NeighborAttr{ + fmt::format("(not supported: {})", reason), + peerOnly(rejectedAttr(name, reason))}; + }; + static const std::map> kHandlers = { + {std::string(kRemoteAsn), + plain(kUsageAsn, asnAttr(kRemoteAsn, setRemoteAsn))}, + {std::string(kLocalAsn), + plain(kUsageAsn, asnAttr(kLocalAsn, setLocalAsn))}, + {std::string(kDescription), + plain( + kUsageText, + joinedStringAttr(kDescription, setDescription))}, + {std::string(kPeerTag), + plain( + kUsageString, stringAttr(kPeerTag, "string", setPeerTag))}, + {std::string(kPeerGroup), + plain( + kUsageName, stringAttr(kPeerGroup, "name", setPeerGroup))}, + {std::string(kIngressPolicy), + plain( + kUsagePolicy, + stringAttr( + kIngressPolicy, "policy-name", setIngressPolicy))}, + {std::string(kEgressPolicy), + plain( + kUsagePolicy, + stringAttr(kEgressPolicy, "policy-name", setEgressPolicy))}, + {std::string(kRrClient), + plain(kUsageBool, boolAttr(kRrClient, setRrClient))}, + {std::string(kConfedPeer), + plain(kUsageBool, boolAttr(kConfedPeer, setConfedPeer))}, + {std::string(kRedistributePeer), + plain( + kUsageBool, + boolAttr(kRedistributePeer, setRedistributePeer))}, + {std::string(kEnhancedRouteRefresh), + plain( + kUsageBool, + boolAttr(kEnhancedRouteRefresh, setEnhancedRouteRefresh))}, + {std::string(kRouteRefresh), + plain(kUsageBool, boolAttr(kRouteRefresh, setRouteRefresh))}, + {std::string(kRemovePrivateAs), + plain( + kUsageBool, + boolAttr(kRemovePrivateAs, setRemovePrivateAs))}, + {std::string(kEnforceFirstAs), + plain( + kUsageBool, boolAttr(kEnforceFirstAs, setEnforceFirstAs))}, + {std::string(kTtlSecurityHops), + plain( + kUsageTtlHops, + intAttr( + kTtlSecurityHops, + "1-255", + kTtlSecurityHopsMin, + kTtlSecurityHopsMax, + setTtlSecurityHops))}, + {std::string(kPassive), + plain(kUsageBool, boolAttr(kPassive, setPassive))}, + {std::string(kPeerPort), rejected(kPeerPort, kNoPeerPortField)}, + {std::string(kAddPathSend), + NeighborAttr{std::string(kUsageBool), addPathSend}}, + {std::string(kAddPathReceive), + NeighborAttr{std::string(kUsageBool), addPathReceive}}, + {std::string(kAfiDisableIpv4Afi), + plain( + kUsageBool, + boolAttr(kAfiDisableIpv4Afi, setDisableIpv4Afi))}, + {std::string(kAfiDisableIpv6Afi), + plain( + kUsageBool, + boolAttr(kAfiDisableIpv6Afi, setDisableIpv6Afi))}, + {std::string(kAfiIpv4OverIpv6Nh), + plain( + kUsageBool, + boolAttr(kAfiIpv4OverIpv6Nh, setIpv4OverIpv6Nh))}, + {std::string(kAfiIpv4LabeledUnicast), + rejected(kAfiIpv4LabeledUnicast, kNoLabeledUnicastField)}, + {std::string(kAfiIpv6LabeledUnicast), + rejected(kAfiIpv6LabeledUnicast, kNoLabeledUnicastField)}, + {std::string(kBindAddrAddress), + plain(kUsageIp, ipAttr(kBindAddrAddress, setBindAddrAddress))}, + {std::string(kBindAddrPort), rejected(kBindAddrPort, kNoPeerPortField)}, + {std::string(kGracefulRestartTime), + timers( + kUsageGracefulRestart, + secondsAttr( + kGracefulRestartTime, + setGracefulRestartTime, + SecondsRange{0, kGracefulRestartMaxSeconds}))}, + {std::string(kGracefulRestartStatefulHa), + plain( + kUsageBool, + boolAttr( + kGracefulRestartStatefulHa, setGracefulRestartStatefulHa))}, + {std::string(kMaxRoutePreFilter), + preLimit( + kUsageRouteCount, + routeCountAttr(kMaxRoutePreFilter, setMaxRoutePreFilter))}, + {std::string(kMaxRoutePostFilter), + postLimit( + kUsageRouteCount, + routeCountAttr( + kMaxRoutePostFilter, setMaxRoutePostFilter))}, + {std::string(kMaxRoutePreWarningThreshold), + preLimit( + kUsageRouteCount, + routeCountAttr( + kMaxRoutePreWarningThreshold, setMaxRoutePreWarningThreshold))}, + {std::string(kMaxRoutePostWarningThreshold), + postLimit( + kUsageRouteCount, + routeCountAttr( + kMaxRoutePostWarningThreshold, + setMaxRoutePostWarningThreshold))}, + {std::string(kMaxRoutePreWarningOnly), + preLimit( + kUsageBool, + boolAttr( + kMaxRoutePreWarningOnly, setMaxRoutePreWarningOnly))}, + {std::string(kMaxRoutePostWarningOnly), + postLimit( + kUsageBool, + boolAttr( + kMaxRoutePostWarningOnly, setMaxRoutePostWarningOnly))}, + {std::string(kTimersHoldTime), + timers( + kUsageHoldTime, + secondsAttr( + kTimersHoldTime, + setTimersHoldTime, + SecondsRange{kHoldTimeMinSeconds, kHoldTimeMaxSeconds}))}, + {std::string(kTimersKeepalive), + timers( + kUsageSeconds, + secondsAttr(kTimersKeepalive, setTimersKeepalive))}, + {std::string(kTimersOutDelay), + timers( + kUsageSeconds, + secondsAttr(kTimersOutDelay, setTimersOutDelay))}, + {std::string(kTimersWithdrawUnprogDelay), + timers( + kUsageSeconds, + secondsAttr( + kTimersWithdrawUnprogDelay, setTimersWithdrawUnprogDelay))}, + {std::string(kNextHop4), + plain( + kUsageIpv4, + ipAttr(kNextHop4, setNextHop4, /* requireV6 */ false))}, + {std::string(kNextHop6), + plain( + kUsageIpv6, + ipAttr(kNextHop6, setNextHop6, /* requireV6 */ true))}, + {std::string(kNextHopSelf), + plain(kUsageBool, boolAttr(kNextHopSelf, setNextHopSelf))}, + {std::string(kPeerId), + plain(kUsageString, stringAttr(kPeerId, "string", setPeerId))}, + {std::string(kPeerType), + plain( + kUsageString, + stringAttr(kPeerType, "string", setPeerType))}, + {std::string(kLinkBandwidth), + plain( + kUsageBitRate, + bitRateAttr(kLinkBandwidth, setLinkBandwidth))}, + {std::string(kAdvertiseLbw), + plain( + enumUsage(), + thriftEnumAttr( + kAdvertiseLbw, setAdvertiseLbw))}, + {std::string(kReceiveLbw), + plain( + enumUsage(), + thriftEnumAttr( + kReceiveLbw, setReceiveLbw))}, + }; + return kHandlers; +} + +std::string validAttrList() { + std::string out; + for (const auto& [name, _] : attrHandlers()) { + if (!out.empty()) { + out += ", "; + } + out += name; + } + return out; +} + +// A fresh peer for peerAddr. Setting an attribute on a not-yet-created +// neighbor implicitly creates it, so command ordering stays forgiving; a bare +// `neighbor ` creates one explicitly. +BgpPeer newPeer(const std::string& peerAddr) { + BgpPeer peer; + peer.peer_addr() = peerAddr; + // local_addr / next_hop4 / next_hop6 are non-optional thrift fields that + // bgpd parses with folly::IPAddress at config load -- an empty string makes + // the daemon abort (IPAddressFormatException) before serving any config. + // Seed them with the unspecified address of the right family; the user + // overrides them via `bind-addr address` / `next-hop4` / `next-hop6`. + const bool isV6 = peerAddr.find(':') != std::string::npos; + peer.local_addr() = isV6 ? "::" : "0.0.0.0"; + peer.next_hop4() = "0.0.0.0"; + peer.next_hop6() = "::"; + return peer; +} + +} // namespace + +std::vector bgpNeighborAttributeNames() { + std::vector names; + for (const auto& [name, _] : attrHandlers()) { + names.push_back(name); + } + return names; +} + +std::string bgpNeighborAttributeHelp() { + size_t width = 0; + for (const auto& [name, _] : attrHandlers()) { + width = std::max(width, name.size()); + } + std::string out = "Attributes ( ):\n"; + for (const auto& [name, attr] : attrHandlers()) { + out += fmt::format(" {:<{}} {}\n", name, width, attr.usage); + } + return out; +} + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpGlobalConfig). +BgpNeighborConfig::BgpNeighborConfig(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty()) { + throw std::invalid_argument( + "Error: neighbor is required, followed by an optional " + " "); + } + auto normalized = bgpcli::normalizeBgpPeerAddr(v[0]); + if (!normalized) { + throw std::invalid_argument( + fmt::format("Error: Invalid neighbor address '{}'", v[0])); + } + peerAddr_ = std::move(*normalized); + + if (v.size() == 1) { + return; // bare `neighbor `: create the peer + } + + // Attributes may span two tokens (e.g. `timers hold-time`); match the + // longest prefix of the remaining tokens against the dispatch table. + const auto& handlers = attrHandlers(); + if (v.size() >= 3) { + std::string twoToken = v[1] + " " + v[2]; + if (handlers.find(twoToken) != handlers.end()) { + attr_ = std::move(twoToken); + values_.assign(v.begin() + 3, v.end()); + return; + } + } + if (handlers.find(v[1]) != handlers.end()) { + attr_ = v[1]; + values_.assign(v.begin() + 2, v.end()); + return; + } + throw std::invalid_argument( + fmt::format( + "Error: unknown neighbor attribute '{}'. Valid attributes: {}", + v[1], + validAttrList())); +} + +CmdConfigProtocolBgpNeighborTraits::RetType +CmdConfigProtocolBgpNeighbor::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + auto& peers = *cfg.peers(); + auto existing = bgpcli::findBgpPeer(cfg, args.peerAddr()); + + // Edit a copy: a rejected value (or a struct seeded for it) must leave the + // in-memory config exactly as it was, since later lookups in the same + // process see it whether or not it was saved. + BgpPeer peer = existing != peers.end() ? *existing : newPeer(args.peerAddr()); + Result result = args.attr().empty() + ? ok(fmt::format("Successfully created BGP neighbor {}", args.peerAddr())) + // The attribute is guaranteed valid: BgpNeighborConfig's constructor + // rejects an unknown attribute before we get here. + : attrHandlers() + .find(args.attr()) + ->second.handle(cfg, peer, args.values()); + if (!result.ok) { + return result.message; + } + if (existing != peers.end()) { + *existing = std::move(peer); + } else { + peers.push_back(std::move(peer)); + } + if (!args.attr().empty()) { + result.message += fmt::format(" for neighbor {}", args.peerAddr()); + } + session.saveBgpConfig(); + result.message += + fmt::format("\nConfig saved to: {}", session.getBgpSessionConfigPath()); + return result.message; +} + +void CmdConfigProtocolBgpNeighbor::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdConfigProtocolBgpNeighbor, + CmdConfigProtocolBgpNeighborTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h b/fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h new file mode 100644 index 0000000000000..22add751f7e68 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h @@ -0,0 +1,97 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `config protocol bgp neighbor [ ...]`, +// validated at construction. The neighbor address is normalized via +// folly::IPAddress so different spellings of the same address (e.g. +// 2001:DB8::1 vs 2001:db8::1) key the same peer. Attributes may span two +// tokens (e.g. `timers hold-time`); the dispatch table is matched +// longest-prefix-first. A bare `neighbor ` (no attribute) creates the +// peer. +class BgpNeighborConfig : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpNeighborConfig(std::vector v); + const std::string& peerAddr() const { + return peerAddr_; + } + const std::string& attr() const { + return attr_; + } + const std::vector& values() const { + return values_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string peerAddr_; + std::string attr_; // matched dispatch key ("" = bare create) + std::vector values_; +}; + +// The ` ` list the neighbor command accepts, rendered one +// attribute per line for `neighbor --help`. Generated from the dispatch table +// in the .cpp, so it cannot drift from what the command actually parses. +std::string bgpNeighborAttributeHelp(); + +// The dispatch keys (e.g. "remote-asn", "timers hold-time"), sorted. +std::vector bgpNeighborAttributeNames(); + +// Single handler for the whole `config protocol bgp neighbor` family. The +// first positional token is the neighbor address, the next one or two tokens +// name the attribute, and the remaining tokens are its value(s); dispatch and +// per-attribute parsing live in the .cpp so adding a new neighbor tunable is +// a one-entry change rather than a new command class. Mirrors +// CmdConfigProtocolBgpGlobal. +struct CmdConfigProtocolBgpNeighborTraits : public WriteCommandTraits { + using ParentCmd = CmdConfigProtocolBgp; + static void addCliArg(CLI::App& cmd, std::vector& args) { + // positionals_at_end: once the first positional (the neighbor address, + // which never collides with a command name) is consumed, CLI11 stops + // classifying later tokens as subcommands. Without this, an attribute + // token that matches a sibling command name (e.g. `peer-group`) is + // stolen by CLI11's parent-chain subcommand fallthrough and the + // remaining tokens are misparsed. + cmd.positionals_at_end(); + cmd.add_option("args", args, " [ ...]"); + // The attribute list is long, so it lives in this subcommand's own help + // footer rather than in the one-line description shown by the parent. + cmd.footer(bgpNeighborAttributeHelp()); + } + using ObjectArgType = BgpNeighborConfig; + using RetType = std::string; +}; + +class CmdConfigProtocolBgpNeighbor : public CmdHandler< + CmdConfigProtocolBgpNeighbor, + CmdConfigProtocolBgpNeighborTraits> { + public: + using ObjectArgType = CmdConfigProtocolBgpNeighborTraits::ObjectArgType; + using RetType = CmdConfigProtocolBgpNeighborTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.cpp index 9fdb301293a89..6393f4f3a1e14 100644 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.cpp +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.cpp @@ -13,30 +13,625 @@ #include "fboss/cli/fboss2/CmdHandler.cpp" #include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "configerator/structs/neteng/fboss/bgp/if/gen-cpp2/bgp_attr_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" namespace facebook::fboss { +namespace { + +// CLI attribute names, exactly as documented (two-token attributes keep their +// space). Kept here (not as raw string literals at the dispatch sites) so the +// valid-attribute set and the handler table stay in sync. +constexpr std::string_view kRemoteAsn = "remote-asn"; +constexpr std::string_view kLocalAsn = "local-asn"; +constexpr std::string_view kDescription = "description"; +constexpr std::string_view kPeerTag = "peer-tag"; +constexpr std::string_view kIngressPolicy = "ingress-policy"; +constexpr std::string_view kEgressPolicy = "egress-policy"; +constexpr std::string_view kRrClient = "rr-client"; +constexpr std::string_view kRedistributePeer = "redistribute-peer"; +constexpr std::string_view kEnhancedRouteRefresh = "enhanced-route-refresh"; +constexpr std::string_view kRouteRefresh = "route-refresh"; +constexpr std::string_view kRemovePrivateAs = "remove-private-as"; +constexpr std::string_view kEnforceFirstAs = "enforce-first-as"; +constexpr std::string_view kTtlSecurityHops = "ttl-security-hops"; +constexpr std::string_view kLinkBandwidth = "link-bandwidth"; +constexpr std::string_view kAdvertiseLbw = "advertise-lbw"; +constexpr std::string_view kReceiveLbw = "receive-lbw"; +constexpr std::string_view kPassive = "passive"; +constexpr std::string_view kAddPathSend = "add-path send"; +constexpr std::string_view kAddPathReceive = "add-path receive"; +constexpr std::string_view kAfiDisableIpv4Afi = "afi disable-ipv4-afi"; +constexpr std::string_view kAfiDisableIpv6Afi = "afi disable-ipv6-afi"; +constexpr std::string_view kAfiIpv4OverIpv6Nh = "afi ipv4-over-ipv6-nh"; +constexpr std::string_view kGracefulRestartTime = + "graceful-restart restart-time"; +constexpr std::string_view kGracefulRestartStatefulHa = + "graceful-restart stateful-ha"; +constexpr std::string_view kMaxRoutePreFilter = "max-route pre-filter"; +constexpr std::string_view kMaxRoutePostFilter = "max-route post-filter"; +constexpr std::string_view kMaxRoutePostWarningThreshold = + "max-route post-warning-threshold"; +constexpr std::string_view kTimersHoldTime = "timers hold-time"; +constexpr std::string_view kTimersKeepalive = "timers keepalive"; +constexpr std::string_view kTimersOutDelay = "timers out-delay"; +// Parity attributes: not in the documented peer-group grammar, but supported +// by the per-attribute peer-group commands this dispatcher replaces (and by +// real PeerGroup thrift fields), so dropping them would regress existing +// usage. +constexpr std::string_view kConfedPeer = "confed-peer"; +constexpr std::string_view kNextHopSelf = "next-hop-self"; +constexpr std::string_view kTimersWithdrawUnprogDelay = + "timers withdraw-unprog-delay"; +constexpr std::string_view kMaxRoutePreWarningThreshold = + "max-route pre-warning-threshold"; +constexpr std::string_view kMaxRoutePreWarningOnly = + "max-route pre-warning-only"; +constexpr std::string_view kMaxRoutePostWarningOnly = + "max-route post-warning-only"; +// PeerGroup fields deliberately NOT exposed, because bgpd never reads them +// from a group: local_addr, next_hop4, next_hop6 (per-peer only, the +// neighbor command owns them), `enabled` and `router_port_id` (read nowhere) +// and bgp_peer_timers.graceful_restart_end_of_rib_seconds (same). Staging +// them would persist config the daemon ignores. + +// Protocol ranges bgpd enforces (or the wire format silently truncates to): +// the OPEN hold time is 16 bits and bgpd refuses 1-2s; the graceful-restart +// restart time is the 12-bit field of RFC 4724; TTL security hops are +// bgpd's kMin/kMaxTtlSecurityHops (it throws on anything else at load). +constexpr int32_t kHoldTimeMinSeconds = 3; +constexpr int32_t kHoldTimeMaxSeconds = 65535; +constexpr int32_t kGracefulRestartMaxSeconds = 4095; +constexpr int32_t kTtlSecurityHopsMin = 1; +constexpr int32_t kTtlSecurityHopsMax = 255; + +using BgpConfig = bgp::thrift::BgpConfig; +using PeerGroup = bgp::thrift::PeerGroup; +using bgpcli::asnAttr; +using bgpcli::AttrHandler; +using bgpcli::bitRateAttr; +using bgpcli::boolAttr; +using bgpcli::err; +using bgpcli::intAttr; +using bgpcli::joinedStringAttr; +using bgpcli::ok; +using bgpcli::parseBool; +using bgpcli::Result; +using bgpcli::routeCountAttr; +using bgpcli::secondsAttr; +using bgpcli::SecondsRange; +using bgpcli::stringAttr; +using bgpcli::thriftEnumAttr; +using bgpcli::Tokens; +using facebook::neteng::fboss::bgp_attr::AddPath; +using facebook::neteng::fboss::bgp_attr::AdvertiseLinkBandwidth; +using facebook::neteng::fboss::bgp_attr::ReceiveLinkBandwidth; + +// ---- per-attribute setters ------------------------------------------------- +// Pure thrift assignment: no validation, no messages. Parsing, bounds and the +// user-facing text all live in the shared factories in BgpCliAttrHandlers.h, +// so a setter is just the field this attribute writes. Field names map 1:1 to +// bgp_config.thrift, so the compiler validates them. + +void setRemoteAsn(PeerGroup& g, int64_t v) { + g.remote_as_4_byte() = v; +} + +void setLocalAsn(PeerGroup& g, int64_t v) { + g.local_as_4_byte() = v; +} + +void setDescription(PeerGroup& g, const std::string& v) { + g.description() = v; +} + +void setPeerTag(PeerGroup& g, const std::string& v) { + g.peer_tag() = v; +} + +void setIngressPolicy(PeerGroup& g, const std::string& v) { + g.ingress_policy_name() = v; +} + +void setEgressPolicy(PeerGroup& g, const std::string& v) { + g.egress_policy_name() = v; +} + +void setRrClient(PeerGroup& g, bool v) { + g.is_rr_client() = v; +} + +void setConfedPeer(PeerGroup& g, bool v) { + g.is_confed_peer() = v; +} + +void setRedistributePeer(PeerGroup& g, bool v) { + g.is_redistribute_peer() = v; +} + +void setEnhancedRouteRefresh(PeerGroup& g, bool v) { + g.enhanced_route_refresh() = v; +} + +void setRouteRefresh(PeerGroup& g, bool v) { + g.route_refresh() = v; +} + +void setRemovePrivateAs(PeerGroup& g, bool v) { + g.remove_private_as() = v; +} + +void setEnforceFirstAs(PeerGroup& g, bool v) { + g.enforce_first_as() = v; +} + +void setTtlSecurityHops(PeerGroup& g, int32_t v) { + g.ttl_security_hops() = v; +} + +void setLinkBandwidth(PeerGroup& g, const std::string& v) { + g.link_bandwidth_bps() = v; +} + +void setAdvertiseLbw(PeerGroup& g, AdvertiseLinkBandwidth v) { + g.advertise_link_bandwidth() = v; +} + +void setReceiveLbw(PeerGroup& g, ReceiveLinkBandwidth v) { + g.receive_link_bandwidth() = v; +} + +// bgpd reads is_passive=true as PASSIVE_ONLY (listen) and false as +// PASSIVE_ACTIVE (listen and connect). Its session code also knows an +// ACTIVE_ONLY mode, but the config model has no way to request it (the mode +// is derived from is_passive alone), so the CLI exposes the flag itself +// rather than inventing connect-mode names. +void setPassive(PeerGroup& g, bool v) { + g.is_passive() = v; +} + +void setDisableIpv4Afi(PeerGroup& g, bool v) { + g.disable_ipv4_afi() = v; +} + +void setDisableIpv6Afi(PeerGroup& g, bool v) { + g.disable_ipv6_afi() = v; +} + +void setIpv4OverIpv6Nh(PeerGroup& g, bool v) { + g.v4_over_v6_nexthop() = v; +} + +void setGracefulRestartTime(PeerGroup& g, int32_t v) { + g.bgp_peer_timers().ensure().graceful_restart_seconds() = v; +} + +void setGracefulRestartStatefulHa(PeerGroup& g, bool v) { + g.enable_stateful_ha() = v; +} + +void setMaxRoutePreFilter(PeerGroup& g, int64_t v) { + g.pre_filter().ensure().max_routes() = v; +} + +void setMaxRoutePostFilter(PeerGroup& g, int64_t v) { + g.post_filter().ensure().max_routes() = v; +} + +// RouteLimit.warning_limit is an absolute route count in the thrift schema, +// not a percentage. +void setMaxRoutePreWarningThreshold(PeerGroup& g, int64_t v) { + g.pre_filter().ensure().warning_limit() = v; +} + +void setMaxRoutePostWarningThreshold(PeerGroup& g, int64_t v) { + g.post_filter().ensure().warning_limit() = v; +} + +void setMaxRoutePreWarningOnly(PeerGroup& g, bool v) { + g.pre_filter().ensure().warning_only() = v; +} + +void setMaxRoutePostWarningOnly(PeerGroup& g, bool v) { + g.post_filter().ensure().warning_only() = v; +} + +void setTimersHoldTime(PeerGroup& g, int32_t v) { + g.bgp_peer_timers().ensure().hold_time_seconds() = v; +} + +void setTimersKeepalive(PeerGroup& g, int32_t v) { + g.bgp_peer_timers().ensure().keep_alive_seconds() = v; +} + +void setTimersOutDelay(PeerGroup& g, int32_t v) { + g.bgp_peer_timers().ensure().out_delay_seconds() = v; +} + +void setTimersWithdrawUnprogDelay(PeerGroup& g, int32_t v) { + g.bgp_peer_timers().ensure().withdraw_unprog_delay_seconds() = v; +} + +void setNextHopSelf(PeerGroup& g, bool v) { + g.next_hop_self() = v; +} + +// ---- inherited values ------------------------------------------------------ +// bgpd applies a peer group's bgp_peer_timers to its members as a WHOLE +// struct: once the group carries one, every member takes hold, keepalive and +// out-delay from it, and hold_time_seconds / keep_alive_seconds are +// non-optional i32 (default 0). Hold time 0 means no keepalives, so a timers +// struct created for one field would silently disable dead-peer detection for +// the whole group. Before the first timer attribute is set on a group, the +// struct is therefore seeded from what its members use today -- the global +// hold time, with the conventional hold/3 keepalive -- and the requested field +// is then changed on top of it. +// +// pre_filter / post_filter are seeded too, though a group has nothing above +// it to inherit from: bgpd applies NO limit to a member without a RouteLimit +// (capRoutesPerPeer returns early) and reads max_routes == 0 as unlimited, +// whereas the thrift default the setter's ensure() would leave behind is a +// 12000-route hard cap with session teardown. A warning-only or threshold +// edit must not introduce a cap the operator never asked for, so the struct +// starts as {0, false, 0} and only the requested field changes. + +void seedTimers(const BgpConfig& cfg, PeerGroup& group) { + if (group.bgp_peer_timers()) { + return; + } + bgp::thrift::BgpPeerTimers timers; + timers.hold_time_seconds() = *cfg.hold_time(); + timers.keep_alive_seconds() = *cfg.hold_time() / 3; + group.bgp_peer_timers() = std::move(timers); +} + +void seedRouteLimit(PeerGroup& group, bool pre) { + auto field = pre ? group.pre_filter() : group.post_filter(); + if (field) { + return; + } + bgp::thrift::RouteLimit unlimited; + unlimited.max_routes() = 0; + unlimited.warning_only() = false; + unlimited.warning_limit() = 0; + field = std::move(unlimited); +} + +// A peer-group handler sees the whole config (for the global defaults) plus +// the group being edited. The shared factories only know the group, so they +// are adapted; the adapters for struct-valued attributes seed first. +using PeerGroupHandler = + std::function; + +PeerGroupHandler groupOnly(AttrHandler handler) { + return [handler = std::move(handler)]( + const BgpConfig&, PeerGroup& group, const Tokens& values) { + return handler(group, values); + }; +} + +PeerGroupHandler withTimers(AttrHandler handler) { + return [handler = std::move(handler)]( + const BgpConfig& cfg, PeerGroup& group, const Tokens& values) { + seedTimers(cfg, group); + return handler(group, values); + }; +} + +PeerGroupHandler withRouteLimit(bool pre, AttrHandler handler) { + return [pre, handler = std::move(handler)]( + const BgpConfig&, PeerGroup& group, const Tokens& values) { + seedRouteLimit(group, pre); + return handler(group, values); + }; +} + +// ---- hand-written handlers ------------------------------------------------- +// Only for value shapes no factory covers, because the shape is unique to this +// attribute rather than reusable. Mirrors the neighbor dispatcher's version, +// which writes the identically-named BgpPeer field. + +// add-path send/receive: a boolean at the CLI, but the two attributes share +// one thrift field — add_path is a single enum whose values form a bitmask by +// design (RECEIVE=1, SEND=2, BOTH=3). Each takes the boolAttr text but merges +// its direction into the current value instead of overwriting it; clearing the +// last direction unsets the field. +Result applyAddPath(PeerGroup& group, bool send, const Tokens& values) { + const std::string_view name = send ? kAddPathSend : kAddPathReceive; + if (values.size() != 1) { + return err(fmt::format("Error: {} requires ", name)); + } + auto enable = parseBool(values[0]); + if (!enable) { + return err( + fmt::format( + "Error: Invalid {} value '{}'; expected true or false", + name, + values[0])); + } + int bits = group.add_path() ? static_cast(*group.add_path()) : 0; + const int bit = send ? static_cast(AddPath::SEND) + : static_cast(AddPath::RECEIVE); + bits = *enable ? (bits | bit) : (bits & ~bit); + if (bits == 0) { + group.add_path().reset(); + } else { + group.add_path() = static_cast(bits); + } + return ok( + fmt::format( + "Successfully {} {}", *enable ? "enabled" : "disabled", name)); +} + +Result addPathSend(PeerGroup& group, const Tokens& values) { + return applyAddPath(group, /* send */ true, values); +} + +Result addPathReceive(PeerGroup& group, const Tokens& values) { + return applyAddPath(group, /* send */ false, values); +} + +// ---- the dispatch table ---------------------------------------------------- +// One line per attribute: dispatch key, value shape, setter. There are no +// handler bodies here by design — if an attribute appears to need one, its +// value shape is missing a factory and the fix is to add the factory. + +const std::map>& attrHandlers() { + auto plain = [](AttrHandler h) { return groupOnly(std::move(h)); }; + auto timers = [](AttrHandler h) { + return withTimers(std::move(h)); + }; + auto preLimit = [](AttrHandler h) { + return withRouteLimit(/* pre */ true, std::move(h)); + }; + auto postLimit = [](AttrHandler h) { + return withRouteLimit(/* pre */ false, std::move(h)); + }; + static const std::map> kHandlers = + { + {std::string(kRemoteAsn), + plain(asnAttr(kRemoteAsn, setRemoteAsn))}, + {std::string(kLocalAsn), + plain(asnAttr(kLocalAsn, setLocalAsn))}, + {std::string(kDescription), + plain(joinedStringAttr(kDescription, setDescription))}, + {std::string(kPeerTag), + plain(stringAttr(kPeerTag, "string", setPeerTag))}, + {std::string(kIngressPolicy), + plain( + stringAttr( + kIngressPolicy, "policy-name", setIngressPolicy))}, + {std::string(kEgressPolicy), + plain( + stringAttr( + kEgressPolicy, "policy-name", setEgressPolicy))}, + {std::string(kRrClient), + plain(boolAttr(kRrClient, setRrClient))}, + {std::string(kConfedPeer), + plain(boolAttr(kConfedPeer, setConfedPeer))}, + {std::string(kRedistributePeer), + plain(boolAttr(kRedistributePeer, setRedistributePeer))}, + {std::string(kEnhancedRouteRefresh), + plain( + boolAttr( + kEnhancedRouteRefresh, setEnhancedRouteRefresh))}, + {std::string(kRouteRefresh), + plain(boolAttr(kRouteRefresh, setRouteRefresh))}, + {std::string(kRemovePrivateAs), + plain(boolAttr(kRemovePrivateAs, setRemovePrivateAs))}, + {std::string(kEnforceFirstAs), + plain(boolAttr(kEnforceFirstAs, setEnforceFirstAs))}, + {std::string(kTtlSecurityHops), + plain( + intAttr( + kTtlSecurityHops, + "1-255", + kTtlSecurityHopsMin, + kTtlSecurityHopsMax, + setTtlSecurityHops))}, + {std::string(kLinkBandwidth), + plain(bitRateAttr(kLinkBandwidth, setLinkBandwidth))}, + {std::string(kAdvertiseLbw), + plain( + thriftEnumAttr( + kAdvertiseLbw, setAdvertiseLbw))}, + {std::string(kReceiveLbw), + plain( + thriftEnumAttr( + kReceiveLbw, setReceiveLbw))}, + {std::string(kPassive), + plain(boolAttr(kPassive, setPassive))}, + {std::string(kAddPathSend), plain(addPathSend)}, + {std::string(kAddPathReceive), plain(addPathReceive)}, + {std::string(kAfiDisableIpv4Afi), + plain(boolAttr(kAfiDisableIpv4Afi, setDisableIpv4Afi))}, + {std::string(kAfiDisableIpv6Afi), + plain(boolAttr(kAfiDisableIpv6Afi, setDisableIpv6Afi))}, + {std::string(kAfiIpv4OverIpv6Nh), + plain(boolAttr(kAfiIpv4OverIpv6Nh, setIpv4OverIpv6Nh))}, + {std::string(kGracefulRestartTime), + timers( + secondsAttr( + kGracefulRestartTime, + setGracefulRestartTime, + SecondsRange{0, kGracefulRestartMaxSeconds}))}, + {std::string(kGracefulRestartStatefulHa), + plain( + boolAttr( + kGracefulRestartStatefulHa, setGracefulRestartStatefulHa))}, + {std::string(kMaxRoutePreFilter), + preLimit( + routeCountAttr( + kMaxRoutePreFilter, setMaxRoutePreFilter))}, + {std::string(kMaxRoutePostFilter), + postLimit( + routeCountAttr( + kMaxRoutePostFilter, setMaxRoutePostFilter))}, + {std::string(kMaxRoutePreWarningThreshold), + preLimit( + routeCountAttr( + kMaxRoutePreWarningThreshold, + setMaxRoutePreWarningThreshold))}, + {std::string(kMaxRoutePostWarningThreshold), + postLimit( + routeCountAttr( + kMaxRoutePostWarningThreshold, + setMaxRoutePostWarningThreshold))}, + {std::string(kMaxRoutePreWarningOnly), + preLimit( + boolAttr( + kMaxRoutePreWarningOnly, setMaxRoutePreWarningOnly))}, + {std::string(kMaxRoutePostWarningOnly), + postLimit( + boolAttr( + kMaxRoutePostWarningOnly, setMaxRoutePostWarningOnly))}, + {std::string(kTimersHoldTime), + timers( + secondsAttr( + kTimersHoldTime, + setTimersHoldTime, + SecondsRange{kHoldTimeMinSeconds, kHoldTimeMaxSeconds}))}, + {std::string(kTimersKeepalive), + timers( + secondsAttr(kTimersKeepalive, setTimersKeepalive))}, + {std::string(kTimersOutDelay), + timers(secondsAttr(kTimersOutDelay, setTimersOutDelay))}, + {std::string(kTimersWithdrawUnprogDelay), + timers( + secondsAttr( + kTimersWithdrawUnprogDelay, setTimersWithdrawUnprogDelay))}, + {std::string(kNextHopSelf), + plain(boolAttr(kNextHopSelf, setNextHopSelf))}, + }; + return kHandlers; +} + +std::string validAttrList() { + std::string out; + for (const auto& [name, _] : attrHandlers()) { + if (!out.empty()) { + out += ", "; + } + out += name; + } + return out; +} + +// The group named `groupName`, or end(). Setting an attribute on a +// not-yet-created group implicitly creates it, so command ordering stays +// forgiving; a bare `peer-group ` creates one explicitly. Unlike a +// BgpPeer, PeerGroup's only non-optional field is `name`, so a new group needs +// no address seeding. +std::vector::iterator findPeerGroup( + std::vector& groups, + const std::string& groupName) { + for (auto it = groups.begin(); it != groups.end(); ++it) { + if (*it->name() == groupName) { + return it; + } + } + return groups.end(); +} + +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpNeighborConfig). +BgpPeerGroupConfig::BgpPeerGroupConfig(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty()) { + throw std::invalid_argument( + "Error: peer-group is required, followed by an optional " + " "); + } + if (v[0].empty()) { + throw std::invalid_argument("Error: peer-group name must not be empty"); + } + groupName_ = v[0]; + + if (v.size() == 1) { + return; // bare `peer-group `: create the group + } + + // Attributes may span two tokens (e.g. `timers hold-time`); match the + // longest prefix of the remaining tokens against the dispatch table. + const auto& handlers = attrHandlers(); + if (v.size() >= 3) { + std::string twoToken = v[1] + " " + v[2]; + if (handlers.find(twoToken) != handlers.end()) { + attr_ = std::move(twoToken); + values_.assign(v.begin() + 3, v.end()); + return; + } + } + if (handlers.find(v[1]) != handlers.end()) { + attr_ = v[1]; + values_.assign(v.begin() + 2, v.end()); + return; + } + throw std::invalid_argument( + fmt::format( + "Error: unknown peer-group attribute '{}'. Valid attributes: {}", + v[1], + validAttrList())); +} + CmdConfigProtocolBgpPeerGroupTraits::RetType CmdConfigProtocolBgpPeerGroup::queryClient( const HostInfo& /* hostInfo */, - const ObjectArgType& groupNameMsg) { - auto& session = BgpConfigSession::getInstance(); + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + auto& groups = cfg.peer_groups().ensure(); + auto existing = findPeerGroup(groups, args.groupName()); - if (groupNameMsg.data().empty()) { - return "Error: peer-group name is required"; + // Edit a copy: a rejected value (or a struct seeded for it) must leave the + // in-memory config exactly as it was, since later lookups in the same + // process see it whether or not it was saved. + PeerGroup group; + if (existing != groups.end()) { + group = *existing; + } else { + group.name() = args.groupName(); } - - std::string groupName = groupNameMsg.data()[0]; - session.createPeerGroup(groupName); - session.saveConfig(); - - return fmt::format( - "BGP peer-group {} created.\n" - "Use subcommands: remote-asn, description, hold-time, keepalive\n" - "Config saved to: {}", - groupName, - session.getSessionConfigPath()); + Result result = args.attr().empty() + ? ok(fmt::format( + "Successfully created BGP peer-group {}", args.groupName())) + // The attribute is guaranteed valid: BgpPeerGroupConfig's constructor + // rejects an unknown attribute before we get here. + : attrHandlers().find(args.attr())->second(cfg, group, args.values()); + if (!result.ok) { + return result.message; + } + if (existing != groups.end()) { + *existing = std::move(group); + } else { + groups.push_back(std::move(group)); + } + if (!args.attr().empty()) { + result.message += fmt::format(" for peer-group {}", args.groupName()); + } + session.saveBgpConfig(); + result.message += + fmt::format("\nConfig saved to: {}", session.getBgpSessionConfigPath()); + return result.message; } void CmdConfigProtocolBgpPeerGroup::printOutput(const RetType& output) { diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h index 1c6d74e0f375a..1eca3e696eff3 100644 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h @@ -10,18 +10,54 @@ #pragma once +#include +#include +#include "CLI/App.hpp" #include "fboss/cli/fboss2/CmdHandler.h" #include "fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" namespace facebook::fboss { +// Parsed `peer-group [ ...]`, validated at +// construction. Attributes may span two tokens (e.g. `timers hold-time`), +// matched longest-prefix-first. Bare `peer-group ` creates the group. +// Mirrors BgpNeighborConfig. +class BgpPeerGroupConfig : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpPeerGroupConfig(std::vector v); + const std::string& groupName() const { + return groupName_; + } + const std::string& attr() const { + return attr_; + } + const std::vector& values() const { + return values_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string groupName_; + std::string attr_; // matched dispatch key ("" = bare create) + std::vector values_; +}; + +// Single handler for the whole `config protocol bgp peer-group` family; +// dispatch and per-attribute parsing live in the .cpp so a new tunable is a +// one-entry change. Mirrors CmdConfigProtocolBgpNeighbor. struct CmdConfigProtocolBgpPeerGroupTraits : public WriteCommandTraits { using ParentCmd = CmdConfigProtocolBgp; static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); + // Stops CLI11 from classifying attribute tokens as subcommands once the + // group name is consumed. See CmdConfigProtocolBgpNeighborTraits. + cmd.positionals_at_end(); + cmd.add_option("args", args, " [ ...]"); } - using ObjectArgType = utils::Message; + using ObjectArgType = BgpPeerGroupConfig; using RetType = std::string; }; @@ -32,7 +68,7 @@ class CmdConfigProtocolBgpPeerGroup : public CmdHandler< using ObjectArgType = CmdConfigProtocolBgpPeerGroupTraits::ObjectArgType; using RetType = CmdConfigProtocolBgpPeerGroupTraits::RetType; - RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& groupName); + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); void printOutput(const RetType& output); }; diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.cpp deleted file mode 100644 index 13b8c1bd31e76..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupConfedPeerTraits::RetType -CmdConfigProtocolBgpPeerGroupConfedPeer::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value required for peer-group '{}'", peerGroupName); - } - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeerGroup(peerGroupName); - session.setPeerGroupConfedPeer(peerGroupName, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} confederation-peer for peer-group '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerGroupName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupConfedPeer::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupConfedPeer, - CmdConfigProtocolBgpPeerGroupConfedPeerTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.h deleted file mode 100644 index 32fb0f19c28a6..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupConfedPeer.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupConfedPeerTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupConfedPeer - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupConfedPeer, - CmdConfigProtocolBgpPeerGroupConfedPeerTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupConfedPeerTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupConfedPeerTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.cpp deleted file mode 100644 index f316e3477be66..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupDescriptionTraits::RetType -CmdConfigProtocolBgpPeerGroupDescription::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& groupNameMsg, - const ObjectArgType& descriptionMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (groupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - if (descriptionMsg.data().empty()) { - return "Error: description is required"; - } - - std::string groupName = groupNameMsg.data()[0]; - std::string description = descriptionMsg.data()[0]; - - session.setPeerGroupDescription(groupName, description); - session.saveConfig(); - - return fmt::format( - "Successfully set peer-group {} description to: {}\nConfig saved to: {}", - groupName, - description, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupDescription::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupDescription, - CmdConfigProtocolBgpPeerGroupDescriptionTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.h deleted file mode 100644 index 37ee936eeaaa1..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDescription.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupDescriptionTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupDescription - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupDescription, - CmdConfigProtocolBgpPeerGroupDescriptionTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupDescriptionTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupDescriptionTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& groupName, - const ObjectArgType& description); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.cpp deleted file mode 100644 index 0ab361e4cbeb7..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupDisableIpv4AfiTraits::RetType -CmdConfigProtocolBgpPeerGroupDisableIpv4Afi::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& disableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (disableMsg.data().empty()) { - return fmt::format( - "Error: disable value required for peer-group '{}'", peerGroupName); - } - std::string disableStr = disableMsg.data()[0]; - bool disable = - (disableStr == "true" || disableStr == "1" || disableStr == "yes"); - - session.createPeerGroup(peerGroupName); - session.setPeerGroupDisableIpv4Afi(peerGroupName, disable); - session.saveConfig(); - - return fmt::format( - "Successfully {} IPv4 AFI for peer-group '{}'\n" - "Config saved to: {}", - disable ? "disabled" : "enabled", - peerGroupName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupDisableIpv4Afi::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupDisableIpv4Afi, - CmdConfigProtocolBgpPeerGroupDisableIpv4AfiTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.h deleted file mode 100644 index cd06d0f7568d8..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupDisableIpv4Afi.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupDisableIpv4AfiTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupDisableIpv4Afi - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupDisableIpv4Afi, - CmdConfigProtocolBgpPeerGroupDisableIpv4AfiTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupDisableIpv4AfiTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupDisableIpv4AfiTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& disable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.cpp deleted file mode 100644 index de09a95dedf21..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupEgressPolicyTraits::RetType -CmdConfigProtocolBgpPeerGroupEgressPolicy::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& policyMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (policyMsg.data().empty()) { - return fmt::format( - "Error: egress policy name is required for peer-group '{}'", - peerGroupName); - } - std::string policyName = policyMsg.data()[0]; - - session.createPeerGroup(peerGroupName); - session.setPeerGroupEgressPolicy(peerGroupName, policyName); - session.saveConfig(); - - return fmt::format( - "Successfully set egress policy for peer-group '{}' to: '{}'\n" - "Config saved to: {}", - peerGroupName, - policyName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupEgressPolicy::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupEgressPolicy, - CmdConfigProtocolBgpPeerGroupEgressPolicyTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.h deleted file mode 100644 index e55ae153a80d7..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupEgressPolicy.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupEgressPolicyTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupEgressPolicy - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupEgressPolicy, - CmdConfigProtocolBgpPeerGroupEgressPolicyTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupEgressPolicyTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupEgressPolicyTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& policyName); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.cpp deleted file mode 100644 index 3e7b7fe50d2b9..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupIngressPolicyTraits::RetType -CmdConfigProtocolBgpPeerGroupIngressPolicy::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& policyMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (policyMsg.data().empty()) { - return fmt::format( - "Error: ingress policy name is required for peer-group '{}'", - peerGroupName); - } - std::string policyName = policyMsg.data()[0]; - - session.createPeerGroup(peerGroupName); - session.setPeerGroupIngressPolicy(peerGroupName, policyName); - session.saveConfig(); - - return fmt::format( - "Successfully set ingress policy for peer-group '{}' to: '{}'\n" - "Config saved to: {}", - peerGroupName, - policyName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupIngressPolicy::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupIngressPolicy, - CmdConfigProtocolBgpPeerGroupIngressPolicyTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.h deleted file mode 100644 index f3345936405ae..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupIngressPolicy.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupIngressPolicyTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupIngressPolicy - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupIngressPolicy, - CmdConfigProtocolBgpPeerGroupIngressPolicyTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupIngressPolicyTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupIngressPolicyTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& policyName); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.cpp deleted file mode 100644 index a3af4c904f8b4..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupMaxRoutesTraits::RetType -CmdConfigProtocolBgpPeerGroupMaxRoutes::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& maxRoutesMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (maxRoutesMsg.data().empty()) { - return fmt::format( - "Error: max-routes value is required for peer-group '{}'", - peerGroupName); - } - - int64_t maxRoutes; - try { - maxRoutes = folly::to(maxRoutesMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid max-routes value '{}': {}", - maxRoutesMsg.data()[0], - e.what()); - } - - session.createPeerGroup(peerGroupName); - session.setPeerGroupPreFilterMaxRoutes(peerGroupName, maxRoutes); - session.saveConfig(); - - return fmt::format( - "Successfully set max-routes for peer-group '{}' to: {}\n" - "Config saved to: {}", - peerGroupName, - maxRoutes, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupMaxRoutes::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupMaxRoutes, - CmdConfigProtocolBgpPeerGroupMaxRoutesTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.h deleted file mode 100644 index 655b445f342cb..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupMaxRoutes.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupMaxRoutesTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupMaxRoutes - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupMaxRoutes, - CmdConfigProtocolBgpPeerGroupMaxRoutesTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupMaxRoutesTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupMaxRoutesTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& maxRoutes); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.cpp deleted file mode 100644 index 6800cdddac8c9..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupNextHopSelfTraits::RetType -CmdConfigProtocolBgpPeerGroupNextHopSelf::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value (true/false) is required for peer-group '{}'", - peerGroupName); - } - - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeerGroup(peerGroupName); - session.setPeerGroupNextHopSelf(peerGroupName, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} next-hop-self for peer-group '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerGroupName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupNextHopSelf::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupNextHopSelf, - CmdConfigProtocolBgpPeerGroupNextHopSelfTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.h deleted file mode 100644 index 83e39041a99b9..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupNextHopSelf.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupNextHopSelfTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupNextHopSelf - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupNextHopSelf, - CmdConfigProtocolBgpPeerGroupNextHopSelfTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupNextHopSelfTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupNextHopSelfTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.cpp deleted file mode 100644 index 1fbb6a228e603..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupPeerTagTraits::RetType -CmdConfigProtocolBgpPeerGroupPeerTag::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& groupNameArg, - const ObjectArgType& peerTagMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (groupNameArg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string groupName = groupNameArg.data()[0]; - - if (peerTagMsg.data().empty()) { - return fmt::format( - "Error: peer-tag value is required for peer-group '{}'", groupName); - } - - session.createPeerGroup(groupName); - session.setPeerGroupPeerTag(groupName, peerTagMsg.data()[0]); - session.saveConfig(); - - return fmt::format( - "Successfully set peer_tag for peer-group '{}' to: '{}'\n" - "Config saved to: {}", - groupName, - peerTagMsg.data()[0], - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupPeerTag::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupPeerTag, - CmdConfigProtocolBgpPeerGroupPeerTagTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.h deleted file mode 100644 index 4715f131fe8b8..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupPeerTag.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupPeerTagTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupPeerTag - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupPeerTag, - CmdConfigProtocolBgpPeerGroupPeerTagTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupPeerTagTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupPeerTagTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& groupName, - const ObjectArgType& peerTag); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.cpp deleted file mode 100644 index ea967b32e0e2d..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupRemoteAsnTraits::RetType -CmdConfigProtocolBgpPeerGroupRemoteAsn::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& groupNameMsg, - const ObjectArgType& remoteAsnMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (groupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - if (remoteAsnMsg.data().empty()) { - return "Error: remote-asn value is required"; - } - - std::string groupName = groupNameMsg.data()[0]; - std::string asnStr = remoteAsnMsg.data()[0]; - uint64_t remoteAsn; - try { - remoteAsn = folly::to(asnStr); - } catch (const std::exception& e) { - return fmt::format("Error: Invalid remote-asn '{}': {}", asnStr, e.what()); - } - - session.setPeerGroupRemoteAsn(groupName, remoteAsn); - session.saveConfig(); - - return fmt::format( - "Successfully set peer-group {} remote-asn to: {}\nConfig saved to: {}", - groupName, - remoteAsn, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupRemoteAsn::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupRemoteAsn, - CmdConfigProtocolBgpPeerGroupRemoteAsnTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.h deleted file mode 100644 index 7f7c79898d233..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRemoteAsn.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupRemoteAsnTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupRemoteAsn - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupRemoteAsn, - CmdConfigProtocolBgpPeerGroupRemoteAsnTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupRemoteAsnTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupRemoteAsnTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& groupName, - const ObjectArgType& remoteAsn); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.cpp deleted file mode 100644 index e6cff47df37bb..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupRrClientTraits::RetType -CmdConfigProtocolBgpPeerGroupRrClient::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value (true/false) is required for peer-group '{}'", - peerGroupName); - } - - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeerGroup(peerGroupName); - session.setPeerGroupRrClient(peerGroupName, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} route-reflector-client for peer-group '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerGroupName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupRrClient::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupRrClient, - CmdConfigProtocolBgpPeerGroupRrClientTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.h deleted file mode 100644 index c98cac2f2f97c..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupRrClient.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupRrClientTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupRrClient - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupRrClient, - CmdConfigProtocolBgpPeerGroupRrClientTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupRrClientTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupRrClientTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.cpp deleted file mode 100644 index f1ebe72da4592..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupTimersTraits::RetType -CmdConfigProtocolBgpPeerGroupTimers::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg) { - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - - std::string groupName = peerGroupNameMsg.data()[0]; - - return fmt::format( - "Configure timers for peer-group {}.\n" - "Use subcommands: hold-time, keepalive, out-delay", - groupName); -} - -void CmdConfigProtocolBgpPeerGroupTimers::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupTimers, - CmdConfigProtocolBgpPeerGroupTimersTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h deleted file mode 100644 index 2b457c60909f3..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupTimersTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - using ObjectArgType = std::monostate; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupTimers - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupTimers, - CmdConfigProtocolBgpPeerGroupTimersTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupTimersTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupTimersTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.cpp deleted file mode 100644 index 0e5b83da36dd2..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupTimersHoldTimeTraits::RetType -CmdConfigProtocolBgpPeerGroupTimersHoldTime::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& holdTimeMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - if (holdTimeMsg.data().empty()) { - return "Error: hold-time value is required"; - } - - std::string groupName = peerGroupNameMsg.data()[0]; - std::string holdTimeStr = holdTimeMsg.data()[0]; - int32_t holdTime; - try { - holdTime = folly::to(holdTimeStr); - } catch (const std::exception& e) { - return fmt::format( - "Error: Invalid hold-time '{}': {}", holdTimeStr, e.what()); - } - - session.setPeerGroupHoldTime(groupName, holdTime); - session.saveConfig(); - - return fmt::format( - "Successfully set peer-group {} hold-time to: {} seconds\nConfig saved to: {}", - groupName, - holdTime, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupTimersHoldTime::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersHoldTime, - CmdConfigProtocolBgpPeerGroupTimersHoldTimeTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.h deleted file mode 100644 index 21351570d9400..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersHoldTime.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupTimersHoldTimeTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroupTimers; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupTimersHoldTime - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersHoldTime, - CmdConfigProtocolBgpPeerGroupTimersHoldTimeTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupTimersHoldTimeTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupTimersHoldTimeTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& holdTime); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.cpp deleted file mode 100644 index 7038f9a0e2c50..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupTimersKeepaliveTraits::RetType -CmdConfigProtocolBgpPeerGroupTimersKeepalive::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& keepaliveMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (keepaliveMsg.data().empty()) { - return fmt::format( - "Error: keepalive interval is required for peer-group '{}'", - peerGroupName); - } - - int32_t keepalive; - try { - keepalive = folly::to(keepaliveMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid keepalive value '{}': {}", - keepaliveMsg.data()[0], - e.what()); - } - - session.createPeerGroup(peerGroupName); - session.setPeerGroupKeepalive(peerGroupName, keepalive); - session.saveConfig(); - - return fmt::format( - "Successfully set keepalive interval for peer-group '{}' to: {} seconds\n" - "Config saved to: {}", - peerGroupName, - keepalive, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupTimersKeepalive::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersKeepalive, - CmdConfigProtocolBgpPeerGroupTimersKeepaliveTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.h deleted file mode 100644 index c2088385b391d..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersKeepalive.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupTimersKeepaliveTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroupTimers; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupTimersKeepalive - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersKeepalive, - CmdConfigProtocolBgpPeerGroupTimersKeepaliveTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupTimersKeepaliveTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupTimersKeepaliveTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& keepalive); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.cpp deleted file mode 100644 index 21e4dc4c47eeb..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupTimersOutDelayTraits::RetType -CmdConfigProtocolBgpPeerGroupTimersOutDelay::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& outDelayMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (outDelayMsg.data().empty()) { - return fmt::format( - "Error: out-delay value is required for peer-group '{}'", - peerGroupName); - } - - int32_t outDelay; - try { - outDelay = folly::to(outDelayMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid out-delay value '{}': {}", - outDelayMsg.data()[0], - e.what()); - } - - session.createPeerGroup(peerGroupName); - session.setPeerGroupOutDelay(peerGroupName, outDelay); - session.saveConfig(); - - return fmt::format( - "Successfully set out-delay for peer-group '{}' to: {} seconds\n" - "Config saved to: {}", - peerGroupName, - outDelay, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupTimersOutDelay::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersOutDelay, - CmdConfigProtocolBgpPeerGroupTimersOutDelayTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.h deleted file mode 100644 index 16542ecc3b225..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersOutDelay.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupTimersOutDelayTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroupTimers; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupTimersOutDelay - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersOutDelay, - CmdConfigProtocolBgpPeerGroupTimersOutDelayTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupTimersOutDelayTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupTimersOutDelayTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& outDelay); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.cpp deleted file mode 100644 index 92ccb97ff4a69..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelayTraits::RetType -CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& groupNameArg, - const ObjectArgType& delayMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (groupNameArg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string groupName = groupNameArg.data()[0]; - - if (delayMsg.data().empty()) { - return fmt::format( - "Error: withdraw-unprog-delay value (seconds) is required for peer-group '{}'", - groupName); - } - - int32_t delay; - try { - delay = std::stoi(delayMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid withdraw-unprog-delay value '{}': {}", - delayMsg.data()[0], - e.what()); - } - - session.createPeerGroup(groupName); - session.setPeerGroupWithdrawUnprogDelay(groupName, delay); - session.saveConfig(); - - return fmt::format( - "Successfully set withdraw-unprog-delay for peer-group '{}' to: {} seconds\n" - "Config saved to: {}", - groupName, - delay, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay, - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelayTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.h deleted file mode 100644 index 83355fc1b1e96..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupTimers.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelayTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroupTimers; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelay, - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelayTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelayTraits:: - ObjectArgType; - using RetType = - CmdConfigProtocolBgpPeerGroupTimersWithdrawUnprogDelayTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& withdrawUnprogDelay); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.cpp deleted file mode 100644 index ae03372ca1686..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupV4OverV6NhTraits::RetType -CmdConfigProtocolBgpPeerGroupV4OverV6Nh::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& peerGroupNameMsg, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value required for peer-group '{}'", peerGroupName); - } - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeerGroup(peerGroupName); - session.setPeerGroupIpv4OverIpv6Nh(peerGroupName, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} v4-over-v6-nexthop for peer-group '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerGroupName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupV4OverV6Nh::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupV4OverV6Nh, - CmdConfigProtocolBgpPeerGroupV4OverV6NhTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.h deleted file mode 100644 index 89e5f72751f7b..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupV4OverV6Nh.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupV4OverV6NhTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupV4OverV6Nh - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupV4OverV6Nh, - CmdConfigProtocolBgpPeerGroupV4OverV6NhTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupV4OverV6NhTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupV4OverV6NhTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& peerGroupName, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.cpp deleted file mode 100644 index ec21c05d82865..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupWarningLimitTraits::RetType -CmdConfigProtocolBgpPeerGroupWarningLimit::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& groupNameArg, - const ObjectArgType& warningLimitMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (groupNameArg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string groupName = groupNameArg.data()[0]; - - if (warningLimitMsg.data().empty()) { - return fmt::format( - "Error: warning-limit value is required for peer-group '{}'", - groupName); - } - - int64_t warningLimit; - try { - warningLimit = std::stoll(warningLimitMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid warning-limit value '{}': {}", - warningLimitMsg.data()[0], - e.what()); - } - - session.createPeerGroup(groupName); - session.setPeerGroupPreFilterWarningLimit(groupName, warningLimit); - session.saveConfig(); - - return fmt::format( - "Successfully set pre_filter warning_limit for peer-group '{}' to: {}\n" - "Config saved to: {}", - groupName, - warningLimit, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupWarningLimit::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupWarningLimit, - CmdConfigProtocolBgpPeerGroupWarningLimitTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.h deleted file mode 100644 index c72b72da59750..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningLimit.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupWarningLimitTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupWarningLimit - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupWarningLimit, - CmdConfigProtocolBgpPeerGroupWarningLimitTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupWarningLimitTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupWarningLimitTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& groupName, - const ObjectArgType& warningLimit); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.cpp deleted file mode 100644 index ca39a63b8ecd4..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerGroupWarningOnlyTraits::RetType -CmdConfigProtocolBgpPeerGroupWarningOnly::queryClient( - const HostInfo& /* hostInfo */, - const utils::Message& groupNameArg, - const ObjectArgType& warningOnlyMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (groupNameArg.data().empty()) { - return "Error: peer-group name is required"; - } - std::string groupName = groupNameArg.data()[0]; - - if (warningOnlyMsg.data().empty()) { - return fmt::format( - "Error: warning-only value (true|false) is required for peer-group '{}'", - groupName); - } - - std::string value = warningOnlyMsg.data()[0]; - bool warningOnly; - if (value == "true" || value == "1") { - warningOnly = true; - } else if (value == "false" || value == "0") { - warningOnly = false; - } else { - return fmt::format( - "Error: invalid warning-only value '{}', expected 'true' or 'false'", - value); - } - - session.createPeerGroup(groupName); - session.setPeerGroupPreFilterWarningOnly(groupName, warningOnly); - session.saveConfig(); - - return fmt::format( - "Successfully set pre_filter warning_only for peer-group '{}' to: {}\n" - "Config saved to: {}", - groupName, - warningOnly ? "true" : "false", - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerGroupWarningOnly::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerGroupWarningOnly, - CmdConfigProtocolBgpPeerGroupWarningOnlyTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.h deleted file mode 100644 index 9288c4dd9ae45..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroupWarningOnly.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerGroupWarningOnlyTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerGroup; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerGroupWarningOnly - : public CmdHandler< - CmdConfigProtocolBgpPeerGroupWarningOnly, - CmdConfigProtocolBgpPeerGroupWarningOnlyTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerGroupWarningOnlyTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerGroupWarningOnlyTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::Message& groupName, - const ObjectArgType& warningOnly); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.cpp deleted file mode 100644 index 756a1f4e958af..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerTraits::RetType CmdConfigProtocolBgpPeer::queryClient( - const HostInfo& /* hostInfo */, - const ObjectArgType& peerAddressList) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - - // Extract the first IP from the list as peer address - std::string peerAddress = peerAddressList.data()[0]; - - // Create peer entry in the peers list (translates to BGP++ thrift format) - session.createPeer(peerAddress); - session.saveConfig(); - - return fmt::format( - "BGP peer {} created.\n" - "Use subcommands: remote-asn, local-addr, next-hop4, next-hop6, peer-group\n" - "Config saved to: {}", - peerAddress, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeer::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void -CmdHandler::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h deleted file mode 100644 index eaee525fe41b3..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgp; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("ipAddrs", args, "IPv4 or IPv6 addr(s)"); - } - using ObjectArgType = utils::IPList; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeer : public CmdHandler< - CmdConfigProtocolBgpPeer, - CmdConfigProtocolBgpPeerTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const ObjectArgType& peerAddress); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.cpp deleted file mode 100644 index 2cc45ff10335e..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerAdvertiseLbwTraits::RetType -CmdConfigProtocolBgpPeerAdvertiseLbw::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value required for peer '{}'", peerAddress); - } - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeer(peerAddress); - session.setPeerAdvertiseLbw(peerAddress, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} advertise-link-bandwidth for peer '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerAddress, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerAdvertiseLbw::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerAdvertiseLbw, - CmdConfigProtocolBgpPeerAdvertiseLbwTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.h deleted file mode 100644 index 0b6de963abefc..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerAdvertiseLbw.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerAdvertiseLbwTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerAdvertiseLbw - : public CmdHandler< - CmdConfigProtocolBgpPeerAdvertiseLbw, - CmdConfigProtocolBgpPeerAdvertiseLbwTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerAdvertiseLbwTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerAdvertiseLbwTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.cpp deleted file mode 100644 index 51f1fcab3265b..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerDescriptionTraits::RetType -CmdConfigProtocolBgpPeerDescription::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& descriptionMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (descriptionMsg.data().empty()) { - return fmt::format( - "Error: description value is required for peer '{}'", peerAddress); - } - - session.createPeer(peerAddress); - session.setPeerDescription(peerAddress, descriptionMsg.data()[0]); - session.saveConfig(); - - return fmt::format( - "Successfully set description for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - descriptionMsg.data()[0], - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerDescription::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerDescription, - CmdConfigProtocolBgpPeerDescriptionTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.h deleted file mode 100644 index 7e689baf6f19e..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDescription.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerDescriptionTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerDescription - : public CmdHandler< - CmdConfigProtocolBgpPeerDescription, - CmdConfigProtocolBgpPeerDescriptionTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerDescriptionTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerDescriptionTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& description); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.cpp deleted file mode 100644 index ccbf99291fce6..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerDisableIpv4AfiTraits::RetType -CmdConfigProtocolBgpPeerDisableIpv4Afi::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& disableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (disableMsg.data().empty()) { - return fmt::format( - "Error: disable value required for peer '{}'", peerAddress); - } - std::string disableStr = disableMsg.data()[0]; - bool disable = - (disableStr == "true" || disableStr == "1" || disableStr == "yes"); - - session.createPeer(peerAddress); - session.setPeerDisableIpv4Afi(peerAddress, disable); - session.saveConfig(); - - return fmt::format( - "Successfully {} IPv4 AFI for peer '{}'\n" - "Config saved to: {}", - disable ? "disabled" : "enabled", - peerAddress, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerDisableIpv4Afi::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerDisableIpv4Afi, - CmdConfigProtocolBgpPeerDisableIpv4AfiTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.h deleted file mode 100644 index 285bc4fb56be1..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerDisableIpv4Afi.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerDisableIpv4AfiTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerDisableIpv4Afi - : public CmdHandler< - CmdConfigProtocolBgpPeerDisableIpv4Afi, - CmdConfigProtocolBgpPeerDisableIpv4AfiTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerDisableIpv4AfiTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerDisableIpv4AfiTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& disable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.cpp deleted file mode 100644 index 816f2e529b030..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerEgressPolicyTraits::RetType -CmdConfigProtocolBgpPeerEgressPolicy::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& policyMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (policyMsg.data().empty()) { - return fmt::format( - "Error: egress policy name is required for peer '{}'", peerAddress); - } - std::string policyName = policyMsg.data()[0]; - - session.createPeer(peerAddress); - session.setPeerEgressPolicy(peerAddress, policyName); - session.saveConfig(); - - return fmt::format( - "Successfully set egress policy for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - policyName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerEgressPolicy::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerEgressPolicy, - CmdConfigProtocolBgpPeerEgressPolicyTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.h deleted file mode 100644 index abf65d3c8a601..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerEgressPolicy.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerEgressPolicyTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerEgressPolicy - : public CmdHandler< - CmdConfigProtocolBgpPeerEgressPolicy, - CmdConfigProtocolBgpPeerEgressPolicyTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerEgressPolicyTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerEgressPolicyTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& policyName); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.cpp deleted file mode 100644 index 521aeb95a135b..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerHoldTimeTraits::RetType -CmdConfigProtocolBgpPeerHoldTime::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& holdTimeMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (holdTimeMsg.data().empty()) { - return fmt::format( - "Error: hold-time value is required for peer '{}'", peerAddress); - } - - int32_t holdTime; - try { - holdTime = folly::to(holdTimeMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid hold-time value '{}': {}", - holdTimeMsg.data()[0], - e.what()); - } - - session.createPeer(peerAddress); - session.setPeerHoldTime(peerAddress, holdTime); - session.saveConfig(); - - return fmt::format( - "Successfully set hold-time for peer '{}' to: {} seconds\n" - "Config saved to: {}", - peerAddress, - holdTime, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerHoldTime::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerHoldTime, - CmdConfigProtocolBgpPeerHoldTimeTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.h deleted file mode 100644 index 22c2149ab7ebc..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerHoldTime.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerHoldTimeTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerHoldTime - : public CmdHandler< - CmdConfigProtocolBgpPeerHoldTime, - CmdConfigProtocolBgpPeerHoldTimeTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerHoldTimeTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerHoldTimeTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& holdTime); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.cpp deleted file mode 100644 index 0f26e9045b9e2..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerIngressPolicyTraits::RetType -CmdConfigProtocolBgpPeerIngressPolicy::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& policyMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (policyMsg.data().empty()) { - return fmt::format( - "Error: ingress policy name is required for peer '{}'", peerAddress); - } - std::string policyName = policyMsg.data()[0]; - - session.createPeer(peerAddress); - session.setPeerIngressPolicy(peerAddress, policyName); - session.saveConfig(); - - return fmt::format( - "Successfully set ingress policy for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - policyName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerIngressPolicy::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerIngressPolicy, - CmdConfigProtocolBgpPeerIngressPolicyTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.h deleted file mode 100644 index 57647d9acd537..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerIngressPolicy.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerIngressPolicyTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerIngressPolicy - : public CmdHandler< - CmdConfigProtocolBgpPeerIngressPolicy, - CmdConfigProtocolBgpPeerIngressPolicyTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerIngressPolicyTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerIngressPolicyTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& policyName); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.cpp deleted file mode 100644 index 7f42aecef6632..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -namespace { -// Parse bandwidth string like "10G", "100M", "1000" to bps -int64_t parseBandwidth(const std::string& bwStr) { - std::string upper = bwStr; - for (auto& c : upper) { - c = std::toupper(c); - } - - int64_t multiplier = 1; - std::string numPart = upper; - - if (upper.back() == 'G') { - multiplier = 1000000000LL; - numPart = upper.substr(0, upper.length() - 1); - } else if (upper.back() == 'M') { - multiplier = 1000000LL; - numPart = upper.substr(0, upper.length() - 1); - } else if (upper.back() == 'K') { - multiplier = 1000LL; - numPart = upper.substr(0, upper.length() - 1); - } - - return folly::to(numPart) * multiplier; -} -} // namespace - -CmdConfigProtocolBgpPeerLinkBandwidthTraits::RetType -CmdConfigProtocolBgpPeerLinkBandwidth::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& bandwidthMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (bandwidthMsg.data().empty()) { - return fmt::format( - "Error: link-bandwidth value is required for peer '{}'", peerAddress); - } - - int64_t bps; - try { - bps = parseBandwidth(bandwidthMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid link-bandwidth value '{}': {}", - bandwidthMsg.data()[0], - e.what()); - } - - session.createPeer(peerAddress); - session.setPeerLbwValue(peerAddress, bps); - session.saveConfig(); - - return fmt::format( - "Successfully set link-bandwidth for peer '{}' to: {} bps\n" - "Config saved to: {}", - peerAddress, - bps, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerLinkBandwidth::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerLinkBandwidth, - CmdConfigProtocolBgpPeerLinkBandwidthTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.h deleted file mode 100644 index 21efd752db201..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLinkBandwidth.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerLinkBandwidthTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerLinkBandwidth - : public CmdHandler< - CmdConfigProtocolBgpPeerLinkBandwidth, - CmdConfigProtocolBgpPeerLinkBandwidthTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerLinkBandwidthTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerLinkBandwidthTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& bandwidth); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.cpp deleted file mode 100644 index 16b702514cb68..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerLocalAddrTraits::RetType -CmdConfigProtocolBgpPeerLocalAddr::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& localAddrMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (localAddrMsg.data().empty()) { - return fmt::format( - "Error: local-addr value required for peer '{}'", peerAddress); - } - std::string localAddr = localAddrMsg.data()[0]; - - session.createPeer(peerAddress); - session.setPeerLocalAddr(peerAddress, localAddr); - session.saveConfig(); - - return fmt::format( - "Successfully set local-addr for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - localAddr, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerLocalAddr::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerLocalAddr, - CmdConfigProtocolBgpPeerLocalAddrTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.h deleted file mode 100644 index e982e20e79d0b..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerLocalAddr.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerLocalAddrTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerLocalAddr - : public CmdHandler< - CmdConfigProtocolBgpPeerLocalAddr, - CmdConfigProtocolBgpPeerLocalAddrTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerLocalAddrTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerLocalAddrTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& localAddr); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.cpp deleted file mode 100644 index 8f7171c157076..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerMaxRoutesTraits::RetType -CmdConfigProtocolBgpPeerMaxRoutes::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& maxRoutesMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (maxRoutesMsg.data().empty()) { - return fmt::format( - "Error: max-routes value is required for peer '{}'", peerAddress); - } - - int64_t maxRoutes; - try { - maxRoutes = folly::to(maxRoutesMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid max-routes value '{}': {}", - maxRoutesMsg.data()[0], - e.what()); - } - - session.createPeer(peerAddress); - session.setPeerPreFilterMaxRoutes(peerAddress, maxRoutes); - session.saveConfig(); - - return fmt::format( - "Successfully set max-routes for peer '{}' to: {}\n" - "Config saved to: {}", - peerAddress, - maxRoutes, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerMaxRoutes::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerMaxRoutes, - CmdConfigProtocolBgpPeerMaxRoutesTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.h deleted file mode 100644 index 4cec005468d9b..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerMaxRoutes.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerMaxRoutesTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerMaxRoutes - : public CmdHandler< - CmdConfigProtocolBgpPeerMaxRoutes, - CmdConfigProtocolBgpPeerMaxRoutesTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerMaxRoutesTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerMaxRoutesTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& maxRoutes); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.cpp deleted file mode 100644 index 2e0846c898931..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerNextHop4Traits::RetType -CmdConfigProtocolBgpPeerNextHop4::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& nextHop4Msg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (nextHop4Msg.data().empty()) { - return fmt::format( - "Error: next-hop4 value required for peer '{}'", peerAddress); - } - std::string nextHop4 = nextHop4Msg.data()[0]; - - session.createPeer(peerAddress); - session.setPeerNextHop4(peerAddress, nextHop4); - session.saveConfig(); - - return fmt::format( - "Successfully set next-hop4 for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - nextHop4, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerNextHop4::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerNextHop4, - CmdConfigProtocolBgpPeerNextHop4Traits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.h deleted file mode 100644 index 9f84c4b6813ab..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop4.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerNextHop4Traits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerNextHop4 - : public CmdHandler< - CmdConfigProtocolBgpPeerNextHop4, - CmdConfigProtocolBgpPeerNextHop4Traits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerNextHop4Traits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerNextHop4Traits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& nextHop4); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.cpp deleted file mode 100644 index da3f6b4a57d9c..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerNextHop6Traits::RetType -CmdConfigProtocolBgpPeerNextHop6::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& nextHop6Msg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (nextHop6Msg.data().empty()) { - return fmt::format( - "Error: next-hop6 value required for peer '{}'", peerAddress); - } - std::string nextHop6 = nextHop6Msg.data()[0]; - - session.createPeer(peerAddress); - session.setPeerNextHop6(peerAddress, nextHop6); - session.saveConfig(); - - return fmt::format( - "Successfully set next-hop6 for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - nextHop6, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerNextHop6::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerNextHop6, - CmdConfigProtocolBgpPeerNextHop6Traits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.h deleted file mode 100644 index 6c7ba583d3743..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHop6.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerNextHop6Traits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerNextHop6 - : public CmdHandler< - CmdConfigProtocolBgpPeerNextHop6, - CmdConfigProtocolBgpPeerNextHop6Traits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerNextHop6Traits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerNextHop6Traits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& nextHop6); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.cpp deleted file mode 100644 index 5e4f9d722770d..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerNextHopSelfTraits::RetType -CmdConfigProtocolBgpPeerNextHopSelf::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value (true/false) is required for peer '{}'", - peerAddress); - } - - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeer(peerAddress); - session.setPeerNextHopSelf(peerAddress, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} next-hop-self for peer '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerAddress, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerNextHopSelf::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerNextHopSelf, - CmdConfigProtocolBgpPeerNextHopSelfTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.h deleted file mode 100644 index 3fd450a707441..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerNextHopSelf.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerNextHopSelfTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerNextHopSelf - : public CmdHandler< - CmdConfigProtocolBgpPeerNextHopSelf, - CmdConfigProtocolBgpPeerNextHopSelfTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerNextHopSelfTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerNextHopSelfTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.cpp deleted file mode 100644 index afdf292bac5d7..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerPassiveTraits::RetType -CmdConfigProtocolBgpPeerPassive::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value required for peer '{}'", peerAddress); - } - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeer(peerAddress); - session.setPeerPassive(peerAddress, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} passive mode for peer '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerAddress, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerPassive::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerPassive, - CmdConfigProtocolBgpPeerPassiveTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.h deleted file mode 100644 index fc03203a62d81..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPassive.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerPassiveTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerPassive - : public CmdHandler< - CmdConfigProtocolBgpPeerPassive, - CmdConfigProtocolBgpPeerPassiveTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerPassiveTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerPassiveTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.cpp deleted file mode 100644 index 6d39e53a2ad90..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerPeerGroupTraits::RetType -CmdConfigProtocolBgpPeerPeerGroup::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& peerGroupNameMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - if (peerGroupNameMsg.data().empty()) { - return "Error: peer-group name is required"; - } - - std::string peerAddress = peerAddressList.data()[0]; - std::string peerGroupName = peerGroupNameMsg.data()[0]; - - session.setPeerGroupName(peerAddress, peerGroupName); - session.saveConfig(); - - return fmt::format( - "Successfully set peer {} peer-group to: {}\nConfig saved to: {}", - peerAddress, - peerGroupName, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerPeerGroup::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerPeerGroup, - CmdConfigProtocolBgpPeerPeerGroupTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.h deleted file mode 100644 index 346af38f9ca42..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerGroup.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerPeerGroupTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerPeerGroup - : public CmdHandler< - CmdConfigProtocolBgpPeerPeerGroup, - CmdConfigProtocolBgpPeerPeerGroupTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerPeerGroupTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerPeerGroupTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& peerGroupName); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.cpp deleted file mode 100644 index a4ac20d0e5ecd..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerPeerIdTraits::RetType -CmdConfigProtocolBgpPeerPeerId::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& peerIdMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (peerIdMsg.data().empty()) { - return fmt::format( - "Error: peer-id value is required for peer '{}'", peerAddress); - } - - session.createPeer(peerAddress); - session.setPeerPeerId(peerAddress, peerIdMsg.data()[0]); - session.saveConfig(); - - return fmt::format( - "Successfully set peer-id for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - peerIdMsg.data()[0], - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerPeerId::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerPeerId, - CmdConfigProtocolBgpPeerPeerIdTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.h deleted file mode 100644 index 2091ffe5a495a..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerPeerId.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerPeerIdTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerPeerId - : public CmdHandler< - CmdConfigProtocolBgpPeerPeerId, - CmdConfigProtocolBgpPeerPeerIdTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerPeerIdTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerPeerIdTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& peerId); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.cpp deleted file mode 100644 index dbfb714a3b9e3..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerRemoteAsnTraits::RetType -CmdConfigProtocolBgpPeerRemoteAsn::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& remoteAsnMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - - if (remoteAsnMsg.data().empty()) { - return "Error: remote-asn value is required"; - } - - // Extract the first IP from the list as peer address - std::string peerAddress = peerAddressList.data()[0]; - - // Extract string and parse to uint64_t (4-byte ASN per RFC 6793) - std::string asnStr = remoteAsnMsg.data()[0]; - uint64_t remoteAsn; - try { - remoteAsn = folly::to(asnStr); - } catch (const std::exception& e) { - return fmt::format( - "Error: Invalid remote-asn value '{}': {}", asnStr, e.what()); - } - - // Set the remote-asn for the peer (internally maps to remote_as_4_byte) - session.setPeerRemoteAsn(peerAddress, remoteAsn); - session.saveConfig(); - - return fmt::format( - "Successfully set BGP peer {} remote-asn to: {}\nConfig saved to: {}", - peerAddress, - remoteAsn, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerRemoteAsn::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerRemoteAsn, - CmdConfigProtocolBgpPeerRemoteAsnTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.h deleted file mode 100644 index 76059df98381a..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRemoteAsn.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerRemoteAsnTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerRemoteAsn - : public CmdHandler< - CmdConfigProtocolBgpPeerRemoteAsn, - CmdConfigProtocolBgpPeerRemoteAsnTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerRemoteAsnTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerRemoteAsnTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& remoteAsn); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.cpp deleted file mode 100644 index 025e3a4d4c797..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerRrClientTraits::RetType -CmdConfigProtocolBgpPeerRrClient::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value (true/false) is required for peer '{}'", - peerAddress); - } - - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeer(peerAddress); - session.setPeerRrClient(peerAddress, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} route-reflector-client for peer '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerAddress, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerRrClient::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerRrClient, - CmdConfigProtocolBgpPeerRrClientTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.h deleted file mode 100644 index 2eaba6e61b7c0..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerRrClient.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerRrClientTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerRrClient - : public CmdHandler< - CmdConfigProtocolBgpPeerRrClient, - CmdConfigProtocolBgpPeerRrClientTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerRrClientTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerRrClientTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.cpp deleted file mode 100644 index c4a43abf408b8..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerTimersTraits::RetType -CmdConfigProtocolBgpPeerTimers::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList) { - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - return fmt::format( - "BGP peer timers configuration for peer '{}'.\n" - "Available subcommands:\n" - " hold-time - Set hold time\n" - " keepalive - Set keepalive interval\n" - " out-delay - Set output delay\n" - " withdraw-unprog-delay - Set withdraw unprog delay", - peerAddress); -} - -void CmdConfigProtocolBgpPeerTimers::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerTimers, - CmdConfigProtocolBgpPeerTimersTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h deleted file mode 100644 index a3fe71082ab57..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerTimersTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - using ObjectArgType = std::monostate; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerTimers - : public CmdHandler< - CmdConfigProtocolBgpPeerTimers, - CmdConfigProtocolBgpPeerTimersTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerTimersTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerTimersTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.cpp deleted file mode 100644 index c6a582ebab8c9..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerTimersKeepaliveTraits::RetType -CmdConfigProtocolBgpPeerTimersKeepalive::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& keepaliveMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (keepaliveMsg.data().empty()) { - return fmt::format( - "Error: keepalive value (seconds) is required for peer '{}'", - peerAddress); - } - - int32_t keepalive; - try { - keepalive = std::stoi(keepaliveMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid keepalive value '{}': {}", - keepaliveMsg.data()[0], - e.what()); - } - - session.createPeer(peerAddress); - session.setPeerKeepalive(peerAddress, keepalive); - session.saveConfig(); - - return fmt::format( - "Successfully set keepalive for peer '{}' to: {} seconds\n" - "Config saved to: {}", - peerAddress, - keepalive, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerTimersKeepalive::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerTimersKeepalive, - CmdConfigProtocolBgpPeerTimersKeepaliveTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.h deleted file mode 100644 index 8449f76d2d7fb..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersKeepalive.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerTimersKeepaliveTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerTimers; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerTimersKeepalive - : public CmdHandler< - CmdConfigProtocolBgpPeerTimersKeepalive, - CmdConfigProtocolBgpPeerTimersKeepaliveTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerTimersKeepaliveTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerTimersKeepaliveTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& keepalive); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.cpp deleted file mode 100644 index 46cb4cbe18168..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerTimersOutDelayTraits::RetType -CmdConfigProtocolBgpPeerTimersOutDelay::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& outDelayMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (outDelayMsg.data().empty()) { - return fmt::format( - "Error: out-delay value (seconds) is required for peer '{}'", - peerAddress); - } - - int32_t outDelay; - try { - outDelay = std::stoi(outDelayMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid out-delay value '{}': {}", - outDelayMsg.data()[0], - e.what()); - } - - session.createPeer(peerAddress); - session.setPeerOutDelay(peerAddress, outDelay); - session.saveConfig(); - - return fmt::format( - "Successfully set out-delay for peer '{}' to: {} seconds\n" - "Config saved to: {}", - peerAddress, - outDelay, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerTimersOutDelay::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerTimersOutDelay, - CmdConfigProtocolBgpPeerTimersOutDelayTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.h deleted file mode 100644 index a4817268b16a1..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersOutDelay.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerTimersOutDelayTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerTimers; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerTimersOutDelay - : public CmdHandler< - CmdConfigProtocolBgpPeerTimersOutDelay, - CmdConfigProtocolBgpPeerTimersOutDelayTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerTimersOutDelayTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerTimersOutDelayTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& outDelay); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.cpp deleted file mode 100644 index ff95a1dce0d78..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelayTraits::RetType -CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& delayMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (delayMsg.data().empty()) { - return fmt::format( - "Error: withdraw-unprog-delay value (seconds) is required for peer '{}'", - peerAddress); - } - - int32_t delay; - try { - delay = std::stoi(delayMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid withdraw-unprog-delay value '{}': {}", - delayMsg.data()[0], - e.what()); - } - - session.createPeer(peerAddress); - session.setPeerWithdrawUnprogDelay(peerAddress, delay); - session.saveConfig(); - - return fmt::format( - "Successfully set withdraw-unprog-delay for peer '{}' to: {} seconds\n" - "Config saved to: {}", - peerAddress, - delay, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay::printOutput( - const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay, - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelayTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.h deleted file mode 100644 index 47b28cd791ce7..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerTimers.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelayTraits - : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeerTimers; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay - : public CmdHandler< - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelay, - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelayTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelayTraits::ObjectArgType; - using RetType = - CmdConfigProtocolBgpPeerTimersWithdrawUnprogDelayTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& withdrawUnprogDelay); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.cpp deleted file mode 100644 index af460feecbf90..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerTypeTraits::RetType -CmdConfigProtocolBgpPeerType::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& peerTypeMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (peerTypeMsg.data().empty()) { - return fmt::format( - "Error: type value is required for peer '{}'", peerAddress); - } - - session.createPeer(peerAddress); - session.setPeerType(peerAddress, peerTypeMsg.data()[0]); - session.saveConfig(); - - return fmt::format( - "Successfully set type for peer '{}' to: '{}'\n" - "Config saved to: {}", - peerAddress, - peerTypeMsg.data()[0], - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerType::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerType, - CmdConfigProtocolBgpPeerTypeTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.h deleted file mode 100644 index 8b5a593677bf1..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerType.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerTypeTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerType : public CmdHandler< - CmdConfigProtocolBgpPeerType, - CmdConfigProtocolBgpPeerTypeTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerTypeTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerTypeTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& peerType); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.cpp deleted file mode 100644 index 94c3500946321..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerV4OverV6NhTraits::RetType -CmdConfigProtocolBgpPeerV4OverV6Nh::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& enableMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (enableMsg.data().empty()) { - return fmt::format( - "Error: enable value required for peer '{}'", peerAddress); - } - std::string enableStr = enableMsg.data()[0]; - bool enable = (enableStr == "true" || enableStr == "1" || enableStr == "yes"); - - session.createPeer(peerAddress); - session.setPeerIpv4OverIpv6Nh(peerAddress, enable); - session.saveConfig(); - - return fmt::format( - "Successfully {} v4-over-v6-nexthop for peer '{}'\n" - "Config saved to: {}", - enable ? "enabled" : "disabled", - peerAddress, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerV4OverV6Nh::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerV4OverV6Nh, - CmdConfigProtocolBgpPeerV4OverV6NhTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.h deleted file mode 100644 index af68ab9d07c62..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerV4OverV6Nh.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerV4OverV6NhTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerV4OverV6Nh - : public CmdHandler< - CmdConfigProtocolBgpPeerV4OverV6Nh, - CmdConfigProtocolBgpPeerV4OverV6NhTraits> { - public: - using ObjectArgType = CmdConfigProtocolBgpPeerV4OverV6NhTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerV4OverV6NhTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& enable); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.cpp deleted file mode 100644 index 6b361f89e0e25..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerWarningLimitTraits::RetType -CmdConfigProtocolBgpPeerWarningLimit::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& warningLimitMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (warningLimitMsg.data().empty()) { - return fmt::format( - "Error: warning-limit value is required for peer '{}'", peerAddress); - } - - int64_t warningLimit; - try { - warningLimit = std::stoll(warningLimitMsg.data()[0]); - } catch (const std::exception& e) { - return fmt::format( - "Error: invalid warning-limit value '{}': {}", - warningLimitMsg.data()[0], - e.what()); - } - - session.createPeer(peerAddress); - session.setPeerPreFilterWarningLimit(peerAddress, warningLimit); - session.saveConfig(); - - return fmt::format( - "Successfully set pre_filter warning_limit for peer '{}' to: {}\n" - "Config saved to: {}", - peerAddress, - warningLimit, - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerWarningLimit::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerWarningLimit, - CmdConfigProtocolBgpPeerWarningLimitTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.h deleted file mode 100644 index 97dbae5059eab..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningLimit.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerWarningLimitTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerWarningLimit - : public CmdHandler< - CmdConfigProtocolBgpPeerWarningLimit, - CmdConfigProtocolBgpPeerWarningLimitTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerWarningLimitTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerWarningLimitTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& warningLimit); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.cpp deleted file mode 100644 index 2871824d4475c..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.h" - -#include "fboss/cli/fboss2/CmdHandler.cpp" - -#include -#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" - -namespace facebook::fboss { - -CmdConfigProtocolBgpPeerWarningOnlyTraits::RetType -CmdConfigProtocolBgpPeerWarningOnly::queryClient( - const HostInfo& /* hostInfo */, - const utils::IPList& peerAddressList, - const ObjectArgType& warningOnlyMsg) { - auto& session = BgpConfigSession::getInstance(); - - if (peerAddressList.data().empty()) { - return "Error: peer address is required"; - } - std::string peerAddress = peerAddressList.data()[0]; - - if (warningOnlyMsg.data().empty()) { - return fmt::format( - "Error: warning-only value (true|false) is required for peer '{}'", - peerAddress); - } - - std::string value = warningOnlyMsg.data()[0]; - bool warningOnly; - if (value == "true" || value == "1") { - warningOnly = true; - } else if (value == "false" || value == "0") { - warningOnly = false; - } else { - return fmt::format( - "Error: invalid warning-only value '{}', expected 'true' or 'false'", - value); - } - - session.createPeer(peerAddress); - session.setPeerPreFilterWarningOnly(peerAddress, warningOnly); - session.saveConfig(); - - return fmt::format( - "Successfully set pre_filter warning_only for peer '{}' to: {}\n" - "Config saved to: {}", - peerAddress, - warningOnly ? "true" : "false", - session.getSessionConfigPath()); -} - -void CmdConfigProtocolBgpPeerWarningOnly::printOutput(const RetType& output) { - std::cout << output << std::endl; -} - -// Explicit template instantiation -template void CmdHandler< - CmdConfigProtocolBgpPeerWarningOnly, - CmdConfigProtocolBgpPeerWarningOnlyTraits>::run(); - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.h b/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.h deleted file mode 100644 index 22f27198597dd..0000000000000 --- a/fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeerWarningOnly.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "fboss/cli/fboss2/CmdHandler.h" -#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer/CmdConfigProtocolBgpPeer.h" -#include "fboss/cli/fboss2/utils/CmdUtils.h" - -namespace facebook::fboss { - -struct CmdConfigProtocolBgpPeerWarningOnlyTraits : public WriteCommandTraits { - using ParentCmd = CmdConfigProtocolBgpPeer; - static void addCliArg(CLI::App& cmd, std::vector& args) { - cmd.add_option("msg", args, "Message"); - } - using ObjectArgType = utils::Message; - using RetType = std::string; -}; - -class CmdConfigProtocolBgpPeerWarningOnly - : public CmdHandler< - CmdConfigProtocolBgpPeerWarningOnly, - CmdConfigProtocolBgpPeerWarningOnlyTraits> { - public: - using ObjectArgType = - CmdConfigProtocolBgpPeerWarningOnlyTraits::ObjectArgType; - using RetType = CmdConfigProtocolBgpPeerWarningOnlyTraits::RetType; - - RetType queryClient( - const HostInfo& hostInfo, - const utils::IPList& peerAddress, - const ObjectArgType& warningOnly); - - void printOutput(const RetType& output); -}; - -} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.cpp new file mode 100644 index 0000000000000..ace33a2b0c2ce --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.cpp @@ -0,0 +1,30 @@ +/* + * 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/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +namespace facebook::fboss { + +CmdConfigProtocolBgpPolicyTraits::RetType +CmdConfigProtocolBgpPolicy::queryClient(const HostInfo& /* hostInfo */) { + return "BGP policy configuration. Use subcommands: as-path-list"; +} + +void CmdConfigProtocolBgpPolicy::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void +CmdHandler::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h new file mode 100644 index 0000000000000..6d0115fb5aa0f --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h @@ -0,0 +1,43 @@ +/* + * 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/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/CmdConfigProtocolBgp.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Grouping node for `config protocol bgp policy ...`. Holds no +// state of its own; the per-object-type dispatchers (as-path-list, +// community-list, and later prefix-list/routing-policy) are its subcommands. +// Mirrors CmdConfigProtocolBgp. +struct CmdConfigProtocolBgpPolicyTraits : public WriteCommandTraits { + using ParentCmd = CmdConfigProtocolBgp; + using ObjectArgType = std::monostate; + using RetType = std::string; +}; + +class CmdConfigProtocolBgpPolicy : public CmdHandler< + CmdConfigProtocolBgpPolicy, + CmdConfigProtocolBgpPolicyTraits> { + public: + using ObjectArgType = CmdConfigProtocolBgpPolicyTraits::ObjectArgType; + using RetType = CmdConfigProtocolBgpPolicyTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h new file mode 100644 index 0000000000000..cb0f40eb5a0b2 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h @@ -0,0 +1,152 @@ +/* + * 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 +#include +#include +#include +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" + +/** + * Lookup/create helpers for the as-path-list CLI family, shared between the + * list-level dispatcher (CmdConfigProtocolBgpPolicyAsPathList), the entry + * subcommand (CmdConfigProtocolBgpPolicyAsPathListEntry), and the delete + * counterparts. An AsPathList is keyed by name; an AsPathListEntry (in + * as_path_list[]) is keyed by sequence_number. + */ +namespace facebook::fboss::bgpcli { + +inline bool asPathListExists( + const bgp::thrift::BgpConfig& cfg, + const std::string& name) { + if (!cfg.policies().has_value()) { + return false; + } + const auto& lists = *cfg.policies()->aspath_lists(); + return std::any_of(lists.begin(), lists.end(), [&](const auto& list) { + return *list.name() == name; + }); +} + +// Find the as-path-list keyed by name, creating it if absent. Setting an +// attribute on a not-yet-created list implicitly creates it, so command +// ordering stays forgiving; a bare `as-path-list ` creates one +// explicitly. AsPathList's only key field is `name`. +inline bgp::bgp_policy::AsPathList& findOrCreateAsPathList( + bgp::thrift::BgpConfig& cfg, + const std::string& name) { + auto& lists = *cfg.policies().ensure().aspath_lists(); + for (auto& list : lists) { + if (*list.name() == name) { + return list; + } + } + lists.emplace_back(); + auto& list = lists.back(); + list.name() = name; + return list; +} + +inline bool asPathListEntryExists( + const bgp::bgp_policy::AsPathList& list, + int64_t seqNum) { + const auto& entries = *list.as_path_list(); + return std::any_of(entries.begin(), entries.end(), [&](const auto& entry) { + return entry.sequence_number().has_value() && + *entry.sequence_number() == seqNum; + }); +} + +// Find the entry keyed by sequence_number within a list, creating it if +// absent. sequence_number is the entry's identity. +inline bgp::bgp_policy::AsPathListEntry& findOrCreateAsPathListEntry( + bgp::bgp_policy::AsPathList& list, + int64_t seqNum) { + auto& entries = *list.as_path_list(); + for (auto& entry : entries) { + if (entry.sequence_number().has_value() && + *entry.sequence_number() == seqNum) { + return entry; + } + } + entries.emplace_back(); + auto& entry = entries.back(); + entry.sequence_number() = seqNum; + return entry; +} + +inline const bgp::bgp_policy::AsPathList* findAsPathList( + const bgp::thrift::BgpConfig& cfg, + const std::string& name) { + if (!cfg.policies().has_value()) { + return nullptr; + } + const auto& lists = *cfg.policies()->aspath_lists(); + auto it = std::find_if(lists.begin(), lists.end(), [&](const auto& list) { + return *list.name() == name; + }); + return it == lists.end() ? nullptr : &*it; +} + +// A routing-policy term whose AS_PATH match names an as-path-list. `label` +// identifies the term the way the CLI addresses it (`policy

term `; +// the term name when it has no sequence number) so a rejection can be acted +// on directly. +struct AsPathListReference { + bgp::bgp_policy::BgpPolicyAtomicMatch* match; + std::string label; +}; + +// Every AS_PATH match whose as_path_filters.as_path_list_names carries +// `listName`. bgpd resolves those names at config load and fails the load on +// a dangling one, so the delete command refuses while this is non-empty, and +// the boolean-operator setter writes through to each match (bgpd requires the +// referencing copy to carry the same operator as the list). +inline std::vector findTermsReferencingAsPathList( + bgp::thrift::BgpConfig& cfg, + const std::string& listName) { + std::vector refs; + if (!cfg.policies().has_value()) { + return refs; + } + for (auto& policy : *cfg.policies()->bgp_policy_statements()) { + for (auto& term : *policy.policy_entries()) { + if (!term.policy_match_entries().has_value()) { + continue; + } + for (auto& match : *term.policy_match_entries()->match_entries()) { + if (!match.as_path_filters().has_value() || + !match.as_path_filters()->as_path_list_names().has_value()) { + continue; + } + const auto& names = *match.as_path_filters()->as_path_list_names(); + if (std::find(names.begin(), names.end(), listName) == names.end()) { + continue; + } + refs.push_back( + {&match, + term.sequence_number().has_value() + ? fmt::format( + "policy {} term {}", + *policy.name(), + *term.sequence_number()) + : fmt::format( + "policy {} term {}", *policy.name(), *term.name())}); + } + } + } + return refs; +} + +} // namespace facebook::fboss::bgpcli diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.cpp new file mode 100644 index 0000000000000..4392e824d916d --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.cpp @@ -0,0 +1,243 @@ +/* + * 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/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/routing_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +namespace { + +// The attribute names, exactly as documented. Kept here so the +// valid-attribute set and the handler table stay in sync. The list's entries +// are their own subcommand, not attributes. +constexpr std::string_view kBooleanOperator = "boolean-operator"; +constexpr std::string_view kDescription = "description"; +constexpr std::string_view kRegex = "regex"; + +constexpr std::string_view kBooleanOperatorAnd = "AND"; +constexpr std::string_view kBooleanOperatorOr = "OR"; + +using AsPathList = bgp::bgp_policy::AsPathList; +using BooleanOperator = bgp::routing_policy::BooleanOperator; +using bgpcli::AttrHandler; +using bgpcli::enumAttr; +using bgpcli::err; +using bgpcli::joinedStringAttr; +using bgpcli::ok; +using bgpcli::Result; +using bgpcli::Tokens; + +// NOT is deliberately not offered: bgpd treats every operator other than OR +// as "all must match", so NOT would silently behave as AND. +std::optional lookupBooleanOperator(const std::string& s) { + if (s == kBooleanOperatorAnd) { + return BooleanOperator::AND; + } + if (s == kBooleanOperatorOr) { + return BooleanOperator::OR; + } + return std::nullopt; +} + +// ---- setters ---------------------------------------------------------------- +// Each writes one already-parsed, already-validated value. Parsing and message +// text belong to the shared factories in BgpCliAttrHandlers.h. + +void setListBooleanOperator(AsPathList& list, BooleanOperator op) { + list.boolean_operator() = op; +} + +void setListDescription(AsPathList& list, const std::string& description) { + list.description() = description; +} + +// Hand-written because no factory covers an accumulating, validated set: +// each `regex` appends one boost::regex to as_paths (the field bgpd matches +// against the `_`-joined AS path), compiled here so a pattern bgpd would +// reject at load never reaches the session. Idempotent on repeats. +Result regex(AsPathList& list, const Tokens& values) { + if (values.size() != 1 || values[0].empty()) { + return err( + fmt::format( + "Error: {} requires (one token; join AS numbers with `_`, " + "e.g. ^65000_65001$)", + kRegex)); + } + const auto& pattern = values[0]; + if (std::any_of(pattern.begin(), pattern.end(), [](unsigned char c) { + return std::isspace(c) != 0; + })) { + return err( + fmt::format( + "Error: {} '{}' must not contain whitespace; bgpd renders the AS " + "path with `_` between AS numbers", + kRegex, + pattern)); + } + try { + boost::regex compiled(pattern); + } catch (const boost::regex_error& e) { + return err( + fmt::format("Error: Malformed {} '{}': {}", kRegex, pattern, e.what())); + } + auto& paths = list.as_paths().ensure(); + if (std::find(paths.begin(), paths.end(), pattern) != paths.end()) { + return ok(fmt::format("{} {} already present", kRegex, pattern)); + } + paths.push_back(pattern); + return ok(fmt::format("Successfully added {} {}", kRegex, pattern)); +} + +// ---- list-level attribute registry ------------------------------------------ +// One line per documented attribute: its dispatch key, its value shape, and +// the setter that stores it. +const std::map, std::less<>>& +listAttrHandlers() { + static const std::string kBooleanOperatorValues = + fmt::format("{}|{}", kBooleanOperatorAnd, kBooleanOperatorOr); + static const std::map, std::less<>> + kHandlers = { + {std::string(kBooleanOperator), + enumAttr( + kBooleanOperator, + kBooleanOperatorValues, + lookupBooleanOperator, + setListBooleanOperator)}, + {std::string(kDescription), + joinedStringAttr(kDescription, setListDescription)}, + {std::string(kRegex), regex}, + }; + return kHandlers; +} + +std::string validAttrList() { + std::string out; + for (const auto& [name, _] : listAttrHandlers()) { + if (!out.empty()) { + out += ", "; + } + out += name; + } + return out; +} + +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpPeerGroupConfig). +BgpAsPathListConfig::BgpAsPathListConfig(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty()) { + throw std::invalid_argument( + "Error: as-path-list is required, optionally followed by an " + " "); + } + if (v[0].empty()) { + throw std::invalid_argument("Error: as-path-list name must not be empty"); + } + listName_ = v[0]; + + if (v.size() == 1) { + return; // bare `as-path-list `: create the list + } + + attr_ = v[1]; + values_.assign(v.begin() + 2, v.end()); + + if (listAttrHandlers().find(attr_) == listAttrHandlers().end()) { + throw std::invalid_argument( + fmt::format( + "Error: unknown as-path-list attribute '{}'. Valid attributes: {}", + attr_, + validAttrList())); + } +} + +CmdConfigProtocolBgpPolicyAsPathListTraits::RetType +CmdConfigProtocolBgpPolicyAsPathList::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + const bool listCreated = !bgpcli::asPathListExists(cfg, args.listName()); + auto& list = bgpcli::findOrCreateAsPathList(cfg, args.listName()); + + Result result = args.attr().empty() + ? ok(listCreated + ? fmt::format( + "Successfully created BGP as-path-list {}", + args.listName()) + : fmt::format( + "BGP as-path-list {} already exists", args.listName())) + // The attribute is guaranteed valid: BgpAsPathListConfig's constructor + // rejects an unknown attribute before we get here. + : listAttrHandlers().find(args.attr())->second(list, args.values()); + + if (result.ok) { + if (!args.attr().empty()) { + result.message += fmt::format(" for as-path-list {}", args.listName()); + } + if (args.attr() == kBooleanOperator) { + // bgpd rejects a term whose inline copy of the list carries a different + // operator ("Conflicting boolean_operator"), so keep every referencing + // match in step with the list. + for (auto& ref : + bgpcli::findTermsReferencingAsPathList(cfg, args.listName())) { + ref.match->as_path_filters()->boolean_operator() = + *list.boolean_operator(); + } + } + session.saveBgpConfig(); + result.message += + fmt::format("\nConfig saved to: {}", session.getBgpSessionConfigPath()); + } else if (listCreated) { + // Drop the phantom list so a rejected value is not visible to later + // lookups in the same process. + cfg.policies()->aspath_lists()->pop_back(); + } + return result.message; +} + +void CmdConfigProtocolBgpPolicyAsPathList::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdConfigProtocolBgpPolicyAsPathList, + CmdConfigProtocolBgpPolicyAsPathListTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h new file mode 100644 index 0000000000000..c3f574ae0693e --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h @@ -0,0 +1,87 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `as-path-list [ ...]`, validated at +// construction. An AsPathList is keyed by . The list's entries are their +// own subcommand (CmdConfigProtocolBgpPolicyAsPathListEntry), not attributes. +// +// Grammar (from the FBOSS proposed syntax): +// as-path-list (create/select list) +// as-path-list description (list attribute) +// as-path-list regex (append to as_paths, the +// field bgpd matches on) +// as-path-list boolean-operator (also written through to +// every term referencing +// the list) +class BgpAsPathListConfig : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpAsPathListConfig(std::vector v); + const std::string& listName() const { + return listName_; + } + const std::string& attr() const { + return attr_; + } + const std::vector& values() const { + return values_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string listName_; + std::string attr_; // matched dispatch key ("" = bare create) + std::vector values_; +}; + +// The list level of the `config protocol bgp policy as-path-list` family; +// dispatch and per-attribute parsing live in the .cpp so a new tunable is a +// one-entry change. Mirrors CmdConfigProtocolBgpPolicyRoutingPolicy, whose +// nested level (term / entry) is likewise its own subcommand. +struct CmdConfigProtocolBgpPolicyAsPathListTraits : public WriteCommandTraits { + using ParentCmd = CmdConfigProtocolBgpPolicy; + static void addCliArg(CLI::App& cmd, std::vector& args) { + // No positionals_at_end() here: CLI11 must stay free to classify the + // `entry` token as this command's subcommand rather than swallowing it + // into args. See CmdConfigProtocolBgpPolicyRoutingPolicyTraits. + cmd.add_option("args", args, " [ ...]"); + } + using ObjectArgType = BgpAsPathListConfig; + using RetType = std::string; +}; + +class CmdConfigProtocolBgpPolicyAsPathList + : public CmdHandler< + CmdConfigProtocolBgpPolicyAsPathList, + CmdConfigProtocolBgpPolicyAsPathListTraits> { + public: + using ObjectArgType = + CmdConfigProtocolBgpPolicyAsPathListTraits::ObjectArgType; + using RetType = CmdConfigProtocolBgpPolicyAsPathListTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.cpp new file mode 100644 index 0000000000000..846ffa28fe29a --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.cpp @@ -0,0 +1,229 @@ +/* + * 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/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/routing_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +namespace { + +// The attribute names, exactly as documented. Kept here so the +// valid-attribute set and the handler table stay in sync. +constexpr std::string_view kDescription = "description"; +constexpr std::string_view kAsnRegexp = "asn-regexp"; +constexpr std::string_view kMatchLogic = "match-logic"; + +// match-logic values (routing_policy.MatchValueLogicOperator names). +constexpr std::string_view kMatchLogicEqual = "EQUAL"; +constexpr std::string_view kMatchLogicNotEqual = "NOT_EQUAL"; + +using AsPathListEntry = bgp::bgp_policy::AsPathListEntry; +using MatchValueLogicOperator = bgp::routing_policy::MatchValueLogicOperator; +using bgpcli::AttrHandler; +using bgpcli::enumAttr; +using bgpcli::joinedStringAttr; +using bgpcli::ok; +using bgpcli::Result; + +// ---- value lookups ---------------------------------------------------------- + +std::optional lookupMatchLogic(const std::string& s) { + if (s == kMatchLogicEqual) { + return MatchValueLogicOperator::EQUAL; + } + if (s == kMatchLogicNotEqual) { + return MatchValueLogicOperator::NOT_EQUAL; + } + return std::nullopt; +} + +// ---- entry-level setters ---------------------------------------------------- +// Each writes one already-parsed, already-validated value. Parsing and message +// text belong to the shared factories in BgpCliAttrHandlers.h. + +void setDescription(AsPathListEntry& entry, const std::string& description) { + entry.description() = description; +} + +// The AS path is modelled as an AsPathType union; the CLI sets the inline +// `as_path` (AsPath) arm and its regexp, matching the thrift path +// .as_path.as_path.asn_regexp. +void setAsnRegexp(AsPathListEntry& entry, const std::string& regexp) { + entry.as_path()->set_as_path().asn_regexp() = regexp; +} + +void setMatchLogic(AsPathListEntry& entry, MatchValueLogicOperator op) { + entry.match_logic_type() = op; +} + +// ---- entry-level attribute registry ----------------------------------------- +// One line per documented attribute: its dispatch key, its value shape, and +// the setter that stores it. asn-regexp is a joined string because an AS-path +// regex separates ASNs with spaces (e.g. `^65000 65001$`), so its tokens must +// be re-joined rather than required to be single. +const std::map, std::less<>>& +entryAttrHandlers() { + static const std::string kMatchLogicValues = + fmt::format("{}|{}", kMatchLogicEqual, kMatchLogicNotEqual); + static const std::map, std::less<>> + kHandlers = { + {std::string(kAsnRegexp), + joinedStringAttr(kAsnRegexp, setAsnRegexp)}, + {std::string(kDescription), + joinedStringAttr(kDescription, setDescription)}, + {std::string(kMatchLogic), + enumAttr( + kMatchLogic, + kMatchLogicValues, + lookupMatchLogic, + setMatchLogic)}, + }; + return kHandlers; +} + +std::string validAttrList() { + std::string out; + for (const auto& [name, _] : entryAttrHandlers()) { + if (!out.empty()) { + out += ", "; + } + out += name; + } + return out; +} + +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpAsPathListConfig). +BgpAsPathListEntryConfig::BgpAsPathListEntryConfig(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty()) { + throw std::invalid_argument( + "Error: entry is required, optionally followed by an " + " "); + } + auto seq = bgpcli::parseNonNegInt64(v[0]); + if (!seq) { + throw std::invalid_argument( + fmt::format( + "Error: entry must be a non-negative integer, got '{}'", + v[0])); + } + seqNum_ = *seq; + if (v.size() == 1) { + return; // bare `entry `: create it + } + + attr_ = v[1]; + values_.assign(v.begin() + 2, v.end()); + + if (entryAttrHandlers().find(attr_) == entryAttrHandlers().end()) { + throw std::invalid_argument( + fmt::format( + "Error: unknown as-path-list entry attribute '{}'. Valid " + "attributes: {}", + attr_, + validAttrList())); + } +} + +CmdConfigProtocolBgpPolicyAsPathListEntryTraits::RetType +CmdConfigProtocolBgpPolicyAsPathListEntry::queryClient( + const HostInfo& /* hostInfo */, + const BgpAsPathListConfig& listArgs, + const ObjectArgType& args) { + // The parent parse accepts `as-path-list ... entry + // ...`, but only the leaf (this command) runs — silently dropping the + // list-level attribute would look like it was staged. Reject the mix. + if (!listArgs.attr().empty()) { + return fmt::format( + "Error: configure as-path-list attributes and entry in separate " + "commands (got as-path-list attribute '{}' alongside entry {})", + listArgs.attr(), + args.seqNum()); + } + + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + const bool listCreated = !bgpcli::asPathListExists(cfg, listArgs.listName()); + auto& list = bgpcli::findOrCreateAsPathList(cfg, listArgs.listName()); + const bool entryCreated = !bgpcli::asPathListEntryExists(list, args.seqNum()); + auto& entry = bgpcli::findOrCreateAsPathListEntry(list, args.seqNum()); + + Result result = args.attr().empty() + ? ok(entryCreated + ? fmt::format( + "Successfully created BGP as-path-list {} entry {}", + listArgs.listName(), + args.seqNum()) + : fmt::format( + "BGP as-path-list {} entry {} already exists", + listArgs.listName(), + args.seqNum())) + // The attribute is guaranteed valid: BgpAsPathListEntryConfig's + // constructor rejects an unknown attribute before we get here. + : entryAttrHandlers().find(args.attr())->second(entry, args.values()); + + if (result.ok) { + if (!args.attr().empty()) { + result.message += fmt::format( + " for as-path-list {} entry {}", listArgs.listName(), args.seqNum()); + } + session.saveBgpConfig(); + result.message += + fmt::format("\nConfig saved to: {}", session.getBgpSessionConfigPath()); + } else { + // A rejected value must not leave a phantom entry (or a phantom list + // implicitly created for it) visible to later lookups in this process. + if (entryCreated) { + list.as_path_list()->pop_back(); + } + if (listCreated) { + cfg.policies()->aspath_lists()->pop_back(); + } + } + return result.message; +} + +void CmdConfigProtocolBgpPolicyAsPathListEntry::printOutput( + const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdConfigProtocolBgpPolicyAsPathListEntry, + CmdConfigProtocolBgpPolicyAsPathListEntryTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h new file mode 100644 index 0000000000000..d2718c4d59b1e --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h @@ -0,0 +1,89 @@ +/* + * 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 +#include "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `entry [ ...]`, validated at +// construction. An entry (bgp_policy.AsPathListEntry in +// AsPathList.as_path_list[]) is keyed by ; the list it belongs to is +// supplied by the parent command's args. +// +// Grammar (from the FBOSS proposed syntax): +// ... as-path-list entry (create/select) +// ... as-path-list entry asn-regexp +// ... as-path-list entry description +// ... as-path-list entry match-logic +class BgpAsPathListEntryConfig : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpAsPathListEntryConfig(std::vector v); + int64_t seqNum() const { + return seqNum_; + } + const std::string& attr() const { + return attr_; + } + const std::vector& values() const { + return values_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + int64_t seqNum_{0}; + std::string attr_; // matched dispatch key ("" = bare create) + std::vector values_; +}; + +// The entry level of the as-path-list family as its own CLI11 subcommand; the +// parent's parsed args arrive through the ancestor-args tuple, mirroring +// CmdConfigProtocolBgpPolicyRoutingPolicyTerm. +struct CmdConfigProtocolBgpPolicyAsPathListEntryTraits + : public WriteCommandTraits { + using ParentCmd = CmdConfigProtocolBgpPolicyAsPathList; + static void addCliArg(CLI::App& cmd, std::vector& args) { + // Entry has no nested subcommands; stop CLI11's parent-chain fallthrough + // from stealing a value token that spells `entry` (e.g. in a description). + cmd.positionals_at_end(); + cmd.add_option("args", args, " [ ...]"); + } + using ObjectArgType = BgpAsPathListEntryConfig; + using RetType = std::string; +}; + +class CmdConfigProtocolBgpPolicyAsPathListEntry + : public CmdHandler< + CmdConfigProtocolBgpPolicyAsPathListEntry, + CmdConfigProtocolBgpPolicyAsPathListEntryTraits> { + public: + using ObjectArgType = + CmdConfigProtocolBgpPolicyAsPathListEntryTraits::ObjectArgType; + using RetType = CmdConfigProtocolBgpPolicyAsPathListEntryTraits::RetType; + + RetType queryClient( + const HostInfo& hostInfo, + const BgpAsPathListConfig& listArgs, + const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h new file mode 100644 index 0000000000000..5b6f5c60394d4 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h @@ -0,0 +1,90 @@ +/* + * 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 +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" + +/** + * Lookup/create helpers for the community-list CLI family, shared between the + * list-level dispatcher (CmdConfigProtocolBgpPolicyCommunityList), the + * community subcommand (CmdConfigProtocolBgpPolicyCommunityListCommunity), and + * the delete counterparts. A CommunityList is keyed by name; the inline + * Community members it holds are keyed by their own name. + */ +namespace facebook::fboss::bgpcli { + +inline bool communityListExists( + const bgp::thrift::BgpConfig& cfg, + const std::string& name) { + if (!cfg.policies().has_value()) { + return false; + } + const auto& lists = *cfg.policies()->community_lists(); + return std::any_of(lists.begin(), lists.end(), [&](const auto& list) { + return *list.name() == name; + }); +} + +// Find the community-list keyed by name, creating it if absent. Setting an +// attribute on a not-yet-created list implicitly creates it, so command +// ordering stays forgiving; a bare `community-list ` creates one +// explicitly. CommunityList's only key field is `name`. +inline bgp::bgp_policy::CommunityList& findOrCreateCommunityList( + bgp::thrift::BgpConfig& cfg, + const std::string& name) { + auto& lists = *cfg.policies().ensure().community_lists(); + for (auto& list : lists) { + if (*list.name() == name) { + return list; + } + } + lists.emplace_back(); + auto& list = lists.back(); + list.name() = name; + return list; +} + +inline bool communityMemberExists( + const bgp::bgp_policy::CommunityList& list, + const std::string& name) { + if (!list.members().has_value()) { + return false; + } + const auto& members = *list.members(); + return std::any_of(members.begin(), members.end(), [&](const auto& member) { + return member.community_ref().has_value() && + *member.community_ref()->name() == name; + }); +} + +// Find the inline Community member keyed by name within a list, creating it +// if absent. The CLI always defines members inline (the CommunityRefType +// union's `community` arm); the member's name is its identity. +inline bgp::bgp_policy::Community& findOrCreateCommunityMember( + bgp::bgp_policy::CommunityList& list, + const std::string& name) { + auto& members = list.members().ensure(); + for (auto& member : members) { + if (member.community_ref().has_value() && + *member.community_ref()->name() == name) { + return *member.community_ref(); + } + } + members.emplace_back(); + auto& community = members.back().set_community(); + community.name() = name; + return community; +} + +} // namespace facebook::fboss::bgpcli diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.cpp new file mode 100644 index 0000000000000..fcf7f8dd73725 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.cpp @@ -0,0 +1,204 @@ +/* + * 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/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/routing_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +namespace { + +// The attribute names, exactly as documented. Kept here so the +// valid-attribute set and the handler table stay in sync. The list's inline +// Community members are their own subcommand, not attributes. +constexpr std::string_view kBooleanOperator = "boolean-operator"; +constexpr std::string_view kDescription = "description"; +constexpr std::string_view kExactMatch = "exact-match"; + +// boolean-operator values (routing_policy.BooleanOperator names). +constexpr std::string_view kBooleanOperatorAnd = "AND"; +constexpr std::string_view kBooleanOperatorOr = "OR"; +constexpr std::string_view kBooleanOperatorNot = "NOT"; + +using BooleanOperator = bgp::routing_policy::BooleanOperator; +using CommunityList = bgp::bgp_policy::CommunityList; +using bgpcli::AttrHandler; +using bgpcli::boolAttr; +using bgpcli::enumAttr; +using bgpcli::joinedStringAttr; +using bgpcli::ok; +using bgpcli::Result; + +// ---- value lookups ---------------------------------------------------------- + +std::optional lookupBooleanOperator(const std::string& s) { + if (s == kBooleanOperatorAnd) { + return BooleanOperator::AND; + } + if (s == kBooleanOperatorOr) { + return BooleanOperator::OR; + } + if (s == kBooleanOperatorNot) { + return BooleanOperator::NOT; + } + return std::nullopt; +} + +// ---- list-level setters ----------------------------------------------------- +// Each writes one already-parsed, already-validated value. Parsing and message +// text belong to the shared factories in BgpCliAttrHandlers.h. + +void setListBooleanOperator(CommunityList& list, BooleanOperator op) { + list.boolean_operator() = op; +} + +void setListDescription(CommunityList& list, const std::string& description) { + list.description() = description; +} + +void setListExactMatch(CommunityList& list, bool exactMatch) { + list.exact_match() = exactMatch; +} + +// ---- list-level attribute registry ------------------------------------------ +// One line per documented attribute: its dispatch key, its value shape, and +// the setter that stores it. +const std::map, std::less<>>& +listAttrHandlers() { + static const std::string kBooleanOperatorValues = fmt::format( + "{}|{}|{}", kBooleanOperatorAnd, kBooleanOperatorOr, kBooleanOperatorNot); + static const std::map, std::less<>> + kHandlers = { + {std::string(kBooleanOperator), + enumAttr( + kBooleanOperator, + kBooleanOperatorValues, + lookupBooleanOperator, + setListBooleanOperator)}, + {std::string(kDescription), + joinedStringAttr(kDescription, setListDescription)}, + {std::string(kExactMatch), + boolAttr(kExactMatch, setListExactMatch)}, + }; + return kHandlers; +} + +std::string validAttrList() { + std::string out; + for (const auto& [name, _] : listAttrHandlers()) { + if (!out.empty()) { + out += ", "; + } + out += name; + } + return out; +} + +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpAsPathListConfig). +BgpCommunityListConfig::BgpCommunityListConfig(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty()) { + throw std::invalid_argument( + "Error: community-list is required, optionally followed by an " + " "); + } + if (v[0].empty()) { + throw std::invalid_argument("Error: community-list name must not be empty"); + } + listName_ = v[0]; + + if (v.size() == 1) { + return; // bare `community-list `: create the list + } + + attr_ = v[1]; + values_.assign(v.begin() + 2, v.end()); + + if (listAttrHandlers().find(attr_) == listAttrHandlers().end()) { + throw std::invalid_argument( + fmt::format( + "Error: unknown community-list attribute '{}'. Valid attributes: " + "{}", + attr_, + validAttrList())); + } +} + +CmdConfigProtocolBgpPolicyCommunityListTraits::RetType +CmdConfigProtocolBgpPolicyCommunityList::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + const bool listCreated = !bgpcli::communityListExists(cfg, args.listName()); + auto& list = bgpcli::findOrCreateCommunityList(cfg, args.listName()); + + Result result = args.attr().empty() + ? ok(listCreated + ? fmt::format( + "Successfully created BGP community-list {}", + args.listName()) + : fmt::format( + "BGP community-list {} already exists", args.listName())) + // The attribute is guaranteed valid: BgpCommunityListConfig's + // constructor rejects an unknown attribute before we get here. + : listAttrHandlers().find(args.attr())->second(list, args.values()); + + if (result.ok) { + if (!args.attr().empty()) { + result.message += fmt::format(" for community-list {}", args.listName()); + } + session.saveBgpConfig(); + result.message += + fmt::format("\nConfig saved to: {}", session.getBgpSessionConfigPath()); + } else if (listCreated) { + // Drop the phantom list so a rejected value is not visible to later + // lookups in the same process. + cfg.policies()->community_lists()->pop_back(); + } + return result.message; +} + +void CmdConfigProtocolBgpPolicyCommunityList::printOutput( + const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdConfigProtocolBgpPolicyCommunityList, + CmdConfigProtocolBgpPolicyCommunityListTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h new file mode 100644 index 0000000000000..e8113a09cbe6d --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h @@ -0,0 +1,86 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/CmdConfigProtocolBgpPolicy.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `community-list [ ...]`, validated at +// construction. A CommunityList is keyed by . Its inline Community +// members are their own subcommand +// (CmdConfigProtocolBgpPolicyCommunityListCommunity), not attributes. +// Mirrors BgpAsPathListConfig. +// +// Grammar (from the FBOSS proposed syntax): +// community-list (create/select list) +// community-list boolean-operator (list attribute) +// community-list description (list attribute) +// community-list exact-match (list attribute) +class BgpCommunityListConfig : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpCommunityListConfig(std::vector v); + const std::string& listName() const { + return listName_; + } + const std::string& attr() const { + return attr_; + } + const std::vector& values() const { + return values_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string listName_; + std::string attr_; // matched dispatch key ("" = bare create) + std::vector values_; +}; + +// Single handler for the whole `config protocol bgp policy community-list` +// family; dispatch and per-attribute parsing live in the .cpp so a new tunable +// is a one-entry change. Mirrors CmdConfigProtocolBgpPolicyAsPathList. +struct CmdConfigProtocolBgpPolicyCommunityListTraits + : public WriteCommandTraits { + using ParentCmd = CmdConfigProtocolBgpPolicy; + static void addCliArg(CLI::App& cmd, std::vector& args) { + // No positionals_at_end() here: CLI11 must stay free to classify the + // `community` token as this command's subcommand rather than swallowing it + // into args. See CmdConfigProtocolBgpPolicyAsPathListTraits. + cmd.add_option("args", args, " [ ...]"); + } + using ObjectArgType = BgpCommunityListConfig; + using RetType = std::string; +}; + +class CmdConfigProtocolBgpPolicyCommunityList + : public CmdHandler< + CmdConfigProtocolBgpPolicyCommunityList, + CmdConfigProtocolBgpPolicyCommunityListTraits> { + public: + using ObjectArgType = + CmdConfigProtocolBgpPolicyCommunityListTraits::ObjectArgType; + using RetType = CmdConfigProtocolBgpPolicyCommunityListTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.cpp new file mode 100644 index 0000000000000..0c1df51f1dfd5 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.cpp @@ -0,0 +1,234 @@ +/* + * 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/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliAttrHandlers.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +namespace { + +// The attribute names, exactly as documented. Kept here so the +// valid-attribute set and the handler table stay in sync. +constexpr std::string_view kDescription = "description"; +constexpr std::string_view kType = "type"; +constexpr std::string_view kValue = "value"; + +// type values (bgp_policy.CommunityType names). +constexpr std::string_view kCommunityTypeNormal = "NORMAL"; +constexpr std::string_view kCommunityTypeExtended = "EXTENDED"; +constexpr std::string_view kCommunityTypeLarge = "LARGE"; + +using Community = bgp::bgp_policy::Community; +using CommunityType = bgp::bgp_policy::CommunityType; +using bgpcli::AttrHandler; +using bgpcli::enumAttr; +using bgpcli::joinedStringAttr; +using bgpcli::ok; +using bgpcli::Result; +using bgpcli::stringAttr; + +// ---- value lookups ---------------------------------------------------------- + +std::optional lookupCommunityType(const std::string& s) { + if (s == kCommunityTypeNormal) { + return CommunityType::NORMAL; + } + if (s == kCommunityTypeExtended) { + return CommunityType::EXTENDED; + } + if (s == kCommunityTypeLarge) { + return CommunityType::LARGE; + } + return std::nullopt; +} + +// ---- community-level setters ------------------------------------------------ +// Each writes one already-parsed, already-validated value. Parsing and message +// text belong to the shared factories in BgpCliAttrHandlers.h. + +void setDescription(Community& community, const std::string& description) { + community.description() = description; +} + +void setType(Community& community, CommunityType type) { + community.type() = type; +} + +void setValue(Community& community, const std::string& value) { + community.value() = value; +} + +// ---- community-level attribute registry ------------------------------------- +// One line per documented attribute: its dispatch key, its value shape, and +// the setter that stores it. +const std::map, std::less<>>& +communityAttrHandlers() { + static const std::string kCommunityTypeValues = fmt::format( + "{}|{}|{}", + kCommunityTypeNormal, + kCommunityTypeExtended, + kCommunityTypeLarge); + static const std::map, std::less<>> + kHandlers = { + {std::string(kDescription), + joinedStringAttr(kDescription, setDescription)}, + {std::string(kType), + enumAttr( + kType, kCommunityTypeValues, lookupCommunityType, setType)}, + {std::string(kValue), + stringAttr(kValue, "string", setValue)}, + }; + return kHandlers; +} + +std::string validAttrList() { + std::string out; + for (const auto& [name, _] : communityAttrHandlers()) { + if (!out.empty()) { + out += ", "; + } + out += name; + } + return out; +} + +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpCommunityListConfig). +BgpCommunityListCommunityConfig::BgpCommunityListCommunityConfig( + std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty() || v[0].empty()) { + throw std::invalid_argument( + "Error: community is required, optionally followed by an " + " "); + } + communityName_ = v[0]; + if (v.size() == 1) { + return; // bare `community `: create it + } + + attr_ = v[1]; + values_.assign(v.begin() + 2, v.end()); + + if (communityAttrHandlers().find(attr_) == communityAttrHandlers().end()) { + throw std::invalid_argument( + fmt::format( + "Error: unknown community-list community attribute '{}'. Valid " + "attributes: {}", + attr_, + validAttrList())); + } +} + +CmdConfigProtocolBgpPolicyCommunityListCommunityTraits::RetType +CmdConfigProtocolBgpPolicyCommunityListCommunity::queryClient( + const HostInfo& /* hostInfo */, + const BgpCommunityListConfig& listArgs, + const ObjectArgType& args) { + // The parent parse accepts `community-list ... + // community ...`, but only the leaf (this command) runs — silently dropping + // the list-level attribute would look like it was staged. Reject the mix. + if (!listArgs.attr().empty()) { + return fmt::format( + "Error: configure community-list attributes and community in separate " + "commands (got community-list attribute '{}' alongside community {})", + listArgs.attr(), + args.communityName()); + } + + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + const bool listCreated = + !bgpcli::communityListExists(cfg, listArgs.listName()); + auto& list = bgpcli::findOrCreateCommunityList(cfg, listArgs.listName()); + const bool hadMembers = list.members().has_value(); + const bool memberCreated = + !bgpcli::communityMemberExists(list, args.communityName()); + auto& community = + bgpcli::findOrCreateCommunityMember(list, args.communityName()); + + Result result = args.attr().empty() + ? ok(memberCreated + ? fmt::format( + "Successfully created BGP community-list {} community {}", + listArgs.listName(), + args.communityName()) + : fmt::format( + "BGP community-list {} community {} already exists", + listArgs.listName(), + args.communityName())) + // The attribute is guaranteed valid: BgpCommunityListCommunityConfig's + // constructor rejects an unknown attribute before we get here. + : communityAttrHandlers() + .find(args.attr()) + ->second(community, args.values()); + + if (result.ok) { + if (!args.attr().empty()) { + result.message += fmt::format( + " for community-list {} community {}", + listArgs.listName(), + args.communityName()); + } + session.saveBgpConfig(); + result.message += + fmt::format("\nConfig saved to: {}", session.getBgpSessionConfigPath()); + } else { + // A rejected value must not leave a phantom member (or a phantom list + // implicitly created for it) visible to later lookups in this process — + // restoring an unset members field if this invocation ensured it. + if (memberCreated) { + if (hadMembers) { + list.members()->pop_back(); + } else { + list.members().reset(); + } + } + if (listCreated) { + cfg.policies()->community_lists()->pop_back(); + } + } + return result.message; +} + +void CmdConfigProtocolBgpPolicyCommunityListCommunity::printOutput( + const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdConfigProtocolBgpPolicyCommunityListCommunity, + CmdConfigProtocolBgpPolicyCommunityListCommunityTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h new file mode 100644 index 0000000000000..857d344622ae4 --- /dev/null +++ b/fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h @@ -0,0 +1,91 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `community [ ...]`, validated at +// construction. A member (bgp_policy.Community, held inline through the +// CommunityRefType union's `community` arm in CommunityList.members[]) is +// keyed by ; the list it belongs to is supplied by the parent command's +// args. +// +// Grammar (from the FBOSS proposed syntax): +// ... community-list community (create/select) +// ... community-list community description +// ... community-list community type +// ... community-list community value +class BgpCommunityListCommunityConfig + : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpCommunityListCommunityConfig(std::vector v); + const std::string& communityName() const { + return communityName_; + } + const std::string& attr() const { + return attr_; + } + const std::vector& values() const { + return values_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string communityName_; + std::string attr_; // matched dispatch key ("" = bare create) + std::vector values_; +}; + +// The community (member) level of the community-list family as its own CLI11 +// subcommand; the parent's parsed args arrive through the ancestor-args tuple, +// mirroring CmdConfigProtocolBgpPolicyAsPathListEntry. +struct CmdConfigProtocolBgpPolicyCommunityListCommunityTraits + : public WriteCommandTraits { + using ParentCmd = CmdConfigProtocolBgpPolicyCommunityList; + static void addCliArg(CLI::App& cmd, std::vector& args) { + // Community has no nested subcommands; stop CLI11's parent-chain + // fallthrough from stealing a value token that spells `community`. + cmd.positionals_at_end(); + cmd.add_option("args", args, " [ ...]"); + } + using ObjectArgType = BgpCommunityListCommunityConfig; + using RetType = std::string; +}; + +class CmdConfigProtocolBgpPolicyCommunityListCommunity + : public CmdHandler< + CmdConfigProtocolBgpPolicyCommunityListCommunity, + CmdConfigProtocolBgpPolicyCommunityListCommunityTraits> { + public: + using ObjectArgType = + CmdConfigProtocolBgpPolicyCommunityListCommunityTraits::ObjectArgType; + using RetType = + CmdConfigProtocolBgpPolicyCommunityListCommunityTraits::RetType; + + RetType queryClient( + const HostInfo& hostInfo, + const BgpCommunityListConfig& listArgs, + const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.cpp b/fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.cpp new file mode 100644 index 0000000000000..26fbfb3f972cb --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.cpp @@ -0,0 +1,32 @@ +/* + * 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/delete/protocol/bgp/CmdDeleteProtocolBgp.h" +#include +#include + +#include "fboss/cli/fboss2/CmdHandler.cpp" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +CmdDeleteProtocolBgpTraits::RetType CmdDeleteProtocolBgp::queryClient( + const HostInfo& /* hostInfo */) { + return "Delete BGP configuration objects. Use subcommands: neighbor"; +} + +void CmdDeleteProtocolBgp::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +template void +CmdHandler::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h b/fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h new file mode 100644 index 0000000000000..a79dfc13580fd --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h @@ -0,0 +1,41 @@ +/* + * 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/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/delete/protocol/CmdDeleteProtocol.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +struct CmdDeleteProtocolBgpTraits : public WriteCommandTraits { + using ParentCmd = CmdDeleteProtocol; + static constexpr utils::ObjectArgTypeId ObjectArgTypeId = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_NONE; + using ObjectArgType = std::monostate; + using RetType = std::string; +}; + +class CmdDeleteProtocolBgp + : public CmdHandler { + public: + using ObjectArgType = CmdDeleteProtocolBgpTraits::ObjectArgType; + using RetType = CmdDeleteProtocolBgpTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.cpp b/fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.cpp new file mode 100644 index 0000000000000..ff337a33e0aeb --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.cpp @@ -0,0 +1,77 @@ +/* + * 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/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpPeerAddr.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpNeighborConfig). +BgpNeighborRef::BgpNeighborRef(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.size() != 1) { + throw std::invalid_argument( + "Error: delete protocol bgp neighbor requires exactly one " + ""); + } + auto normalized = bgpcli::normalizeBgpPeerAddr(v[0]); + if (!normalized) { + throw std::invalid_argument( + fmt::format("Error: Invalid neighbor address '{}'", v[0])); + } + peerAddr_ = std::move(*normalized); +} + +CmdDeleteProtocolBgpNeighborTraits::RetType +CmdDeleteProtocolBgpNeighbor::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + auto it = bgpcli::findBgpPeer(cfg, args.peerAddr()); + if (it == cfg.peers()->end()) { + // Nothing is persisted for an unknown neighbor, so a typo'd delete can't + // stage an unrelated session change. + return fmt::format("Error: BGP neighbor {} not found", args.peerAddr()); + } + cfg.peers()->erase(it); + session.saveBgpConfig(); + return fmt::format( + "Successfully deleted BGP neighbor {}\nConfig saved to: {}", + args.peerAddr(), + session.getBgpSessionConfigPath()); +} + +void CmdDeleteProtocolBgpNeighbor::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdDeleteProtocolBgpNeighbor, + CmdDeleteProtocolBgpNeighborTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h b/fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h new file mode 100644 index 0000000000000..fa71727255c67 --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h @@ -0,0 +1,61 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `delete protocol bgp neighbor `, validated at +// construction. The address is normalized the same way the config command +// normalizes it, so any spelling of a peer's address deletes that peer. +class BgpNeighborRef : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpNeighborRef(std::vector v); + const std::string& peerAddr() const { + return peerAddr_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string peerAddr_; +}; + +struct CmdDeleteProtocolBgpNeighborTraits : public WriteCommandTraits { + using ParentCmd = CmdDeleteProtocolBgp; + static void addCliArg(CLI::App& cmd, std::vector& args) { + cmd.add_option("args", args, ""); + } + using ObjectArgType = BgpNeighborRef; + using RetType = std::string; +}; + +class CmdDeleteProtocolBgpNeighbor : public CmdHandler< + CmdDeleteProtocolBgpNeighbor, + CmdDeleteProtocolBgpNeighborTraits> { + public: + using ObjectArgType = CmdDeleteProtocolBgpNeighborTraits::ObjectArgType; + using RetType = CmdDeleteProtocolBgpNeighborTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.cpp b/fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.cpp new file mode 100644 index 0000000000000..b34cd75ece87c --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.cpp @@ -0,0 +1,99 @@ +/* + * 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/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +// Parse + validate at construction so queryClient stays a thin dispatch. +// Throwing std::invalid_argument is how the framework surfaces arg parse +// errors (same mechanism as BgpPeerGroupConfig). +BgpPeerGroupRef::BgpPeerGroupRef(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.size() != 1) { + throw std::invalid_argument( + "Error: delete protocol bgp peer-group requires exactly one "); + } + if (v[0].empty()) { + throw std::invalid_argument("Error: peer-group name must not be empty"); + } + groupName_ = std::move(v[0]); +} + +CmdDeleteProtocolBgpPeerGroupTraits::RetType +CmdDeleteProtocolBgpPeerGroup::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + if (!cfg.peer_groups().has_value()) { + // Nothing is persisted for an unknown group, so a typo'd delete can't + // stage an unrelated session change. + return fmt::format("Error: BGP peer-group {} not found", args.groupName()); + } + auto& groups = *cfg.peer_groups(); + auto it = std::find_if(groups.begin(), groups.end(), [&](const auto& group) { + return *group.name() == args.groupName(); + }); + if (it == groups.end()) { + return fmt::format("Error: BGP peer-group {} not found", args.groupName()); + } + // A neighbor's peer_group_name resolves against this group by name at + // daemon load; erasing the group while a neighbor still points at it would + // commit a dangling reference. Refuse and name the neighbors instead. + std::vector referencingPeers; + for (const auto& peer : *cfg.peers()) { + if (peer.peer_group_name().has_value() && + *peer.peer_group_name() == args.groupName()) { + referencingPeers.push_back(*peer.peer_addr()); + } + } + if (!referencingPeers.empty()) { + return fmt::format( + "Error: BGP peer-group {} is still referenced by neighbor(s): {}; " + "delete or re-point those neighbors first", + args.groupName(), + folly::join(", ", referencingPeers)); + } + groups.erase(it); + session.saveBgpConfig(); + return fmt::format( + "Successfully deleted BGP peer-group {}\nConfig saved to: {}", + args.groupName(), + session.getBgpSessionConfigPath()); +} + +void CmdDeleteProtocolBgpPeerGroup::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdDeleteProtocolBgpPeerGroup, + CmdDeleteProtocolBgpPeerGroupTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h b/fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h new file mode 100644 index 0000000000000..c9793226e9a24 --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h @@ -0,0 +1,61 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `delete protocol bgp peer-group `, validated at construction. +// The group name is the group's identity (an arbitrary string), matched +// exactly the same way the config command stores it. Mirrors BgpNeighborRef. +class BgpPeerGroupRef : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpPeerGroupRef(std::vector v); + const std::string& groupName() const { + return groupName_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string groupName_; +}; + +struct CmdDeleteProtocolBgpPeerGroupTraits : public WriteCommandTraits { + using ParentCmd = CmdDeleteProtocolBgp; + static void addCliArg(CLI::App& cmd, std::vector& args) { + cmd.add_option("args", args, ""); + } + using ObjectArgType = BgpPeerGroupRef; + using RetType = std::string; +}; + +class CmdDeleteProtocolBgpPeerGroup : public CmdHandler< + CmdDeleteProtocolBgpPeerGroup, + CmdDeleteProtocolBgpPeerGroupTraits> { + public: + using ObjectArgType = CmdDeleteProtocolBgpPeerGroupTraits::ObjectArgType; + using RetType = CmdDeleteProtocolBgpPeerGroupTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.cpp b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.cpp new file mode 100644 index 0000000000000..bc15db0a326f4 --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.cpp @@ -0,0 +1,32 @@ +/* + * 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/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h" +#include +#include + +#include "fboss/cli/fboss2/CmdHandler.cpp" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +CmdDeleteProtocolBgpPolicyTraits::RetType +CmdDeleteProtocolBgpPolicy::queryClient(const HostInfo& /* hostInfo */) { + return "Delete BGP policy objects. Use subcommands: as-path-list"; +} + +void CmdDeleteProtocolBgpPolicy::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +template void +CmdHandler::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h new file mode 100644 index 0000000000000..d658fee78a4ba --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h @@ -0,0 +1,43 @@ +/* + * 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 "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/CmdDeleteProtocolBgp.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Grouping node for `delete protocol bgp policy `. Holds no +// state of its own; the per-object-type delete commands are its subcommands. +struct CmdDeleteProtocolBgpPolicyTraits : public WriteCommandTraits { + using ParentCmd = CmdDeleteProtocolBgp; + static constexpr utils::ObjectArgTypeId ObjectArgTypeId = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_NONE; + using ObjectArgType = std::monostate; + using RetType = std::string; +}; + +class CmdDeleteProtocolBgpPolicy : public CmdHandler< + CmdDeleteProtocolBgpPolicy, + CmdDeleteProtocolBgpPolicyTraits> { + public: + using ObjectArgType = CmdDeleteProtocolBgpPolicyTraits::ObjectArgType; + using RetType = CmdDeleteProtocolBgpPolicyTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.cpp b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.cpp new file mode 100644 index 0000000000000..9b025078f29dd --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.cpp @@ -0,0 +1,147 @@ +/* + * 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/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +namespace { +constexpr std::string_view kRegexKeyword = "regex"; +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +BgpAsPathListRef::BgpAsPathListRef(std::vector v) + : utils::BaseObjectArgType(v) { + if (v.empty()) { + throw std::invalid_argument( + "Error: delete protocol bgp policy as-path-list requires , " + "optionally followed by `regex `"); + } + if (v[0].empty()) { + throw std::invalid_argument("Error: as-path-list name must not be empty"); + } + listName_ = v[0]; + if (v.size() == 1) { + return; + } + if (v[1] != kRegexKeyword) { + throw std::invalid_argument( + fmt::format( + "Error: unexpected token '{}'. Usage: delete protocol bgp policy " + "as-path-list [regex ]", + v[1])); + } + if (v.size() < 3 || v[2].empty()) { + throw std::invalid_argument("Error: `regex` requires a "); + } + if (v.size() > 3) { + throw std::invalid_argument( + fmt::format("Error: unexpected token '{}' after regex ", v[3])); + } + hasRegex_ = true; + regex_ = v[2]; +} + +CmdDeleteProtocolBgpPolicyAsPathListTraits::RetType +CmdDeleteProtocolBgpPolicyAsPathList::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + if (!cfg.policies().has_value()) { + // Nothing is persisted for an unknown list, so a typo'd delete can't stage + // an unrelated session change. + return fmt::format("Error: BGP as-path-list {} not found", args.listName()); + } + auto& lists = *cfg.policies()->aspath_lists(); + auto it = std::find_if(lists.begin(), lists.end(), [&](const auto& list) { + return *list.name() == args.listName(); + }); + if (it == lists.end()) { + return fmt::format("Error: BGP as-path-list {} not found", args.listName()); + } + if (args.hasRegex()) { + // Removing one pattern from a referenced list is safe: the name the terms + // resolve stays defined. + auto& list = *it; + if (list.as_paths().has_value()) { + auto& paths = *list.as_paths(); + auto pathIt = std::find(paths.begin(), paths.end(), args.regex()); + if (pathIt != paths.end()) { + paths.erase(pathIt); + if (paths.empty()) { + list.as_paths().reset(); + } + session.saveBgpConfig(); + return fmt::format( + "Successfully deleted BGP as-path-list {} regex {}\n" + "Config saved to: {}", + args.listName(), + args.regex(), + session.getBgpSessionConfigPath()); + } + } + return fmt::format( + "Error: BGP as-path-list {} regex {} not found", + args.listName(), + args.regex()); + } + // A term's as_path_list_names resolves against this list by name at daemon + // load; erasing the list while a term still names it would commit a + // dangling reference and fail the load. Refuse and name the terms instead. + std::vector referrers; + for (const auto& ref : + bgpcli::findTermsReferencingAsPathList(cfg, args.listName())) { + referrers.push_back(ref.label); + } + if (!referrers.empty()) { + return fmt::format( + "Error: BGP as-path-list {} is still referenced by {}; remove those " + "matches first", + args.listName(), + folly::join(", ", referrers)); + } + lists.erase(it); + session.saveBgpConfig(); + return fmt::format( + "Successfully deleted BGP as-path-list {}\nConfig saved to: {}", + args.listName(), + session.getBgpSessionConfigPath()); +} + +void CmdDeleteProtocolBgpPolicyAsPathList::printOutput(const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdDeleteProtocolBgpPolicyAsPathList, + CmdDeleteProtocolBgpPolicyAsPathListTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h new file mode 100644 index 0000000000000..874460157ef52 --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h @@ -0,0 +1,73 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `delete protocol bgp policy as-path-list [regex ]`, +// validated at construction. The name is the list's identity, matched exactly +// the same way the config command stores it; the optional `regex` selector +// removes one as_paths pattern instead of the whole list. Mirrors +// BgpPeerGroupRef. +class BgpAsPathListRef : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpAsPathListRef(std::vector v); + const std::string& listName() const { + return listName_; + } + bool hasRegex() const { + return hasRegex_; + } + const std::string& regex() const { + return regex_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string listName_; + bool hasRegex_{false}; + std::string regex_; +}; + +struct CmdDeleteProtocolBgpPolicyAsPathListTraits : public WriteCommandTraits { + using ParentCmd = CmdDeleteProtocolBgpPolicy; + static void addCliArg(CLI::App& cmd, std::vector& args) { + cmd.add_option("args", args, " [regex ]"); + } + using ObjectArgType = BgpAsPathListRef; + using RetType = std::string; +}; + +class CmdDeleteProtocolBgpPolicyAsPathList + : public CmdHandler< + CmdDeleteProtocolBgpPolicyAsPathList, + CmdDeleteProtocolBgpPolicyAsPathListTraits> { + public: + using ObjectArgType = + CmdDeleteProtocolBgpPolicyAsPathListTraits::ObjectArgType; + using RetType = CmdDeleteProtocolBgpPolicyAsPathListTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.cpp b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.cpp new file mode 100644 index 0000000000000..7788aa590c96f --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.cpp @@ -0,0 +1,138 @@ +/* + * 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/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h" + +#include "fboss/cli/fboss2/CmdHandler.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpCliValueParsers.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" +#include "fmt/format.h" + +namespace facebook::fboss { + +namespace { +// CLI keyword selecting the nested community member, matching the config +// command's grammar. +constexpr std::string_view kObjectName = "community-list"; +constexpr std::string_view kCommunityKeyword = "community"; +} // namespace + +// Parse + validate at construction so queryClient stays a thin dispatch. +BgpCommunityListRef::BgpCommunityListRef(std::vector v) + : utils::BaseObjectArgType(v) { + auto selector = bgpcli::parseListMemberSelector( + v, + kObjectName, + kCommunityKeyword, + "Error: delete protocol bgp policy community-list requires , " + "optionally followed by `community `"); + // Unlike the config grammar, nothing may follow the parsed prefix: there + // are no attributes to delete through this command. + if (selector.restStart < v.size()) { + throw std::invalid_argument( + selector.memberName + ? fmt::format( + "Error: unexpected token '{}' after community ", + v[selector.restStart]) + : fmt::format( + "Error: unexpected token '{}'. Usage: delete protocol bgp " + "policy community-list [community ]", + v[selector.restStart])); + } + listName_ = std::move(selector.listName); + if (selector.memberName) { + hasCommunity_ = true; + communityName_ = std::move(*selector.memberName); + } +} + +CmdDeleteProtocolBgpPolicyCommunityListTraits::RetType +CmdDeleteProtocolBgpPolicyCommunityList::queryClient( + const HostInfo& /* hostInfo */, + const ObjectArgType& args) { + auto& session = ConfigSession::getInstance(); + auto& cfg = session.getBgpConfig(); + if (!cfg.policies().has_value()) { + // Nothing is persisted for an unknown list, so a typo'd delete can't stage + // an unrelated session change. + return fmt::format( + "Error: BGP community-list {} not found", args.listName()); + } + auto& lists = *cfg.policies()->community_lists(); + auto it = std::find_if(lists.begin(), lists.end(), [&](const auto& list) { + return *list.name() == args.listName(); + }); + if (it == lists.end()) { + return fmt::format( + "Error: BGP community-list {} not found", args.listName()); + } + if (args.hasCommunity()) { + // Delete a single inline member; the list itself stays. + auto& list = *it; + if (list.members().has_value()) { + auto& members = *list.members(); + auto memberIt = + std::find_if(members.begin(), members.end(), [&](const auto& member) { + return member.community_ref().has_value() && + *member.community_ref()->name() == args.communityName(); + }); + if (memberIt != members.end()) { + members.erase(memberIt); + if (members.empty()) { + // Leave the list looking like one that never had members, so a + // later `community ` add starts from the same shape. + list.members().reset(); + } + session.saveBgpConfig(); + return fmt::format( + "Successfully deleted BGP community-list {} community {}\n" + "Config saved to: {}", + args.listName(), + args.communityName(), + session.getBgpSessionConfigPath()); + } + } + return fmt::format( + "Error: BGP community-list {} community {} not found", + args.listName(), + args.communityName()); + } + lists.erase(it); + session.saveBgpConfig(); + return fmt::format( + "Successfully deleted BGP community-list {}\nConfig saved to: {}", + args.listName(), + session.getBgpSessionConfigPath()); +} + +void CmdDeleteProtocolBgpPolicyCommunityList::printOutput( + const RetType& output) { + std::cout << output << std::endl; +} + +// Explicit template instantiation +template void CmdHandler< + CmdDeleteProtocolBgpPolicyCommunityList, + CmdDeleteProtocolBgpPolicyCommunityListTraits>::run(); + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h new file mode 100644 index 0000000000000..127ea9d6e8e74 --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h @@ -0,0 +1,74 @@ +/* + * 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 "CLI/App.hpp" +#include "fboss/cli/fboss2/CmdHandler.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/policy/CmdDeleteProtocolBgpPolicy.h" +#include "fboss/cli/fboss2/utils/CmdUtilsCommon.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +namespace facebook::fboss { + +// Parsed `delete protocol bgp policy community-list +// [community ]`, validated at construction. The names are the list's +// and member's identity, matched exactly the same way the config command +// stores them. Without the `community` selector the whole list is deleted; +// with it, only that inline member. Mirrors BgpAsPathListRef. +class BgpCommunityListRef : public utils::BaseObjectArgType { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + /* implicit */ BgpCommunityListRef(std::vector v); + const std::string& listName() const { + return listName_; + } + bool hasCommunity() const { + return hasCommunity_; + } + const std::string& communityName() const { + return communityName_; + } + const static utils::ObjectArgTypeId id = + utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_MESSAGE; + + private: + std::string listName_; + bool hasCommunity_{false}; + std::string communityName_; +}; + +struct CmdDeleteProtocolBgpPolicyCommunityListTraits + : public WriteCommandTraits { + using ParentCmd = CmdDeleteProtocolBgpPolicy; + static void addCliArg(CLI::App& cmd, std::vector& args) { + cmd.add_option("args", args, " [community ]"); + } + using ObjectArgType = BgpCommunityListRef; + using RetType = std::string; +}; + +class CmdDeleteProtocolBgpPolicyCommunityList + : public CmdHandler< + CmdDeleteProtocolBgpPolicyCommunityList, + CmdDeleteProtocolBgpPolicyCommunityListTraits> { + public: + using ObjectArgType = + CmdDeleteProtocolBgpPolicyCommunityListTraits::ObjectArgType; + using RetType = CmdDeleteProtocolBgpPolicyCommunityListTraits::RetType; + + RetType queryClient(const HostInfo& hostInfo, const ObjectArgType& args); + + void printOutput(const RetType& output); +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/docs/BGP_CLI_TEST_PLAN.md b/fboss/cli/fboss2/docs/BGP_CLI_TEST_PLAN.md index 65cb71b350d0c..3b995ef3cb4e1 100644 --- a/fboss/cli/fboss2/docs/BGP_CLI_TEST_PLAN.md +++ b/fboss/cli/fboss2/docs/BGP_CLI_TEST_PLAN.md @@ -73,16 +73,16 @@ fboss2 config protocol bgp peer-group RSW-FSW-V6 next-hop-self true fboss2 config protocol bgp peer-group RSW-FSW-V6 confed-peer true fboss2 config protocol bgp peer-group RSW-FSW-V6 ingress-policy PROPAGATE_RSW_FSW_IN fboss2 config protocol bgp peer-group RSW-FSW-V6 egress-policy PROPAGATE_RSW_FSW_OUT -fboss2 config protocol bgp peer-group RSW-FSW-V6 v4-over-v6-nh true +fboss2 config protocol bgp peer-group RSW-FSW-V6 afi ipv4-over-ipv6-nh true fboss2 config protocol bgp peer-group RSW-FSW-V6 timers hold-time 30 fboss2 config protocol bgp peer-group RSW-FSW-V6 timers keepalive 10 -fboss2 config protocol bgp peer-group RSW-RTSW-V6 disable-ipv4-afi true +fboss2 config protocol bgp peer-group RSW-RTSW-V6 afi disable-ipv4-afi true # Peer configuration (with explicit disable_ipv4_afi: false preserved) -fboss2 config protocol bgp peer 2401:db00:501c::/64 remote-asn 65000 -fboss2 config protocol bgp peer 2401:db00:501c::/64 local-addr 2401:db00:501c::a -fboss2 config protocol bgp peer 2401:db00:501c::/64 passive true -fboss2 config protocol bgp peer 2401:db00:501c::/64 disable-ipv4-afi false +fboss2 config protocol bgp neighbor 2401:db00:501c::/64 remote-asn 65000 +fboss2 config protocol bgp neighbor 2401:db00:501c::/64 bind-addr address 2401:db00:501c::a +fboss2 config protocol bgp neighbor 2401:db00:501c::/64 passive true +fboss2 config protocol bgp neighbor 2401:db00:501c::/64 afi disable-ipv4-afi false ``` ### Step 5: Execute Script @@ -131,17 +131,18 @@ diff <(jq -S . /tmp/rsw_bgp_nao.txt) <(jq -S . ~/.fboss2/bgp_config.json) | Peer Group | `config protocol bgp peer-group remote-asn ` | Set remote AS | | Peer Group | `config protocol bgp peer-group description ` | Set description | | Peer Group | `config protocol bgp peer-group next-hop-self ` | Enable/disable next-hop-self | -| Peer Group | `config protocol bgp peer-group disable-ipv4-afi ` | Disable IPv4 AFI | -| Peer Group | `config protocol bgp peer-group v4-over-v6-nh ` | Enable v4-over-v6 nexthop | +| Peer Group | `config protocol bgp peer-group afi disable-ipv4-afi ` | Disable IPv4 AFI | +| Peer Group | `config protocol bgp peer-group afi ipv4-over-ipv6-nh ` | Enable v4-over-v6 nexthop | | Peer Group | `config protocol bgp peer-group timers hold-time ` | Set hold time | | Peer Group | `config protocol bgp peer-group timers keepalive ` | Set keepalive interval | -| Peer Group | `config protocol bgp peer-group max-routes ` | Set max routes | -| Peer | `config protocol bgp peer remote-asn ` | Set remote AS | -| Peer | `config protocol bgp peer peer-group ` | Assign to peer group | -| Peer | `config protocol bgp peer local-addr ` | Set local address | -| Peer | `config protocol bgp peer passive ` | Set passive mode | -| Peer | `config protocol bgp peer disable-ipv4-afi ` | Disable IPv4 AFI | -| Peer | `config protocol bgp peer link-bandwidth ` | Set link bandwidth | +| Peer Group | `config protocol bgp peer-group max-route pre-filter ` | Set max routes (pre-policy; 0 = unlimited) | +| Neighbor | `config protocol bgp neighbor remote-asn ` | Set remote AS | +| Neighbor | `config protocol bgp neighbor peer-group ` | Assign to peer group | +| Neighbor | `config protocol bgp neighbor bind-addr address ` | Set local address | +| Neighbor | `config protocol bgp neighbor passive ` | Listen-only (true) or listen-and-connect (false) session | +| Neighbor | `delete protocol bgp neighbor ` | Delete a neighbor | +| Neighbor | `config protocol bgp neighbor afi disable-ipv4-afi ` | Disable IPv4 AFI | +| Neighbor | `config protocol bgp neighbor link-bandwidth ` | Set link bandwidth | ## Automated Test Script diff --git a/fboss/cli/fboss2/test/config/BUCK b/fboss/cli/fboss2/test/config/BUCK index 7c8b16e885119..e4c8903a4dd50 100644 --- a/fboss/cli/fboss2/test/config/BUCK +++ b/fboss/cli/fboss2/test/config/BUCK @@ -5,12 +5,23 @@ oncall("fboss_oss") cpp_unittest( name = "cmd_config_test", + # The suite is ~60 files of ConfigSession tests, each doing several + # fsync-backed writeFileAtomic calls; on CI disks the run can exceed the + # default "moderate" (300s) Bazel test timeout. + size = "large", srcs = [ "BgpConfigSessionTest.cpp", "CmdConfigAclRuleTest.cpp", "CmdConfigAdminDistanceTest.cpp", "CmdConfigAppliedInfoTest.cpp", "CmdConfigArpTest.cpp", + "CmdConfigBgpGlobalTest.cpp", + "CmdConfigBgpNeighborTest.cpp", + "CmdConfigBgpPeerGroupTest.cpp", + "CmdConfigBgpPolicyAsPathListEntryTest.cpp", + "CmdConfigBgpPolicyAsPathListTest.cpp", + "CmdConfigBgpPolicyCommunityListCommunityTest.cpp", + "CmdConfigBgpPolicyCommunityListTest.cpp", "CmdConfigCoppTest.cpp", "CmdConfigDhcpTest.cpp", "CmdConfigHistoryTest.cpp", @@ -44,6 +55,7 @@ cpp_unittest( "CmdConfigVlanTest.cpp", "CmdDeleteAclRuleTest.cpp", "CmdDeleteArpTest.cpp", + "CmdDeleteBgpNeighborTest.cpp", "CmdDeleteConfigInterfaceTest.cpp", "CmdDeleteCoppQueueTest.cpp", "CmdDeleteCoppReasonTest.cpp", @@ -57,6 +69,9 @@ cpp_unittest( "CmdDeleteTunnelIpInIpTest.cpp", "CmdDeleteVlanTest.cpp", "ConfigSessionSystemdTest.cpp", + "CmdDeleteBgpPeerGroupTest.cpp", + "CmdDeleteBgpPolicyAsPathListTest.cpp", + "CmdDeleteBgpPolicyCommunityListTest.cpp", "ProfileValidationTest.cpp", ], headers = [ diff --git a/fboss/cli/fboss2/test/config/BgpConfigSessionTest.cpp b/fboss/cli/fboss2/test/config/BgpConfigSessionTest.cpp index efaa968b47704..e2d6d848d3310 100644 --- a/fboss/cli/fboss2/test/config/BgpConfigSessionTest.cpp +++ b/fboss/cli/fboss2/test/config/BgpConfigSessionTest.cpp @@ -2,11 +2,11 @@ #include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" -#include // NOLINT(misc-include-cleaner) #include #include #include +#include #include "fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.h" @@ -75,14 +75,6 @@ TEST_F(BgpConfigSessionTest, setConfedAsn) { EXPECT_EQ(config["local_confed_as_4_byte"].asInt(), 65100); } -TEST_F(BgpConfigSessionTest, setClusterId) { - session_->setClusterId("1.2.3.4"); - EXPECT_EQ(session_->getClusterId(), "1.2.3.4"); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ(config["cluster_id"].asString(), "1.2.3.4"); -} - TEST_F(BgpConfigSessionTest, setListenAddress) { session_->setListenAddress("0.0.0.0"); EXPECT_EQ(session_->getListenAddress(), "0.0.0.0"); @@ -207,160 +199,6 @@ TEST_F(BgpConfigSessionTest, addMultipleNetworks6) { EXPECT_FALSE(config["networks6"][1]["install_to_fib"].asBool()); } -// ==================== Peer Configuration Tests ==================== - -TEST_F(BgpConfigSessionTest, createPeerAndSetRemoteAsn) { - session_->createPeer("2001:db8::1"); - session_->setPeerRemoteAsn("2001:db8::1", 65001); - - auto& config = session_->getBgpConfig(); - ASSERT_TRUE(config.count("peers")); - ASSERT_EQ(config["peers"].size(), 1); - - auto& peer = config["peers"][0]; - EXPECT_EQ(peer["peer_addr"].asString(), "2001:db8::1"); - EXPECT_EQ(peer["remote_as_4_byte"].asInt(), 65001); -} - -TEST_F(BgpConfigSessionTest, setPeerDescription) { - session_->createPeer("10.0.0.1"); - session_->setPeerDescription("10.0.0.1", "Test peer description"); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ( - config["peers"][0]["description"].asString(), "Test peer description"); -} - -TEST_F(BgpConfigSessionTest, setPeerPeerGroupName) { - session_->createPeer("2001:db8::1"); - session_->setPeerGroupName("2001:db8::1", "RSW-FSW-V6"); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ(config["peers"][0]["peer_group_name"].asString(), "RSW-FSW-V6"); -} - -TEST_F(BgpConfigSessionTest, setPeerDisableIpv4AfiTrue) { - session_->createPeer("2001:db8::1"); - session_->setPeerDisableIpv4Afi("2001:db8::1", true); - - auto& config = session_->getBgpConfig(); - EXPECT_TRUE(config["peers"][0]["disable_ipv4_afi"].asBool()); -} - -TEST_F(BgpConfigSessionTest, setPeerDisableIpv4AfiFalse) { - // Critical test: Verify that disable_ipv4_afi: false is preserved - session_->createPeer("2001:db8::1"); - session_->setPeerDisableIpv4Afi("2001:db8::1", false); - - auto& config = session_->getBgpConfig(); - // Must be explicitly set to false, not omitted - ASSERT_TRUE(config["peers"][0].count("disable_ipv4_afi")); - EXPECT_FALSE(config["peers"][0]["disable_ipv4_afi"].asBool()); -} - -TEST_F(BgpConfigSessionTest, setPeerNextHopSelf) { - session_->createPeer("2001:db8::1"); - session_->setPeerNextHopSelf("2001:db8::1", true); - - auto& config = session_->getBgpConfig(); - EXPECT_TRUE(config["peers"][0]["next_hop_self"].asBool()); -} - -TEST_F(BgpConfigSessionTest, setPeerConfedPeer) { - session_->createPeer("2001:db8::1"); - session_->setPeerConfedPeer("2001:db8::1", true); - - auto& config = session_->getBgpConfig(); - EXPECT_TRUE(config["peers"][0]["is_confed_peer"].asBool()); -} - -TEST_F(BgpConfigSessionTest, setPeerPassive) { - session_->createPeer("2001:db8::1"); - session_->setPeerPassive("2001:db8::1", true); - - auto& config = session_->getBgpConfig(); - EXPECT_TRUE(config["peers"][0]["is_passive"].asBool()); -} - -TEST_F(BgpConfigSessionTest, setPeerRrClient) { - session_->createPeer("2001:db8::1"); - session_->setPeerRrClient("2001:db8::1", true); - - auto& config = session_->getBgpConfig(); - EXPECT_TRUE(config["peers"][0]["is_rr_client"].asBool()); -} - -TEST_F(BgpConfigSessionTest, setPeerTimers) { - session_->createPeer("2001:db8::1"); - session_->setPeerHoldTime("2001:db8::1", 90); - session_->setPeerKeepalive("2001:db8::1", 30); - session_->setPeerOutDelay("2001:db8::1", 5); - - auto& config = session_->getBgpConfig(); - ASSERT_TRUE(config["peers"][0].count("bgp_peer_timers")); - - auto& timers = config["peers"][0]["bgp_peer_timers"]; - EXPECT_EQ(timers["hold_time_seconds"].asInt(), 90); - EXPECT_EQ(timers["keep_alive_seconds"].asInt(), 30); - EXPECT_EQ(timers["out_delay_seconds"].asInt(), 5); -} - -TEST_F(BgpConfigSessionTest, setPeerPolicies) { - session_->createPeer("2001:db8::1"); - session_->setPeerIngressPolicy("2001:db8::1", "INGRESS_POLICY"); - session_->setPeerEgressPolicy("2001:db8::1", "EGRESS_POLICY"); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ( - config["peers"][0]["ingress_policy_name"].asString(), "INGRESS_POLICY"); - EXPECT_EQ( - config["peers"][0]["egress_policy_name"].asString(), "EGRESS_POLICY"); -} - -TEST_F(BgpConfigSessionTest, setPeerPreFilterMaxRoutes) { - session_->createPeer("2001:db8::1"); - session_->setPeerPreFilterMaxRoutes("2001:db8::1", 1000); - - auto& config = session_->getBgpConfig(); - ASSERT_TRUE(config["peers"][0].count("pre_filter")); - EXPECT_EQ(config["peers"][0]["pre_filter"]["max_routes"].asInt(), 1000); -} - -TEST_F(BgpConfigSessionTest, setPeerLinkBandwidth) { - session_->createPeer("2001:db8::1"); - session_->setPeerAdvertiseLbw("2001:db8::1", true); - session_->setPeerLbwValue("2001:db8::1", 10000000000); - - auto& config = session_->getBgpConfig(); - EXPECT_TRUE(config["peers"][0]["advertise_link_bandwidth"].asBool()); - EXPECT_EQ(config["peers"][0]["link_bandwidth_bps"].asInt(), 10000000000); -} - -TEST_F(BgpConfigSessionTest, setPeerIpv4OverIpv6Nh) { - session_->createPeer("2001:db8::1"); - session_->setPeerIpv4OverIpv6Nh("2001:db8::1", true); - - auto& config = session_->getBgpConfig(); - EXPECT_TRUE(config["peers"][0]["v4_over_v6_nexthop"].asBool()); -} - -TEST_F(BgpConfigSessionTest, setPeerPeerId) { - session_->createPeer("2001:db8::1"); - session_->setPeerPeerId("2001:db8::1", "fsw001.p001.f01.qzd1:v6:1"); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ( - config["peers"][0]["peer_id"].asString(), "fsw001.p001.f01.qzd1:v6:1"); -} - -TEST_F(BgpConfigSessionTest, setPeerType) { - session_->createPeer("2001:db8::1"); - session_->setPeerType("2001:db8::1", "FSW"); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ(config["peers"][0]["type"].asString(), "FSW"); -} - // ==================== Peer Group Configuration Tests ==================== TEST_F(BgpConfigSessionTest, createPeerGroupAndSetDescription) { @@ -488,14 +326,6 @@ TEST_F(BgpConfigSessionTest, fullRswConfigurationWorkflow) { session_->createPeerGroup("RSW-RTSW-V6"); session_->setPeerGroupDisableIpv4Afi("RSW-RTSW-V6", true); - // Create peer with peer_group_name - session_->createPeer("2401:db00:e50e:1300::20"); - session_->setPeerRemoteAsn("2401:db00:e50e:1300::20", 65000); - session_->setPeerGroupName("2401:db00:e50e:1300::20", "RSW-FSW-V6"); - session_->setPeerDisableIpv4Afi("2401:db00:e50e:1300::20", false); - session_->setPeerPeerId( - "2401:db00:e50e:1300::20", "fsw001.p001.f01.qzd1:v6:1"); - // Verify the complete configuration auto& config = session_->getBgpConfig(); @@ -541,17 +371,6 @@ TEST_F(BgpConfigSessionTest, fullRswConfigurationWorkflow) { } EXPECT_TRUE(foundFswGroup); EXPECT_TRUE(foundRtswGroup); - - // Verify peer - ASSERT_EQ(config["peers"].size(), 1); - auto& peer = config["peers"][0]; - EXPECT_EQ(peer["peer_addr"].asString(), "2401:db00:e50e:1300::20"); - EXPECT_EQ(peer["remote_as_4_byte"].asInt(), 65000); - EXPECT_EQ(peer["peer_group_name"].asString(), "RSW-FSW-V6"); - // Critical: disable_ipv4_afi: false must be preserved - ASSERT_TRUE(peer.count("disable_ipv4_afi")); - EXPECT_FALSE(peer["disable_ipv4_afi"].asBool()); - EXPECT_EQ(peer["peer_id"].asString(), "fsw001.p001.f01.qzd1:v6:1"); } TEST_F(BgpConfigSessionTest, exportConfigProducesValidJson) { @@ -559,8 +378,6 @@ TEST_F(BgpConfigSessionTest, exportConfigProducesValidJson) { session_->setLocalAsn(65000); session_->addNetwork6("2001:db8::/32", "TEST_POLICY", true, 0); session_->createPeerGroup("TEST-GROUP"); - session_->createPeer("2001:db8::1"); - session_->setPeerRemoteAsn("2001:db8::1", 65001); std::string jsonStr = session_->exportConfig(); @@ -571,41 +388,6 @@ TEST_F(BgpConfigSessionTest, exportConfigProducesValidJson) { EXPECT_EQ(parsed["local_as_4_byte"].asInt(), 65000); EXPECT_EQ(parsed["networks6"].size(), 1); EXPECT_EQ(parsed["peer_groups"].size(), 1); - EXPECT_EQ(parsed["peers"].size(), 1); -} - -TEST_F(BgpConfigSessionTest, multiplePeersWithDifferentConfigs) { - // Create multiple peers with different configurations - session_->createPeer("2001:db8::1"); - session_->setPeerRemoteAsn("2001:db8::1", 65001); - session_->setPeerDisableIpv4Afi("2001:db8::1", true); - session_->setPeerNextHopSelf("2001:db8::1", true); - - session_->createPeer("2001:db8::2"); - session_->setPeerRemoteAsn("2001:db8::2", 65002); - session_->setPeerDisableIpv4Afi("2001:db8::2", false); - session_->setPeerPassive("2001:db8::2", true); - - session_->createPeer("10.0.0.1"); - session_->setPeerRemoteAsn("10.0.0.1", 65003); - session_->setPeerRrClient("10.0.0.1", true); - - auto& config = session_->getBgpConfig(); - ASSERT_EQ(config["peers"].size(), 3); - - // Find each peer and verify its config - for (const auto& peer : config["peers"]) { - std::string addr = peer["peer_addr"].asString(); - if (addr == "2001:db8::1") { - EXPECT_TRUE(peer["disable_ipv4_afi"].asBool()); - EXPECT_TRUE(peer["next_hop_self"].asBool()); - } else if (addr == "2001:db8::2") { - EXPECT_FALSE(peer["disable_ipv4_afi"].asBool()); - EXPECT_TRUE(peer["is_passive"].asBool()); - } else if (addr == "10.0.0.1") { - EXPECT_TRUE(peer["is_rr_client"].asBool()); - } - } } TEST_F(BgpConfigSessionTest, stubModePreventsSave) { @@ -623,7 +405,6 @@ TEST_F(BgpConfigSessionTest, stubModeCanClearSession) { // In stub mode, clearSession() should not throw // The actual clearing behavior depends on implementation details session_->setRouterId("10.0.0.1"); - session_->createPeer("2001:db8::1"); EXPECT_EQ(session_->getRouterId(), "10.0.0.1"); @@ -633,24 +414,6 @@ TEST_F(BgpConfigSessionTest, stubModeCanClearSession) { // ==================== Edge Case Tests ==================== -TEST_F(BgpConfigSessionTest, updateExistingPeer) { - // Create peer and set initial values - session_->createPeer("2001:db8::1"); - session_->setPeerRemoteAsn("2001:db8::1", 65001); - session_->setPeerDescription("2001:db8::1", "Initial description"); - - // Update the same peer - session_->setPeerRemoteAsn("2001:db8::1", 65002); - session_->setPeerDescription("2001:db8::1", "Updated description"); - - auto& config = session_->getBgpConfig(); - // Should still only have one peer - ASSERT_EQ(config["peers"].size(), 1); - EXPECT_EQ(config["peers"][0]["remote_as_4_byte"].asInt(), 65002); - EXPECT_EQ( - config["peers"][0]["description"].asString(), "Updated description"); -} - TEST_F(BgpConfigSessionTest, updateExistingPeerGroup) { session_->createPeerGroup("TEST-GROUP"); session_->setPeerGroupDescription("TEST-GROUP", "Initial"); @@ -662,39 +425,10 @@ TEST_F(BgpConfigSessionTest, updateExistingPeerGroup) { EXPECT_EQ(config["peer_groups"][0]["description"].asString(), "Updated"); } -TEST_F(BgpConfigSessionTest, ipv4PeerAddress) { - // Test with IPv4 peer address - session_->createPeer("192.168.1.1"); - session_->setPeerRemoteAsn("192.168.1.1", 65000); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ(config["peers"][0]["peer_addr"].asString(), "192.168.1.1"); -} - -TEST_F(BgpConfigSessionTest, specialCharactersInDescription) { - session_->createPeer("2001:db8::1"); - session_->setPeerDescription( - "2001:db8::1", "Test \"quotes\" and 'apostrophes' and special: !@#$%"); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ( - config["peers"][0]["description"].asString(), - "Test \"quotes\" and 'apostrophes' and special: !@#$%"); -} - TEST_F(BgpConfigSessionTest, largeAsnNumber) { // Test with 4-byte ASN session_->setLocalAsn(4200000000); EXPECT_EQ(session_->getLocalAsn(), 4200000000); } -TEST_F(BgpConfigSessionTest, largeLinkBandwidth) { - // Test with 100G bandwidth - session_->createPeer("2001:db8::1"); - session_->setPeerLbwValue("2001:db8::1", 100000000000); - - auto& config = session_->getBgpConfig(); - EXPECT_EQ(config["peers"][0]["link_bandwidth_bps"].asInt(), 100000000000); -} - } // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdConfigBgpGlobalTest.cpp b/fboss/cli/fboss2/test/config/CmdConfigBgpGlobalTest.cpp new file mode 100644 index 0000000000000..15f543608052b --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdConfigBgpGlobalTest.cpp @@ -0,0 +1,173 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "fboss/cli/fboss2/commands/config/protocol/bgp/global/CmdConfigProtocolBgpGlobal.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; + +namespace facebook::fboss { + +// The global dispatcher only touches the BGP side of ConfigSession, which +// seeds from thrift schema defaults when neither a staged session nor a +// system bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpNeighborTest). +class CmdConfigBgpGlobalTestFixture : public CmdConfigTestBase { + public: + CmdConfigBgpGlobalTestFixture() + : CmdConfigTestBase("bgp_global_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string run(const std::vector& tokens) { + CmdConfigProtocolBgpGlobal cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpGlobalConfig(tokens)); + } + + bgp::thrift::BgpConfig& config() { + return ConfigSession::getInstance().getBgpConfig(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpGlobalConfig (arg) validation +// ============================================================================== + +TEST_F(CmdConfigBgpGlobalTestFixture, argValidation) { + auto parsed = BgpGlobalConfig({"router-id", "10.0.0.1"}); + EXPECT_EQ(parsed.attr(), "router-id"); + EXPECT_EQ(parsed.values(), std::vector({"10.0.0.1"})); + + // Invalid: empty, unknown attribute. + EXPECT_THROW(BgpGlobalConfig({}), std::invalid_argument); + EXPECT_THROW(BgpGlobalConfig({"no-such-attr", "1"}), std::invalid_argument); +} + +// ============================================================================== +// router-id +// ============================================================================== + +TEST_F(CmdConfigBgpGlobalTestFixture, routerIdAccepted) { + auto result = run({"router-id", "10.0.0.1"}); + EXPECT_THAT(result, HasSubstr("Successfully set BGP router-id to: 10.0.0.1")); + EXPECT_EQ(*config().router_id(), "10.0.0.1"); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F(CmdConfigBgpGlobalTestFixture, routerIdInvalidRejected) { + const auto before = *config().router_id(); + auto result = run({"router-id", "not-an-ip"}); + EXPECT_THAT(result, HasSubstr("Invalid router-id address")); + EXPECT_EQ(*config().router_id(), before); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpGlobalTestFixture, routerIdV6Rejected) { + const auto before = *config().router_id(); + auto result = run({"router-id", "2001:db8::1"}); + EXPECT_THAT(result, HasSubstr("requires an IPv4 address")); + EXPECT_EQ(*config().router_id(), before); + EXPECT_FALSE(sessionFileExists()); +} + +TEST_F(CmdConfigBgpGlobalTestFixture, routerIdArityRejected) { + EXPECT_THAT(run({"router-id"}), HasSubstr("requires ")); + EXPECT_THAT( + run({"router-id", "1.1.1.1", "2.2.2.2"}), + HasSubstr("requires ")); + EXPECT_FALSE(sessionFileExists()); +} + +TEST_F(CmdConfigBgpGlobalTestFixture, rejectedRouterIdKeepsStagedValue) { + run({"router-id", "10.0.0.1"}); + auto result = run({"router-id", "not-an-ip"}); + EXPECT_THAT(result, HasSubstr("Invalid router-id address")); + EXPECT_EQ(*config().router_id(), "10.0.0.1"); +} + +// ============================================================================== +// switch-limit-overload-protection-mode +// ============================================================================== + +TEST_F(CmdConfigBgpGlobalTestFixture, overloadProtectionModeIntegerAccepted) { + auto result = run({"switch-limit-overload-protection-mode", "1"}); + EXPECT_THAT(result, HasSubstr("overload_protection_mode to: 1")); + ASSERT_TRUE(config().switch_limit_config().has_value()); + EXPECT_EQ( + *config().switch_limit_config()->overload_protection_mode(), + bgp::thrift::OverloadProtectionMode::DROP_EXCESS_PREFIXES); + + result = run({"switch-limit-overload-protection-mode", "2"}); + EXPECT_THAT(result, HasSubstr("overload_protection_mode to: 2")); + EXPECT_EQ( + *config().switch_limit_config()->overload_protection_mode(), + bgp::thrift::OverloadProtectionMode::APPLY_GOLDEN_PREFIX_POLICY); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F(CmdConfigBgpGlobalTestFixture, overloadProtectionModeNameAccepted) { + auto result = + run({"switch-limit-overload-protection-mode", "DROP_EXCESS_PREFIXES"}); + // The success message prints the integer so the j2c round-trip is stable. + EXPECT_THAT(result, HasSubstr("overload_protection_mode to: 1")); + ASSERT_TRUE(config().switch_limit_config().has_value()); + EXPECT_EQ( + *config().switch_limit_config()->overload_protection_mode(), + bgp::thrift::OverloadProtectionMode::DROP_EXCESS_PREFIXES); + + result = run( + {"switch-limit-overload-protection-mode", "APPLY_GOLDEN_PREFIX_POLICY"}); + EXPECT_THAT(result, HasSubstr("overload_protection_mode to: 2")); + EXPECT_EQ( + *config().switch_limit_config()->overload_protection_mode(), + bgp::thrift::OverloadProtectionMode::APPLY_GOLDEN_PREFIX_POLICY); +} + +TEST_F(CmdConfigBgpGlobalTestFixture, overloadProtectionModeInvalidRejected) { + for (const auto& bad : {"0", "-5", "999", "fast"}) { + auto result = run({"switch-limit-overload-protection-mode", bad}); + EXPECT_THAT(result, HasSubstr("is not a valid mode")) << bad; + // The rejection lists every valid name. + EXPECT_THAT(result, HasSubstr("DROP_EXCESS_PREFIXES")) << bad; + EXPECT_THAT(result, HasSubstr("APPLY_GOLDEN_PREFIX_POLICY")) << bad; + } + // Validation happens before ensure(): a rejected value must not create the + // nested switch_limit_config. + EXPECT_FALSE(config().switch_limit_config().has_value()); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F( + CmdConfigBgpGlobalTestFixture, + rejectedOverloadProtectionModeKeepsStagedValue) { + run({"switch-limit-overload-protection-mode", "2"}); + auto result = run({"switch-limit-overload-protection-mode", "999"}); + EXPECT_THAT(result, HasSubstr("is not a valid mode")); + ASSERT_TRUE(config().switch_limit_config().has_value()); + EXPECT_EQ( + *config().switch_limit_config()->overload_protection_mode(), + bgp::thrift::OverloadProtectionMode::APPLY_GOLDEN_PREFIX_POLICY); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdConfigBgpNeighborTest.cpp b/fboss/cli/fboss2/test/config/CmdConfigBgpNeighborTest.cpp new file mode 100644 index 0000000000000..141e879b7f48f --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdConfigBgpNeighborTest.cpp @@ -0,0 +1,597 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "configerator/structs/neteng/fboss/bgp/if/gen-cpp2/bgp_attr_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; +using facebook::neteng::fboss::bgp_attr::AddPath; + +namespace facebook::fboss { + +// The neighbor dispatcher only touches the BGP side of ConfigSession, which +// seeds from thrift schema defaults when neither a staged session nor a +// system bgpcpp.conf exists — so no seed agent config is needed (mirrors +// BgpConfigSessionTest). +class CmdConfigBgpNeighborTestFixture : public CmdConfigTestBase { + public: + CmdConfigBgpNeighborTestFixture() + : CmdConfigTestBase("bgp_neighbor_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string run(const std::vector& tokens) { + CmdConfigProtocolBgpNeighbor cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpNeighborConfig(tokens)); + } + + const std::vector& peers() { + return *ConfigSession::getInstance().getBgpConfig().peers(); + } + + bgp::thrift::BgpConfig& config() { + return ConfigSession::getInstance().getBgpConfig(); + } + + // Stage a peer group directly in the session config (the peer-group command + // is out of scope here); returns a reference valid until the next insert. + bgp::thrift::PeerGroup& addPeerGroup(const std::string& name) { + auto& groups = config().peer_groups().ensure(); + groups.emplace_back(); + groups.back().name() = name; + return groups.back(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpNeighborConfig (arg) validation +// ============================================================================== + +TEST_F(CmdConfigBgpNeighborTestFixture, argValidation) { + // Bare create. + EXPECT_EQ(BgpNeighborConfig({"10.0.0.1"}).peerAddr(), "10.0.0.1"); + EXPECT_TRUE(BgpNeighborConfig({"10.0.0.1"}).attr().empty()); + + // Address normalization. + EXPECT_EQ(BgpNeighborConfig({"2001:DB8::1"}).peerAddr(), "2001:db8::1"); + + // Prefix notation (passive listening sessions); host bits are cleared the + // way bgpd keys dynamic peers, so spellings that differ only there agree. + EXPECT_EQ(BgpNeighborConfig({"2001:db8::/64"}).peerAddr(), "2001:db8::/64"); + EXPECT_EQ(BgpNeighborConfig({"10.0.0.1/24"}).peerAddr(), "10.0.0.0/24"); + EXPECT_EQ( + BgpNeighborConfig({"2001:db8::1234/64"}).peerAddr(), "2001:db8::/64"); + + // Single-token attribute. + auto singleTok = BgpNeighborConfig({"10.0.0.1", "remote-asn", "65000"}); + EXPECT_EQ(singleTok.attr(), "remote-asn"); + EXPECT_EQ(singleTok.values(), std::vector({"65000"})); + + // Two-token attribute wins longest-prefix match. + auto twoTok = BgpNeighborConfig({"10.0.0.1", "timers", "hold-time", "30"}); + EXPECT_EQ(twoTok.attr(), "timers hold-time"); + EXPECT_EQ(twoTok.values(), std::vector({"30"})); + + // Invalid: empty, bad address, unknown attribute, incomplete group. + EXPECT_THROW(BgpNeighborConfig({}), std::invalid_argument); + EXPECT_THROW(BgpNeighborConfig({"not-an-ip"}), std::invalid_argument); + EXPECT_THROW( + BgpNeighborConfig({"10.0.0.1", "no-such-attr", "1"}), + std::invalid_argument); + EXPECT_THROW( + BgpNeighborConfig({"10.0.0.1", "timers"}), std::invalid_argument); +} + +// ============================================================================== +// Attribute handlers +// ============================================================================== + +TEST_F(CmdConfigBgpNeighborTestFixture, bareCreate) { + auto result = run({"10.0.0.1"}); + EXPECT_THAT(result, HasSubstr("Successfully created BGP neighbor 10.0.0.1")); + ASSERT_EQ(peers().size(), 1); + EXPECT_EQ(*peers()[0].peer_addr(), "10.0.0.1"); + // bgpd aborts at config load on unparseable (empty) address fields, so a + // new peer must be born with parseable any-addresses of the right family. + EXPECT_EQ(*peers()[0].local_addr(), "0.0.0.0"); + EXPECT_EQ(*peers()[0].next_hop4(), "0.0.0.0"); + EXPECT_EQ(*peers()[0].next_hop6(), "::"); + EXPECT_TRUE(sessionFileExists()); + + run({"2001:db8::1"}); + ASSERT_EQ(peers().size(), 2); + EXPECT_EQ(*peers()[1].local_addr(), "::"); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, setRemoteAsn) { + auto result = run({"10.0.0.1", "remote-asn", "65000"}); + EXPECT_THAT(result, HasSubstr("65000")); + ASSERT_EQ(peers().size(), 1); + EXPECT_EQ(peers()[0].remote_as_4_byte().value_or(0), 65000); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, timersAccumulate) { + run({"10.0.0.1", "timers", "hold-time", "90"}); + run({"10.0.0.1", "timers", "keepalive", "30"}); + run({"10.0.0.1", "graceful-restart", "restart-time", "120"}); + ASSERT_EQ(peers().size(), 1); + const auto& timers = *peers()[0].bgp_peer_timers(); + EXPECT_EQ(*timers.hold_time_seconds(), 90); + EXPECT_EQ(*timers.keep_alive_seconds(), 30); + EXPECT_EQ(timers.graceful_restart_seconds().value_or(0), 120); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, addPathMerge) { + run({"10.0.0.1", "add-path", "receive", "true"}); + EXPECT_EQ(peers()[0].add_path().value_or(AddPath(0)), AddPath::RECEIVE); + + run({"10.0.0.1", "add-path", "send", "true"}); + EXPECT_EQ(peers()[0].add_path().value_or(AddPath(0)), AddPath::BOTH); + + run({"10.0.0.1", "add-path", "receive", "false"}); + EXPECT_EQ(peers()[0].add_path().value_or(AddPath(0)), AddPath::SEND); + + run({"10.0.0.1", "add-path", "send", "false"}); + EXPECT_FALSE(peers()[0].add_path().has_value()); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, passive) { + // is_passive is the only session-mode knob bgpd has: true = listen only, + // false = listen and connect. The CLI exposes the flag as-is. + run({"10.0.0.1", "passive", "true"}); + EXPECT_TRUE(peers()[0].is_passive().value_or(false)); + + run({"10.0.0.1", "passive", "false"}); + EXPECT_FALSE(peers()[0].is_passive().value_or(true)); + + EXPECT_THAT(run({"10.0.0.1", "passive", "BOTH"}), HasSubstr("Invalid")); + EXPECT_FALSE(peers()[0].is_passive().value_or(true)); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, boolAndStringAttributes) { + run({"10.0.0.1", "rr-client", "true"}); + run({"10.0.0.1", "confed-peer", "false"}); + run({"10.0.0.1", "redistribute-peer", "true"}); + run({"10.0.0.1", "enhanced-route-refresh", "true"}); + run({"10.0.0.1", "route-refresh", "true"}); + run({"10.0.0.1", "remove-private-as", "true"}); + run({"10.0.0.1", "enforce-first-as", "false"}); + run({"10.0.0.1", "afi", "disable-ipv4-afi", "true"}); + run({"10.0.0.1", "afi", "disable-ipv6-afi", "false"}); + run({"10.0.0.1", "afi", "ipv4-over-ipv6-nh", "true"}); + run({"10.0.0.1", "graceful-restart", "stateful-ha", "true"}); + run({"10.0.0.1", "peer-group", "MY-GROUP"}); + run({"10.0.0.1", "peer-tag", "FSW"}); + run({"10.0.0.1", "ingress-policy", "IN_POLICY"}); + run({"10.0.0.1", "egress-policy", "OUT_POLICY"}); + run({"10.0.0.1", "bind-addr", "address", "10.0.0.2"}); + run({"10.0.0.1", "local-asn", "64512"}); + run({"10.0.0.1", "description", "spine", "peer"}); + + ASSERT_EQ(peers().size(), 1); + const auto& peer = peers()[0]; + EXPECT_TRUE(peer.is_rr_client().value_or(false)); + EXPECT_FALSE(peer.is_confed_peer().value_or(true)); + EXPECT_TRUE(peer.is_redistribute_peer().value_or(false)); + EXPECT_TRUE(peer.enhanced_route_refresh().value_or(false)); + EXPECT_TRUE(peer.route_refresh().value_or(false)); + EXPECT_TRUE(peer.remove_private_as().value_or(false)); + EXPECT_FALSE(peer.enforce_first_as().value_or(true)); + EXPECT_TRUE(peer.disable_ipv4_afi().value_or(false)); + EXPECT_FALSE(peer.disable_ipv6_afi().value_or(true)); + EXPECT_TRUE(peer.v4_over_v6_nexthop().value_or(false)); + EXPECT_TRUE(peer.enable_stateful_ha().value_or(false)); + EXPECT_EQ(peer.peer_group_name().value_or(""), "MY-GROUP"); + EXPECT_EQ(peer.peer_tag().value_or(""), "FSW"); + EXPECT_EQ(peer.ingress_policy_name().value_or(""), "IN_POLICY"); + EXPECT_EQ(peer.egress_policy_name().value_or(""), "OUT_POLICY"); + EXPECT_EQ(*peer.local_addr(), "10.0.0.2"); + EXPECT_EQ(peer.local_as_4_byte().value_or(0), 64512); + // Multi-token description is re-joined. + EXPECT_EQ(peer.description().value_or(""), "spine peer"); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, parityAttributes) { + // Attributes carried over from the deleted per-attribute peer commands. + run({"10.0.0.1", "next-hop4", "10.1.1.1"}); + run({"10.0.0.1", "next-hop6", "2001:db8::a"}); + run({"10.0.0.1", "next-hop-self", "true"}); + run({"10.0.0.1", "peer-id", "test-peer:v6:1"}); + run({"10.0.0.1", "type", "BGP_MONITOR"}); + run({"10.0.0.1", "link-bandwidth", "100G"}); + EXPECT_EQ(peers()[0].link_bandwidth_bps().value_or(""), "100G"); + // Every spelling bgpd's own parser takes must be accepted, or an existing + // config could not be recreated through the CLI. + run({"10.0.0.1", "link-bandwidth", "1.5G"}); + EXPECT_EQ(peers()[0].link_bandwidth_bps().value_or(""), "1.5G"); + run({"10.0.0.1", "link-bandwidth", "150g"}); + EXPECT_EQ(peers()[0].link_bandwidth_bps().value_or(""), "150g"); + run({"10.0.0.1", "link-bandwidth", "auto"}); + EXPECT_EQ(peers()[0].link_bandwidth_bps().value_or(""), "auto"); + run({"10.0.0.1", "link-bandwidth", "100G"}); + run({"10.0.0.1", "advertise-lbw", "1"}); + run({"10.0.0.1", "receive-lbw", "ACCEPT"}); // enum value name accepted + run( + {"10.0.0.1", + "advertise-lbw", + "true"}); // bool alias (old peer cmd compat) + run({"10.0.0.1", "timers", "withdraw-unprog-delay", "5"}); + run({"10.0.0.1", "max-route", "pre-warning-threshold", "100"}); + run({"10.0.0.1", "max-route", "pre-warning-only", "true"}); + run({"10.0.0.1", "max-route", "post-warning-only", "false"}); + + ASSERT_EQ(peers().size(), 1); + const auto& peer = peers()[0]; + EXPECT_EQ(*peer.next_hop4(), "10.1.1.1"); + EXPECT_EQ(*peer.next_hop6(), "2001:db8::a"); + EXPECT_TRUE(peer.next_hop_self().value_or(false)); + EXPECT_EQ(peer.peer_id().value_or(""), "test-peer:v6:1"); + EXPECT_EQ(peer.type().value_or(""), "BGP_MONITOR"); + EXPECT_EQ(peer.link_bandwidth_bps().value_or(""), "100G"); + EXPECT_EQ( + static_cast(peer.advertise_link_bandwidth().value_or( + facebook::neteng::fboss::bgp_attr::AdvertiseLinkBandwidth::DISABLE)), + 1); + EXPECT_EQ( + static_cast(peer.receive_link_bandwidth().value_or( + facebook::neteng::fboss::bgp_attr::ReceiveLinkBandwidth::DISABLE)), + 1); + EXPECT_EQ(*peer.bgp_peer_timers()->withdraw_unprog_delay_seconds(), 5); + EXPECT_EQ(*peer.pre_filter()->warning_limit(), 100); + EXPECT_TRUE(*peer.pre_filter()->warning_only()); + EXPECT_FALSE(*peer.post_filter()->warning_only()); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, valueValidation) { + // ASN must fit in 4 bytes (RFC 6793). + EXPECT_THAT( + run({"10.0.0.1", "remote-asn", "4294967296"}), HasSubstr("Invalid")); + EXPECT_THAT( + run({"10.0.0.1", "local-asn", "18446744073709551615"}), + HasSubstr("Invalid")); + // IP-valued attributes validate the address and its family. + EXPECT_THAT( + run({"10.0.0.1", "bind-addr", "address", "not-an-ip"}), + HasSubstr("Invalid")); + EXPECT_THAT(run({"10.0.0.1", "next-hop4", "2001:db8::a"}), HasSubstr("IPv4")); + EXPECT_THAT(run({"10.0.0.1", "next-hop6", "10.1.1.1"}), HasSubstr("IPv6")); + // Enum-valued attributes reject non-member modes but accept value names + // and true/false aliases (compat with the replaced boolean peer commands). + EXPECT_THAT( + run({"10.0.0.1", "advertise-lbw", "99"}), HasSubstr("not a valid mode")); + // link-bandwidth rejects exactly what bgpd's parser rejects. + EXPECT_THAT(run({"10.0.0.1", "link-bandwidth", "10Q"}), HasSubstr("Invalid")); + EXPECT_THAT(run({"10.0.0.1", "link-bandwidth", "G10"}), HasSubstr("Invalid")); + EXPECT_THAT( + run({"10.0.0.1", "link-bandwidth", "1.2.3G"}), HasSubstr("Invalid")); + EXPECT_THAT(run({"10.0.0.1", "link-bandwidth", "G"}), HasSubstr("Invalid")); + EXPECT_THAT(run({"10.0.0.1", "link-bandwidth", "10T"}), HasSubstr("Invalid")); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpNeighborTestFixture, routeLimits) { + run({"10.0.0.1", "max-route", "pre-filter", "45000"}); + run({"10.0.0.1", "max-route", "post-filter", "50000"}); + run({"10.0.0.1", "max-route", "post-warning-threshold", "48000"}); + const auto& peer = peers()[0]; + EXPECT_EQ(*peer.pre_filter()->max_routes(), 45000); + EXPECT_EQ(*peer.post_filter()->max_routes(), 50000); + EXPECT_EQ(*peer.post_filter()->warning_limit(), 48000); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, normalizedAddressesShareOnePeer) { + run({"2001:DB8::1", "remote-asn", "65000"}); + run({"2001:db8:0:0::1", "timers", "hold-time", "30"}); + ASSERT_EQ(peers().size(), 1); + EXPECT_EQ(*peers()[0].peer_addr(), "2001:db8::1"); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, prefixSpellingsShareOnePeer) { + // bgpd masks a dynamic peer's prefix at load, so these are one neighbor to + // it; a second CLI spelling must update that peer, not create a twin. + run({"10.0.0.1/24", "remote-asn", "65000"}); + run({"10.0.0.2/24", "passive", "true"}); + ASSERT_EQ(peers().size(), 1); + EXPECT_EQ(*peers()[0].peer_addr(), "10.0.0.0/24"); + EXPECT_EQ(peers()[0].remote_as_4_byte().value_or(0), 65000); + EXPECT_TRUE(peers()[0].is_passive().value_or(false)); +} + +// ============================================================================== +// Inherited values: bgpd resolves timers / route limits per STRUCT, so the +// first field set on a peer must not silently zero the rest of the struct. +// ============================================================================== + +TEST_F(CmdConfigBgpNeighborTestFixture, timersSeededFromPeerGroup) { + auto& group = addPeerGroup("SPINE"); + bgp::thrift::BgpPeerTimers groupTimers; + groupTimers.hold_time_seconds() = 90; + groupTimers.keep_alive_seconds() = 30; + groupTimers.out_delay_seconds() = 5; + groupTimers.graceful_restart_seconds() = 120; + group.bgp_peer_timers() = groupTimers; + + run({"10.0.0.1", "peer-group", "SPINE"}); + run({"10.0.0.1", "graceful-restart", "restart-time", "60"}); + + ASSERT_EQ(peers().size(), 1); + const auto& timers = *peers()[0].bgp_peer_timers(); + EXPECT_EQ(*timers.hold_time_seconds(), 90); + EXPECT_EQ(*timers.keep_alive_seconds(), 30); + EXPECT_EQ(*timers.out_delay_seconds(), 5); + EXPECT_EQ(timers.graceful_restart_seconds().value_or(0), 60); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, timersSeededFromGlobalWithoutGroup) { + config().hold_time() = 180; + run({"10.0.0.1", "graceful-restart", "restart-time", "60"}); + + const auto& timers = *peers()[0].bgp_peer_timers(); + EXPECT_EQ(*timers.hold_time_seconds(), 180); + EXPECT_EQ(*timers.keep_alive_seconds(), 60); + EXPECT_EQ(*timers.out_delay_seconds(), 0); + EXPECT_EQ(timers.graceful_restart_seconds().value_or(0), 60); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, timersNotReseededOncePresent) { + auto& group = addPeerGroup("SPINE"); + bgp::thrift::BgpPeerTimers groupTimers; + groupTimers.hold_time_seconds() = 90; + groupTimers.keep_alive_seconds() = 30; + group.bgp_peer_timers() = groupTimers; + + run({"10.0.0.1", "peer-group", "SPINE"}); + run({"10.0.0.1", "timers", "hold-time", "45"}); + run({"10.0.0.1", "timers", "out-delay", "2"}); + + const auto& timers = *peers()[0].bgp_peer_timers(); + EXPECT_EQ(*timers.hold_time_seconds(), 45); + EXPECT_EQ(*timers.keep_alive_seconds(), 30); + EXPECT_EQ(*timers.out_delay_seconds(), 2); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, routeLimitSeededFromPeerGroup) { + auto& group = addPeerGroup("SPINE"); + bgp::thrift::RouteLimit limit; + limit.max_routes() = 45000; + limit.warning_limit() = 40000; + group.pre_filter() = limit; + + run({"10.0.0.1", "peer-group", "SPINE"}); + run({"10.0.0.1", "max-route", "pre-warning-only", "true"}); + + const auto& pre = *peers()[0].pre_filter(); + EXPECT_EQ(*pre.max_routes(), 45000); + EXPECT_EQ(*pre.warning_limit(), 40000); + EXPECT_TRUE(*pre.warning_only()); + // post_filter was not touched and stays inherited. + EXPECT_FALSE(peers()[0].post_filter().has_value()); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, routeLimitSeededUnlimitedWithoutGroup) { + // Neither the peer nor its group carries a RouteLimit: bgpd applies no cap + // at all, so the struct created for a warning-only/threshold edit must keep + // that (max_routes 0 = unlimited) rather than the 12000 thrift default. + run({"10.0.0.1", "max-route", "pre-warning-only", "true"}); + const auto& pre = *peers()[0].pre_filter(); + EXPECT_EQ(*pre.max_routes(), 0); + EXPECT_EQ(*pre.warning_limit(), 0); + EXPECT_TRUE(*pre.warning_only()); + EXPECT_FALSE(peers()[0].post_filter().has_value()); + + run({"10.0.0.1", "max-route", "post-warning-threshold", "30000"}); + const auto& post = *peers()[0].post_filter(); + EXPECT_EQ(*post.max_routes(), 0); + EXPECT_EQ(*post.warning_limit(), 30000); + EXPECT_FALSE(*post.warning_only()); + + // A group with no struct of its own is the same as no group. + addPeerGroup("SPINE"); + run({"10.0.0.2", "peer-group", "SPINE"}); + run({"10.0.0.2", "max-route", "pre-warning-threshold", "30000"}); + EXPECT_EQ(*peers()[1].pre_filter()->max_routes(), 0); + EXPECT_EQ(*peers()[1].pre_filter()->warning_limit(), 30000); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, routeLimitNotReseededOncePresent) { + run({"10.0.0.1", "max-route", "pre-filter", "45000"}); + run({"10.0.0.1", "max-route", "pre-warning-threshold", "30000"}); + EXPECT_EQ(*peers()[0].pre_filter()->max_routes(), 45000); + EXPECT_EQ(*peers()[0].pre_filter()->warning_limit(), 30000); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, ttlSecurityHopsRange) { + // bgpd throws at config load outside kMin/kMaxTtlSecurityHops (1..255). + EXPECT_THAT(run({"10.0.0.1", "ttl-security-hops", "0"}), HasSubstr("Error")); + EXPECT_THAT( + run({"10.0.0.1", "ttl-security-hops", "256"}), HasSubstr("Error")); + EXPECT_THAT(run({"10.0.0.1", "ttl-security-hops", "x"}), HasSubstr("Error")); + EXPECT_TRUE(peers().empty()) + << "rejected first attribute must not leave a half-created peer"; + run({"10.0.0.1", "ttl-security-hops", "1"}); + EXPECT_EQ(peers()[0].ttl_security_hops().value_or(0), 1); + run({"10.0.0.1", "ttl-security-hops", "255"}); + EXPECT_EQ(peers()[0].ttl_security_hops().value_or(0), 255); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, addPathMergesWithPeerGroupValue) { + addPeerGroup("SPINE").add_path() = AddPath::SEND; + run({"10.0.0.1", "peer-group", "SPINE"}); + + // Enabling the other direction keeps the group's SEND. + run({"10.0.0.1", "add-path", "receive", "true"}); + EXPECT_EQ(peers()[0].add_path().value_or(AddPath(0)), AddPath::BOTH); + + // Turning receive back off leaves the peer at the group's value explicitly. + run({"10.0.0.1", "add-path", "receive", "false"}); + EXPECT_EQ(peers()[0].add_path().value_or(AddPath(0)), AddPath::SEND); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, addPathCannotDisableGroupDirection) { + // The enum has no disabled value and an unset peer field inherits the + // group, so "send false" here would be a no-op reported as success. + addPeerGroup("SPINE").add_path() = AddPath::SEND; + run({"10.0.0.1", "peer-group", "SPINE"}); + + auto result = run({"10.0.0.1", "add-path", "send", "false"}); + EXPECT_THAT(result, HasSubstr("Error")); + EXPECT_THAT(result, HasSubstr("SPINE")); + EXPECT_FALSE(peers()[0].add_path().has_value()); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, addPathNarrowsGroupBothToOneDirection) { + addPeerGroup("SPINE").add_path() = AddPath::BOTH; + run({"10.0.0.1", "peer-group", "SPINE"}); + + // Dropping one direction of BOTH is representable: the peer gets an + // explicit RECEIVE that overrides the group. + run({"10.0.0.1", "add-path", "send", "false"}); + EXPECT_EQ(peers()[0].add_path().value_or(AddPath(0)), AddPath::RECEIVE); + // ... but the remaining direction cannot be switched off (see above). + EXPECT_THAT( + run({"10.0.0.1", "add-path", "receive", "false"}), HasSubstr("Error")); + EXPECT_EQ(peers()[0].add_path().value_or(AddPath(0)), AddPath::RECEIVE); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, timerRanges) { + // Hold time: 0 or 3..65535 (bgpd refuses 1-2; the OPEN field is 16 bits). + EXPECT_THAT( + run({"10.0.0.1", "timers", "hold-time", "1"}), HasSubstr("Error")); + EXPECT_THAT( + run({"10.0.0.1", "timers", "hold-time", "2"}), HasSubstr("Error")); + EXPECT_THAT( + run({"10.0.0.1", "timers", "hold-time", "65536"}), HasSubstr("Error")); + EXPECT_FALSE(sessionFileExists()); + run({"10.0.0.1", "timers", "hold-time", "0"}); + EXPECT_EQ(*peers()[0].bgp_peer_timers()->hold_time_seconds(), 0); + run({"10.0.0.1", "timers", "hold-time", "3"}); + EXPECT_EQ(*peers()[0].bgp_peer_timers()->hold_time_seconds(), 3); + run({"10.0.0.1", "timers", "hold-time", "65535"}); + EXPECT_EQ(*peers()[0].bgp_peer_timers()->hold_time_seconds(), 65535); + + // Graceful restart: 0..4095 (12-bit RFC 4724 field). + EXPECT_THAT( + run({"10.0.0.1", "graceful-restart", "restart-time", "4096"}), + HasSubstr("Error")); + run({"10.0.0.1", "graceful-restart", "restart-time", "4095"}); + EXPECT_EQ( + peers()[0].bgp_peer_timers()->graceful_restart_seconds().value_or(0), + 4095); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, rejectedValueLeavesPeerUntouched) { + run({"10.0.0.1", "remote-asn", "65000"}); + // A rejected timer must not leave a seeded timers struct behind. + EXPECT_THAT( + run({"10.0.0.1", "timers", "hold-time", "1"}), HasSubstr("Error")); + EXPECT_FALSE(peers()[0].bgp_peer_timers().has_value()); + // Likewise a rejected route-limit value must not leave a seeded RouteLimit. + EXPECT_THAT( + run({"10.0.0.1", "max-route", "pre-warning-threshold", "-1"}), + HasSubstr("Error")); + EXPECT_FALSE(peers()[0].pre_filter().has_value()); + EXPECT_EQ(peers().size(), 1); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, distinctAddressesCreateDistinctPeers) { + run({"10.0.0.1", "remote-asn", "65000"}); + run({"10.0.0.2", "remote-asn", "65001"}); + EXPECT_EQ(peers().size(), 2); +} + +// ============================================================================== +// Help text — generated from the dispatch table and wired into `--help` +// ============================================================================== + +TEST_F(CmdConfigBgpNeighborTestFixture, attributeHelpListsEveryAttribute) { + const auto help = bgpNeighborAttributeHelp(); + const auto names = bgpNeighborAttributeNames(); + ASSERT_FALSE(names.empty()); + for (const auto& name : names) { + // One line per attribute: indented name, then its value placeholder. + EXPECT_THAT(help, HasSubstr("\n " + name + " ")) << name; + // ... and every listed spelling is one the parser accepts. + std::vector tokens = {"10.0.0.1"}; + folly::split(' ', name, tokens); + tokens.emplace_back("x"); + EXPECT_EQ(BgpNeighborConfig(tokens).attr(), name); + } + // Spot-check the value shapes users most need to see. + EXPECT_THAT(help, HasSubstr("passive")); + EXPECT_THAT(help, HasSubstr("")); + EXPECT_THAT(help, HasSubstr("timers hold-time")); + EXPECT_THAT(help, HasSubstr("0 or 3-65535")); + EXPECT_THAT(help, HasSubstr("graceful-restart restart-time")); + EXPECT_THAT(help, HasSubstr("0-4095")); + EXPECT_THAT(help, HasSubstr("| auto")); + EXPECT_THAT(help, HasSubstr("not supported")); + EXPECT_THAT(help, Not(HasSubstr("connect-mode"))); +} + +TEST_F(CmdConfigBgpNeighborTestFixture, cliHelpFooterCarriesAttributeList) { + CLI::App app; + std::vector args; + CmdConfigProtocolBgpNeighborTraits::addCliArg(app, args); + EXPECT_EQ(app.get_footer(), bgpNeighborAttributeHelp()); + EXPECT_THAT(app.help(), HasSubstr("timers hold-time")); +} + +// ============================================================================== +// Reject paths — the error is surfaced and nothing is persisted +// ============================================================================== + +TEST_F(CmdConfigBgpNeighborTestFixture, invalidValuesRejected) { + EXPECT_THAT(run({"10.0.0.1", "rr-client", "maybe"}), HasSubstr("Invalid")); + EXPECT_THAT( + run({"10.0.0.1", "timers", "hold-time", "-1"}), + HasSubstr("non-negative")); + EXPECT_THAT( + run({"10.0.0.1", "remote-asn", "not-a-number"}), HasSubstr("Invalid")); + EXPECT_THAT( + run({"10.0.0.1", "max-route", "pre-filter", "-5"}), + HasSubstr("non-negative")); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpNeighborTestFixture, unsupportedAttributesRejected) { + // No thrift representation (or not per-neighbor) — refuse instead of + // persisting dead config. + EXPECT_THAT( + run({"10.0.0.1", "peer-port", "1179"}), HasSubstr("not supported")); + EXPECT_THAT( + run({"10.0.0.1", "bind-addr", "port", "1179"}), + HasSubstr("not supported")); + EXPECT_THAT( + run({"10.0.0.1", "afi", "ipv4-labeled-unicast", "true"}), + HasSubstr("not supported")); + EXPECT_THAT( + run({"10.0.0.1", "afi", "ipv6-labeled-unicast", "true"}), + HasSubstr("not supported")); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdConfigBgpPeerGroupTest.cpp b/fboss/cli/fboss2/test/config/CmdConfigBgpPeerGroupTest.cpp new file mode 100644 index 0000000000000..a80c525bb273e --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdConfigBgpPeerGroupTest.cpp @@ -0,0 +1,445 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "configerator/structs/neteng/fboss/bgp/if/gen-cpp2/bgp_attr_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; +using facebook::neteng::fboss::bgp_attr::AddPath; + +namespace facebook::fboss { + +// The peer-group dispatcher only touches the BGP side of ConfigSession, which +// seeds from thrift schema defaults when neither a staged session nor a +// system bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpNeighborTest). +class CmdConfigBgpPeerGroupTestFixture : public CmdConfigTestBase { + public: + CmdConfigBgpPeerGroupTestFixture() + : CmdConfigTestBase("bgp_peer_group_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string run(const std::vector& tokens) { + CmdConfigProtocolBgpPeerGroup cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpPeerGroupConfig(tokens)); + } + + const std::vector& groups() { + return ConfigSession::getInstance().getBgpConfig().peer_groups().ensure(); + } + + bgp::thrift::BgpConfig& config() { + return ConfigSession::getInstance().getBgpConfig(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpPeerGroupConfig (arg) validation +// ============================================================================== + +TEST_F(CmdConfigBgpPeerGroupTestFixture, argValidation) { + // Bare create. + EXPECT_EQ(BgpPeerGroupConfig({"SPINE"}).groupName(), "SPINE"); + EXPECT_TRUE(BgpPeerGroupConfig({"SPINE"}).attr().empty()); + + // Single-token attribute. + auto singleTok = BgpPeerGroupConfig({"SPINE", "remote-asn", "65000"}); + EXPECT_EQ(singleTok.attr(), "remote-asn"); + EXPECT_EQ(singleTok.values(), std::vector({"65000"})); + + // Two-token attribute wins longest-prefix match. + auto twoTok = BgpPeerGroupConfig({"SPINE", "timers", "hold-time", "30"}); + EXPECT_EQ(twoTok.attr(), "timers hold-time"); + EXPECT_EQ(twoTok.values(), std::vector({"30"})); + + // Invalid: empty, empty name, unknown attribute, incomplete group. + EXPECT_THROW(BgpPeerGroupConfig({}), std::invalid_argument); + EXPECT_THROW(BgpPeerGroupConfig({""}), std::invalid_argument); + EXPECT_THROW( + BgpPeerGroupConfig({"SPINE", "no-such-attr", "1"}), + std::invalid_argument); + EXPECT_THROW(BgpPeerGroupConfig({"SPINE", "timers"}), std::invalid_argument); +} + +// ============================================================================== +// Attribute handlers +// ============================================================================== + +TEST_F(CmdConfigBgpPeerGroupTestFixture, bareCreate) { + auto result = run({"SPINE"}); + EXPECT_THAT(result, HasSubstr("Successfully created BGP peer-group SPINE")); + ASSERT_EQ(groups().size(), 1); + EXPECT_EQ(*groups()[0].name(), "SPINE"); + // PeerGroup's only non-optional field is `name`; nothing else is seeded. + EXPECT_FALSE(groups()[0].local_addr().has_value()); + EXPECT_TRUE(sessionFileExists()); + + run({"LEAF"}); + ASSERT_EQ(groups().size(), 2); + EXPECT_EQ(*groups()[1].name(), "LEAF"); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, setRemoteAsn) { + auto result = run({"SPINE", "remote-asn", "65000"}); + EXPECT_THAT(result, HasSubstr("65000")); + ASSERT_EQ(groups().size(), 1); + EXPECT_EQ(groups()[0].remote_as_4_byte().value_or(0), 65000); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, timersAccumulate) { + run({"SPINE", "timers", "hold-time", "90"}); + run({"SPINE", "timers", "keepalive", "30"}); + run({"SPINE", "timers", "out-delay", "5"}); + run({"SPINE", "timers", "withdraw-unprog-delay", "10"}); + run({"SPINE", "graceful-restart", "restart-time", "120"}); + ASSERT_EQ(groups().size(), 1); + const auto& timers = *groups()[0].bgp_peer_timers(); + EXPECT_EQ(*timers.hold_time_seconds(), 90); + EXPECT_EQ(*timers.keep_alive_seconds(), 30); + EXPECT_EQ(*timers.out_delay_seconds(), 5); + EXPECT_EQ(*timers.withdraw_unprog_delay_seconds(), 10); + EXPECT_EQ(timers.graceful_restart_seconds().value_or(0), 120); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, addPathMerge) { + run({"SPINE", "add-path", "receive", "true"}); + EXPECT_EQ(groups()[0].add_path().value_or(AddPath(0)), AddPath::RECEIVE); + + run({"SPINE", "add-path", "send", "true"}); + EXPECT_EQ(groups()[0].add_path().value_or(AddPath(0)), AddPath::BOTH); + + run({"SPINE", "add-path", "receive", "false"}); + EXPECT_EQ(groups()[0].add_path().value_or(AddPath(0)), AddPath::SEND); + + run({"SPINE", "add-path", "send", "false"}); + EXPECT_FALSE(groups()[0].add_path().has_value()); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, passive) { + // is_passive=true is PASSIVE_ONLY in bgpd, false is PASSIVE_ACTIVE (listen + // and connect); the CLI exposes the flag rather than connect-mode names. + run({"SPINE", "passive", "true"}); + EXPECT_TRUE(groups()[0].is_passive().value_or(false)); + + run({"SPINE", "passive", "false"}); + EXPECT_FALSE(groups()[0].is_passive().value_or(true)); + + auto result = run({"SPINE", "passive", "BOTH"}); + EXPECT_THAT(result, HasSubstr("Invalid")); + EXPECT_FALSE(groups()[0].is_passive().value_or(true)); + EXPECT_THROW( + run({"SPINE", "connect-mode", "PASSIVE"}), std::invalid_argument); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, boolAndStringAttributes) { + run({"SPINE", "rr-client", "true"}); + run({"SPINE", "confed-peer", "false"}); + run({"SPINE", "redistribute-peer", "true"}); + run({"SPINE", "enhanced-route-refresh", "true"}); + run({"SPINE", "route-refresh", "true"}); + run({"SPINE", "remove-private-as", "true"}); + run({"SPINE", "enforce-first-as", "false"}); + run({"SPINE", "afi", "disable-ipv4-afi", "true"}); + run({"SPINE", "afi", "disable-ipv6-afi", "false"}); + run({"SPINE", "afi", "ipv4-over-ipv6-nh", "true"}); + run({"SPINE", "graceful-restart", "stateful-ha", "true"}); + run({"SPINE", "peer-tag", "FSW"}); + run({"SPINE", "ingress-policy", "IN_POLICY"}); + run({"SPINE", "egress-policy", "OUT_POLICY"}); + run({"SPINE", "local-asn", "64512"}); + run({"SPINE", "next-hop-self", "true"}); + run({"SPINE", "description", "spine", "group"}); + + ASSERT_EQ(groups().size(), 1); + const auto& group = groups()[0]; + EXPECT_TRUE(group.is_rr_client().value_or(false)); + EXPECT_FALSE(group.is_confed_peer().value_or(true)); + EXPECT_TRUE(group.is_redistribute_peer().value_or(false)); + EXPECT_TRUE(group.enhanced_route_refresh().value_or(false)); + EXPECT_TRUE(group.route_refresh().value_or(false)); + EXPECT_TRUE(group.remove_private_as().value_or(false)); + EXPECT_FALSE(group.enforce_first_as().value_or(true)); + EXPECT_TRUE(group.disable_ipv4_afi().value_or(false)); + EXPECT_FALSE(group.disable_ipv6_afi().value_or(true)); + EXPECT_TRUE(group.v4_over_v6_nexthop().value_or(false)); + EXPECT_TRUE(group.enable_stateful_ha().value_or(false)); + EXPECT_EQ(group.peer_tag().value_or(""), "FSW"); + EXPECT_EQ(group.ingress_policy_name().value_or(""), "IN_POLICY"); + EXPECT_EQ(group.egress_policy_name().value_or(""), "OUT_POLICY"); + EXPECT_EQ(group.local_as_4_byte().value_or(0), 64512); + EXPECT_TRUE(group.next_hop_self().value_or(false)); + // Multi-token description is re-joined. + EXPECT_EQ(group.description().value_or(""), "spine group"); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, routeLimits) { + run({"SPINE", "max-route", "pre-filter", "45000"}); + run({"SPINE", "max-route", "post-filter", "50000"}); + run({"SPINE", "max-route", "post-warning-threshold", "48000"}); + // Parity attributes carried over from the deleted per-attribute commands. + run({"SPINE", "max-route", "pre-warning-threshold", "40000"}); + run({"SPINE", "max-route", "pre-warning-only", "true"}); + run({"SPINE", "max-route", "post-warning-only", "false"}); + const auto& group = groups()[0]; + EXPECT_EQ(*group.pre_filter()->max_routes(), 45000); + EXPECT_EQ(*group.post_filter()->max_routes(), 50000); + EXPECT_EQ(*group.post_filter()->warning_limit(), 48000); + EXPECT_EQ(*group.pre_filter()->warning_limit(), 40000); + EXPECT_TRUE(*group.pre_filter()->warning_only()); + EXPECT_FALSE(*group.post_filter()->warning_only()); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, linkBandwidthAttributes) { + // Same spellings and validation as the neighbor command: bgpd resolves the + // link-bandwidth trio peer > group. + run({"SPINE", "link-bandwidth", "10G"}); + EXPECT_EQ(groups()[0].link_bandwidth_bps().value_or(""), "10G"); + run({"SPINE", "link-bandwidth", "1.5G"}); + EXPECT_EQ(groups()[0].link_bandwidth_bps().value_or(""), "1.5G"); + run({"SPINE", "link-bandwidth", "auto"}); + EXPECT_EQ(groups()[0].link_bandwidth_bps().value_or(""), "auto"); + EXPECT_THAT(run({"SPINE", "link-bandwidth", "10Q"}), HasSubstr("Invalid")); + EXPECT_THAT(run({"SPINE", "link-bandwidth", "10T"}), HasSubstr("Invalid")); + EXPECT_EQ(groups()[0].link_bandwidth_bps().value_or(""), "auto"); + + run({"SPINE", "advertise-lbw", "1"}); + run({"SPINE", "receive-lbw", "ACCEPT"}); // enum value name accepted + const auto& group = groups()[0]; + EXPECT_EQ( + static_cast(group.advertise_link_bandwidth().value_or( + facebook::neteng::fboss::bgp_attr::AdvertiseLinkBandwidth::DISABLE)), + 1); + EXPECT_EQ( + static_cast(group.receive_link_bandwidth().value_or( + facebook::neteng::fboss::bgp_attr::ReceiveLinkBandwidth::DISABLE)), + 1); + EXPECT_THAT( + run({"SPINE", "advertise-lbw", "99"}), HasSubstr("not a valid mode")); + EXPECT_THAT( + run({"SPINE", "receive-lbw", "SOMETIMES"}), + HasSubstr("not a valid mode")); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, ttlSecurityHopsRange) { + // bgpd throws at config load outside kMin/kMaxTtlSecurityHops (1..255). + EXPECT_THAT(run({"SPINE", "ttl-security-hops", "0"}), HasSubstr("Error")); + EXPECT_THAT(run({"SPINE", "ttl-security-hops", "256"}), HasSubstr("Error")); + EXPECT_THAT(run({"SPINE", "ttl-security-hops", "x"}), HasSubstr("Error")); + EXPECT_TRUE(groups().empty()) + << "rejected first attribute must not leave a half-created group"; + run({"SPINE", "ttl-security-hops", "1"}); + EXPECT_EQ(groups()[0].ttl_security_hops().value_or(0), 1); + run({"SPINE", "ttl-security-hops", "255"}); + EXPECT_EQ(groups()[0].ttl_security_hops().value_or(0), 255); + EXPECT_THAT(run({"SPINE", "ttl-security-hops", "256"}), HasSubstr("Error")); + EXPECT_EQ(groups()[0].ttl_security_hops().value_or(0), 255); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, valueValidation) { + // ASN must fit in 4 bytes (RFC 6793). + EXPECT_THAT(run({"SPINE", "remote-asn", "4294967296"}), HasSubstr("Invalid")); + EXPECT_THAT( + run({"SPINE", "local-asn", "18446744073709551615"}), + HasSubstr("Invalid")); + // The rejected value implicitly created SPINE; the rollback must remove it, + // not just skip persisting it. + EXPECT_TRUE(groups().empty()) + << "rejected first attribute must not leave a half-created group"; + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, wrongValueCountRejected) { + // Known attribute, but no value: parses fine, handler rejects at runtime. + EXPECT_THAT(run({"SPINE", "remote-asn"}), HasSubstr("requires")); + EXPECT_THAT(run({"SPINE", "timers", "hold-time"}), HasSubstr("requires")); + EXPECT_THAT(run({"SPINE", "description"}), HasSubstr("requires")); + // Too many values for single-value attributes. + EXPECT_THAT( + run({"SPINE", "remote-asn", "65000", "65001"}), HasSubstr("requires")); + EXPECT_THAT( + run({"SPINE", "rr-client", "true", "false"}), HasSubstr("requires")); + EXPECT_TRUE(groups().empty()) + << "rejected first attribute must not leave a half-created group"; + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, overwriteSameAttributeLastWins) { + run({"SPINE", "remote-asn", "65000"}); + run({"SPINE", "remote-asn", "65001"}); + ASSERT_EQ(groups().size(), 1); + EXPECT_EQ(groups()[0].remote_as_4_byte().value_or(0), 65001); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, namedGroupsAreDistinct) { + run({"SPINE", "remote-asn", "65000"}); + run({"LEAF", "remote-asn", "65001"}); + ASSERT_EQ(groups().size(), 2); + // Re-referencing an existing group by name updates it, not appends — + // and mutating one field preserves the group's other fields and leaves + // the sibling group untouched. + run({"SPINE", "local-asn", "64512"}); + ASSERT_EQ(groups().size(), 2); + EXPECT_EQ(groups()[0].remote_as_4_byte().value_or(0), 65000); + EXPECT_EQ(groups()[0].local_as_4_byte().value_or(0), 64512); + EXPECT_EQ(groups()[1].remote_as_4_byte().value_or(0), 65001); + EXPECT_FALSE(groups()[1].local_as_4_byte().has_value()); +} + +// ============================================================================== +// Inherited values: bgpd applies a group's timers to its members as a whole +// struct, so the first timer attribute must seed the rest from the global +// defaults instead of leaving them at the thrift default of 0. +// ============================================================================== + +TEST_F(CmdConfigBgpPeerGroupTestFixture, timersSeededFromGlobal) { + config().hold_time() = 180; + run({"SPINE", "graceful-restart", "restart-time", "60"}); + + ASSERT_EQ(groups().size(), 1); + const auto& timers = *groups()[0].bgp_peer_timers(); + EXPECT_EQ(*timers.hold_time_seconds(), 180); + EXPECT_EQ(*timers.keep_alive_seconds(), 60); + EXPECT_EQ(*timers.out_delay_seconds(), 0); + EXPECT_EQ(*timers.withdraw_unprog_delay_seconds(), 0); + EXPECT_EQ(timers.graceful_restart_seconds().value_or(0), 60); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, timersNotReseededOncePresent) { + run({"SPINE", "timers", "hold-time", "90"}); + run({"SPINE", "timers", "keepalive", "30"}); + // Seeding happens once: a later global change does not leak into a group + // that already carries its own timers, and other fields are preserved. + config().hold_time() = 180; + run({"SPINE", "timers", "out-delay", "2"}); + + const auto& timers = *groups()[0].bgp_peer_timers(); + EXPECT_EQ(*timers.hold_time_seconds(), 90); + EXPECT_EQ(*timers.keep_alive_seconds(), 30); + EXPECT_EQ(*timers.out_delay_seconds(), 2); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, routeLimitSeededUnlimited) { + // A group with no RouteLimit imposes none on its members (bgpd skips the + // check when the struct is absent), so the struct created for a + // warning-only/threshold edit must keep that: max_routes 0 = unlimited, + // not the 12000 thrift default, which would be a hard cap with teardown. + run({"SPINE", "max-route", "pre-warning-only", "true"}); + const auto& pre = *groups()[0].pre_filter(); + EXPECT_EQ(*pre.max_routes(), 0); + EXPECT_EQ(*pre.warning_limit(), 0); + EXPECT_TRUE(*pre.warning_only()); + EXPECT_FALSE(groups()[0].post_filter().has_value()); + + run({"SPINE", "max-route", "post-warning-threshold", "30000"}); + const auto& post = *groups()[0].post_filter(); + EXPECT_EQ(*post.max_routes(), 0); + EXPECT_EQ(*post.warning_limit(), 30000); + EXPECT_FALSE(*post.warning_only()); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, routeLimitNotReseededOncePresent) { + run({"SPINE", "max-route", "pre-filter", "45000"}); + run({"SPINE", "max-route", "pre-warning-threshold", "30000"}); + EXPECT_EQ(*groups()[0].pre_filter()->max_routes(), 45000); + EXPECT_EQ(*groups()[0].pre_filter()->warning_limit(), 30000); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, timerRanges) { + // Hold time: 0 or 3..65535 (bgpd refuses 1-2; the OPEN field is 16 bits). + EXPECT_THAT(run({"SPINE", "timers", "hold-time", "1"}), HasSubstr("Error")); + EXPECT_THAT(run({"SPINE", "timers", "hold-time", "2"}), HasSubstr("Error")); + EXPECT_THAT( + run({"SPINE", "timers", "hold-time", "65536"}), HasSubstr("Error")); + EXPECT_TRUE(groups().empty()) + << "rejected first attribute must not leave a half-created group"; + run({"SPINE", "timers", "hold-time", "0"}); + EXPECT_EQ(*groups()[0].bgp_peer_timers()->hold_time_seconds(), 0); + run({"SPINE", "timers", "hold-time", "3"}); + EXPECT_EQ(*groups()[0].bgp_peer_timers()->hold_time_seconds(), 3); + run({"SPINE", "timers", "hold-time", "65535"}); + EXPECT_EQ(*groups()[0].bgp_peer_timers()->hold_time_seconds(), 65535); + + // Graceful-restart time: 0..4095 (12-bit field, RFC 4724). + EXPECT_THAT( + run({"SPINE", "graceful-restart", "restart-time", "4096"}), + HasSubstr("Error")); + EXPECT_FALSE(groups()[0].bgp_peer_timers()->graceful_restart_seconds()); + run({"SPINE", "graceful-restart", "restart-time", "4095"}); + EXPECT_EQ( + groups()[0].bgp_peer_timers()->graceful_restart_seconds().value_or(0), + 4095); +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, rejectedValueLeavesGroupUntouched) { + run({"SPINE", "remote-asn", "65000"}); + // A rejected timer must not leave behind the timers struct that was seeded + // for it: that would silently pin the group's members to the current + // global hold time. + EXPECT_THAT(run({"SPINE", "timers", "hold-time", "2"}), HasSubstr("Error")); + ASSERT_EQ(groups().size(), 1); + EXPECT_FALSE(groups()[0].bgp_peer_timers().has_value()); + EXPECT_EQ(groups()[0].remote_as_4_byte().value_or(0), 65000); + // Likewise a rejected route-limit value must not leave a seeded RouteLimit. + EXPECT_THAT( + run({"SPINE", "max-route", "pre-warning-threshold", "-1"}), + HasSubstr("Error")); + EXPECT_FALSE(groups()[0].pre_filter().has_value()); + EXPECT_THAT(run({"SPINE", "ttl-security-hops", "0"}), HasSubstr("Error")); + EXPECT_FALSE(groups()[0].ttl_security_hops().has_value()); +} + +// ============================================================================== +// Reject paths — the error is surfaced and nothing is persisted +// ============================================================================== + +TEST_F(CmdConfigBgpPeerGroupTestFixture, invalidValuesRejected) { + EXPECT_THAT(run({"SPINE", "rr-client", "maybe"}), HasSubstr("Invalid")); + EXPECT_THAT( + run({"SPINE", "timers", "hold-time", "-1"}), HasSubstr("non-negative")); + EXPECT_THAT( + run({"SPINE", "remote-asn", "not-a-number"}), HasSubstr("Invalid")); + EXPECT_THAT( + run({"SPINE", "max-route", "pre-filter", "-5"}), + HasSubstr("non-negative")); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpPeerGroupTestFixture, unsupportedAttributesRejected) { + // These attributes have no per-peer-group thrift field, so they are not in + // the dispatch table and are refused at parse time as unknown attributes. + EXPECT_THROW(run({"SPINE", "peer-port", "1179"}), std::invalid_argument); + EXPECT_THROW( + run({"SPINE", "afi", "ipv4-labeled-unicast", "true"}), + std::invalid_argument); + EXPECT_THROW( + run({"SPINE", "afi", "ipv6-labeled-unicast", "true"}), + std::invalid_argument); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListEntryTest.cpp b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListEntryTest.cpp new file mode 100644 index 0000000000000..85fa203e819b8 --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListEntryTest.cpp @@ -0,0 +1,210 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/routing_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/entry/CmdConfigProtocolBgpPolicyAsPathListEntry.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; +using facebook::bgp::routing_policy::MatchValueLogicOperator; + +namespace facebook::fboss { + +// The entry dispatcher only touches the BGP side of ConfigSession, which seeds +// from thrift schema defaults when neither a staged session nor a system +// bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpPolicyAsPathListTest). +class CmdConfigBgpPolicyAsPathListEntryTestFixture : public CmdConfigTestBase { + public: + CmdConfigBgpPolicyAsPathListEntryTestFixture() + : CmdConfigTestBase("bgp_aspath_list_entry_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + // Invoke the entry handler the way the framework does: the parent's parsed + // args plus the entry's own tokens. + std::string runEntry( + const std::vector& listTokens, + const std::vector& entryTokens) { + CmdConfigProtocolBgpPolicyAsPathListEntry cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient( + hostInfo, + BgpAsPathListConfig(listTokens), + BgpAsPathListEntryConfig(entryTokens)); + } + + std::string runList(const std::vector& tokens) { + CmdConfigProtocolBgpPolicyAsPathList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpAsPathListConfig(tokens)); + } + + const std::vector& lists() { + return *ConfigSession::getInstance() + .getBgpConfig() + .policies() + .ensure() + .aspath_lists(); + } + + const std::vector& entries(size_t listIdx) { + return *lists()[listIdx].as_path_list(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpAsPathListEntryConfig (arg) validation +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyAsPathListEntryTestFixture, argValidation) { + // Bare create. + auto bare = BgpAsPathListEntryConfig({"10"}); + EXPECT_EQ(bare.seqNum(), 10); + EXPECT_TRUE(bare.attr().empty()); + + // Attribute with values. + auto attr = BgpAsPathListEntryConfig({"10", "asn-regexp", "^65000_"}); + EXPECT_EQ(attr.seqNum(), 10); + EXPECT_EQ(attr.attr(), "asn-regexp"); + EXPECT_EQ(attr.values(), std::vector({"^65000_"})); + + // Invalid: empty, non-numeric or negative seq-num, unknown attribute. + EXPECT_THROW(BgpAsPathListEntryConfig({}), std::invalid_argument); + EXPECT_THROW(BgpAsPathListEntryConfig({"not-a-num"}), std::invalid_argument); + EXPECT_THROW(BgpAsPathListEntryConfig({"-1"}), std::invalid_argument); + EXPECT_THROW( + BgpAsPathListEntryConfig({"10", "no-such-attr", "1"}), + std::invalid_argument); +} + +// ============================================================================== +// Entry-level handlers +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyAsPathListEntryTestFixture, bareCreateEntry) { + auto result = runEntry({"AS100"}, {"10"}); + EXPECT_THAT( + result, + HasSubstr("Successfully created BGP as-path-list AS100 entry 10")); + ASSERT_EQ(lists().size(), 1); + ASSERT_EQ(entries(0).size(), 1); + EXPECT_EQ(*entries(0)[0].sequence_number(), 10); +} + +TEST_F(CmdConfigBgpPolicyAsPathListEntryTestFixture, setEntryAttributes) { + runEntry({"AS100"}, {"10", "asn-regexp", "^65000_"}); + runEntry({"AS100"}, {"10", "description", "match", "65000"}); + runEntry({"AS100"}, {"10", "match-logic", "NOT_EQUAL"}); + + ASSERT_EQ(lists().size(), 1); + ASSERT_EQ(entries(0).size(), 1); + const auto& entry = entries(0)[0]; + EXPECT_EQ(*entry.sequence_number(), 10); + EXPECT_EQ(entry.as_path()->as_path_ref()->asn_regexp().value(), "^65000_"); + EXPECT_EQ(*entry.description(), "match 65000"); + EXPECT_EQ(*entry.match_logic_type(), MatchValueLogicOperator::NOT_EQUAL); +} + +TEST_F( + CmdConfigBgpPolicyAsPathListEntryTestFixture, + entriesAccumulateAndAreKeyed) { + runEntry({"AS100"}, {"10", "asn-regexp", "^65000_"}); + runEntry({"AS100"}, {"20", "asn-regexp", "^65001_"}); + ASSERT_EQ(lists().size(), 1); + ASSERT_EQ(entries(0).size(), 2); + + // Re-referencing an existing entry by seq-num updates it, not appends. + runEntry({"AS100"}, {"10", "match-logic", "EQUAL"}); + EXPECT_EQ(entries(0).size(), 2); +} + +TEST_F(CmdConfigBgpPolicyAsPathListEntryTestFixture, asnRegexpAllowsSpaces) { + // AS-path regexes separate ASNs with spaces; the pattern must survive as a + // single re-joined value. + runEntry({"AS100"}, {"10", "asn-regexp", "^65000", "65001$"}); + EXPECT_EQ( + entries(0)[0].as_path()->as_path_ref()->asn_regexp().value(), + "^65000 65001$"); +} + +TEST_F( + CmdConfigBgpPolicyAsPathListEntryTestFixture, + reReferenceReportsExisting) { + runEntry({"AS100"}, {"10"}); + EXPECT_THAT( + runEntry({"AS100"}, {"10"}), HasSubstr("entry 10 already exists")); + EXPECT_EQ(entries(0).size(), 1); +} + +TEST_F(CmdConfigBgpPolicyAsPathListEntryTestFixture, matchLogicDefaultIsEqual) { + runEntry({"AS100"}, {"10"}); + EXPECT_EQ(*entries(0)[0].match_logic_type(), MatchValueLogicOperator::EQUAL); +} + +// ============================================================================== +// Reject paths — the error is surfaced and nothing is persisted +// ============================================================================== + +TEST_F( + CmdConfigBgpPolicyAsPathListEntryTestFixture, + invalidMatchLogicRejected) { + auto result = runEntry({"AS100"}, {"10", "match-logic", "MAYBE"}); + // The full message matters: the accepted-values suffix once printed garbage + // (a string_view capture dangling over a fmt::format temporary). + EXPECT_THAT( + result, + HasSubstr("Invalid match-logic value 'MAYBE'; expected EQUAL|NOT_EQUAL")); + // The rejected value must not leave a phantom list/entry behind. + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F( + CmdConfigBgpPolicyAsPathListEntryTestFixture, + rejectedEntryOnExistingListKeepsList) { + // Create the list first, then reject an entry value on it. + runList({"AS100", "description", "keep-me"}); + ASSERT_EQ(lists().size(), 1); + + auto result = runEntry({"AS100"}, {"10", "match-logic", "MAYBE"}); + EXPECT_THAT(result, HasSubstr("Invalid")); + // The pre-existing list survives; only the phantom entry is rolled back. + ASSERT_EQ(lists().size(), 1); + EXPECT_TRUE(entries(0).empty()); + EXPECT_EQ(*lists()[0].description(), "keep-me"); +} + +// A list-level attribute alongside an entry must be rejected: only the leaf +// command runs, so the list attribute would be silently dropped. +TEST_F( + CmdConfigBgpPolicyAsPathListEntryTestFixture, + listAttributeMixedWithEntryRejected) { + auto result = runEntry({"AS100", "description", "mixed"}, {"10"}); + EXPECT_THAT(result, HasSubstr("separate commands")); + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListTest.cpp b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListTest.cpp new file mode 100644 index 0000000000000..2d6ef6bc4fa16 --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyAsPathListTest.cpp @@ -0,0 +1,264 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/routing_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/BgpAsPathListCliUtils.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; + +namespace facebook::fboss { + +// The as-path-list dispatcher only touches the BGP side of ConfigSession, +// which seeds from thrift schema defaults when neither a staged session nor a +// system bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpPeerGroupTest). +class CmdConfigBgpPolicyAsPathListTestFixture : public CmdConfigTestBase { + public: + CmdConfigBgpPolicyAsPathListTestFixture() + : CmdConfigTestBase("bgp_aspath_list_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string run(const std::vector& tokens) { + CmdConfigProtocolBgpPolicyAsPathList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpAsPathListConfig(tokens)); + } + + const std::vector& lists() { + return *ConfigSession::getInstance() + .getBgpConfig() + .policies() + .ensure() + .aspath_lists(); + } + + // Seed a routing-policy term whose AS_PATH match names `listName` (the + // term/match CLIs live in higher PRs), the way bgpd reads the reference: + // as_path_filters.as_path_list_names. Returns the seeded atomic match. + bgp::bgp_policy::BgpPolicyAtomicMatch& addPolicyTermMatching( + const std::string& policy, + int64_t seq, + const std::string& listName) { + auto& cfg = ConfigSession::getInstance().getBgpConfig(); + auto& policies = *cfg.policies().ensure().bgp_policy_statements(); + policies.emplace_back(); + policies.back().name() = policy; + auto& terms = *policies.back().policy_entries(); + terms.emplace_back(); + terms.back().sequence_number() = seq; + auto& matches = + *terms.back().policy_match_entries().ensure().match_entries(); + matches.emplace_back(); + matches.back().type() = bgp::bgp_policy::BgpPolicyAtomicMatchType::AS_PATH; + matches.back().as_path_filters().ensure().as_path_list_names().ensure() = { + listName}; + ConfigSession::getInstance().saveBgpConfig(); + return matches.back(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpAsPathListConfig (arg) validation +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, argValidation) { + // Bare create list. + auto bareList = BgpAsPathListConfig({"AS100"}); + EXPECT_EQ(bareList.listName(), "AS100"); + EXPECT_TRUE(bareList.attr().empty()); + + // List-level attribute. + auto listAttr = BgpAsPathListConfig({"AS100", "description", "my", "list"}); + EXPECT_EQ(listAttr.attr(), "description"); + EXPECT_EQ(listAttr.values(), std::vector({"my", "list"})); + + // Invalid: empty, empty name, unknown attribute. + EXPECT_THROW(BgpAsPathListConfig({}), std::invalid_argument); + EXPECT_THROW(BgpAsPathListConfig({""}), std::invalid_argument); + EXPECT_THROW( + BgpAsPathListConfig({"AS100", "no-such-attr", "1"}), + std::invalid_argument); + // asn-regexp is an entry attribute, not a list attribute. + EXPECT_THROW( + BgpAsPathListConfig({"AS100", "asn-regexp", "^65000_"}), + std::invalid_argument); + + // List-level regex and boolean-operator are recognized. + EXPECT_EQ(BgpAsPathListConfig({"AS100", "regex", "^65000_"}).attr(), "regex"); + EXPECT_EQ( + BgpAsPathListConfig({"AS100", "boolean-operator", "AND"}).attr(), + "boolean-operator"); +} + +// ============================================================================== +// List-level handlers +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, bareCreateList) { + auto result = run({"AS100"}); + EXPECT_THAT(result, HasSubstr("Successfully created BGP as-path-list AS100")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "AS100"); + EXPECT_TRUE(lists()[0].as_path_list()->empty()); + EXPECT_TRUE(sessionFileExists()); + + run({"AS200"}); + ASSERT_EQ(lists().size(), 2); + EXPECT_EQ(*lists()[1].name(), "AS200"); +} + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, setListDescription) { + auto result = run({"AS100", "description", "spine", "as-paths"}); + EXPECT_THAT(result, HasSubstr("Successfully set description")); + ASSERT_EQ(lists().size(), 1); + // Multi-token description is re-joined. + EXPECT_EQ(*lists()[0].description(), "spine as-paths"); +} + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, namedListsAreDistinct) { + run({"AS100", "description", "one"}); + run({"AS200", "description", "two"}); + ASSERT_EQ(lists().size(), 2); + // Re-referencing an existing list by name updates it, not appends. + run({"AS100", "description", "one-updated"}); + ASSERT_EQ(lists().size(), 2); + EXPECT_EQ(*lists()[0].description(), "one-updated"); +} + +// ============================================================================== +// Reject paths — the error is surfaced and nothing is persisted +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, reReferenceReportsExisting) { + run({"AS100"}); + // A second bare reference must not claim to have created it again. + EXPECT_THAT(run({"AS100"}), HasSubstr("already exists")); + ASSERT_EQ(lists().size(), 1); +} + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, unknownAttributeRejected) { + EXPECT_THROW(run({"AS100", "no-such-attr", "1"}), std::invalid_argument); + // The rejected command must not leave a phantom list behind. + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +// ============================================================================== +// regex — as_paths, the field bgpd matches against +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, regexAppends) { + auto result = run({"AS100", "regex", "^65000_"}); + EXPECT_THAT(result, HasSubstr("Successfully added regex ^65000_")); + EXPECT_THAT(result, HasSubstr("for as-path-list AS100")); + run({"AS100", "regex", "_65001$"}); + ASSERT_EQ(lists().size(), 1); + ASSERT_TRUE(lists()[0].as_paths().has_value()); + EXPECT_EQ( + *lists()[0].as_paths(), std::vector({"^65000_", "_65001$"})); + // The dead entry list is left alone. + EXPECT_TRUE(lists()[0].as_path_list()->empty()); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, regexAddAgainIsIdempotent) { + run({"AS100", "regex", "^65000_"}); + auto result = run({"AS100", "regex", "^65000_"}); + EXPECT_THAT(result, HasSubstr("already present")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(lists()[0].as_paths()->size(), 1); +} + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, malformedRegexRejected) { + auto result = run({"AS100", "regex", "^65000_("}); + EXPECT_THAT(result, HasSubstr("Error: Malformed regex")); + // A rejected first attribute leaves no phantom list and nothing on disk. + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, regexWithWhitespaceRejected) { + run({"AS100"}); + auto result = run({"AS100", "regex", "65000 65001"}); + EXPECT_THAT(result, HasSubstr("must not contain whitespace")); + EXPECT_THAT(result, HasSubstr("_")); + ASSERT_EQ(lists().size(), 1); + EXPECT_FALSE(lists()[0].as_paths().has_value()); + + EXPECT_THAT(run({"AS100", "regex"}), HasSubstr("requires ")); + EXPECT_THAT( + run({"AS100", "regex", "^65000_", "_65001$"}), + HasSubstr("requires ")); +} + +// ============================================================================== +// boolean-operator — AND|OR, written through to referencing terms +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, booleanOperatorAccepted) { + auto result = run({"AS100", "boolean-operator", "AND"}); + EXPECT_THAT(result, HasSubstr("Successfully set boolean-operator to: AND")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ( + *lists()[0].boolean_operator(), + bgp::routing_policy::BooleanOperator::AND); + run({"AS100", "boolean-operator", "OR"}); + EXPECT_EQ( + *lists()[0].boolean_operator(), bgp::routing_policy::BooleanOperator::OR); +} + +TEST_F(CmdConfigBgpPolicyAsPathListTestFixture, booleanOperatorNotRejected) { + auto result = run({"AS100", "boolean-operator", "NOT"}); + EXPECT_THAT(result, HasSubstr("Invalid boolean-operator value 'NOT'")); + EXPECT_THAT(result, HasSubstr("AND|OR")); + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()); +} + +TEST_F( + CmdConfigBgpPolicyAsPathListTestFixture, + booleanOperatorPropagatesToReferencingTerms) { + run({"AS100"}); + run({"AS200"}); + const auto& match = addPolicyTermMatching("RM100", 10, "AS100"); + const auto& other = addPolicyTermMatching("RM100", 20, "AS200"); + EXPECT_EQ( + *match.as_path_filters()->boolean_operator(), + bgp::routing_policy::BooleanOperator::OR); + + run({"AS100", "boolean-operator", "AND"}); + // bgpd requires the term's inline copy to carry the list's operator. + EXPECT_EQ( + *match.as_path_filters()->boolean_operator(), + bgp::routing_policy::BooleanOperator::AND); + // A term referencing a different list is untouched. + EXPECT_EQ( + *other.as_path_filters()->boolean_operator(), + bgp::routing_policy::BooleanOperator::OR); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListCommunityTest.cpp b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListCommunityTest.cpp new file mode 100644 index 0000000000000..805eeae8b49f3 --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListCommunityTest.cpp @@ -0,0 +1,219 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/community/CmdConfigProtocolBgpPolicyCommunityListCommunity.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; + +namespace facebook::fboss { + +// The community dispatcher only touches the BGP side of ConfigSession, which +// seeds from thrift schema defaults when neither a staged session nor a system +// bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpPolicyCommunityListTest). +class CmdConfigBgpPolicyCommunityListCommunityTestFixture + : public CmdConfigTestBase { + public: + CmdConfigBgpPolicyCommunityListCommunityTestFixture() + : CmdConfigTestBase("bgp_community_list_community_test_%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + // Invoke the community handler the way the framework does: the parent's + // parsed args plus the community's own tokens. + std::string runCommunity( + const std::vector& listTokens, + const std::vector& communityTokens) { + CmdConfigProtocolBgpPolicyCommunityListCommunity cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient( + hostInfo, + BgpCommunityListConfig(listTokens), + BgpCommunityListCommunityConfig(communityTokens)); + } + + std::string runList(const std::vector& tokens) { + CmdConfigProtocolBgpPolicyCommunityList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpCommunityListConfig(tokens)); + } + + const std::vector& lists() { + return *ConfigSession::getInstance() + .getBgpConfig() + .policies() + .ensure() + .community_lists(); + } + + // The inline Community held by members[idx] of list[listIdx]. + const bgp::bgp_policy::Community& member(size_t listIdx, size_t idx) { + return *lists()[listIdx].members()->at(idx).community_ref(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpCommunityListCommunityConfig (arg) validation +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyCommunityListCommunityTestFixture, argValidation) { + // Bare create. + auto bare = BgpCommunityListCommunityConfig({"CM1"}); + EXPECT_EQ(bare.communityName(), "CM1"); + EXPECT_TRUE(bare.attr().empty()); + + // Attribute with values. + auto attr = BgpCommunityListCommunityConfig({"CM1", "value", "65000:100"}); + EXPECT_EQ(attr.communityName(), "CM1"); + EXPECT_EQ(attr.attr(), "value"); + EXPECT_EQ(attr.values(), std::vector({"65000:100"})); + + // Invalid: empty, empty name, unknown attribute. + EXPECT_THROW(BgpCommunityListCommunityConfig({}), std::invalid_argument); + EXPECT_THROW(BgpCommunityListCommunityConfig({""}), std::invalid_argument); + EXPECT_THROW( + BgpCommunityListCommunityConfig({"CM1", "no-such-attr", "1"}), + std::invalid_argument); + // exact-match is a list attribute, not a community attribute. + EXPECT_THROW( + BgpCommunityListCommunityConfig({"CM1", "exact-match", "true"}), + std::invalid_argument); +} + +// ============================================================================== +// Community-level handlers +// ============================================================================== + +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + bareCreateCommunity) { + auto result = runCommunity({"CL100"}, {"CM1"}); + EXPECT_THAT( + result, + HasSubstr("Successfully created BGP community-list CL100 community CM1")); + ASSERT_EQ(lists().size(), 1); + ASSERT_EQ(lists()[0].members()->size(), 1); + EXPECT_EQ(*member(0, 0).name(), "CM1"); +} + +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + setCommunityAttributes) { + runCommunity({"CL100"}, {"CM1", "value", "65000:100"}); + runCommunity({"CL100"}, {"CM1", "type", "LARGE"}); + runCommunity({"CL100"}, {"CM1", "description", "spine", "tag"}); + + ASSERT_EQ(lists().size(), 1); + ASSERT_EQ(lists()[0].members()->size(), 1); + const auto& community = member(0, 0); + EXPECT_EQ(*community.name(), "CM1"); + EXPECT_EQ(*community.value(), "65000:100"); + EXPECT_EQ(*community.type(), bgp::bgp_policy::CommunityType::LARGE); + EXPECT_EQ(*community.description(), "spine tag"); +} + +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + communitiesAccumulateAndAreKeyed) { + runCommunity({"CL100"}, {"CM1", "value", "65000:100"}); + runCommunity({"CL100"}, {"CM2", "value", "65000:200"}); + ASSERT_EQ(lists().size(), 1); + ASSERT_EQ(lists()[0].members()->size(), 2); + + // Re-referencing an existing member by name updates it, not appends. + runCommunity({"CL100"}, {"CM1", "type", "NORMAL"}); + EXPECT_EQ(lists()[0].members()->size(), 2); + EXPECT_EQ(*member(0, 0).value(), "65000:100"); + EXPECT_EQ(*member(0, 0).type(), bgp::bgp_policy::CommunityType::NORMAL); +} + +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + reReferenceReportsExisting) { + runCommunity({"CL100"}, {"CM1"}); + EXPECT_THAT( + runCommunity({"CL100"}, {"CM1"}), + HasSubstr("community CM1 already exists")); + EXPECT_EQ(lists()[0].members()->size(), 1); +} + +// ============================================================================== +// Reject paths — the error is surfaced and nothing is persisted +// ============================================================================== + +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + invalidCommunityTypeRejected) { + auto result = runCommunity({"CL100"}, {"CM1", "type", "MEDIUM"}); + // The full message matters: the accepted-values suffix once printed garbage + // (a string_view capture dangling over a fmt::format temporary). + EXPECT_THAT( + result, + HasSubstr("Invalid type value 'MEDIUM'; expected NORMAL|EXTENDED|LARGE")); + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + rejectedCommunityOnExistingListKeepsList) { + // Create the list first, then reject a member value on it. + runList({"CL100", "description", "keep-me"}); + ASSERT_EQ(lists().size(), 1); + + auto result = runCommunity({"CL100"}, {"CM1", "type", "MEDIUM"}); + EXPECT_THAT(result, HasSubstr("Invalid")); + // The pre-existing list survives; only the phantom member is rolled back — + // including the members field itself, which was unset before. + ASSERT_EQ(lists().size(), 1); + EXPECT_FALSE(lists()[0].members().has_value()); + EXPECT_EQ(*lists()[0].description(), "keep-me"); +} + +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + rejectedCommunityKeepsExistingMembers) { + runCommunity({"CL100"}, {"CM1", "value", "65000:100"}); + ASSERT_EQ(lists()[0].members()->size(), 1); + + auto result = runCommunity({"CL100"}, {"CM2", "type", "MEDIUM"}); + EXPECT_THAT(result, HasSubstr("Invalid")); + // Only the phantom member is rolled back; the existing one survives. + ASSERT_EQ(lists()[0].members()->size(), 1); + EXPECT_EQ(*member(0, 0).name(), "CM1"); +} + +// A list-level attribute alongside a community must be rejected: only the leaf +// command runs, so the list attribute would be silently dropped. +TEST_F( + CmdConfigBgpPolicyCommunityListCommunityTestFixture, + listAttributeMixedWithCommunityRejected) { + auto result = runCommunity({"CL100", "description", "mixed"}, {"CM1"}); + EXPECT_THAT(result, HasSubstr("separate commands")); + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListTest.cpp b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListTest.cpp new file mode 100644 index 0000000000000..186bce550c7ec --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdConfigBgpPolicyCommunityListTest.cpp @@ -0,0 +1,181 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/routing_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; +using facebook::bgp::routing_policy::BooleanOperator; + +namespace facebook::fboss { + +// The community-list dispatcher only touches the BGP side of ConfigSession, +// which seeds from thrift schema defaults when neither a staged session nor a +// system bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpPolicyAsPathListTest). +class CmdConfigBgpPolicyCommunityListTestFixture : public CmdConfigTestBase { + public: + CmdConfigBgpPolicyCommunityListTestFixture() + : CmdConfigTestBase("bgp_community_list_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string run(const std::vector& tokens) { + CmdConfigProtocolBgpPolicyCommunityList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpCommunityListConfig(tokens)); + } + + const std::vector& lists() { + return *ConfigSession::getInstance() + .getBgpConfig() + .policies() + .ensure() + .community_lists(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpCommunityListConfig (arg) validation +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, argValidation) { + // Bare create list. + auto bareList = BgpCommunityListConfig({"CL100"}); + EXPECT_EQ(bareList.listName(), "CL100"); + EXPECT_TRUE(bareList.attr().empty()); + + // List-level attribute. + auto listAttr = + BgpCommunityListConfig({"CL100", "description", "my", "list"}); + EXPECT_EQ(listAttr.attr(), "description"); + EXPECT_EQ(listAttr.values(), std::vector({"my", "list"})); + + // Invalid: empty, empty name, unknown attribute. + EXPECT_THROW(BgpCommunityListConfig({}), std::invalid_argument); + EXPECT_THROW(BgpCommunityListConfig({""}), std::invalid_argument); + EXPECT_THROW( + BgpCommunityListConfig({"CL100", "no-such-attr", "1"}), + std::invalid_argument); + // value is a community attribute, not a list attribute. + EXPECT_THROW( + BgpCommunityListConfig({"CL100", "value", "65000:100"}), + std::invalid_argument); +} + +// ============================================================================== +// List-level handlers +// ============================================================================== + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, bareCreateList) { + auto result = run({"CL100"}); + EXPECT_THAT( + result, HasSubstr("Successfully created BGP community-list CL100")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "CL100"); + EXPECT_FALSE(lists()[0].members().has_value()); + EXPECT_TRUE(sessionFileExists()); + + run({"CL200"}); + ASSERT_EQ(lists().size(), 2); + EXPECT_EQ(*lists()[1].name(), "CL200"); +} + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, setListDescription) { + auto result = run({"CL100", "description", "spine", "communities"}); + EXPECT_THAT(result, HasSubstr("Successfully set description")); + ASSERT_EQ(lists().size(), 1); + // Multi-token description is re-joined. + EXPECT_EQ(*lists()[0].description(), "spine communities"); +} + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, setBooleanOperator) { + auto result = run({"CL100", "boolean-operator", "AND"}); + EXPECT_THAT(result, HasSubstr("Successfully set boolean-operator")); + EXPECT_EQ(*lists()[0].boolean_operator(), BooleanOperator::AND); + + run({"CL100", "boolean-operator", "NOT"}); + EXPECT_EQ(*lists()[0].boolean_operator(), BooleanOperator::NOT); +} + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, booleanOperatorDefaultIsOr) { + run({"CL100"}); + EXPECT_EQ(*lists()[0].boolean_operator(), BooleanOperator::OR); +} + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, setExactMatch) { + auto result = run({"CL100", "exact-match", "true"}); + EXPECT_THAT(result, HasSubstr("Successfully enabled exact-match")); + ASSERT_TRUE(lists()[0].exact_match().has_value()); + EXPECT_TRUE(*lists()[0].exact_match()); + + auto disabled = run({"CL100", "exact-match", "false"}); + EXPECT_THAT(disabled, HasSubstr("Successfully disabled exact-match")); + EXPECT_FALSE(*lists()[0].exact_match()); +} + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, namedListsAreDistinct) { + run({"CL100", "description", "one"}); + run({"CL200", "description", "two"}); + ASSERT_EQ(lists().size(), 2); + // Re-referencing an existing list by name updates it, not appends. + run({"CL100", "description", "one-updated"}); + ASSERT_EQ(lists().size(), 2); + EXPECT_EQ(*lists()[0].description(), "one-updated"); +} + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, reReferenceReportsExisting) { + run({"CL100"}); + // A second bare reference must not claim to have created it again. + EXPECT_THAT(run({"CL100"}), HasSubstr("already exists")); + ASSERT_EQ(lists().size(), 1); +} + +// ============================================================================== +// Reject paths — the error is surfaced and nothing is persisted +// ============================================================================== + +TEST_F( + CmdConfigBgpPolicyCommunityListTestFixture, + invalidBooleanOperatorRejected) { + auto result = run({"CL100", "boolean-operator", "XOR"}); + // The full message matters: the accepted-values suffix once printed garbage + // (a string_view capture dangling over a fmt::format temporary). + EXPECT_THAT( + result, + HasSubstr("Invalid boolean-operator value 'XOR'; expected AND|OR|NOT")); + // The rejected value must not leave a phantom list behind. + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +TEST_F(CmdConfigBgpPolicyCommunityListTestFixture, invalidExactMatchRejected) { + auto result = run({"CL100", "exact-match", "maybe"}); + EXPECT_THAT(result, HasSubstr("Invalid")); + EXPECT_TRUE(lists().empty()); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected input"; +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdDeleteBgpNeighborTest.cpp b/fboss/cli/fboss2/test/config/CmdDeleteBgpNeighborTest.cpp new file mode 100644 index 0000000000000..7d59a8541e6ab --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdDeleteBgpNeighborTest.cpp @@ -0,0 +1,128 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "fboss/cli/fboss2/commands/config/protocol/bgp/neighbor/CmdConfigProtocolBgpNeighbor.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; + +namespace facebook::fboss { + +// Deleting only touches the BGP side of ConfigSession, which seeds from +// thrift schema defaults when neither a staged session nor a system +// bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpNeighborTest). +class CmdDeleteBgpNeighborTestFixture : public CmdConfigTestBase { + public: + CmdDeleteBgpNeighborTestFixture() + : CmdConfigTestBase("bgp_neighbor_delete_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string configure(const std::vector& tokens) { + CmdConfigProtocolBgpNeighbor cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpNeighborConfig(tokens)); + } + + std::string del(const std::vector& tokens) { + CmdDeleteProtocolBgpNeighbor cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpNeighborRef(tokens)); + } + + const std::vector& peers() { + return *ConfigSession::getInstance().getBgpConfig().peers(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpNeighborRef (arg) validation +// ============================================================================== + +TEST_F(CmdDeleteBgpNeighborTestFixture, argValidation) { + // Address normalization mirrors the config command. + EXPECT_EQ(BgpNeighborRef({"2001:DB8::1"}).peerAddr(), "2001:db8::1"); + EXPECT_EQ(BgpNeighborRef({"2001:db8::/64"}).peerAddr(), "2001:db8::/64"); + EXPECT_EQ(BgpNeighborRef({"10.0.0.1/24"}).peerAddr(), "10.0.0.0/24"); + + // Invalid: empty, extra tokens, bad address. + EXPECT_THROW(BgpNeighborRef({}), std::invalid_argument); + EXPECT_THROW(BgpNeighborRef({"10.0.0.1", "10.0.0.2"}), std::invalid_argument); + EXPECT_THROW(BgpNeighborRef({"not-an-ip"}), std::invalid_argument); +} + +// ============================================================================== +// queryClient +// ============================================================================== + +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteExistingNeighbor) { + configure({"10.0.0.1", "remote-asn", "65000"}); + configure({"10.0.0.2", "remote-asn", "65001"}); + ASSERT_EQ(peers().size(), 2); + + auto result = del({"10.0.0.1"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted BGP neighbor 10.0.0.1")); + // The other neighbor survives untouched. + ASSERT_EQ(peers().size(), 1); + EXPECT_EQ(*peers()[0].peer_addr(), "10.0.0.2"); + EXPECT_EQ(peers()[0].remote_as_4_byte().value_or(0), 65001); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteByAnySpelling) { + configure({"2001:db8::1", "remote-asn", "65000"}); + ASSERT_EQ(peers().size(), 1); + // A different spelling of the same address deletes the same peer. + auto result = del({"2001:DB8:0:0::1"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted")); + EXPECT_TRUE(peers().empty()); +} + +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteByEquivalentPrefixSpelling) { + // bgpd masks dynamic-peer prefixes, so 10.0.0.1/24 and 10.0.0.2/24 name the + // same neighbor; deleting via either spelling must find it. + configure({"10.0.0.1/24", "remote-asn", "65000"}); + ASSERT_EQ(peers().size(), 1); + EXPECT_EQ(*peers()[0].peer_addr(), "10.0.0.0/24"); + auto result = del({"10.0.0.2/24"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted")); + EXPECT_TRUE(peers().empty()); +} + +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteUnknownNeighborRejected) { + auto result = del({"192.0.2.99"}); + EXPECT_THAT(result, HasSubstr("Error: BGP neighbor 192.0.2.99 not found")); + // Nothing was persisted for the failed delete. + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected delete"; +} + +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteUnknownLeavesOthersIntact) { + configure({"10.0.0.1", "remote-asn", "65000"}); + auto result = del({"10.0.0.99"}); + EXPECT_THAT(result, HasSubstr("not found")); + ASSERT_EQ(peers().size(), 1); + EXPECT_EQ(*peers()[0].peer_addr(), "10.0.0.1"); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdDeleteBgpPeerGroupTest.cpp b/fboss/cli/fboss2/test/config/CmdDeleteBgpPeerGroupTest.cpp new file mode 100644 index 0000000000000..5ef75558b0e4e --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdDeleteBgpPeerGroupTest.cpp @@ -0,0 +1,160 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "fboss/cli/fboss2/commands/config/protocol/bgp/peer-group/CmdConfigProtocolBgpPeerGroup.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/peer-group/CmdDeleteProtocolBgpPeerGroup.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; + +namespace facebook::fboss { + +// Deleting only touches the BGP side of ConfigSession, which seeds from +// thrift schema defaults when neither a staged session nor a system +// bgpcpp.conf exists — so no seed agent config is needed (mirrors +// CmdConfigBgpPeerGroupTest). +class CmdDeleteBgpPeerGroupTestFixture : public CmdConfigTestBase { + public: + CmdDeleteBgpPeerGroupTestFixture() + : CmdConfigTestBase("bgp_peer_group_delete_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string configure(const std::vector& tokens) { + CmdConfigProtocolBgpPeerGroup cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpPeerGroupConfig(tokens)); + } + + std::string del(const std::vector& tokens) { + CmdDeleteProtocolBgpPeerGroup cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpPeerGroupRef(tokens)); + } + + const std::vector& groups() { + return ConfigSession::getInstance().getBgpConfig().peer_groups().ensure(); + } + + // Reference a peer-group from a neighbor the way a committed config would, + // without pulling the neighbor command (and its whole dispatch table) into + // this test binary. + void addPeerReferencing( + const std::string& peerAddr, + const std::string& groupName) { + bgp::thrift::BgpPeer peer; + peer.local_addr() = "10.0.0.1"; + peer.peer_addr() = peerAddr; + peer.peer_group_name() = groupName; + ConfigSession::getInstance().getBgpConfig().peers()->push_back( + std::move(peer)); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpPeerGroupRef (arg) validation +// ============================================================================== + +TEST_F(CmdDeleteBgpPeerGroupTestFixture, argValidation) { + EXPECT_EQ(BgpPeerGroupRef({"SPINE"}).groupName(), "SPINE"); + + // Invalid: empty, empty name, extra tokens. + EXPECT_THROW(BgpPeerGroupRef({}), std::invalid_argument); + EXPECT_THROW(BgpPeerGroupRef({""}), std::invalid_argument); + EXPECT_THROW(BgpPeerGroupRef({"SPINE", "LEAF"}), std::invalid_argument); +} + +// ============================================================================== +// queryClient +// ============================================================================== + +TEST_F(CmdDeleteBgpPeerGroupTestFixture, deleteExistingGroup) { + configure({"SPINE", "remote-asn", "65000"}); + configure({"LEAF", "remote-asn", "65001"}); + ASSERT_EQ(groups().size(), 2); + + auto result = del({"SPINE"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted BGP peer-group SPINE")); + // The other group survives untouched. + ASSERT_EQ(groups().size(), 1); + EXPECT_EQ(*groups()[0].name(), "LEAF"); + EXPECT_EQ(groups()[0].remote_as_4_byte().value_or(0), 65001); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F(CmdDeleteBgpPeerGroupTestFixture, deleteUnknownGroupRejected) { + auto result = del({"NO-SUCH-GROUP"}); + EXPECT_THAT( + result, HasSubstr("Error: BGP peer-group NO-SUCH-GROUP not found")); + // Nothing was persisted for the failed delete. + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected delete"; +} + +TEST_F(CmdDeleteBgpPeerGroupTestFixture, deleteUnknownLeavesOthersIntact) { + configure({"SPINE", "remote-asn", "65000"}); + auto result = del({"LEAF"}); + EXPECT_THAT(result, HasSubstr("not found")); + ASSERT_EQ(groups().size(), 1); + EXPECT_EQ(*groups()[0].name(), "SPINE"); +} + +TEST_F(CmdDeleteBgpPeerGroupTestFixture, deleteReferencedGroupRejected) { + configure({"SPINE", "remote-asn", "65000"}); + addPeerReferencing("10.0.0.2", "SPINE"); + addPeerReferencing("10.0.0.3", "SPINE"); + + auto result = del({"SPINE"}); + EXPECT_THAT(result, HasSubstr("still referenced")); + // The refusal names every referencing neighbor so the user can act on it. + EXPECT_THAT(result, HasSubstr("10.0.0.2")); + EXPECT_THAT(result, HasSubstr("10.0.0.3")); + ASSERT_EQ(groups().size(), 1); + EXPECT_EQ(*groups()[0].name(), "SPINE"); + EXPECT_EQ(groups()[0].remote_as_4_byte().value_or(0), 65000); +} + +TEST_F(CmdDeleteBgpPeerGroupTestFixture, deleteUnreferencedGroupSucceeds) { + configure({"SPINE", "remote-asn", "65000"}); + configure({"LEAF", "remote-asn", "65001"}); + // A neighbor referencing SPINE must not block deleting LEAF. + addPeerReferencing("10.0.0.2", "SPINE"); + + auto result = del({"LEAF"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted BGP peer-group LEAF")); + ASSERT_EQ(groups().size(), 1); + EXPECT_EQ(*groups()[0].name(), "SPINE"); +} + +TEST_F(CmdDeleteBgpPeerGroupTestFixture, deleteThenRecreateStartsClean) { + configure({"SPINE", "remote-asn", "65000"}); + auto result = del({"SPINE"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted")); + EXPECT_TRUE(groups().empty()); + + configure({"SPINE"}); + ASSERT_EQ(groups().size(), 1); + // The recreated group starts clean — no fields leak across the + // delete/recreate boundary. + EXPECT_FALSE(groups()[0].remote_as_4_byte().has_value()); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyAsPathListTest.cpp b/fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyAsPathListTest.cpp new file mode 100644 index 0000000000000..a8337a0ba9a6c --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyAsPathListTest.cpp @@ -0,0 +1,263 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/as-path-list/CmdConfigProtocolBgpPolicyAsPathList.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/policy/as-path-list/CmdDeleteProtocolBgpPolicyAsPathList.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; + +namespace facebook::fboss { + +// Deleting only touches the BGP side of ConfigSession, which seeds from thrift +// schema defaults when neither a staged session nor a system bgpcpp.conf +// exists — so no seed agent config is needed (mirrors +// CmdConfigBgpPeerGroupTest). +class CmdDeleteBgpPolicyAsPathListTestFixture : public CmdConfigTestBase { + public: + CmdDeleteBgpPolicyAsPathListTestFixture() + : CmdConfigTestBase("bgp_aspath_list_delete_test_%%%%-%%%%-%%%%", "") {} + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string configure(const std::vector& tokens) { + CmdConfigProtocolBgpPolicyAsPathList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpAsPathListConfig(tokens)); + } + + std::string del(const std::vector& tokens) { + CmdDeleteProtocolBgpPolicyAsPathList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpAsPathListRef(tokens)); + } + + const std::vector& lists() { + return *ConfigSession::getInstance() + .getBgpConfig() + .policies() + .ensure() + .aspath_lists(); + } + + // Seed a routing-policy term whose AS_PATH match names `listName` (the + // term/match CLIs live in higher PRs), the way bgpd reads the reference: + // as_path_filters.as_path_list_names. + void addPolicyTermMatching( + const std::string& policy, + int64_t seq, + const std::string& listName) { + auto& cfg = ConfigSession::getInstance().getBgpConfig(); + auto& policies = *cfg.policies().ensure().bgp_policy_statements(); + auto it = std::find_if(policies.begin(), policies.end(), [&](auto& p) { + return *p.name() == policy; + }); + if (it == policies.end()) { + policies.emplace_back(); + policies.back().name() = policy; + it = std::prev(policies.end()); + } + auto& terms = *it->policy_entries(); + terms.emplace_back(); + terms.back().sequence_number() = seq; + auto& matches = + *terms.back().policy_match_entries().ensure().match_entries(); + matches.emplace_back(); + matches.back().type() = bgp::bgp_policy::BgpPolicyAtomicMatchType::AS_PATH; + matches.back().as_path_filters().ensure().as_path_list_names().ensure() = { + listName}; + ConfigSession::getInstance().saveBgpConfig(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpAsPathListRef (arg) validation +// ============================================================================== + +TEST_F(CmdDeleteBgpPolicyAsPathListTestFixture, argValidation) { + EXPECT_EQ(BgpAsPathListRef({"AS100"}).listName(), "AS100"); + EXPECT_FALSE(BgpAsPathListRef({"AS100"}).hasRegex()); + + // Optional `regex ` selector. + auto withRegex = BgpAsPathListRef({"AS100", "regex", "^65000_"}); + EXPECT_EQ(withRegex.listName(), "AS100"); + EXPECT_TRUE(withRegex.hasRegex()); + EXPECT_EQ(withRegex.regex(), "^65000_"); + + // Invalid: empty, empty name, extra tokens, incomplete or trailing selector. + EXPECT_THROW(BgpAsPathListRef({}), std::invalid_argument); + EXPECT_THROW(BgpAsPathListRef({""}), std::invalid_argument); + EXPECT_THROW(BgpAsPathListRef({"AS100", "AS200"}), std::invalid_argument); + EXPECT_THROW(BgpAsPathListRef({"AS100", "regex"}), std::invalid_argument); + EXPECT_THROW(BgpAsPathListRef({"AS100", "regex", ""}), std::invalid_argument); + EXPECT_THROW( + BgpAsPathListRef({"AS100", "regex", "^65000_", "extra"}), + std::invalid_argument); +} + +// ============================================================================== +// queryClient +// ============================================================================== + +TEST_F(CmdDeleteBgpPolicyAsPathListTestFixture, deleteExistingList) { + configure({"AS100", "description", "delete-me"}); + configure({"AS200", "description", "keep"}); + ASSERT_EQ(lists().size(), 2); + + auto result = del({"AS100"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted BGP as-path-list AS100")); + // The other list survives untouched. + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "AS200"); + EXPECT_EQ(*lists()[0].description(), "keep"); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F(CmdDeleteBgpPolicyAsPathListTestFixture, deleteUnknownListRejected) { + auto result = del({"NO-SUCH-LIST"}); + EXPECT_THAT( + result, HasSubstr("Error: BGP as-path-list NO-SUCH-LIST not found")); + // Nothing was persisted for the failed delete. + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected delete"; +} + +TEST_F( + CmdDeleteBgpPolicyAsPathListTestFixture, + deleteUnknownLeavesOthersIntact) { + configure({"AS100", "description", "one"}); + auto result = del({"AS200"}); + EXPECT_THAT(result, HasSubstr("not found")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "AS100"); +} + +// ============================================================================== +// Reference guard — a list a routing-policy term still names is not deletable +// ============================================================================== + +TEST_F(CmdDeleteBgpPolicyAsPathListTestFixture, deleteReferencedListRejected) { + configure({"AS100", "description", "in-use"}); + addPolicyTermMatching("RM100", 10, "AS100"); + + auto result = del({"AS100"}); + EXPECT_THAT(result, HasSubstr("still referenced")); + // The refusal names the policy and term so the user can act on it. + EXPECT_THAT(result, HasSubstr("policy RM100 term 10")); + EXPECT_THAT(result, HasSubstr("remove those matches first")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "AS100"); + EXPECT_EQ(*lists()[0].description(), "in-use"); +} + +TEST_F( + CmdDeleteBgpPolicyAsPathListTestFixture, + deleteReferencedListNamesEveryReferrer) { + configure({"AS100"}); + addPolicyTermMatching("RM100", 10, "AS100"); + addPolicyTermMatching("RM200", 20, "AS100"); + + auto result = del({"AS100"}); + EXPECT_THAT(result, HasSubstr("policy RM100 term 10")); + EXPECT_THAT(result, HasSubstr("policy RM200 term 20")); + ASSERT_EQ(lists().size(), 1); +} + +TEST_F( + CmdDeleteBgpPolicyAsPathListTestFixture, + referenceToOtherListDoesNotBlockDelete) { + configure({"AS100"}); + configure({"AS200"}); + // A term referencing AS100 must not block deleting AS200. + addPolicyTermMatching("RM100", 10, "AS100"); + + auto result = del({"AS200"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted BGP as-path-list AS200")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "AS100"); +} + +TEST_F( + CmdDeleteBgpPolicyAsPathListTestFixture, + rejectedDeleteDoesNotStageWhenNothingStaged) { + // Seed the reference without going through the session file, then confirm + // the refused delete writes nothing. + auto& cfg = ConfigSession::getInstance().getBgpConfig(); + auto& list = cfg.policies().ensure().aspath_lists()->emplace_back(); + list.name() = "AS100"; + auto& policies = *cfg.policies()->bgp_policy_statements(); + policies.emplace_back(); + policies.back().name() = "RM100"; + auto& term = policies.back().policy_entries()->emplace_back(); + term.sequence_number() = 10; + auto& match = + term.policy_match_entries().ensure().match_entries()->emplace_back(); + match.type() = bgp::bgp_policy::BgpPolicyAtomicMatchType::AS_PATH; + match.as_path_filters().ensure().as_path_list_names().ensure() = {"AS100"}; + ASSERT_FALSE(sessionFileExists()); + + auto result = del({"AS100"}); + EXPECT_THAT(result, HasSubstr("still referenced")); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected delete"; +} + +// ============================================================================== +// regex selector — remove one pattern, keep the list +// ============================================================================== + +TEST_F(CmdDeleteBgpPolicyAsPathListTestFixture, deleteOneRegex) { + configure({"AS100", "regex", "^65000_"}); + configure({"AS100", "regex", "_65001$"}); + // Removing a pattern from a referenced list is fine: the name stays defined. + addPolicyTermMatching("RM100", 10, "AS100"); + + auto result = del({"AS100", "regex", "^65000_"}); + EXPECT_THAT( + result, + HasSubstr("Successfully deleted BGP as-path-list AS100 regex ^65000_")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].as_paths(), std::vector({"_65001$"})); + + // Deleting the last pattern clears as_paths but keeps the list. + del({"AS100", "regex", "_65001$"}); + ASSERT_EQ(lists().size(), 1); + EXPECT_FALSE(lists()[0].as_paths().has_value()); +} + +TEST_F(CmdDeleteBgpPolicyAsPathListTestFixture, deleteUnknownRegexRejected) { + configure({"AS100", "regex", "^65000_"}); + auto result = del({"AS100", "regex", "^65002_"}); + EXPECT_THAT( + result, + HasSubstr("Error: BGP as-path-list AS100 regex ^65002_ not found")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(lists()[0].as_paths()->size(), 1); + + EXPECT_THAT( + del({"NO-SUCH-LIST", "regex", "^65000_"}), + HasSubstr("Error: BGP as-path-list NO-SUCH-LIST not found")); +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyCommunityListTest.cpp b/fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyCommunityListTest.cpp new file mode 100644 index 0000000000000..23daff00d388f --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdDeleteBgpPolicyCommunityListTest.cpp @@ -0,0 +1,223 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#include +#include +#include + +#include +#include +#include +#include + +#include "configerator/structs/neteng/bgp_policy/thrift/gen-cpp2/bgp_policy_types.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/BgpCommunityListCliUtils.h" +#include "fboss/cli/fboss2/commands/config/protocol/bgp/policy/community-list/CmdConfigProtocolBgpPolicyCommunityList.h" +#include "fboss/cli/fboss2/commands/delete/protocol/bgp/policy/community-list/CmdDeleteProtocolBgpPolicyCommunityList.h" +#include "fboss/cli/fboss2/session/ConfigSession.h" +#include "fboss/cli/fboss2/test/config/CmdConfigTestBase.h" +#include "fboss/cli/fboss2/utils/HostInfo.h" + +using namespace ::testing; + +namespace facebook::fboss { + +// Deleting only touches the BGP side of ConfigSession, which seeds from thrift +// schema defaults when neither a staged session nor a system bgpcpp.conf +// exists — so no seed agent config is needed (mirrors +// CmdDeleteBgpPolicyAsPathListTest). +class CmdDeleteBgpPolicyCommunityListTestFixture : public CmdConfigTestBase { + public: + CmdDeleteBgpPolicyCommunityListTestFixture() + : CmdConfigTestBase("bgp_community_list_delete_test_%%%%-%%%%-%%%%", "") { + } + + void SetUp() override { + CmdConfigTestBase::SetUp(); + setupTestableConfigSession(); + } + + std::string configure(const std::vector& tokens) { + CmdConfigProtocolBgpPolicyCommunityList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpCommunityListConfig(tokens)); + } + + // Seed an inline Community member. The member level is its own subcommand + // (CmdConfigProtocolBgpPolicyCommunityListCommunity), which lands above this + // command in the stack — so stage it through the shared helpers rather than + // depending on that handler. + void configureCommunity( + const std::string& listName, + const std::string& communityName, + const std::string& value) { + auto& session = ConfigSession::getInstance(); + auto& list = + bgpcli::findOrCreateCommunityList(session.getBgpConfig(), listName); + bgpcli::findOrCreateCommunityMember(list, communityName).value() = value; + session.saveBgpConfig(); + } + + std::string del(const std::vector& tokens) { + CmdDeleteProtocolBgpPolicyCommunityList cmd; + HostInfo hostInfo("testhost"); + return cmd.queryClient(hostInfo, BgpCommunityListRef(tokens)); + } + + const std::vector& lists() { + return *ConfigSession::getInstance() + .getBgpConfig() + .policies() + .ensure() + .community_lists(); + } + + bool sessionFileExists() { + return std::filesystem::exists( + ConfigSession::getInstance().getBgpSessionConfigPath()); + } +}; + +// ============================================================================== +// BgpCommunityListRef (arg) validation +// ============================================================================== + +TEST_F(CmdDeleteBgpPolicyCommunityListTestFixture, argValidation) { + auto listOnly = BgpCommunityListRef({"CL100"}); + EXPECT_EQ(listOnly.listName(), "CL100"); + EXPECT_FALSE(listOnly.hasCommunity()); + + auto withMember = BgpCommunityListRef({"CL100", "community", "CM1"}); + EXPECT_EQ(withMember.listName(), "CL100"); + EXPECT_TRUE(withMember.hasCommunity()); + EXPECT_EQ(withMember.communityName(), "CM1"); + + // Invalid: empty, empty names, non-`community` second token, missing + // community name, extra tokens. + EXPECT_THROW(BgpCommunityListRef({}), std::invalid_argument); + EXPECT_THROW(BgpCommunityListRef({""}), std::invalid_argument); + EXPECT_THROW(BgpCommunityListRef({"CL100", "CL200"}), std::invalid_argument); + EXPECT_THROW( + BgpCommunityListRef({"CL100", "community"}), std::invalid_argument); + EXPECT_THROW( + BgpCommunityListRef({"CL100", "community", ""}), std::invalid_argument); + EXPECT_THROW( + BgpCommunityListRef({"CL100", "community", "CM1", "extra"}), + std::invalid_argument); +} + +// ============================================================================== +// queryClient +// ============================================================================== + +TEST_F(CmdDeleteBgpPolicyCommunityListTestFixture, deleteExistingList) { + configureCommunity("CL100", "CM1", "65000:100"); + configure({"CL200", "description", "keep"}); + ASSERT_EQ(lists().size(), 2); + + auto result = del({"CL100"}); + EXPECT_THAT( + result, HasSubstr("Successfully deleted BGP community-list CL100")); + // The other list survives untouched. + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "CL200"); + EXPECT_EQ(*lists()[0].description(), "keep"); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F(CmdDeleteBgpPolicyCommunityListTestFixture, deleteUnknownListRejected) { + auto result = del({"NO-SUCH-LIST"}); + EXPECT_THAT( + result, HasSubstr("Error: BGP community-list NO-SUCH-LIST not found")); + // Nothing was persisted for the failed delete. + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected delete"; +} + +TEST_F( + CmdDeleteBgpPolicyCommunityListTestFixture, + deleteUnknownLeavesOthersIntact) { + configure({"CL100", "description", "one"}); + auto result = del({"CL200"}); + EXPECT_THAT(result, HasSubstr("not found")); + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "CL100"); +} + +// ============================================================================== +// queryClient: single community member deletion +// ============================================================================== + +TEST_F(CmdDeleteBgpPolicyCommunityListTestFixture, deleteExistingCommunity) { + configureCommunity("CL100", "CM1", "65000:100"); + configureCommunity("CL100", "CM2", "65000:200"); + + auto result = del({"CL100", "community", "CM1"}); + EXPECT_THAT( + result, + HasSubstr("Successfully deleted BGP community-list CL100 community CM1")); + // The list and its other member survive. + ASSERT_EQ(lists().size(), 1); + ASSERT_TRUE(lists()[0].members().has_value()); + ASSERT_EQ(lists()[0].members()->size(), 1); + EXPECT_EQ(*(*lists()[0].members())[0].community_ref()->name(), "CM2"); + EXPECT_TRUE(sessionFileExists()); +} + +TEST_F( + CmdDeleteBgpPolicyCommunityListTestFixture, + deleteLastCommunityUnsetsMembers) { + configureCommunity("CL100", "CM1", "65000:100"); + + auto result = del({"CL100", "community", "CM1"}); + EXPECT_THAT(result, HasSubstr("Successfully deleted")); + // The list stays, shaped like one that never had members. + ASSERT_EQ(lists().size(), 1); + EXPECT_EQ(*lists()[0].name(), "CL100"); + EXPECT_FALSE(lists()[0].members().has_value()); +} + +TEST_F( + CmdDeleteBgpPolicyCommunityListTestFixture, + deleteUnknownCommunityRejected) { + configureCommunity("CL100", "CM1", "65000:100"); + // Consume the session file created by configure so we can assert the + // rejected delete does not persist anything new. + ASSERT_TRUE(sessionFileExists()); + + auto result = del({"CL100", "community", "NO-SUCH-COMMUNITY"}); + EXPECT_THAT( + result, + HasSubstr( + "Error: BGP community-list CL100 community NO-SUCH-COMMUNITY not " + "found")); + // The existing member is untouched. + ASSERT_EQ(lists().size(), 1); + ASSERT_TRUE(lists()[0].members().has_value()); + ASSERT_EQ(lists()[0].members()->size(), 1); + EXPECT_EQ(*(*lists()[0].members())[0].community_ref()->name(), "CM1"); +} + +TEST_F( + CmdDeleteBgpPolicyCommunityListTestFixture, + deleteCommunityFromMemberlessListRejected) { + configure({"CL100", "description", "no", "members"}); + + auto result = del({"CL100", "community", "CM1"}); + EXPECT_THAT( + result, + HasSubstr("Error: BGP community-list CL100 community CM1 not found")); + ASSERT_EQ(lists().size(), 1); + EXPECT_FALSE(lists()[0].members().has_value()); +} + +TEST_F( + CmdDeleteBgpPolicyCommunityListTestFixture, + deleteCommunityFromUnknownListRejected) { + auto result = del({"NO-SUCH-LIST", "community", "CM1"}); + EXPECT_THAT( + result, HasSubstr("Error: BGP community-list NO-SUCH-LIST not found")); + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after rejected delete"; +} + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/integration_test/BUCK b/fboss/cli/fboss2/test/integration_test/BUCK index 2c95f4ddadf44..f000b53f2f8a9 100644 --- a/fboss/cli/fboss2/test/integration_test/BUCK +++ b/fboss/cli/fboss2/test/integration_test/BUCK @@ -13,12 +13,21 @@ oncall("fboss_oss") # # Unlike the Python-based CLI tests, these C++ tests directly invoke the CLI # library code rather than spawning a subprocess. +# bazelify: extra_dep = //neteng/fboss/bgp/if:bgp_thrift cpp_binary( name = "fboss2_integration_test", srcs = [ "ConfigAclRuleTest.cpp", "ConfigAdminDistanceTest.cpp", "ConfigArpTest.cpp", + "ConfigBgpGlobalTest.cpp", + "ConfigBgpNeighborTest.cpp", + "ConfigBgpPeerGroupTest.cpp", + "ConfigBgpPolicyAsPathListEntryTest.cpp", + "ConfigBgpPolicyAsPathListTest.cpp", + "ConfigBgpPolicyCommunityListCommunityTest.cpp", + "ConfigBgpPolicyCommunityListTest.cpp", + "ConfigBgpSessionTest.cpp", "ConfigConcurrentSessionsTest.cpp", "ConfigCoppTest.cpp", "ConfigDhcpSourceOverrideTest.cpp", @@ -65,10 +74,14 @@ cpp_binary( "TunnelIpInIpTest.cpp", "//fboss/cli/fboss2:cmd-list-config-impl", ], + headers = [ + "ConfigBgpTestBase.h", + ], deps = [ "fbsource//third-party/fmt:fmt", "fbsource//third-party/googletest:gmock", "fbsource//third-party/googletest:gtest", + "//configerator/structs/neteng/fboss/bgp:bgp_config-cpp2-types", "//fboss/agent:fboss-types", "//fboss/agent:platform_config-cpp2-types", "//fboss/agent:switch_config-cpp2-types", @@ -94,6 +107,7 @@ cpp_binary( "//folly/json:dynamic", "//folly/logging:init", "//folly/logging:logging", + "//neteng/fboss/bgp/if:bgp_thrift-cpp2-services", "//thrift/lib/cpp/util:enum_utils", ], external_deps = [ diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpGlobalTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpGlobalTest.cpp new file mode 100644 index 0000000000000..9c3a68d779672 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpGlobalTest.cpp @@ -0,0 +1,160 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for `fboss2-dev config protocol bgp global `. + * + * Scope: the BGP *global* tunables only. Each positive test stages the change + * AND commits it, then asserts the value landed at the correct thrift field + * path in bgpd's own running config — fetched over its getRunningConfig RPC + * (see ConfigBgpTestBase::setAndCommit) — which proves the daemon parsed and + * adopted the promoted /etc/coop/bgpcpp/bgpcpp.conf after the commit-triggered + * restart, not merely that the CLI wrote the file. Session-lifecycle behavior + * (clear / diff / rollback / commit-restart mechanics) lives in + * ConfigBgpSessionTest. + * + * - count-confeds-in-as-path-len + * -> BgpConfig.count_confeds_in_as_path_len + * - graceful-restart-time + * -> BgpConfig.graceful_restart_convergence_seconds + * - rib-allocated-path-ids + * -> BgpConfig.bgp_setting_config.enable_rib_allocated_path_id + * - switch-limit-overload-protection-mode + * -> BgpConfig.switch_limit_config.overload_protection_mode + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd is installed/active (commit restarts it). + */ + +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" + +using namespace facebook::fboss; +using ::testing::HasSubstr; + +class ConfigBgpGlobalTest : public ConfigBgpTestBase {}; + +TEST_F(ConfigBgpGlobalTest, SetCountConfedsInAsPathLenTrue) { + auto config = setAndCommit("count-confeds-in-as-path-len", "true"); + ASSERT_TRUE(config.count("count_confeds_in_as_path_len")); + EXPECT_TRUE(config["count_confeds_in_as_path_len"].asBool()); +} + +TEST_F(ConfigBgpGlobalTest, SetCountConfedsInAsPathLenFalse) { + auto config = setAndCommit("count-confeds-in-as-path-len", "false"); + ASSERT_TRUE(config.count("count_confeds_in_as_path_len")); + EXPECT_FALSE(config["count_confeds_in_as_path_len"].asBool()); +} + +TEST_F(ConfigBgpGlobalTest, SetGracefulRestartTime) { + // Stage a value that differs from the current system config: committing an + // unchanged config is a no-op that yields no SHA, and the shipped + // bgpcpp.conf already sets 120. + int64_t target = 120; + try { + auto current = readSystemBgpConfig(); + if (current.count("graceful_restart_convergence_seconds") && + current["graceful_restart_convergence_seconds"].asInt() == target) { + target = 121; + } + } catch (const std::exception&) { + // No readable system config yet — any value is a change. + } + auto config = setAndCommit("graceful-restart-time", std::to_string(target)); + ASSERT_TRUE(config.count("graceful_restart_convergence_seconds")); + EXPECT_EQ(config["graceful_restart_convergence_seconds"].asInt(), target); + // Must not write the per-peer timer field name. + EXPECT_FALSE(config.count("graceful_restart_seconds")); +} + +TEST_F(ConfigBgpGlobalTest, SetRibAllocatedPathIdsTrue) { + auto config = setAndCommit("rib-allocated-path-ids", "true"); + ASSERT_TRUE(config.count("bgp_setting_config")); + ASSERT_TRUE( + config["bgp_setting_config"].count("enable_rib_allocated_path_id")); + EXPECT_TRUE( + config["bgp_setting_config"]["enable_rib_allocated_path_id"].asBool()); + // Must be nested, not at the top level. + EXPECT_FALSE(config.count("enable_rib_allocated_path_id")); +} + +TEST_F(ConfigBgpGlobalTest, SetRibAllocatedPathIdsFalse) { + auto config = setAndCommit("rib-allocated-path-ids", "false"); + ASSERT_TRUE(config.count("bgp_setting_config")); + ASSERT_TRUE( + config["bgp_setting_config"].count("enable_rib_allocated_path_id")); + EXPECT_FALSE( + config["bgp_setting_config"]["enable_rib_allocated_path_id"].asBool()); +} + +TEST_F(ConfigBgpGlobalTest, InvalidBoolValueRejected) { + clearBgpSession(); + auto result = runCli( + {"config", + "protocol", + "bgp", + "global", + "count-confeds-in-as-path-len", + "maybe"}); + // The handler reports the error on stdout; ensure it did not silently + // succeed and persist a bogus value. + EXPECT_THAT(result.stdout, HasSubstr("Invalid value")); + // The handler must bail out before saving, so no session file is written for + // the rejected input. + EXPECT_FALSE(std::filesystem::exists(bgpSessionPath())) + << "session file should not exist after rejected input"; +} + +TEST_F(ConfigBgpGlobalTest, RouterIdInvalidRejected) { + clearBgpSession(); + auto result = + runCli({"config", "protocol", "bgp", "global", "router-id", "not-an-ip"}); + EXPECT_THAT(result.stdout, HasSubstr("Invalid router-id address")); + EXPECT_FALSE(std::filesystem::exists(bgpSessionPath())) + << "session file should not exist after rejected input"; +} + +TEST_F(ConfigBgpGlobalTest, RouterIdV6Rejected) { + clearBgpSession(); + auto result = runCli( + {"config", "protocol", "bgp", "global", "router-id", "2001:db8::1"}); + EXPECT_THAT(result.stdout, HasSubstr("requires an IPv4 address")); + EXPECT_FALSE(std::filesystem::exists(bgpSessionPath())) + << "session file should not exist after rejected input"; +} + +TEST_F(ConfigBgpGlobalTest, OverloadProtectionModeOutOfRangeRejected) { + clearBgpSession(); + auto result = runCli( + {"config", + "protocol", + "bgp", + "global", + "switch-limit-overload-protection-mode", + "999"}); + EXPECT_THAT(result.stdout, HasSubstr("is not a valid mode")); + EXPECT_THAT(result.stdout, HasSubstr("APPLY_GOLDEN_PREFIX_POLICY")); + EXPECT_FALSE(std::filesystem::exists(bgpSessionPath())) + << "session file should not exist after rejected input"; +} + +TEST_F(ConfigBgpGlobalTest, SetOverloadProtectionModeAndCommit) { + auto config = setAndCommit("switch-limit-overload-protection-mode", "2"); + ASSERT_TRUE(config.count("switch_limit_config")); + ASSERT_TRUE(config["switch_limit_config"].count("overload_protection_mode")); + EXPECT_EQ( + config["switch_limit_config"]["overload_protection_mode"].asInt(), 2); +} + +TEST_F(ConfigBgpGlobalTest, NegativeGracefulRestartTimeRejected) { + clearBgpSession(); + auto result = runCli( + {"config", "protocol", "bgp", "global", "graceful-restart-time", "-1"}); + EXPECT_THAT(result.stdout, HasSubstr("non-negative")); + EXPECT_FALSE(std::filesystem::exists(bgpSessionPath())) + << "session file should not exist after rejected input"; +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpNeighborTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpNeighborTest.cpp new file mode 100644 index 0000000000000..684de21966273 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpNeighborTest.cpp @@ -0,0 +1,192 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for `fboss2-dev config protocol bgp neighbor + * [ ...]`. + * + * Scope: the per-neighbor attributes. Every test stages the change AND + * commits it, then asserts the value landed at the correct thrift field path + * inside the matching .peers[] entry of the promoted system config + * (/etc/coop/bgpcpp/bgpcpp.conf) and of bgpd's own running config. Stage-only + * behavior (attribute parsing, validation, rejection) is covered by the unit + * tests; an integration test that never commits exercises no daemon. + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd is installed/active (commit restarts it). + */ + +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" +#include "folly/json/dynamic.h" +#include "gmock/gmock.h" + +using namespace facebook::fboss; +using ::testing::HasSubstr; +using ::testing::Not; + +namespace { +// Test-only neighbor addresses, from the documentation range (RFC 3849) so +// they never collide with a real peer in the device's running BGP config. +const std::string kNeighbor = "2001:db8::1234"; +} // namespace + +class ConfigBgpNeighborTest : public ConfigBgpTestBase { + protected: + // Stage `config protocol bgp neighbor ` WITHOUT clearing the + // staged session, so attributes can accumulate across invocations. Returns + // the staged session JSON. + folly::dynamic stageNeighbor(const std::vector& tokens) { + std::vector args = {"config", "protocol", "bgp", "neighbor"}; + args.insert(args.end(), tokens.begin(), tokens.end()); + auto result = runCli(args); + EXPECT_EQ(result.exitCode, 0) + << "stdout=" << result.stdout << " stderr=" << result.stderr; + EXPECT_THAT(result.stdout, Not(HasSubstr("Error:"))); + return readBgpSessionConfig(); + } + + // The .peers[] entry for addr, or nullptr. + static const folly::dynamic* findPeer( + const folly::dynamic& config, + const std::string& addr) { + if (config.count("peers") == 0) { + return nullptr; + } + for (const auto& peer : config["peers"]) { + if (peer.count("peer_addr") && peer["peer_addr"].asString() == addr) { + return &peer; + } + } + return nullptr; + } +}; + +TEST_F(ConfigBgpNeighborTest, SetRemoteAsnAndCommit) { + discardSession(); + clearBgpSession(); + stageNeighbor({kNeighbor, "remote-asn", "65000"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Two layers: the promoted file proves what the commit wrote; the daemon's + // getRunningConfig RPC proves bgpd parsed and adopted it after the restart. + auto config = readSystemBgpConfig(); + const auto* peer = findPeer(config, kNeighbor); + ASSERT_NE(peer, nullptr) << "committed config has no peer " << kNeighbor; + ASSERT_TRUE(peer->count("remote_as_4_byte")); + EXPECT_EQ((*peer)["remote_as_4_byte"].asInt(), 65000); + + auto running = readRunningBgpConfigViaRpc(); + const auto* runningPeer = findPeer(running, kNeighbor); + ASSERT_NE(runningPeer, nullptr) + << "bgpd's running config has no peer " << kNeighbor; + EXPECT_EQ((*runningPeer)["remote_as_4_byte"].asInt(), 65000); +} + +TEST_F(ConfigBgpNeighborTest, SetTimersHoldTimeAndCommit) { + discardSession(); + clearBgpSession(); + stageNeighbor({kNeighbor, "timers", "hold-time", "90"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Verify nested timer fields through the daemon's own view of its config. + auto running = readRunningBgpConfigViaRpc(); + const auto* peer = findPeer(running, kNeighbor); + ASSERT_NE(peer, nullptr) << "bgpd's running config has no peer " << kNeighbor; + ASSERT_TRUE(peer->count("bgp_peer_timers")); + EXPECT_EQ((*peer)["bgp_peer_timers"]["hold_time_seconds"].asInt(), 90); +} + +TEST_F(ConfigBgpNeighborTest, GracefulRestartKeepsInheritedHoldTime) { + // bgpd takes a peer's whole timers struct once any field of it is set, so + // the first per-neighbor timer must carry the inherited hold time along + // instead of the thrift default (0 = no keepalives on the session). + discardSession(); + clearBgpSession(); + auto global = + runCli({"config", "protocol", "bgp", "global", "hold-time", "180"}); + ASSERT_EQ(global.exitCode, 0) << global.stdout << global.stderr; + stageNeighbor({kNeighbor, "graceful-restart", "restart-time", "60"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* peer = findPeer(running, kNeighbor); + ASSERT_NE(peer, nullptr) << "bgpd's running config has no peer " << kNeighbor; + ASSERT_TRUE(peer->count("bgp_peer_timers")); + EXPECT_EQ((*peer)["bgp_peer_timers"]["hold_time_seconds"].asInt(), 180); + EXPECT_EQ((*peer)["bgp_peer_timers"]["keep_alive_seconds"].asInt(), 60); + EXPECT_EQ((*peer)["bgp_peer_timers"]["graceful_restart_seconds"].asInt(), 60); +} + +TEST_F(ConfigBgpNeighborTest, SessionKnobsAndCommit) { + // The per-session knobs bgpd resolves peer > group (remove-private-as, + // ttl-security-hops) reach the daemon's running config via the same path. + discardSession(); + clearBgpSession(); + stageNeighbor({kNeighbor, "remove-private-as", "true"}); + stageNeighbor({kNeighbor, "ttl-security-hops", "2"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* peer = findPeer(running, kNeighbor); + ASSERT_NE(peer, nullptr) << "bgpd's running config has no peer " << kNeighbor; + ASSERT_TRUE(peer->count("remove_private_as")); + EXPECT_TRUE((*peer)["remove_private_as"].asBool()); + ASSERT_TRUE(peer->count("ttl_security_hops")); + EXPECT_EQ((*peer)["ttl_security_hops"].asInt(), 2); +} + +TEST_F(ConfigBgpNeighborTest, RouteLimitWarningKeepsUnlimitedCap) { + // A warning threshold on a peer with no route limit above it must not + // introduce the 12000-route thrift default: bgpd reads max_routes 0 as + // unlimited, which is what the peer had before the edit. + discardSession(); + clearBgpSession(); + stageNeighbor({kNeighbor, "max-route", "pre-warning-threshold", "30000"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* peer = findPeer(running, kNeighbor); + ASSERT_NE(peer, nullptr) << "bgpd's running config has no peer " << kNeighbor; + ASSERT_TRUE(peer->count("pre_filter")); + EXPECT_EQ((*peer)["pre_filter"]["max_routes"].asInt(), 0); + EXPECT_EQ((*peer)["pre_filter"]["warning_limit"].asInt(), 30000); +} + +TEST_F(ConfigBgpNeighborTest, DeleteNeighborAndCommit) { + // Land a neighbor in the system config, then delete it through a second + // commit and verify it is gone from the promoted config. + discardSession(); + clearBgpSession(); + stageNeighbor({kNeighbor, "remote-asn", "65000"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + ASSERT_NE(findPeer(readRunningBgpConfigViaRpc(), kNeighbor), nullptr) + << "setup commit did not land the neighbor in bgpd's running config"; + + clearBgpSession(); + auto result = runCli({"delete", "protocol", "bgp", "neighbor", kNeighbor}); + EXPECT_THAT(result.stdout, HasSubstr("Successfully deleted")); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after delete commit; state=" + << bgpDaemonActiveState(); + EXPECT_EQ(findPeer(readRunningBgpConfigViaRpc(), kNeighbor), nullptr) + << "deleted neighbor still present in bgpd's running config"; +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpPeerGroupTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpPeerGroupTest.cpp new file mode 100644 index 0000000000000..f1ede5067661b --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpPeerGroupTest.cpp @@ -0,0 +1,195 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for `fboss2-dev config protocol bgp peer-group + * [ ...]`. + * + * Scope: the per-peer-group attributes. Every test stages the change AND + * commits it, then asserts the value landed at the correct thrift field path + * inside the matching .peer_groups[] entry of bgpd's running config (via + * getRunningConfig RPC). Stage-only behavior (attribute parsing, validation, + * rejection) is covered by the unit tests; an integration test that never + * commits exercises no daemon. + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd is installed/active (commit restarts it). + */ + +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" +#include "folly/json/dynamic.h" +#include "gmock/gmock.h" + +using namespace facebook::fboss; +using ::testing::HasSubstr; +using ::testing::Not; + +namespace { +// Test-only peer-group names, unlikely to collide with a real group in the +// device's running BGP config. +const std::string kGroup = "FBOSS2-TEST-GROUP"; +} // namespace + +class ConfigBgpPeerGroupTest : public ConfigBgpTestBase { + protected: + // Stage `config protocol bgp peer-group ` WITHOUT clearing the + // staged session, so attributes can accumulate across invocations. Returns + // the staged session JSON. + folly::dynamic stagePeerGroup(const std::vector& tokens) { + std::vector args = {"config", "protocol", "bgp", "peer-group"}; + args.insert(args.end(), tokens.begin(), tokens.end()); + auto result = runCli(args); + EXPECT_EQ(result.exitCode, 0) + << "stdout=" << result.stdout << " stderr=" << result.stderr; + EXPECT_THAT(result.stdout, Not(HasSubstr("Error:"))); + return readBgpSessionConfig(); + } + + // The .peer_groups[] entry named `name`, or nullptr. + static const folly::dynamic* findGroup( + const folly::dynamic& config, + const std::string& name) { + if (config.count("peer_groups") == 0) { + return nullptr; + } + for (const auto& group : config["peer_groups"]) { + if (group.count("name") && group["name"].asString() == name) { + return &group; + } + } + return nullptr; + } +}; + +TEST_F(ConfigBgpPeerGroupTest, SetRemoteAsnAndCommit) { + discardSession(); + clearBgpSession(); + stagePeerGroup({kGroup, "remote-asn", "65000"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Two layers: the promoted file proves what the commit wrote; the daemon's + // getRunningConfig RPC proves bgpd parsed and adopted it after the restart. + auto config = readSystemBgpConfig(); + const auto* group = findGroup(config, kGroup); + ASSERT_NE(group, nullptr) << "committed config has no peer-group " << kGroup; + ASSERT_TRUE(group->count("remote_as_4_byte")); + EXPECT_EQ((*group)["remote_as_4_byte"].asInt(), 65000); + + auto running = readRunningBgpConfigViaRpc(); + const auto* runningGroup = findGroup(running, kGroup); + ASSERT_NE(runningGroup, nullptr) + << "bgpd's running config has no peer-group " << kGroup; + EXPECT_EQ((*runningGroup)["remote_as_4_byte"].asInt(), 65000); +} + +TEST_F(ConfigBgpPeerGroupTest, SetTimersHoldTimeAndCommit) { + discardSession(); + clearBgpSession(); + stagePeerGroup({kGroup, "timers", "hold-time", "90"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Verify nested timer fields through the daemon's own view of its config. + auto running = readRunningBgpConfigViaRpc(); + const auto* group = findGroup(running, kGroup); + ASSERT_NE(group, nullptr) + << "bgpd's running config has no peer-group " << kGroup; + ASSERT_TRUE(group->count("bgp_peer_timers")); + EXPECT_EQ((*group)["bgp_peer_timers"]["hold_time_seconds"].asInt(), 90); + // bgpd applies the group's timers struct to its members as a whole, so the + // fields the user did not set must carry the global defaults (hold/3 + // keepalive), not the thrift default of 0 that would disable keepalives. + ASSERT_TRUE(running.count("hold_time")); + EXPECT_EQ( + (*group)["bgp_peer_timers"]["keep_alive_seconds"].asInt(), + running["hold_time"].asInt() / 3); +} + +TEST_F(ConfigBgpPeerGroupTest, SetPassiveAndCommit) { + discardSession(); + clearBgpSession(); + stagePeerGroup({kGroup, "passive", "true"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* group = findGroup(running, kGroup); + ASSERT_NE(group, nullptr) + << "bgpd's running config has no peer-group " << kGroup; + ASSERT_TRUE(group->count("is_passive")); + EXPECT_TRUE((*group)["is_passive"].asBool()); +} + +TEST_F(ConfigBgpPeerGroupTest, SessionKnobsAndCommit) { + // The per-session knobs bgpd resolves peer > group (remove-private-as, + // ttl-security-hops) reach the daemon's running config via the same path. + discardSession(); + clearBgpSession(); + stagePeerGroup({kGroup, "remove-private-as", "true"}); + stagePeerGroup({kGroup, "ttl-security-hops", "2"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* group = findGroup(running, kGroup); + ASSERT_NE(group, nullptr) + << "bgpd's running config has no peer-group " << kGroup; + ASSERT_TRUE(group->count("remove_private_as")); + EXPECT_TRUE((*group)["remove_private_as"].asBool()); + ASSERT_TRUE(group->count("ttl_security_hops")); + EXPECT_EQ((*group)["ttl_security_hops"].asInt(), 2); +} + +TEST_F(ConfigBgpPeerGroupTest, RouteLimitWarningKeepsUnlimitedCap) { + // A warning threshold on a group with no route limit must not introduce the + // 12000-route thrift default: bgpd reads max_routes 0 as unlimited, which + // is what the group's members had before the edit. + discardSession(); + clearBgpSession(); + stagePeerGroup({kGroup, "max-route", "pre-warning-threshold", "30000"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* group = findGroup(running, kGroup); + ASSERT_NE(group, nullptr) + << "bgpd's running config has no peer-group " << kGroup; + ASSERT_TRUE(group->count("pre_filter")); + EXPECT_EQ((*group)["pre_filter"]["max_routes"].asInt(), 0); + EXPECT_EQ((*group)["pre_filter"]["warning_limit"].asInt(), 30000); +} + +TEST_F(ConfigBgpPeerGroupTest, DeleteGroupAndCommit) { + // Land a peer-group in the system config, then delete it through a second + // commit and verify it is gone from bgpd's running config. + discardSession(); + clearBgpSession(); + stagePeerGroup({kGroup, "remote-asn", "65000"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + ASSERT_NE(findGroup(readRunningBgpConfigViaRpc(), kGroup), nullptr) + << "setup commit did not land the peer-group in bgpd's running config"; + + clearBgpSession(); + auto result = runCli({"delete", "protocol", "bgp", "peer-group", kGroup}); + EXPECT_THAT(result.stdout, HasSubstr("Successfully deleted")); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after delete commit; state=" + << bgpDaemonActiveState(); + EXPECT_EQ(findGroup(readRunningBgpConfigViaRpc(), kGroup), nullptr) + << "deleted peer-group still present in bgpd's running config"; +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListEntryTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListEntryTest.cpp new file mode 100644 index 0000000000000..13c374ef60894 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListEntryTest.cpp @@ -0,0 +1,127 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for `fboss2-dev config protocol bgp policy as-path-list + * entry [ ...]`. + * + * Scope: the entry level. Every test stages the change AND commits it, then + * asserts the value landed at the correct thrift field path inside the + * matching .policies.aspath_lists[].as_path_list[] entry of bgpd's running + * config (via getRunningConfig RPC). Stage-only behavior (attribute parsing, + * validation, rejection) is covered by the unit tests; an integration test + * that never commits exercises no daemon. + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd is installed/active (commit restarts it). + */ + +#include +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" +#include "folly/json/dynamic.h" +#include "gmock/gmock.h" + +using namespace facebook::fboss; +using ::testing::HasSubstr; +using ::testing::Not; + +namespace { +// Test-only AS-path-list name, unlikely to collide with a real list in the +// device's running BGP config. +const std::string kList = "FBOSS2-TEST-ASPATH-ENTRY"; +} // namespace + +class ConfigBgpPolicyAsPathListEntryTest : public ConfigBgpTestBase { + protected: + // Stage `config protocol bgp policy as-path-list entry ` + // WITHOUT clearing the staged session, so attributes can accumulate across + // invocations. Returns the staged session JSON. + folly::dynamic stageEntry(const std::vector& tokens) { + std::vector args = { + "config", "protocol", "bgp", "policy", "as-path-list", kList, "entry"}; + args.insert(args.end(), tokens.begin(), tokens.end()); + auto result = runCli(args); + EXPECT_EQ(result.exitCode, 0) + << "stdout=" << result.stdout << " stderr=" << result.stderr; + EXPECT_THAT(result.stdout, Not(HasSubstr("Error:"))); + return readBgpSessionConfig(); + } + + // The .policies.aspath_lists[] entry named `name`, or nullptr. + static const folly::dynamic* findList( + const folly::dynamic& config, + const std::string& name) { + if (config.count("policies") == 0 || + config["policies"].count("aspath_lists") == 0) { + return nullptr; + } + for (const auto& list : config["policies"]["aspath_lists"]) { + if (list.count("name") && list["name"].asString() == name) { + return &list; + } + } + return nullptr; + } + + // The .as_path_list[] entry with sequence_number `seq` inside `list`, or + // nullptr. + static const folly::dynamic* findEntry( + const folly::dynamic& list, + int64_t seq) { + if (list.count("as_path_list") == 0) { + return nullptr; + } + for (const auto& entry : list["as_path_list"]) { + if (entry.count("sequence_number") && + entry["sequence_number"].asInt() == seq) { + return &entry; + } + } + return nullptr; + } +}; + +TEST_F(ConfigBgpPolicyAsPathListEntryTest, SetEntryAsnRegexpAndCommit) { + discardSession(); + clearBgpSession(); + stageEntry({"10", "asn-regexp", "^65000_"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Verify the nested union path .as_path.as_path.asn_regexp through the + // daemon's own view of its config — which also confirms the entry + // subcommand's implicit creation of its parent list reaches bgpd. + auto running = readRunningBgpConfigViaRpc(); + const auto* list = findList(running, kList); + ASSERT_NE(list, nullptr) << "bgpd's running config has no as-path-list " + << kList; + const auto* entry = findEntry(*list, 10); + ASSERT_NE(entry, nullptr) << "running config has no entry 10"; + EXPECT_EQ((*entry)["as_path"]["as_path"]["asn_regexp"].asString(), "^65000_"); +} + +TEST_F(ConfigBgpPolicyAsPathListEntryTest, SetEntryMatchLogicAndCommit) { + discardSession(); + clearBgpSession(); + stageEntry({"20", "asn-regexp", "^65001_"}); + stageEntry({"20", "match-logic", "NOT_EQUAL"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* list = findList(running, kList); + ASSERT_NE(list, nullptr) << "bgpd's running config has no as-path-list " + << kList; + const auto* entry = findEntry(*list, 20); + ASSERT_NE(entry, nullptr) << "running config has no entry 20"; + // Enums ride the SimpleJSON wire format as integers: + // routing_policy.MatchValueLogicOperator.NOT_EQUAL = 1. + EXPECT_EQ((*entry)["match_logic_type"].asInt(), 1); +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListTest.cpp new file mode 100644 index 0000000000000..7597b59716247 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyAsPathListTest.cpp @@ -0,0 +1,162 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for `fboss2-dev config protocol bgp policy as-path-list + * [ ...]`. + * + * Scope: the AS-path list level (its entries are covered by + * ConfigBgpPolicyAsPathListEntryTest). Every test stages the change AND + * commits it, then asserts the value landed at the correct thrift field path + * inside the matching .policies.aspath_lists[] entry of bgpd's running config + * (via getRunningConfig RPC) — which also confirms bgpd accepts and adopts a + * `.policies` blob at all. Stage-only behavior (attribute parsing, + * validation, rejection) is covered by the unit tests; an integration test + * that never commits exercises no daemon. + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd is installed/active (commit restarts it). + */ + +#include +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" +#include "folly/json/dynamic.h" +#include "gmock/gmock.h" + +using namespace facebook::fboss; +using ::testing::HasSubstr; +using ::testing::Not; + +namespace { +// Test-only AS-path-list names, unlikely to collide with a real list in the +// device's running BGP config. +const std::string kList = "FBOSS2-TEST-ASPATH"; +} // namespace + +class ConfigBgpPolicyAsPathListTest : public ConfigBgpTestBase { + protected: + // Stage `config protocol bgp policy as-path-list ` WITHOUT + // clearing the staged session, so attributes can accumulate across + // invocations. Returns the staged session JSON. + folly::dynamic stageAsPathList(const std::vector& tokens) { + std::vector args = { + "config", "protocol", "bgp", "policy", "as-path-list"}; + args.insert(args.end(), tokens.begin(), tokens.end()); + auto result = runCli(args); + EXPECT_EQ(result.exitCode, 0) + << "stdout=" << result.stdout << " stderr=" << result.stderr; + EXPECT_THAT(result.stdout, Not(HasSubstr("Error:"))); + return readBgpSessionConfig(); + } + + // The .policies.aspath_lists[] entry named `name`, or nullptr. + static const folly::dynamic* findList( + const folly::dynamic& config, + const std::string& name) { + if (config.count("policies") == 0 || + config["policies"].count("aspath_lists") == 0) { + return nullptr; + } + for (const auto& list : config["policies"]["aspath_lists"]) { + if (list.count("name") && list["name"].asString() == name) { + return &list; + } + } + return nullptr; + } +}; + +TEST_F(ConfigBgpPolicyAsPathListTest, SetListDescriptionAndCommit) { + discardSession(); + clearBgpSession(); + stageAsPathList({kList, "description", "test", "spine", "as-paths"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Two layers: the promoted file proves what the commit wrote; the daemon's + // getRunningConfig RPC proves bgpd parsed and adopted the .policies blob. + auto config = readSystemBgpConfig(); + const auto* list = findList(config, kList); + ASSERT_NE(list, nullptr) << "committed config has no as-path-list " << kList; + EXPECT_EQ((*list)["description"].asString(), "test spine as-paths"); + + auto running = readRunningBgpConfigViaRpc(); + const auto* runningList = findList(running, kList); + ASSERT_NE(runningList, nullptr) + << "bgpd's running config has no as-path-list " << kList; + EXPECT_EQ((*runningList)["description"].asString(), "test spine as-paths"); +} + +TEST_F(ConfigBgpPolicyAsPathListTest, SetRegexAndOperatorAndCommit) { + discardSession(); + clearBgpSession(); + stageAsPathList({kList, "regex", "^65000_"}); + stageAsPathList({kList, "regex", "_65001$"}); + stageAsPathList({kList, "boolean-operator", "AND"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // bgpd matches on as_paths + boolean_operator (not the entry list); the + // running config proves it parsed and adopted both. + auto running = readRunningBgpConfigViaRpc(); + const auto* list = findList(running, kList); + ASSERT_NE(list, nullptr) << "bgpd's running config has no as-path-list " + << kList; + ASSERT_TRUE(list->count("as_paths")); + std::vector paths; + for (const auto& p : (*list)["as_paths"]) { + paths.push_back(p.asString()); + } + EXPECT_EQ(paths, std::vector({"^65000_", "_65001$"})); + ASSERT_TRUE(list->count("boolean_operator")); + // routing_policy.BooleanOperator.AND == 1 + EXPECT_EQ((*list)["boolean_operator"].asInt(), 1); +} + +TEST_F(ConfigBgpPolicyAsPathListTest, MalformedRegexRejected) { + clearBgpSession(); + auto result = runCli( + {"config", + "protocol", + "bgp", + "policy", + "as-path-list", + kList, + "regex", + "^65000_("}); + EXPECT_THAT(result.stdout, HasSubstr("Malformed regex")); + EXPECT_FALSE(std::filesystem::exists(bgpSessionPath())) + << "session file should not exist after rejected input"; +} + +TEST_F(ConfigBgpPolicyAsPathListTest, DeleteListAndCommit) { + // Land an as-path-list in the system config, then delete it through a second + // commit and verify it is gone from bgpd's running config. + discardSession(); + clearBgpSession(); + stageAsPathList({kList, "description", "delete-me"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + ASSERT_NE(findList(readRunningBgpConfigViaRpc(), kList), nullptr) + << "setup commit did not land the as-path-list in bgpd's running config"; + + clearBgpSession(); + auto result = + runCli({"delete", "protocol", "bgp", "policy", "as-path-list", kList}); + EXPECT_THAT(result.stdout, HasSubstr("Successfully deleted")); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after delete commit; state=" + << bgpDaemonActiveState(); + EXPECT_EQ(findList(readRunningBgpConfigViaRpc(), kList), nullptr) + << "deleted as-path-list still present in bgpd's running config"; +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListCommunityTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListCommunityTest.cpp new file mode 100644 index 0000000000000..8b06618521fc9 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListCommunityTest.cpp @@ -0,0 +1,174 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for `fboss2-dev config protocol bgp policy community-list + * community [ ...]`. + * + * Scope: the inline community-member level. Every test stages the change AND + * commits it, then asserts the value landed at the correct thrift field path + * inside the matching .policies.community_lists[].members[] entry of bgpd's + * running config (via getRunningConfig RPC). Stage-only behavior (attribute + * parsing, validation, rejection) is covered by the unit tests; an integration + * test that never commits exercises no daemon. + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd is installed/active (commit restarts it). + */ + +#include +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" +#include "folly/json/dynamic.h" +#include "gmock/gmock.h" + +using namespace facebook::fboss; +using ::testing::HasSubstr; +using ::testing::Not; + +namespace { +// Test-only community-list / member names, unlikely to collide with a real +// list in the device's running BGP config. +const std::string kList = "FBOSS2-TEST-COMMUNITY-MEMBER"; +const std::string kMember = "FBOSS2-TEST-CM1"; +const std::string kMember2 = "FBOSS2-TEST-CM2"; +} // namespace + +class ConfigBgpPolicyCommunityListCommunityTest : public ConfigBgpTestBase { + protected: + // Stage `config protocol bgp policy community-list community + // ` WITHOUT clearing the staged session, so attributes can + // accumulate across invocations. Returns the staged session JSON. + folly::dynamic stageCommunity(const std::vector& tokens) { + std::vector args = { + "config", + "protocol", + "bgp", + "policy", + "community-list", + kList, + "community"}; + args.insert(args.end(), tokens.begin(), tokens.end()); + auto result = runCli(args); + EXPECT_EQ(result.exitCode, 0) + << "stdout=" << result.stdout << " stderr=" << result.stderr; + EXPECT_THAT(result.stdout, Not(HasSubstr("Error:"))); + return readBgpSessionConfig(); + } + + // The .policies.community_lists[] entry named `name`, or nullptr. + static const folly::dynamic* findList( + const folly::dynamic& config, + const std::string& name) { + if (config.count("policies") == 0 || + config["policies"].count("community_lists") == 0) { + return nullptr; + } + for (const auto& list : config["policies"]["community_lists"]) { + if (list.count("name") && list["name"].asString() == name) { + return &list; + } + } + return nullptr; + } + + // The inline Community held by a .members[] entry named `name`, or nullptr. + static const folly::dynamic* findMember( + const folly::dynamic& list, + const std::string& name) { + if (list.count("members") == 0) { + return nullptr; + } + for (const auto& member : list["members"]) { + if (member.count("community") && member["community"].count("name") && + member["community"]["name"].asString() == name) { + return &member["community"]; + } + } + return nullptr; + } +}; + +TEST_F(ConfigBgpPolicyCommunityListCommunityTest, SetAttributesAndCommit) { + discardSession(); + clearBgpSession(); + stageCommunity({kMember, "value", "65000:100"}); + stageCommunity({kMember, "type", "NORMAL"}); + stageCommunity({kMember, "description", "test"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Verify the nested member path .members[].community.{value,type, + // description} through the daemon's own view of its config — which also + // confirms the community subcommand's implicit creation of its parent list + // reaches bgpd. + auto running = readRunningBgpConfigViaRpc(); + const auto* list = findList(running, kList); + ASSERT_NE(list, nullptr) << "bgpd's running config has no community-list " + << kList; + const auto* community = findMember(*list, kMember); + ASSERT_NE(community, nullptr) + << "running config has no member community " << kMember; + EXPECT_EQ((*community)["value"].asString(), "65000:100"); + // bgp_policy.CommunityType.NORMAL = 1 (integer on the SimpleJSON wire). + EXPECT_EQ((*community)["type"].asInt(), 1); + EXPECT_EQ((*community)["description"].asString(), "test"); +} + +TEST_F(ConfigBgpPolicyCommunityListCommunityTest, DeleteCommunityAndCommit) { + // Land a community-list with two inline members, delete one member through + // a second commit, and verify the list and the other member survive in + // bgpd's running config. + discardSession(); + clearBgpSession(); + stageCommunity({kMember, "value", "65000:100"}); + stageCommunity({kMember2, "value", "65000:200"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + { + auto running = readRunningBgpConfigViaRpc(); + const auto* list = findList(running, kList); + ASSERT_NE(list, nullptr) + << "setup commit did not land the community-list in bgpd's running " + "config"; + ASSERT_NE(findMember(*list, kMember), nullptr); + ASSERT_NE(findMember(*list, kMember2), nullptr); + } + + clearBgpSession(); + auto result = runCli( + {"delete", + "protocol", + "bgp", + "policy", + "community-list", + kList, + "community", + kMember}); + EXPECT_THAT( + result.stdout, + HasSubstr( + fmt::format( + "Successfully deleted BGP community-list {} community {}", + kList, + kMember))); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after delete commit; state=" + << bgpDaemonActiveState(); + auto running = readRunningBgpConfigViaRpc(); + const auto* list = findList(running, kList); + ASSERT_NE(list, nullptr) + << "deleting one member must not delete the community-list"; + EXPECT_EQ(findMember(*list, kMember), nullptr) + << "deleted member still present in bgpd's running config"; + EXPECT_NE(findMember(*list, kMember2), nullptr) + << "surviving member missing from bgpd's running config"; +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListTest.cpp new file mode 100644 index 0000000000000..7a30d7425dcc1 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpPolicyCommunityListTest.cpp @@ -0,0 +1,126 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for `fboss2-dev config protocol bgp policy community-list + * [ ...]`. + * + * Scope: the community-list level (its inline community members are covered by + * ConfigBgpPolicyCommunityListCommunityTest). Every test + * stages the change AND commits it, then asserts the value landed at the + * correct thrift field path inside the matching .policies.community_lists[] + * entry of bgpd's running config (via getRunningConfig RPC) — which also + * confirms bgpd accepts and adopts a `.policies` blob at all. Stage-only + * behavior (attribute parsing, validation, rejection) is covered by the unit + * tests; an integration test that never commits exercises no daemon. + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd is installed/active (commit restarts it). + */ + +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" +#include "folly/json/dynamic.h" +#include "gmock/gmock.h" + +using namespace facebook::fboss; +using ::testing::HasSubstr; +using ::testing::Not; + +namespace { +// Test-only community-list name, unlikely to collide with a real list in the +// device's running BGP config. +const std::string kList = "FBOSS2-TEST-COMMUNITY"; +} // namespace + +class ConfigBgpPolicyCommunityListTest : public ConfigBgpTestBase { + protected: + // Stage `config protocol bgp policy community-list ` WITHOUT + // clearing the staged session, so attributes can accumulate across + // invocations. Returns the staged session JSON. + folly::dynamic stageCommunityList(const std::vector& tokens) { + std::vector args = { + "config", "protocol", "bgp", "policy", "community-list"}; + args.insert(args.end(), tokens.begin(), tokens.end()); + auto result = runCli(args); + EXPECT_EQ(result.exitCode, 0) + << "stdout=" << result.stdout << " stderr=" << result.stderr; + EXPECT_THAT(result.stdout, Not(HasSubstr("Error:"))); + return readBgpSessionConfig(); + } + + // The .policies.community_lists[] entry named `name`, or nullptr. + static const folly::dynamic* findList( + const folly::dynamic& config, + const std::string& name) { + if (config.count("policies") == 0 || + config["policies"].count("community_lists") == 0) { + return nullptr; + } + for (const auto& list : config["policies"]["community_lists"]) { + if (list.count("name") && list["name"].asString() == name) { + return &list; + } + } + return nullptr; + } +}; + +TEST_F(ConfigBgpPolicyCommunityListTest, SetListAttributesAndCommit) { + discardSession(); + clearBgpSession(); + stageCommunityList({kList, "description", "test", "spine", "communities"}); + stageCommunityList({kList, "boolean-operator", "AND"}); + stageCommunityList({kList, "exact-match", "true"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + // Two layers: the promoted file proves what the commit wrote; the daemon's + // getRunningConfig RPC proves bgpd parsed and adopted the .policies blob. + auto config = readSystemBgpConfig(); + const auto* list = findList(config, kList); + ASSERT_NE(list, nullptr) << "committed config has no community-list " + << kList; + EXPECT_EQ((*list)["description"].asString(), "test spine communities"); + + auto running = readRunningBgpConfigViaRpc(); + const auto* runningList = findList(running, kList); + ASSERT_NE(runningList, nullptr) + << "bgpd's running config has no community-list " << kList; + EXPECT_EQ((*runningList)["description"].asString(), "test spine communities"); + // Enums ride the SimpleJSON wire format as integers: + // routing_policy.BooleanOperator.AND = 1. + EXPECT_EQ((*runningList)["boolean_operator"].asInt(), 1); + EXPECT_TRUE((*runningList)["exact_match"].asBool()); +} + +TEST_F(ConfigBgpPolicyCommunityListTest, DeleteListAndCommit) { + // Land a community-list in the system config, then delete it through a + // second commit and verify it is gone from bgpd's running config. + discardSession(); + clearBgpSession(); + stageCommunityList({kList, "description", "delete-me"}); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + ASSERT_NE(findList(readRunningBgpConfigViaRpc(), kList), nullptr) + << "setup commit did not land the community-list in bgpd's running " + "config"; + + clearBgpSession(); + auto result = + runCli({"delete", "protocol", "bgp", "policy", "community-list", kList}); + EXPECT_THAT(result.stdout, HasSubstr("Successfully deleted")); + commitAndGetSha(); + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after delete commit; state=" + << bgpDaemonActiveState(); + EXPECT_EQ(findList(readRunningBgpConfigViaRpc(), kList), nullptr) + << "deleted community-list still present in bgpd's running config"; +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpSessionTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigBgpSessionTest.cpp new file mode 100644 index 0000000000000..ead4bf28cecd2 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpSessionTest.cpp @@ -0,0 +1,218 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +/** + * End-to-end tests for BGP config *session* management, mirroring the agent + * config session tests (e.g. ConfigSessionClearTest) but for the BGP domain. + * + * BGP global edits stage ~/.fboss2/bgp_config.json (no agent.conf), and + * `config session commit` promotes it to /etc/coop/bgpcpp/bgpcpp.conf and + * restarts bgpd. These tests exercise the session lifecycle around that: + * - clear (including a BGP-only session) + * - diff (a staged BGP change must be visible) + * - commit (bgpd is restarted) + * - rollback (the bgpd system config is restored) + * + * Per-attribute global config behavior lives in ConfigBgpGlobalTest. + * + * Requirements: + * - The fboss2-dev binary under test (config subcommand tree). + * - HOME is set (the session file lives under $HOME/.fboss2). + * - bgpd installed; commit/rollback tests skip if bgpd is not active. + */ + +#include +#include +#include + +#include "fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h" + +namespace fs = std::filesystem; + +using namespace facebook::fboss; +using ::testing::HasSubstr; +using ::testing::Not; + +class ConfigBgpSessionTest : public ConfigBgpTestBase {}; + +// Staging a BGP global change creates a session file; `config session clear` +// removes it (a BGP-only session has no agent.conf to key off of). +TEST_F(ConfigBgpSessionTest, CreateAndClearBgpSession) { + runBgpGlobal("graceful-restart-time", "55"); + ASSERT_TRUE(fs::exists(bgpSessionPath())) + << "expected a staged BGP session after a global change"; + + auto result = runCli({"config", "session", "clear"}); + ASSERT_EQ(result.exitCode, 0) << "stderr=" << result.stderr; + + EXPECT_FALSE(fs::exists(bgpSessionPath())) + << "config session clear must remove the staged BGP config"; +} + +TEST_F(ConfigBgpSessionTest, ClearNonExistentBgpSession) { + // Start fully clean: no agent session, no BGP session, no metadata. + discardSession(); + clearBgpSession(); + + auto result = runCli({"config", "session", "clear"}); + ASSERT_EQ(result.exitCode, 0) << "stderr=" << result.stderr; + EXPECT_THAT(result.stdout, HasSubstr("No config session exists")); +} + +// `config session diff` must surface a staged BGP global change. +TEST_F(ConfigBgpSessionTest, DiffShowsStagedBgpChange) { + runBgpGlobal("graceful-restart-time", "77"); + + auto result = runCli({"config", "session", "diff"}); + ASSERT_EQ(result.exitCode, 0) << "stderr=" << result.stderr; + EXPECT_THAT(result.stdout, Not(HasSubstr("No config session exists"))); + EXPECT_THAT(result.stdout, HasSubstr("graceful_restart_convergence_seconds")); +} + +// Committing a staged BGP change restarts the bgpd daemon. +TEST_F(ConfigBgpSessionTest, CommitRestartsBgpDaemon) { + if (bgpDaemonActiveState() != "active") { + GTEST_SKIP() << "bgpd is not active on this DUT; skipping commit/restart " + "verification"; + } + discardSession(); + const std::string pidBefore = bgpDaemonMainPid(); + + runBgpGlobal("graceful-restart-time", "123"); + ASSERT_FALSE(commitAndGetSha().empty()); + + EXPECT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return to active after commit; state=" + << bgpDaemonActiveState(); + + const std::string pidAfter = bgpDaemonMainPid(); + EXPECT_NE(pidAfter, "0") << "bgpd has no MainPID after commit"; + EXPECT_NE(pidAfter, pidBefore) << "bgpd MainPID unchanged (" << pidBefore + << "); expected a restart on commit"; +} + +// Rolling back a committed BGP change restores the bgpd system config and +// restarts bgpd (not just the agent config). +TEST_F(ConfigBgpSessionTest, RollbackRestoresBgpConfig) { + if (bgpDaemonActiveState() != "active") { + GTEST_SKIP() << "bgpd is not active on this DUT; skipping rollback " + "verification"; + } + discardSession(); + + auto convergenceSeconds = [&]() { + return readSystemBgpConfig()["graceful_restart_convergence_seconds"] + .asInt(); + }; + + // Commit A (111), then commit B (222). + runBgpGlobal("graceful-restart-time", "111"); + std::string shaA = commitAndGetSha(); + ASSERT_FALSE(shaA.empty()) << "could not parse commit A sha"; + ASSERT_TRUE(waitForBgpDaemonActive()); + + runBgpGlobal("graceful-restart-time", "222"); + ASSERT_FALSE(commitAndGetSha().empty()) << "could not parse commit B sha"; + ASSERT_TRUE(waitForBgpDaemonActive()); + + EXPECT_EQ(convergenceSeconds(), 222) + << "system bgp config should reflect the latest commit"; + + // Roll back to commit A -> system bgp config restored to 111, bgpd restart. + resetBgpDaemonLimit(); // avoid systemd start-limit on the rollback's restart + auto rb = runCli({"config", "rollback", shaA}); + EXPECT_EQ(rb.exitCode, 0) << "stderr=" << rb.stderr; + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after rollback"; + + EXPECT_EQ(convergenceSeconds(), 111) + << "rollback must restore the bgpd system config"; +} + +// Committing a BGP config identical to what is already running must be a no-op: +// "Nothing to commit" and, crucially, NO bgpd restart (a restart is traffic +// affecting). saveBgpConfig() always records SERVICE_RESTART, so commit() has +// to compare the staged config against the running one. +TEST_F(ConfigBgpSessionTest, CommitUnchangedBgpConfigDoesNotRestart) { + if (bgpDaemonActiveState() != "active") { + GTEST_SKIP() << "bgpd is not active on this DUT; skipping no-op restart " + "verification"; + } + discardSession(); + + // Establish a known running value. + runBgpGlobal("graceful-restart-time", "88"); + ASSERT_FALSE(commitAndGetSha().empty()); + ASSERT_TRUE(waitForBgpDaemonActive()); + const std::string pidBefore = bgpDaemonMainPid(); + ASSERT_NE(pidBefore, "0"); + + // Stage the SAME value and commit again -> nothing to commit, no restart. + runBgpGlobal("graceful-restart-time", "88"); + auto result = runCli({"config", "session", "commit"}); + EXPECT_EQ(result.exitCode, 0) << "stderr=" << result.stderr; + EXPECT_THAT(result.stdout, HasSubstr("Nothing to commit")) + << "an unchanged BGP commit should report nothing to commit"; + + EXPECT_EQ(bgpDaemonMainPid(), pidBefore) + << "bgpd was restarted for an unchanged BGP config (pidBefore=" + << pidBefore << ")"; +} + +// An agent-only commit (no BGP staged) must still snapshot the running bgpd +// config, so a rollback to it restores BGP instead of wiping it. Pre-fix, +// bgpcpp.conf was untracked at such commits and rollback deleted it. +TEST_F(ConfigBgpSessionTest, AgentCommitSnapshotsBgpAndRollbackPreservesIt) { + if (bgpDaemonActiveState() != "active") { + GTEST_SKIP() << "bgpd is not active on this DUT; skipping cross-domain " + "rollback verification"; + } + discardSession(); + clearBgpSession(); + + // The running (default) BGP config that must survive a rollback unchanged. + ASSERT_TRUE(fs::exists(systemBgpConfigPath())) + << "expected a running bgpd config on an active-BGP DUT"; + const folly::dynamic defaultBgp = readSystemBgpConfig(); + + // Step 1: an agent-only commit (interface description). No BGP is staged. + Interface intf = getInterfaceInfo(getRandomInterfacePortName()); + auto setRes = + runCli({"config", "interface", intf.name, "description", "bgp-rb-test"}); + ASSERT_EQ(setRes.exitCode, 0) << "stderr=" << setRes.stderr; + ASSERT_FALSE(fs::exists(bgpSessionPath())) + << "an interface-description change must not stage a BGP session"; + resetBgpDaemonLimit(); + commitConfig(); + waitForAgentReady(); + const std::string agentSha = gitHead(); + ASSERT_FALSE(agentSha.empty()) + << "could not read git HEAD after agent commit"; + + // The fix: bgpcpp.conf must be tracked at the agent-only commit, so a later + // rollback to it has a faithful BGP snapshot to restore. + EXPECT_TRUE(bgpTrackedAtRevision(agentSha)) + << "agent-only commit " << agentSha + << " did not snapshot bgpcpp.conf; a rollback to it would wipe BGP"; + + // Step 2: stage + commit a BGP change so the running config diverges from the + // snapshot captured at agentSha. + runBgpGlobal("graceful-restart-time", "222"); + ASSERT_FALSE(commitAndGetSha().empty()) << "BGP commit produced no SHA"; + ASSERT_TRUE(waitForBgpDaemonActive()); + EXPECT_EQ( + readSystemBgpConfig()["graceful_restart_convergence_seconds"].asInt(), + 222); + + // Step 3: roll back to the agent-only commit. BGP must be RESTORED to the + // original default snapshot (not deleted), and bgpd must come back. + resetBgpDaemonLimit(); + auto rb = runCli({"config", "rollback", agentSha}); + EXPECT_EQ(rb.exitCode, 0) << "stderr=" << rb.stderr; + ASSERT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after rollback"; + + EXPECT_TRUE(fs::exists(systemBgpConfigPath())) + << "rollback to an agent-era commit wiped the running bgpd config"; + EXPECT_EQ(readSystemBgpConfig(), defaultBgp) + << "rollback did not restore the original (default) bgpd config"; +} diff --git a/fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h b/fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h new file mode 100644 index 0000000000000..195372f1f0456 --- /dev/null +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h @@ -0,0 +1,389 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#pragma once + +/** + * Shared fixture + helpers for BGP config integration tests. + * + * BGP config is not pushed into the agent's running config like the + * SwitchConfig-backed commands. Instead the CLI stages a BGP++ + * thrift-compatible JSON file at ~/.fboss2/bgp_config.json; `config session + * commit` promotes it to /etc/coop/bgpcpp/bgpcpp.conf and restarts the bgpd + * daemon. These helpers let the global-attribute tests and the + * session-lifecycle tests share the staging, commit, daemon-state, and + * system-config plumbing. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "fboss/cli/fboss2/test/integration_test/Fboss2IntegrationTest.h" +#include "fboss/cli/fboss2/utils/CmdClientUtilsCommon.h" +#include "neteng/fboss/bgp/if/gen-cpp2/TBgpService.h" + +namespace facebook::fboss { + +class ConfigBgpTestBase : public Fboss2IntegrationTest { + protected: + void SetUp() override { + Fboss2IntegrationTest::SetUp(); + if (bgpDaemonActiveState() != "active") { + GTEST_SKIP() << "skipping because bgp is not active"; + } + // systemd "active" is a point sample: on an image where bgpd cannot hold + // its boot config it flaps activating -> active -> failed, and a single + // sample can land inside the brief active window (seen on fboss-sim, + // where the sibling tests' samples all landed on inactive and skipped). + // Require the daemon to actually answer its thrift RPC before running, + // and skip otherwise — the same skip the rest of the flap cycle produces. + try { + readRunningBgpConfigViaRpc(std::chrono::seconds(10)); + } catch (const std::exception& ex) { + GTEST_SKIP() << "skipping because bgpd is active but not answering its " + << "thrift RPC (flapping unit?): " << ex.what(); + } + // Snapshot the config bgpd runs with; TearDown restores it, so committed + // test objects never outlive the test regardless of where it failed. + haveBgpSnapshot_ = folly::readFile(kBgpDaemonConfigPath, bgpSnapshot_); + if (haveBgpSnapshot_) { + bgpSnapshotConfig_ = parseBgpConfig(bgpSnapshot_); + } + } + + void TearDown() override { + restoreBgpConfigSnapshot(); + Fboss2IntegrationTest::TearDown(); + } + + // Staged session file written by the CLI (~/.fboss2/bgp_config.json). + std::string bgpSessionPath() const { + // NOLINTNEXTLINE(concurrency-mt-unsafe): HOME is read-only in practice + const char* home = std::getenv("HOME"); + if (home == nullptr) { + throw std::runtime_error("HOME environment variable not set"); + } + return std::string(home) + "/.fboss2/bgp_config.json"; + } + + // System config consumed by the bgpd daemon: the stable path bgpd is started + // with (--config). After the first commit this is a symlink to the promoted + // bgpcpp/bgpcpp.conf; before it, it is the plain file the bgp++ RPM installs. + // Reading it (rather than the promoted path directly) means the fixture does + // not require a commit to have happened, so these tests can run first on a + // freshly imaged box. + std::string systemBgpConfigPath() const { + return "/etc/coop/bgpcpp.conf"; + } + + // Remove any pre-existing staged BGP session so each test starts clean. + void clearBgpSession() const { + std::remove(bgpSessionPath().c_str()); + } + + folly::dynamic readBgpSessionConfig() const { + std::string content; + if (!folly::readFile(bgpSessionPath().c_str(), content)) { + throw std::runtime_error( + "Failed to read BGP session config at " + bgpSessionPath()); + } + return folly::parseJson(content); + } + + folly::dynamic readSystemBgpConfig() const { + std::string content; + if (!folly::readFile(systemBgpConfigPath().c_str(), content)) { + const int promotedErrno = errno; + // On a box where no BGP commit ever happened, the promoted + // bgpcpp/bgpcpp.conf doesn't exist yet; the effective config is the + // plain file the RPM installed at /etc/coop/bgpcpp.conf (a symlink to + // the promoted file afterwards, so it is always safe to read). + if (promotedErrno != ENOENT) { + throw std::runtime_error( + "Failed to read system BGP config at " + systemBgpConfigPath() + + ": " + folly::errnoStr(promotedErrno)); + } + if (!folly::readFile(kBgpDaemonConfigPath, content)) { + throw std::runtime_error( + "Failed to read system BGP config: " + systemBgpConfigPath() + + " does not exist and reading " + kBgpDaemonConfigPath + + " failed: " + folly::errnoStr(errno)); + } + } + return folly::parseJson(content); + } + + // The running config as reported by the bgpd daemon itself, over its + // TBgpService::getRunningConfig thrift RPC. Unlike readSystemBgpConfig() + // (the promoted file on disk, which only proves what the CLI wrote), this + // proves the daemon parsed and adopted the config after the post-commit + // restart. Mirrors Fboss2IntegrationTest::getRunningConfig() for the agent. + // + // Retries on connection errors: systemd reports bgpd "active" as soon as + // the process starts, but the thrift server only binds its port a few + // seconds later — an RPC issued right after a commit-triggered restart + // would otherwise race that window and get ECONNREFUSED. + folly::dynamic readRunningBgpConfigViaRpc( + std::chrono::seconds timeout = std::chrono::seconds(90)) const { + auto deadline = std::chrono::steady_clock::now() + timeout; + HostInfo hostInfo("localhost"); + std::string configStr; + while (true) { + try { + auto client = utils::createClient>(hostInfo); + client->sync_getRunningConfig(configStr); + break; + } catch (const std::exception& ex) { + // A NOT_OPEN transport exception (connect refused / connection + // closed) is the expected bind-window race; any other RPC error is + // worth surfacing even though we keep retrying until the deadline. + // The refused connect reaches us as a channel-level + // TTransportException wrapping the socket error, so the inner errno + // is gone but the NOT_OPEN type survives. + const auto* tex = + dynamic_cast( + &ex); + if (tex == nullptr || + tex->getType() != + apache::thrift::transport::TTransportException::NOT_OPEN) { + XLOG(WARN) << "BGP getRunningConfig failed with an unexpected " + "error (not a connection refused error): " + << ex.what(); + } + if (std::chrono::steady_clock::now() >= deadline) { + throw; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + return folly::parseJson(configStr); + } + + // Root of the git repo that versions both the agent config (cli/agent.conf) + // and the BGP config (bgpcpp/bgpcpp.conf). + std::string coopRepoDir() const { + return "/etc/coop"; + } + + // Current git HEAD sha of the /etc/coop repo (trimmed; "" on failure). + // runCmd uses folly::Subprocess, which does not search PATH, so git is + // invoked by full path. -c safe.directory mirrors the CLI's Git class: + // /etc/coop is typically owned by another user (e.g. coop), which git + // otherwise rejects as dubious ownership. + std::string gitHead() const { + auto r = runCmd( + {"/usr/bin/git", + "-c", + "safe.directory=" + coopRepoDir(), + "-C", + coopRepoDir(), + "rev-parse", + "HEAD"}); + std::string s = r.stdout; + while (!s.empty() && + (s.back() == '\n' || s.back() == '\r' || s.back() == ' ')) { + s.pop_back(); + } + return s; + } + + // True if bgpcpp/bgpcpp.conf is tracked (present in the tree) at the given + // revision. `git cat-file -e :` exits 0 iff the blob exists. + bool bgpTrackedAtRevision(const std::string& sha) const { + auto r = runCmd( + {"/usr/bin/git", + "-c", + "safe.directory=" + coopRepoDir(), + "-C", + coopRepoDir(), + "cat-file", + "-e", + sha + ":bgpcpp/bgpcpp.conf"}); + return r.exitCode == 0; + } + + // Stage a `config protocol bgp global ` change (no commit). + // Returns the staged session JSON. + folly::dynamic runBgpGlobal(const std::string& attr, const std::string& value) + const { + clearBgpSession(); + XLOG(INFO) << "Running: config protocol bgp global " << attr << " " + << value; + auto result = runCli({"config", "protocol", "bgp", "global", attr, value}); + EXPECT_EQ(result.exitCode, 0) + << "stdout=" << result.stdout << " stderr=" << result.stderr; + // Handler error strings are printed to stdout with exit code 0, so guard + // against silent failures by rejecting an "Error" prefix. + EXPECT_THAT(result.stdout, ::testing::Not(::testing::HasSubstr("Error:"))); + return readBgpSessionConfig(); + } + + // Reset bgpd's systemd start-limit burst counter. Tests commit several + // times in quick succession, and each commit restarts bgpd; without this, + // systemd's default StartLimitBurst (5 starts / 10s) refuses a rapid restart + // with "start limit hit" — a test artifact, not a product issue. + void resetBgpDaemonLimit() const { + runCmd({"/usr/bin/systemctl", "reset-failed", "bgpd"}); + } + + // Run `config session commit` and return the short SHA parsed from + // "Config session committed successfully as ". Empty string on failure. + std::string commitAndGetSha() const { + resetBgpDaemonLimit(); + auto r = runCli({"config", "session", "commit"}); + EXPECT_EQ(r.exitCode, 0) << "stderr=" << r.stderr; + const std::string marker = "as "; + auto pos = r.stdout.find(marker); + if (pos == std::string::npos) { + return ""; + } + pos += marker.size(); + auto end = r.stdout.find_first_of(" .\n", pos); + return r.stdout.substr(pos, end - pos); + } + + // Stage a global change, commit it, wait for bgpd to come back, and return + // bgpd's own running config as reported over its getRunningConfig RPC. + // + // The returned config is the daemon's adopted view, which is strictly + // stronger than the promoted file on disk (readSystemBgpConfig): a value + // present here proves the commit promoted /etc/coop/bgpcpp/bgpcpp.conf AND + // that bgpd parsed and adopted it after the commit-triggered restart, not + // merely that the CLI wrote the file. The running config shares the same + // BgpConfig JSON schema as the promoted file, so callers assert the same + // field paths against it. + // + // A no-op commit (staged value already current) yields no SHA and is fine: + // the caller's assertions on the returned config still verify the value; + // commit/restart mechanics are covered by ConfigBgpSessionTest. + folly::dynamic setAndCommit(const std::string& attr, const std::string& value) + const { + discardSession(); + runBgpGlobal(attr, value); + commitAndGetSha(); + EXPECT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after commit; state=" + << bgpDaemonActiveState(); + return readRunningBgpConfigViaRpc(); + } + + // Trailing-whitespace-trimmed stdout of a `systemctl` query for bgpd. + std::string systemctlValue(const std::string& property) const { + // Full path: folly::Subprocess (used by runCmd) does not search PATH. + auto r = runCmd( + {"/usr/bin/systemctl", "show", "bgpd", "-p", property, "--value"}); + std::string s = r.stdout; + while (!s.empty() && + (s.back() == '\n' || s.back() == '\r' || s.back() == ' ')) { + s.pop_back(); + } + return s; + } + + // "active" / "inactive" / "failed" / ... (trimmed). + std::string bgpDaemonActiveState() const { + auto r = runCmd({"/usr/bin/systemctl", "is-active", "bgpd"}); + std::string s = r.stdout; + while (!s.empty() && + (s.back() == '\n' || s.back() == '\r' || s.back() == ' ')) { + s.pop_back(); + } + return s; + } + + // bgpd main PID per systemd ("0" if not running). + std::string bgpDaemonMainPid() const { + return systemctlValue("MainPID"); + } + + // Poll until bgpd reports active or the timeout elapses. + bool waitForBgpDaemonActive( + std::chrono::seconds timeout = std::chrono::seconds(60)) const { + auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { + if (bgpDaemonActiveState() == "active") { + return true; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + return bgpDaemonActiveState() == "active"; + } + + private: + // The stable path bgpd reads (--config): a plain file on a device the CLI + // never committed on, a symlink into bgpcpp/ afterwards. Reading follows + // the symlink, so the snapshot is always bgpd's effective config. + static constexpr auto kBgpDaemonConfigPath = "/etc/coop/bgpcpp.conf"; + + // Deserialized view of a serialized BgpConfig; nullopt if unparseable. + static std::optional parseBgpConfig( + const std::string& content) { + try { + return apache::thrift::SimpleJSONSerializer::deserialize< + bgp::thrift::BgpConfig>(content); + } catch (const std::exception&) { + return std::nullopt; + } + } + + // True when `content` matches the snapshot: by thrift struct equality when + // both sides parse (formatting-only differences from a commit round-trip + // don't count), else by bytes. + bool matchesBgpSnapshot(const std::string& content) const { + auto current = parseBgpConfig(content); + if (bgpSnapshotConfig_ && current) { + return *current == *bgpSnapshotConfig_; + } + return content == bgpSnapshot_; + } + + void restoreBgpConfigSnapshot() { + if (!haveBgpSnapshot_) { + return; // SetUp skipped or there was no config to protect + } + discardSession(); + clearBgpSession(); + std::string current; + if (folly::readFile(kBgpDaemonConfigPath, current) && + matchesBgpSnapshot(current)) { + return; // semantically unchanged; skip the restart + } + // Write the real file, never through the bgpd.conf symlink. + std::string target = std::filesystem::exists(systemBgpConfigPath()) + ? systemBgpConfigPath() + : std::string(kBgpDaemonConfigPath); + EXPECT_TRUE(folly::writeFile(bgpSnapshot_, target.c_str())) + << "failed to restore BGP config snapshot to " << target; + resetBgpDaemonLimit(); + runCmd({"/usr/bin/systemctl", "restart", "bgpd"}); + EXPECT_TRUE(waitForBgpDaemonActive()) + << "bgpd did not return active after snapshot restore; state=" + << bgpDaemonActiveState(); + } + + bool haveBgpSnapshot_{false}; + // Raw bytes, written back verbatim on restore; the typed view drives the + // changed/unchanged decision. + std::string bgpSnapshot_; + std::optional bgpSnapshotConfig_; +}; + +} // namespace facebook::fboss diff --git a/fboss/cli/fboss2/test/integration_test/ConfigVlanDefaultTest.cpp b/fboss/cli/fboss2/test/integration_test/ConfigVlanDefaultTest.cpp index 0b6578cc37436..861da08d48e73 100644 --- a/fboss/cli/fboss2/test/integration_test/ConfigVlanDefaultTest.cpp +++ b/fboss/cli/fboss2/test/integration_test/ConfigVlanDefaultTest.cpp @@ -307,6 +307,34 @@ TEST_F(ConfigVlanDefaultTest, ChangeDefaultVlanWithPortInNonDefaultVlan) { << "Failed to move " << ifName << " to side VLAN " << sideVlan; XLOG(INFO) << "[Step 1] Moved " << ifName << " to VLAN " << sideVlan; + // Vacate every other eth port still parked on the current default VLAN + // (baselines generated before the per-port ingress VLAN change park all + // ports there). The change-default guard refuses while ANY port uses the + // old default VLAN and it has no interface, not just the test port. + std::vector movedPorts; + for (const auto& port : initialConfig["sw"]["ports"]) { + auto name = port.getDefault("name", "").asString(); + if (name.rfind("eth", 0) != 0 || name == ifName) { + continue; + } + if (port.getDefault("ingressVlan", -1).asInt() != currentDefault) { + continue; + } + auto r = runCli( + {"config", + "interface", + name, + "switchport", + "access", + "vlan", + std::to_string(sideVlan)}); + if (r.exitCode == 0) { + movedPorts.push_back(name); + } + } + XLOG(INFO) << "[Step 1b] Vacated " << movedPorts.size() + << " other ports off default VLAN " << currentDefault; + // Set a new default VLAN — the command must succeed even when no ports // remain on the old default VLAN. The target is auto-created, so it must be // an ID the agent can back with a route table (T284228086). @@ -329,7 +357,7 @@ TEST_F(ConfigVlanDefaultTest, ChangeDefaultVlanWithPortInNonDefaultVlan) { waitForAgentReady(); EXPECT_EQ(getSwConfigField("defaultVlan"), newDefault); - // Restore: move the port back and reset defaultVlan. + // Restore: move the ports back and reset defaultVlan. XLOG(INFO) << "[Restore] Moving " << ifName << " back to VLAN " << originalPortVlan; runCli( @@ -340,6 +368,16 @@ TEST_F(ConfigVlanDefaultTest, ChangeDefaultVlanWithPortInNonDefaultVlan) { "access", "vlan", std::to_string(originalPortVlan)}); + for (const auto& name : movedPorts) { + runCli( + {"config", + "interface", + name, + "switchport", + "access", + "vlan", + std::to_string(currentDefault)}); + } auto restoreDefault = runCli({"config", "vlan", "default", std::to_string(currentDefault)}); diff --git a/fboss/cli/fboss2/tools/bgp_json_to_cli.py b/fboss/cli/fboss2/tools/bgp_json_to_cli.py index ddf328c4fd3ca..f3c3fbb236a95 100644 --- a/fboss/cli/fboss2/tools/bgp_json_to_cli.py +++ b/fboss/cli/fboss2/tools/bgp_json_to_cli.py @@ -10,25 +10,34 @@ This enables round-trip conversion: JSON -> CLI -> JSON (via fboss2). Usage: - python3 bgp_json_to_cli.py [--output output.sh] [--stub] + python3 bgp_json_to_cli.py [--output output.sh] Example: - python3 bgp_json_to_cli.py rsw_bgp_config.json --stub > configure_bgp.sh + python3 bgp_json_to_cli.py rsw_bgp_config.json > configure_bgp.sh """ import argparse import json import shlex import sys -from typing import Any, Dict, List +from typing import Any -def escape_shell_arg(arg: str) -> str: - """Escape argument for shell usage.""" +def escape_shell_arg(arg: Any) -> str: + """Quote a JSON-sourced value for the generated shell script. + + Applied to every value taken from the input JSON, numeric fields included: + the script does not validate types, and shlex.quote leaves plain digits + and identifiers unchanged, so valid input produces identical output. + """ return shlex.quote(str(arg)) -def _generate_global_basic_commands(config: Dict[str, Any]) -> List[str]: +def _shell_bool(value: Any) -> str: + return escape_shell_arg(str(value).lower()) + + +def _generate_global_basic_commands(config: dict[str, Any]) -> list[str]: """Generate basic global BGP commands.""" commands = [] if "router_id" in config: @@ -37,24 +46,20 @@ def _generate_global_basic_commands(config: Dict[str, Any]) -> List[str]: ) if "local_as_4_byte" in config: commands.append( - f"config protocol bgp global local-asn {escape_shell_arg(str(config['local_as_4_byte']))}" + f"config protocol bgp global local-asn {escape_shell_arg(config['local_as_4_byte'])}" ) if "hold_time" in config: commands.append( - f"config protocol bgp global hold-time {escape_shell_arg(str(config['hold_time']))}" + f"config protocol bgp global hold-time {escape_shell_arg(config['hold_time'])}" ) if "local_confed_as_4_byte" in config: commands.append( - f"config protocol bgp global confed-asn {escape_shell_arg(str(config['local_confed_as_4_byte']))}" - ) - if "cluster_id" in config: - commands.append( - f"config protocol bgp global cluster-id {escape_shell_arg(config['cluster_id'])}" + f"config protocol bgp global confed-asn {escape_shell_arg(config['local_confed_as_4_byte'])}" ) return commands -def _generate_network6_commands(networks: List[Dict[str, Any]]) -> List[str]: +def _generate_network6_commands(networks: list[dict[str, Any]]) -> list[str]: """Generate network6 commands for global BGP config.""" commands = [] for network in networks: @@ -67,38 +72,38 @@ def _generate_network6_commands(networks: List[Dict[str, Any]]) -> List[str]: if "policy_name" in network: cmd_parts.append(f"policy {escape_shell_arg(network['policy_name'])}") if "install_to_fib" in network: - cmd_parts.append(f"install-to-fib {str(network['install_to_fib']).lower()}") + cmd_parts.append(f"install-to-fib {_shell_bool(network['install_to_fib'])}") if "minimum_supporting_routes" in network: cmd_parts.append( - f"min-routes {escape_shell_arg(str(network['minimum_supporting_routes']))}" + f"min-routes {escape_shell_arg(network['minimum_supporting_routes'])}" ) commands.append(" ".join(cmd_parts)) return commands -def _generate_switch_limit_commands(switch_limit: Dict[str, Any]) -> List[str]: +def _generate_switch_limit_commands(switch_limit: dict[str, Any]) -> list[str]: """Generate switch_limit_config commands.""" commands = [] if "prefix_limit" in switch_limit: commands.append( - f"config protocol bgp global switch-limit {escape_shell_arg(str(switch_limit['prefix_limit']))}" + f"config protocol bgp global switch-limit {escape_shell_arg(switch_limit['prefix_limit'])}" ) if "total_path_limit" in switch_limit: commands.append( - f"config protocol bgp global switch-limit-total-path {escape_shell_arg(str(switch_limit['total_path_limit']))}" + f"config protocol bgp global switch-limit-total-path {escape_shell_arg(switch_limit['total_path_limit'])}" ) if "max_golden_vips" in switch_limit: commands.append( - f"config protocol bgp global switch-limit-max-golden-vips {escape_shell_arg(str(switch_limit['max_golden_vips']))}" + f"config protocol bgp global switch-limit-max-golden-vips {escape_shell_arg(switch_limit['max_golden_vips'])}" ) if "overload_protection_mode" in switch_limit: commands.append( - f"config protocol bgp global switch-limit-overload-protection-mode {escape_shell_arg(str(switch_limit['overload_protection_mode']))}" + f"config protocol bgp global switch-limit-overload-protection-mode {escape_shell_arg(switch_limit['overload_protection_mode'])}" ) return commands -def generate_global_commands(config: Dict[str, Any]) -> List[str]: +def generate_global_commands(config: dict[str, Any]) -> list[str]: """Generate CLI commands for global BGP settings.""" commands = [] commands.extend(_generate_global_basic_commands(config)) @@ -109,298 +114,487 @@ def generate_global_commands(config: Dict[str, Any]) -> List[str]: return commands -def _generate_peer_group_basic_commands( - peer_group: Dict[str, Any], escaped_name: str -) -> List[str]: - """Generate basic peer-group commands (remote-asn, description, policies).""" +def format_bandwidth(bps: int) -> str: + """Format bandwidth in bps with the largest exact K/M/G suffix. + + Only K, M and G are emitted: those are the multipliers bgpd's + link-bandwidth parser accepts, so a "T" spelling would not round-trip. + """ + if bps >= 1_000_000_000 and bps % 1_000_000_000 == 0: + return f"{bps // 1_000_000_000}G" + if bps >= 1_000_000 and bps % 1_000_000 == 0: + return f"{bps // 1_000_000}M" + if bps >= 1_000 and bps % 1_000 == 0: + return f"{bps // 1_000}K" + return str(bps) + + +def _printable(text: Any) -> str: + """A JSON-sourced name made safe for a `#` comment line: no control + characters, so it cannot break out of the comment.""" + return "".join(ch if ch.isprintable() else "?" for ch in str(text)) + + +# --------------------------------------------------------------------------- +# Fields BgpPeer and PeerGroup share. The neighbor and peer-group dispatchers +# spell these attributes identically, so one table drives both generators. +# --------------------------------------------------------------------------- + +# (json field, CLI attribute) — boolean flags. +_BOOL_FIELDS = [ + ("is_rr_client", "rr-client"), + ("is_confed_peer", "confed-peer"), + ("is_redistribute_peer", "redistribute-peer"), + ("enhanced_route_refresh", "enhanced-route-refresh"), + ("route_refresh", "route-refresh"), + ("remove_private_as", "remove-private-as"), + ("enforce_first_as", "enforce-first-as"), + ("disable_ipv4_afi", "afi disable-ipv4-afi"), + ("disable_ipv6_afi", "afi disable-ipv6-afi"), + ("v4_over_v6_nexthop", "afi ipv4-over-ipv6-nh"), + ("enable_stateful_ha", "graceful-restart stateful-ha"), + ("next_hop_self", "next-hop-self"), +] + +# (json field, CLI attribute) — integer-valued attributes. +_INT_FIELDS = [("ttl_security_hops", "ttl-security-hops")] + +_TIMER_FIELDS = [ + ("hold_time_seconds", "timers hold-time"), + ("keep_alive_seconds", "timers keepalive"), + ("out_delay_seconds", "timers out-delay"), + ("withdraw_unprog_delay_seconds", "timers withdraw-unprog-delay"), + ("graceful_restart_seconds", "graceful-restart restart-time"), +] + +# RouteLimit leaves, as (json field, attribute suffix, is_bool); the prefix is +# `max-route pre-` or `max-route post-`. +_ROUTE_LIMIT_FIELDS = [ + ("max_routes", "filter", False), + ("warning_limit", "warning-threshold", False), + ("warning_only", "warning-only", True), +] + +# Every shared key the generators below convert. Anything else present on the +# JSON object is reported (see _unconverted_field_warnings) instead of being +# dropped silently. +_SHARED_HANDLED_FIELDS = ( + { + "is_passive", + "link_bandwidth_bps", + "advertise_link_bandwidth", + "receive_link_bandwidth", + "add_path", + "bgp_peer_timers", + "pre_filter", + "post_filter", + } + | {field for field, _ in _BOOL_FIELDS} + | {field for field, _ in _INT_FIELDS} +) +_TIMER_HANDLED_FIELDS = {field for field, _ in _TIMER_FIELDS} +_ROUTE_LIMIT_HANDLED_FIELDS = {field for field, _, _ in _ROUTE_LIMIT_FIELDS} + + +def _generate_scalar_commands( + obj: dict[str, Any], prefix: str, fields: list[tuple[str, str]] +) -> list[str]: + """Generate ` ` for each present scalar field.""" commands = [] - if "remote_as_4_byte" in peer_group: - commands.append( - f"config protocol bgp peer-group {escaped_name} remote-asn {escape_shell_arg(str(peer_group['remote_as_4_byte']))}" - ) - if "description" in peer_group: - commands.append( - f"config protocol bgp peer-group {escaped_name} description {escape_shell_arg(peer_group['description'])}" - ) - if "ingress_policy_name" in peer_group: - commands.append( - f"config protocol bgp peer-group {escaped_name} ingress-policy {escape_shell_arg(peer_group['ingress_policy_name'])}" - ) - if "egress_policy_name" in peer_group: - commands.append( - f"config protocol bgp peer-group {escaped_name} egress-policy {escape_shell_arg(peer_group['egress_policy_name'])}" - ) - if "peer_tag" in peer_group: - commands.append( - f"config protocol bgp peer-group {escaped_name} peer-tag {escape_shell_arg(peer_group['peer_tag'])}" - ) + for field, cli_name in fields: + if field in obj: + commands.append(f"{prefix} {cli_name} {escape_shell_arg(obj[field])}") return commands -def _generate_peer_group_bool_commands( - peer_group: Dict[str, Any], escaped_name: str -) -> List[str]: - """Generate boolean flag commands for peer-group.""" +def _generate_bool_commands(obj: dict[str, Any], prefix: str) -> list[str]: + """Generate boolean flag commands for a neighbor or peer group.""" commands = [] - bool_fields = [ - ("is_rr_client", "rr-client"), - ("next_hop_self", "next-hop-self"), - ("is_confed_peer", "confed-peer"), - ("v4_over_v6_nexthop", "v4-over-v6-nh"), - ("disable_ipv4_afi", "disable-ipv4-afi"), - ] - for field, cli_name in bool_fields: - if field in peer_group: - commands.append( - f"config protocol bgp peer-group {escaped_name} {cli_name} {str(peer_group[field]).lower()}" - ) + for field, cli_name in _BOOL_FIELDS: + if field in obj: + commands.append(f"{prefix} {cli_name} {_shell_bool(obj[field])}") return commands -def _generate_peer_group_timer_commands( - timers: Dict[str, Any], escaped_name: str -) -> List[str]: - """Generate timer commands for peer-group.""" +def _generate_link_bandwidth_commands(obj: dict[str, Any], prefix: str) -> list[str]: + """Generate the link-bandwidth trio for a neighbor or peer group. + + advertise/receive enum values are passed through as found (name or + integer): the dispatchers accept both spellings for a thrift enum. + """ commands = [] - if timers.get("hold_time_seconds"): - commands.append( - f"config protocol bgp peer-group {escaped_name} timers hold-time {escape_shell_arg(str(timers['hold_time_seconds']))}" - ) - if timers.get("keep_alive_seconds"): - commands.append( - f"config protocol bgp peer-group {escaped_name} timers keepalive {escape_shell_arg(str(timers['keep_alive_seconds']))}" - ) - if "out_delay_seconds" in timers: + if "link_bandwidth_bps" in obj: + bw = obj["link_bandwidth_bps"] + bw_str = format_bandwidth(bw) if isinstance(bw, int) else str(bw) + commands.append(f"{prefix} link-bandwidth {escape_shell_arg(bw_str)}") + if "advertise_link_bandwidth" in obj: commands.append( - f"config protocol bgp peer-group {escaped_name} timers out-delay {escape_shell_arg(str(timers['out_delay_seconds']))}" + f"{prefix} advertise-lbw {escape_shell_arg(obj['advertise_link_bandwidth'])}" ) - if "withdraw_unprog_delay_seconds" in timers: + if "receive_link_bandwidth" in obj: commands.append( - f"config protocol bgp peer-group {escaped_name} timers withdraw-unprog-delay {escape_shell_arg(str(timers['withdraw_unprog_delay_seconds']))}" + f"{prefix} receive-lbw {escape_shell_arg(obj['receive_link_bandwidth'])}" ) return commands -def _generate_peer_group_prefilter_commands( - pre_filter: Dict[str, Any], escaped_name: str -) -> List[str]: - """Generate pre_filter commands for peer-group.""" +def _generate_add_path_commands(obj: dict[str, Any], prefix: str) -> list[str]: + """Generate add-path commands from the AddPath enum (RECEIVE=1, SEND=2, BOTH=3).""" + if "add_path" not in obj: + return [] + raw = obj["add_path"] + value = ( + {"RECEIVE": 1, "SEND": 2, "BOTH": 3}.get(raw, raw) + if isinstance(raw, str) + else raw + ) + value = int(value) commands = [] - if pre_filter.get("max_routes"): - commands.append( - f"config protocol bgp peer-group {escaped_name} max-routes {escape_shell_arg(str(pre_filter['max_routes']))}" - ) - if "warning_limit" in pre_filter: - commands.append( - f"config protocol bgp peer-group {escaped_name} warning-limit {escape_shell_arg(str(pre_filter['warning_limit']))}" - ) - if "warning_only" in pre_filter: - commands.append( - f"config protocol bgp peer-group {escaped_name} warning-only {str(pre_filter['warning_only']).lower()}" - ) + if value & 1: + commands.append(f"{prefix} add-path receive true") + if value & 2: + commands.append(f"{prefix} add-path send true") + return commands + + +def _generate_timer_commands(timers: dict[str, Any], prefix: str) -> list[str]: + """Generate timer and graceful-restart commands for a neighbor or peer group.""" + return _generate_scalar_commands(timers, prefix, _TIMER_FIELDS) + + +def _generate_route_limit_commands(obj: dict[str, Any], prefix: str) -> list[str]: + """Generate max-route commands from pre_filter/post_filter.""" + commands = [] + for struct, side in (("pre_filter", "pre"), ("post_filter", "post")): + limit = obj.get(struct, {}) + for field, suffix, is_bool in _ROUTE_LIMIT_FIELDS: + if field in limit: + value = ( + _shell_bool(limit[field]) + if is_bool + else escape_shell_arg(limit[field]) + ) + commands.append(f"{prefix} max-route {side}-{suffix} {value}") + return commands + + +def _generate_session_commands(obj: dict[str, Any], prefix: str) -> list[str]: + """Generate the commands for every field BgpPeer and PeerGroup share.""" + commands = [] + if "is_passive" in obj: + commands.append(f"{prefix} passive {_shell_bool(obj['is_passive'])}") + commands.extend(_generate_bool_commands(obj, prefix)) + commands.extend(_generate_scalar_commands(obj, prefix, _INT_FIELDS)) + commands.extend(_generate_link_bandwidth_commands(obj, prefix)) + commands.extend(_generate_add_path_commands(obj, prefix)) + commands.extend(_generate_timer_commands(obj.get("bgp_peer_timers", {}), prefix)) + commands.extend(_generate_route_limit_commands(obj, prefix)) + return commands + + +def _unconverted_field_warnings( + obj: dict[str, Any], handled: set[str], subject: str +) -> list[str]: + """One `# WARNING` comment line per JSON field no generator converts. + + The dispatchers only expose the fields bgpd reads at that level, so a + field left here is either dead in the daemon or per-peer-only; either + way the operator must see it was not carried over. + """ + warnings = [] + + def warn(field: str) -> None: + warnings.append( + f"# WARNING: {subject}: field {_printable(field)} has no CLI " + "equivalent, not converted" + ) + + for field in obj: + if field not in handled: + warn(field) + for field in obj.get("bgp_peer_timers", {}): + if field not in _TIMER_HANDLED_FIELDS: + warn(f"bgp_peer_timers.{field}") + for struct in ("pre_filter", "post_filter"): + for field in obj.get(struct, {}): + if field not in _ROUTE_LIMIT_HANDLED_FIELDS: + warn(f"{struct}.{field}") + return warnings + + +# (json field, CLI attribute) — BgpPeer-only fields whose value maps 1:1 onto +# a neighbor attribute token. The deprecated i32 ASNs are converted to the +# 4-byte attribute: bgpd reads either representation and the CLI stores the +# 4-byte field. +_NEIGHBOR_SCALAR_FIELDS = [ + ("remote_as_4_byte", "remote-asn"), + ("remote_as", "remote-asn"), + ("local_as_4_byte", "local-asn"), + ("local_as", "local-asn"), + ("peer_group_name", "peer-group"), + ("description", "description"), + ("peer_tag", "peer-tag"), + ("local_addr", "bind-addr address"), + ("ingress_policy_name", "ingress-policy"), + ("egress_policy_name", "egress-policy"), + ("next_hop4", "next-hop4"), + ("next_hop6", "next-hop6"), + ("peer_id", "peer-id"), + ("type", "type"), +] + +_NEIGHBOR_HANDLED_FIELDS = ( + {"peer_addr"} + | {field for field, _ in _NEIGHBOR_SCALAR_FIELDS} + | _SHARED_HANDLED_FIELDS +) + + +def generate_peer_commands(peer: dict[str, Any]) -> list[str]: + """Generate `config protocol bgp neighbor` CLI commands for a peer.""" + peer_addr = peer.get("peer_addr", "") + if not peer_addr: + return [] + + prefix = f"config protocol bgp neighbor {escape_shell_arg(peer_addr)}" + commands = _unconverted_field_warnings( + peer, _NEIGHBOR_HANDLED_FIELDS, f"neighbor {_printable(peer_addr)}" + ) + commands.extend(_generate_scalar_commands(peer, prefix, _NEIGHBOR_SCALAR_FIELDS)) + commands.extend(_generate_session_commands(peer, prefix)) return commands -def generate_peer_group_commands(peer_group: Dict[str, Any]) -> List[str]: - """Generate CLI commands for a peer group.""" +# (json field, CLI attribute) — PeerGroup-only fields whose value maps 1:1 +# onto a peer-group attribute token. Deprecated i32 ASNs map to the 4-byte +# attribute, as for neighbors. PeerGroup also carries local_addr, next_hop4, +# next_hop6, enabled and router_port_id, but bgpd reads none of them from a +# group (the first three per peer only, the last two nowhere), so the +# dispatcher has no attribute for them and they are flagged instead. +_PEER_GROUP_SCALAR_FIELDS = [ + ("remote_as_4_byte", "remote-asn"), + ("remote_as", "remote-asn"), + ("local_as_4_byte", "local-asn"), + ("local_as", "local-asn"), + ("description", "description"), + ("peer_tag", "peer-tag"), + ("ingress_policy_name", "ingress-policy"), + ("egress_policy_name", "egress-policy"), +] + +_PEER_GROUP_HANDLED_FIELDS = ( + {"name"} + | {field for field, _ in _PEER_GROUP_SCALAR_FIELDS} + | _SHARED_HANDLED_FIELDS +) + + +def generate_peer_group_commands(peer_group: dict[str, Any]) -> list[str]: + """Generate `config protocol bgp peer-group` CLI commands for a peer group. + + The peer-group and neighbor dispatchers share one attribute grammar for + the fields both thrift structs carry, so the per-shape generators are + shared; only the scalar field list differs. + """ name = peer_group.get("name", "") if not name: return [] - escaped_name = escape_shell_arg(name) - commands = [] - commands.extend(_generate_peer_group_basic_commands(peer_group, escaped_name)) - commands.extend(_generate_peer_group_bool_commands(peer_group, escaped_name)) - commands.extend( - _generate_peer_group_timer_commands( - peer_group.get("bgp_peer_timers", {}), escaped_name - ) + prefix = f"config protocol bgp peer-group {escape_shell_arg(name)}" + commands = _unconverted_field_warnings( + peer_group, _PEER_GROUP_HANDLED_FIELDS, f"peer-group {_printable(name)}" ) commands.extend( - _generate_peer_group_prefilter_commands( - peer_group.get("pre_filter", {}), escaped_name - ) + _generate_scalar_commands(peer_group, prefix, _PEER_GROUP_SCALAR_FIELDS) ) + commands.extend(_generate_session_commands(peer_group, prefix)) return commands -def format_bandwidth(bps: int) -> str: - """Format bandwidth in bps to human-readable format (e.g., 10G).""" - if bps >= 1_000_000_000_000 and bps % 1_000_000_000_000 == 0: - return f"{bps // 1_000_000_000_000}T" - elif bps >= 1_000_000_000 and bps % 1_000_000_000 == 0: - return f"{bps // 1_000_000_000}G" - elif bps >= 1_000_000 and bps % 1_000_000 == 0: - return f"{bps // 1_000_000}M" - elif bps >= 1_000 and bps % 1_000 == 0: - return f"{bps // 1_000}K" - return str(bps) +# Emitted verbatim (never prefixed with the binary) for JSON the CLI cannot +# express, so a replayed script neither fails nor silently drops the field. +_WARNING_PREFIX = "# WARNING:" -def _generate_peer_basic_commands(peer: Dict[str, Any], peer_addr: str) -> List[str]: - """Generate basic peer commands (remote-asn, peer-group, description, etc.).""" - commands = [] - if "remote_as_4_byte" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} remote-asn {escape_shell_arg(str(peer['remote_as_4_byte']))}" - ) - if "peer_group_name" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} peer-group {escape_shell_arg(peer['peer_group_name'])}" - ) - if "description" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} description {escape_shell_arg(peer['description'])}" - ) - if "local_addr" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} local-addr {escape_shell_arg(peer['local_addr'])}" - ) - if "ingress_policy_name" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} ingress-policy {escape_shell_arg(peer['ingress_policy_name'])}" - ) - if "egress_policy_name" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} egress-policy {escape_shell_arg(peer['egress_policy_name'])}" - ) - return commands +def _warning(text: str) -> str: + return f"{_WARNING_PREFIX} {text}" -def _generate_peer_bool_commands(peer: Dict[str, Any], peer_addr: str) -> List[str]: - """Generate boolean flag commands for peer.""" - commands = [] - bool_fields = [ - ("is_passive", "passive"), - ("is_rr_client", "rr-client"), - ("next_hop_self", "next-hop-self"), - ("v4_over_v6_nexthop", "v4-over-v6-nh"), - ("disable_ipv4_afi", "disable-ipv4-afi"), - ("advertise_link_bandwidth", "advertise-lbw"), - ] - for field, cli_name in bool_fields: - if field in peer: - commands.append( - f"config protocol bgp peer {peer_addr} {cli_name} {str(peer[field]).lower()}" - ) - return commands +_BOOLEAN_OPERATOR_NAMES = {1: "AND", 2: "OR", 3: "NOT"} + + +def _boolean_operator_name(raw: Any) -> str: + """routing_policy.BooleanOperator as its name, from the int or the name.""" + if isinstance(raw, str): + return raw + return _BOOLEAN_OPERATOR_NAMES.get(int(raw), str(raw)) -def _generate_peer_nexthop_commands(peer: Dict[str, Any], peer_addr: str) -> List[str]: - """Generate next-hop and link-bandwidth commands for peer.""" +def generate_as_path_list_commands(as_path_list: dict[str, Any]) -> list[str]: + """Generate `config protocol bgp policy as-path-list` commands for one list. + + bgpd matches on `as_paths` (one `regex` line each) and `boolean_operator` + (emitted only when it differs from the OR default). `as_path_list_names` + and the `as_path_list` entries are never read by bgpd and have no CLI + spelling here, so they surface as warnings rather than vanishing. + """ + name = as_path_list.get("name", "") + if not name: + return [] + + prefix = f"config protocol bgp policy as-path-list {escape_shell_arg(name)}" commands = [] - if "next_hop4" in peer: + if as_path_list.get("description"): commands.append( - f"config protocol bgp peer {peer_addr} next-hop4 {escape_shell_arg(peer['next_hop4'])}" + f"{prefix} description {escape_shell_arg(as_path_list['description'])}" ) - if "next_hop6" in peer: + for regex in as_path_list.get("as_paths") or []: + commands.append(f"{prefix} regex {escape_shell_arg(regex)}") + if "boolean_operator" in as_path_list: + operator = _boolean_operator_name(as_path_list["boolean_operator"]) + if operator == "NOT": + commands.append( + _warning( + f"as-path-list {name}: boolean_operator NOT is not " + "accepted by the CLI (bgpd treats it as AND); not emitted" + ) + ) + elif operator != "OR": + commands.append(f"{prefix} boolean-operator {escape_shell_arg(operator)}") + if as_path_list.get("as_path_list_names"): commands.append( - f"config protocol bgp peer {peer_addr} next-hop6 {escape_shell_arg(peer['next_hop6'])}" + _warning( + f"as-path-list {name}: as_path_list_names is not read by bgpd " + "and has no CLI equivalent; not emitted" + ) ) - if "link_bandwidth_bps" in peer: - bw = peer["link_bandwidth_bps"] - bw_str = format_bandwidth(bw) if isinstance(bw, int) else str(bw) + if as_path_list.get("as_path_list"): commands.append( - f"config protocol bgp peer {peer_addr} link-bandwidth {escape_shell_arg(bw_str)}" + _warning( + f"as-path-list {name}: as_path_list entries are not read by " + "bgpd (bgpd matches as_paths); not emitted" + ) ) + if not commands: + # Nothing to set: still recreate the (empty) list by name. + commands.append(prefix) return commands -def _generate_peer_timer_commands(timers: Dict[str, Any], peer_addr: str) -> List[str]: - """Generate timer commands for peer.""" +def generate_community_list_commands(community_list: dict[str, Any]) -> list[str]: + """Generate `config protocol bgp policy community-list` commands for one list. + + The deprecated inline `communities` and the `community_list_names` + references have no CLI spelling; they surface as warnings. + """ + name = community_list.get("name", "") + if not name: + return [] + + prefix = f"config protocol bgp policy community-list {escape_shell_arg(name)}" commands = [] - if timers.get("hold_time_seconds"): + if community_list.get("description"): commands.append( - f"config protocol bgp peer {peer_addr} hold-time {escape_shell_arg(str(timers['hold_time_seconds']))}" + f"{prefix} description {escape_shell_arg(community_list['description'])}" ) - if timers.get("keep_alive_seconds"): + if "boolean_operator" in community_list: + operator = _boolean_operator_name(community_list["boolean_operator"]) + if operator != "OR": + commands.append(f"{prefix} boolean-operator {escape_shell_arg(operator)}") + if "exact_match" in community_list: commands.append( - f"config protocol bgp peer {peer_addr} timers keepalive {escape_shell_arg(str(timers['keep_alive_seconds']))}" + f"{prefix} exact-match {_shell_bool(community_list['exact_match'])}" ) - if "out_delay_seconds" in timers: + if community_list.get("communities"): commands.append( - f"config protocol bgp peer {peer_addr} timers out-delay {escape_shell_arg(str(timers['out_delay_seconds']))}" + _warning( + f"community-list {name}: inline `communities` has no CLI " + "equivalent (use members); not emitted" + ) ) - if "withdraw_unprog_delay_seconds" in timers: + if community_list.get("community_list_names"): commands.append( - f"config protocol bgp peer {peer_addr} timers withdraw-unprog-delay {escape_shell_arg(str(timers['withdraw_unprog_delay_seconds']))}" + _warning( + f"community-list {name}: community_list_names has no CLI " + "equivalent; not emitted" + ) ) + for member in community_list.get("members") or []: + commands.extend(generate_community_list_community_commands(name, member)) + if not commands: + # Nothing to set: still recreate the (empty) community-list by name. + commands.append(prefix) return commands -def _generate_peer_prefilter_commands( - pre_filter: Dict[str, Any], peer_addr: str -) -> List[str]: - """Generate pre_filter commands for peer.""" - commands = [] - if pre_filter.get("max_routes"): - commands.append( - f"config protocol bgp peer {peer_addr} max-routes {escape_shell_arg(str(pre_filter['max_routes']))}" - ) - if "warning_limit" in pre_filter: - commands.append( - f"config protocol bgp peer {peer_addr} warning-limit {escape_shell_arg(str(pre_filter['warning_limit']))}" - ) - if "warning_only" in pre_filter: - commands.append( - f"config protocol bgp peer {peer_addr} warning-only {str(pre_filter['warning_only']).lower()}" - ) - return commands +_COMMUNITY_TYPE_NAMES = {1: "NORMAL", 2: "EXTENDED", 3: "LARGE"} -def _generate_peer_id_commands(peer: Dict[str, Any], peer_addr: str) -> List[str]: - """Generate peer identification commands.""" +def generate_community_list_community_commands( + list_name: str, member: dict[str, Any] +) -> list[str]: + """Generate `... community-list community ` commands for one + member. Only inline `community` members have a CLI spelling; a + `community_name` reference surfaces as a warning.""" + if "community" not in member: + return [ + _warning( + f"community-list {list_name}: member references community " + f"'{member.get('community_name', '')}' by name, which has no " + "CLI equivalent; not emitted" + ) + ] + community = member["community"] + name = community.get("name", "") + if not name: + return [ + _warning( + f"community-list {list_name}: unnamed community member cannot " + "be addressed by the CLI; not emitted" + ) + ] + + prefix = ( + f"config protocol bgp policy community-list {escape_shell_arg(list_name)} " + f"community {escape_shell_arg(name)}" + ) commands = [] - if "peer_id" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} peer-id {escape_shell_arg(peer['peer_id'])}" - ) - if "type" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} type {escape_shell_arg(peer['type'])}" - ) + if community.get("description"): + commands.append( + f"{prefix} description {escape_shell_arg(community['description'])}" + ) + if "type" in community: + raw = community["type"] + type_name = ( + raw + if isinstance(raw, str) + else _COMMUNITY_TYPE_NAMES.get(int(raw), str(raw)) + ) + commands.append(f"{prefix} type {escape_shell_arg(type_name)}") + if community.get("value"): + commands.append(f"{prefix} value {escape_shell_arg(community['value'])}") + if not commands: + commands.append(prefix) return commands -def generate_peer_commands(peer: Dict[str, Any]) -> List[str]: - """Generate CLI commands for a peer.""" - peer_addr = peer.get("peer_addr", "") - if not peer_addr: - return [] +def generate_policy_commands(config: dict[str, Any]) -> list[str]: + """Generate the `config protocol bgp policy ...` commands. - escaped_peer_addr = escape_shell_arg(peer_addr) + Lists come before the routing-policies that reference them, and the whole + block precedes the peer-group/peer commands that name a policy, so a + replayed script never stages a dangling reference. + """ + policies = config.get("policies", {}) commands = [] - commands.extend(_generate_peer_basic_commands(peer, escaped_peer_addr)) - commands.extend(_generate_peer_bool_commands(peer, escaped_peer_addr)) - commands.extend(_generate_peer_nexthop_commands(peer, escaped_peer_addr)) - commands.extend( - _generate_peer_timer_commands( - peer.get("bgp_peer_timers", {}), escaped_peer_addr - ) - ) - commands.extend( - _generate_peer_prefilter_commands(peer.get("pre_filter", {}), escaped_peer_addr) - ) - commands.extend(_generate_peer_id_commands(peer, escaped_peer_addr)) + for as_path_list in policies.get("aspath_lists", []): + commands.extend(generate_as_path_list_commands(as_path_list)) + for community_list in policies.get("community_lists", []): + commands.extend(generate_community_list_commands(community_list)) return commands -def generate_stub_commands(commands: List[str], binary: str = "fboss2") -> List[str]: - """Wrap commands for stub mode execution.""" - stub_commands = [ - "#!/bin/bash", - "# BGP configuration script generated by j2c", - "# Run with FBOSS_BGP_STUB_MODE=1 for testing", - "", - "export FBOSS_BGP_STUB_MODE=1", - "", - ] - for cmd in commands: - stub_commands.append(f"{binary} {cmd}") - return stub_commands - - -def generate_exec_commands(commands: List[str], binary: str = "fboss2") -> List[str]: +def generate_exec_commands(commands: list[str], binary: str = "fboss2") -> list[str]: """Generate executable commands with custom binary.""" exec_commands = [ "#!/bin/bash", @@ -411,7 +605,7 @@ def generate_exec_commands(commands: List[str], binary: str = "fboss2") -> List[ "", ] for cmd in commands: - exec_commands.append(f"{binary} {cmd}") + exec_commands.append(cmd if cmd.startswith("#") else f"{binary} {cmd}") exec_commands.extend( [ @@ -424,27 +618,23 @@ def generate_exec_commands(commands: List[str], binary: str = "fboss2") -> List[ return exec_commands -def json_to_cli( - config: Dict[str, Any], stub: bool = False, binary: str = "fboss2" -) -> List[str]: - """Convert BGP++ JSON config to CLI commands.""" +def generate_commands(config: dict[str, Any]) -> list[str]: + """Convert BGP++ JSON config to raw CLI commands (plus `#` warning lines + for fields that have no CLI equivalent).""" commands = [] - - # Generate global commands commands.extend(generate_global_commands(config)) - - # Generate peer-group commands + # Policy objects before the peer-group/peer lines that reference them. + commands.extend(generate_policy_commands(config)) for peer_group in config.get("peer_groups", []): commands.extend(generate_peer_group_commands(peer_group)) - - # Generate peer commands for peer in config.get("peers", []): commands.extend(generate_peer_commands(peer)) + return commands - if stub: - return generate_stub_commands(commands, binary) - return generate_exec_commands(commands, binary) +def json_to_cli(config: dict[str, Any], binary: str = "fboss2") -> list[str]: + """Convert BGP++ JSON config to an executable CLI script.""" + return generate_exec_commands(generate_commands(config), binary) def main() -> int: @@ -455,11 +645,6 @@ def main() -> int: parser.add_argument( "--output", "-o", help="Output file (default: stdout)", default=None ) - parser.add_argument( - "--stub", - action="store_true", - help="Generate stub mode script with FBOSS_BGP_STUB_MODE=1", - ) parser.add_argument( "--binary", "-b", @@ -474,7 +659,7 @@ def main() -> int: args = parser.parse_args() try: - with open(args.input, "r") as f: + with open(args.input) as f: config = json.load(f) except FileNotFoundError: print(f"Error: Input file '{args.input}' not found", file=sys.stderr) @@ -483,18 +668,15 @@ def main() -> int: print(f"Error: Invalid JSON in '{args.input}': {e}", file=sys.stderr) return 1 - if args.raw: - # Generate raw CLI commands only (no shell wrapper) - commands = [] - commands.extend(generate_global_commands(config)) - for peer_group in config.get("peer_groups", []): - commands.extend(generate_peer_group_commands(peer_group)) - for peer in config.get("peers", []): - commands.extend(generate_peer_commands(peer)) - output = "\n".join(commands) - else: - commands = json_to_cli(config, stub=args.stub, binary=args.binary) - output = "\n".join(commands) + commands = generate_commands(config) + # Fields with no CLI equivalent are flagged in the script AND on stderr, + # so a scripted conversion cannot lose them silently. + for line in commands: + if line.startswith("# WARNING"): + print(line[2:], file=sys.stderr) + if not args.raw: + commands = generate_exec_commands(commands, args.binary) + output = "\n".join(commands) if args.output: with open(args.output, "w") as f: diff --git a/fboss/cli/fboss2/tools/tests/test_bgp_json_to_cli.py b/fboss/cli/fboss2/tools/tests/test_bgp_json_to_cli.py index 0c5f7a604aad2..3b27b2b9ae5b8 100644 --- a/fboss/cli/fboss2/tools/tests/test_bgp_json_to_cli.py +++ b/fboss/cli/fboss2/tools/tests/test_bgp_json_to_cli.py @@ -20,11 +20,15 @@ from fboss.cli.fboss2.tools.bgp_json_to_cli import ( escape_shell_arg, format_bandwidth, + generate_as_path_list_commands, + generate_commands, + generate_community_list_commands, + generate_community_list_community_commands, generate_exec_commands, generate_global_commands, generate_peer_commands, generate_peer_group_commands, - generate_stub_commands, + generate_policy_commands, json_to_cli, ) @@ -72,14 +76,19 @@ def test_backtick_injection_neutralized(self) -> None: result = escape_shell_arg("`reboot`") self.assertEqual(result, "'`reboot`'") + def test_command_separator_neutralized(self) -> None: + """Separators like ; & | must not split the generated command.""" + self.assertEqual(escape_shell_arg("x;id"), "'x;id'") + self.assertEqual(escape_shell_arg("a&&b|c"), "'a&&b|c'") + class FormatBandwidthTest(unittest.TestCase): """Tests for format_bandwidth function.""" - def test_terabits(self) -> None: - """Bandwidth in terabits should format as 'T'.""" + def test_terabits_stay_in_gigabits(self) -> None: + """bgpd's parser knows only K/M/G, so 1 Tbps must be emitted as 1000G.""" result = format_bandwidth(1_000_000_000_000) - self.assertEqual(result, "1T") + self.assertEqual(result, "1000G") def test_gigabits(self) -> None: """Bandwidth in gigabits should format as 'G'.""" @@ -134,14 +143,6 @@ def test_confed_asn(self) -> None: commands = generate_global_commands(config) self.assertEqual(commands, ["config protocol bgp global confed-asn 65001"]) - def test_cluster_id(self) -> None: - """Cluster ID should generate correct command.""" - config = {"cluster_id": "192.168.1.1"} - commands = generate_global_commands(config) - self.assertEqual( - commands, ["config protocol bgp global cluster-id 192.168.1.1"] - ) - def test_networks6_with_all_fields(self) -> None: """networks6 should generate command with all fields.""" config = { @@ -162,14 +163,7 @@ def test_networks6_with_all_fields(self) -> None: def test_networks6_with_install_to_fib_true(self) -> None: """networks6 with install_to_fib=true should preserve value.""" - config = { - "networks6": [ - { - "prefix": "2001:db8::/32", - "install_to_fib": True, - } - ] - } + config = {"networks6": [{"prefix": "2001:db8::/32", "install_to_fib": True}]} commands = generate_global_commands(config) self.assertIn("install-to-fib true", commands[0]) @@ -242,7 +236,9 @@ def test_empty_config(self) -> None: class GeneratePeerGroupCommandsTest(unittest.TestCase): - """Tests for generate_peer_group_commands function.""" + """Tests for generate_peer_group_commands (peer-group grammar).""" + + PREFIX = "config protocol bgp peer-group TEST" def test_empty_name_returns_empty(self) -> None: """Peer group without name should return empty list.""" @@ -258,42 +254,68 @@ def test_remote_asn(self) -> None: "config protocol bgp peer-group TEST-GROUP remote-asn 65000", commands ) + def test_local_asn(self) -> None: + """local_as_4_byte should generate local-asn command.""" + peer_group = {"name": "TEST", "local_as_4_byte": 64512} + commands = generate_peer_group_commands(peer_group) + self.assertIn(f"{self.PREFIX} local-asn 64512", commands) + def test_description_with_spaces(self) -> None: - """Description with spaces should be properly quoted.""" + """Description with spaces should be quoted.""" peer_group = {"name": "TEST", "description": "Test peer group"} commands = generate_peer_group_commands(peer_group) - self.assertIn( - "config protocol bgp peer-group TEST description 'Test peer group'", - commands, - ) + self.assertIn(f"{self.PREFIX} description 'Test peer group'", commands) def test_boolean_flag_true(self) -> None: - """Boolean flag with true value should generate 'true'.""" + """Boolean true should generate 'true'.""" peer_group = {"name": "TEST", "next_hop_self": True} commands = generate_peer_group_commands(peer_group) - self.assertIn( - "config protocol bgp peer-group TEST next-hop-self true", commands - ) + self.assertIn(f"{self.PREFIX} next-hop-self true", commands) def test_boolean_flag_false(self) -> None: - """Boolean flag with false value should generate 'false' (not omitted).""" + """Boolean false should generate 'false' (not be omitted).""" peer_group = {"name": "TEST", "disable_ipv4_afi": False} commands = generate_peer_group_commands(peer_group) - self.assertIn( - "config protocol bgp peer-group TEST disable-ipv4-afi false", commands - ) + self.assertIn(f"{self.PREFIX} afi disable-ipv4-afi false", commands) - def test_is_rr_client(self) -> None: - """is_rr_client should generate rr-client command.""" - peer_group = {"name": "TEST", "is_rr_client": True} + def test_afi_flags(self) -> None: + """AFI flags use the two-token `afi ...` attributes.""" + peer_group = { + "name": "TEST", + "disable_ipv4_afi": True, + "disable_ipv6_afi": False, + "v4_over_v6_nexthop": True, + } commands = generate_peer_group_commands(peer_group) - self.assertIn("config protocol bgp peer-group TEST rr-client true", commands) + self.assertIn(f"{self.PREFIX} afi disable-ipv4-afi true", commands) + self.assertIn(f"{self.PREFIX} afi disable-ipv6-afi false", commands) + self.assertIn(f"{self.PREFIX} afi ipv4-over-ipv6-nh true", commands) - def test_is_confed_peer(self) -> None: - """is_confed_peer should generate confed-peer command.""" - peer_group = {"name": "TEST", "is_confed_peer": True} + def test_session_flags(self) -> None: + """Session flags map onto their single-token boolean attributes.""" + peer_group = { + "name": "TEST", + "is_rr_client": True, + "is_confed_peer": True, + "is_redistribute_peer": False, + "enhanced_route_refresh": True, + "enable_stateful_ha": True, + } commands = generate_peer_group_commands(peer_group) - self.assertIn("config protocol bgp peer-group TEST confed-peer true", commands) + self.assertIn(f"{self.PREFIX} rr-client true", commands) + self.assertIn(f"{self.PREFIX} confed-peer true", commands) + self.assertIn(f"{self.PREFIX} redistribute-peer false", commands) + self.assertIn(f"{self.PREFIX} enhanced-route-refresh true", commands) + self.assertIn(f"{self.PREFIX} graceful-restart stateful-ha true", commands) + + def test_is_passive(self) -> None: + """is_passive should generate the passive flag.""" + peer_group = {"name": "TEST", "is_passive": True} + commands = generate_peer_group_commands(peer_group) + self.assertIn(f"{self.PREFIX} passive true", commands) + peer_group["is_passive"] = False + commands = generate_peer_group_commands(peer_group) + self.assertIn(f"{self.PREFIX} passive false", commands) def test_policies(self) -> None: """Policy names should generate correct commands.""" @@ -303,23 +325,22 @@ def test_policies(self) -> None: "egress_policy_name": "OUT_POLICY", } commands = generate_peer_group_commands(peer_group) - self.assertIn( - "config protocol bgp peer-group TEST ingress-policy IN_POLICY", commands - ) - self.assertIn( - "config protocol bgp peer-group TEST egress-policy OUT_POLICY", commands - ) + self.assertIn(f"{self.PREFIX} ingress-policy IN_POLICY", commands) + self.assertIn(f"{self.PREFIX} egress-policy OUT_POLICY", commands) - def test_v4_over_v6_nexthop(self) -> None: - """v4_over_v6_nexthop should generate v4-over-v6-nh command.""" - peer_group = {"name": "TEST", "v4_over_v6_nexthop": True} + def test_add_path(self) -> None: + """add_path BOTH should expand to both directions; RECEIVE to one.""" + peer_group = {"name": "TEST", "add_path": 3} commands = generate_peer_group_commands(peer_group) - self.assertIn( - "config protocol bgp peer-group TEST v4-over-v6-nh true", commands - ) + self.assertIn(f"{self.PREFIX} add-path receive true", commands) + self.assertIn(f"{self.PREFIX} add-path send true", commands) + peer_group["add_path"] = "RECEIVE" + commands = generate_peer_group_commands(peer_group) + self.assertIn(f"{self.PREFIX} add-path receive true", commands) + self.assertNotIn(f"{self.PREFIX} add-path send true", commands) def test_timers(self) -> None: - """Timer fields should generate correct commands.""" + """Timers, including zero values, should generate correct commands.""" peer_group = { "name": "TEST", "bgp_peer_timers": { @@ -327,48 +348,220 @@ def test_timers(self) -> None: "keep_alive_seconds": 10, "out_delay_seconds": 5, "withdraw_unprog_delay_seconds": 0, + "graceful_restart_seconds": 120, }, } commands = generate_peer_group_commands(peer_group) - self.assertIn( - "config protocol bgp peer-group TEST timers hold-time 30", commands - ) - self.assertIn( - "config protocol bgp peer-group TEST timers keepalive 10", commands - ) - self.assertIn( - "config protocol bgp peer-group TEST timers out-delay 5", commands - ) - self.assertIn( - "config protocol bgp peer-group TEST timers withdraw-unprog-delay 0", - commands, - ) + self.assertIn(f"{self.PREFIX} timers hold-time 30", commands) + self.assertIn(f"{self.PREFIX} timers keepalive 10", commands) + self.assertIn(f"{self.PREFIX} timers out-delay 5", commands) + self.assertIn(f"{self.PREFIX} timers withdraw-unprog-delay 0", commands) + self.assertIn(f"{self.PREFIX} graceful-restart restart-time 120", commands) + + def test_hold_time_zero_preserved(self) -> None: + """hold_time_seconds: 0 is a real value (no keepalives), not 'unset'.""" + peer_group = {"name": "TEST", "bgp_peer_timers": {"hold_time_seconds": 0}} + commands = generate_peer_group_commands(peer_group) + self.assertIn(f"{self.PREFIX} timers hold-time 0", commands) - def test_pre_filter_max_routes(self) -> None: - """pre_filter.max_routes should generate max-routes command.""" - peer_group = {"name": "TEST", "pre_filter": {"max_routes": 45000}} + def test_pre_filter(self) -> None: + """pre_filter maps onto the max-route pre-* attributes.""" + peer_group = { + "name": "TEST", + "pre_filter": { + "max_routes": 45000, + "warning_limit": 100, + "warning_only": True, + }, + } commands = generate_peer_group_commands(peer_group) - self.assertIn("config protocol bgp peer-group TEST max-routes 45000", commands) + self.assertIn(f"{self.PREFIX} max-route pre-filter 45000", commands) + self.assertIn(f"{self.PREFIX} max-route pre-warning-threshold 100", commands) + self.assertIn(f"{self.PREFIX} max-route pre-warning-only true", commands) - def test_pre_filter_warning_settings(self) -> None: - """pre_filter warning settings should generate correct commands.""" + def test_post_filter(self) -> None: + """post_filter maps onto the max-route post-* attributes.""" peer_group = { "name": "TEST", - "pre_filter": {"warning_limit": 100, "warning_only": True}, + "post_filter": { + "max_routes": 50000, + "warning_limit": 48000, + "warning_only": False, + }, } commands = generate_peer_group_commands(peer_group) - self.assertIn("config protocol bgp peer-group TEST warning-limit 100", commands) - self.assertIn("config protocol bgp peer-group TEST warning-only true", commands) + self.assertIn(f"{self.PREFIX} max-route post-filter 50000", commands) + self.assertIn(f"{self.PREFIX} max-route post-warning-threshold 48000", commands) + self.assertIn(f"{self.PREFIX} max-route post-warning-only false", commands) def test_peer_tag(self) -> None: """peer_tag should generate peer-tag command.""" peer_group = {"name": "TEST", "peer_tag": "FSW"} commands = generate_peer_group_commands(peer_group) - self.assertIn("config protocol bgp peer-group TEST peer-tag FSW", commands) + self.assertIn(f"{self.PREFIX} peer-tag FSW", commands) + + def test_complete_peer_group_loses_no_field(self) -> None: + """Every CLI-settable PeerGroup field produces exactly one command, in + the grammar the peer-group dispatcher accepts.""" + peer_group = { + "name": "TEST", + "remote_as_4_byte": 65000, + "local_as_4_byte": 64512, + "description": "spine group", + "peer_tag": "FSW", + "ingress_policy_name": "IN_POLICY", + "egress_policy_name": "OUT_POLICY", + "is_passive": True, + "is_rr_client": True, + "is_confed_peer": False, + "is_redistribute_peer": True, + "enhanced_route_refresh": True, + "route_refresh": False, + "remove_private_as": True, + "enforce_first_as": True, + "disable_ipv4_afi": True, + "disable_ipv6_afi": False, + "v4_over_v6_nexthop": True, + "enable_stateful_ha": True, + "next_hop_self": True, + "ttl_security_hops": 2, + "link_bandwidth_bps": "10G", + "advertise_link_bandwidth": 1, + "receive_link_bandwidth": "ACCEPT", + "add_path": 3, + "bgp_peer_timers": { + "hold_time_seconds": 90, + "keep_alive_seconds": 30, + "out_delay_seconds": 5, + "withdraw_unprog_delay_seconds": 10, + "graceful_restart_seconds": 120, + }, + "pre_filter": { + "max_routes": 45000, + "warning_limit": 40000, + "warning_only": True, + }, + "post_filter": { + "max_routes": 50000, + "warning_limit": 48000, + "warning_only": False, + }, + } + expected = [ + "remote-asn 65000", + "local-asn 64512", + "description 'spine group'", + "peer-tag FSW", + "ingress-policy IN_POLICY", + "egress-policy OUT_POLICY", + "passive true", + "rr-client true", + "confed-peer false", + "redistribute-peer true", + "enhanced-route-refresh true", + "route-refresh false", + "remove-private-as true", + "enforce-first-as true", + "afi disable-ipv4-afi true", + "afi disable-ipv6-afi false", + "afi ipv4-over-ipv6-nh true", + "graceful-restart stateful-ha true", + "next-hop-self true", + "ttl-security-hops 2", + "link-bandwidth 10G", + "advertise-lbw 1", + "receive-lbw ACCEPT", + "add-path receive true", + "add-path send true", + "timers hold-time 90", + "timers keepalive 30", + "timers out-delay 5", + "timers withdraw-unprog-delay 10", + "graceful-restart restart-time 120", + "max-route pre-filter 45000", + "max-route pre-warning-threshold 40000", + "max-route pre-warning-only true", + "max-route post-filter 50000", + "max-route post-warning-threshold 48000", + "max-route post-warning-only false", + ] + commands = generate_peer_group_commands(peer_group) + self.assertEqual(commands, [f"{self.PREFIX} {e}" for e in expected]) + # One command per leaf field: nothing in the input was dropped. + leaf_fields = ( + len(peer_group) + - 1 # name is the key, not an attribute + - 3 # the three nested structs are counted by their leaves below + + 1 # add_path BOTH expands to two commands + + len(peer_group["bgp_peer_timers"]) + + len(peer_group["pre_filter"]) + + len(peer_group["post_filter"]) + ) + self.assertEqual(len(commands), leaf_fields) + self.assertEqual(len(commands), 36) + + def test_link_bandwidth(self) -> None: + """The link-bandwidth trio bgpd resolves peer > group is generated.""" + peer_group = { + "name": "TEST", + "link_bandwidth_bps": 10_000_000_000, + "advertise_link_bandwidth": "ADVERTISE", + "receive_link_bandwidth": 1, + } + commands = generate_peer_group_commands(peer_group) + self.assertIn(f"{self.PREFIX} link-bandwidth 10G", commands) + self.assertIn(f"{self.PREFIX} advertise-lbw ADVERTISE", commands) + self.assertIn(f"{self.PREFIX} receive-lbw 1", commands) + + def test_deprecated_asn_fields_map_to_4_byte_attribute(self) -> None: + """The deprecated i32 ASNs are carried over, not dropped.""" + peer_group = {"name": "TEST", "remote_as": 65000, "local_as": 64512} + commands = generate_peer_group_commands(peer_group) + self.assertIn(f"{self.PREFIX} remote-asn 65000", commands) + self.assertIn(f"{self.PREFIX} local-asn 64512", commands) + self.assertFalse(any(c.startswith("# WARNING") for c in commands)) + + def test_unrepresentable_peer_group_fields_warn(self) -> None: + """PeerGroup fields bgpd never reads from a group are reported, not + silently dropped, and produce no config command.""" + peer_group = { + "name": "TEST", + "remote_as_4_byte": 65000, + "local_addr": "2001:db8::2", + "next_hop4": "10.1.1.1", + "next_hop6": "2001:db8::a", + "enabled": True, + "router_port_id": 7, + "bgp_peer_timers": { + "hold_time_seconds": 90, + "graceful_restart_end_of_rib_seconds": 30, + }, + } + commands = generate_peer_group_commands(peer_group) + warnings = [c for c in commands if c.startswith("# WARNING")] + self.assertEqual( + warnings, + [ + f"# WARNING: peer-group TEST: field {f} has no CLI equivalent, " + "not converted" + for f in ( + "local_addr", + "next_hop4", + "next_hop6", + "enabled", + "router_port_id", + "bgp_peer_timers.graceful_restart_end_of_rib_seconds", + ) + ], + ) + self.assertEqual( + [c for c in commands if not c.startswith("#")], + [f"{self.PREFIX} remote-asn 65000", f"{self.PREFIX} timers hold-time 90"], + ) class GeneratePeerCommandsTest(unittest.TestCase): - """Tests for generate_peer_commands function.""" + """Tests for generate_peer_commands (neighbor grammar).""" def test_empty_peer_addr_returns_empty(self) -> None: """Peer without peer_addr should return empty list.""" @@ -380,36 +573,53 @@ def test_remote_asn(self) -> None: """remote_as_4_byte should generate correct command.""" peer = {"peer_addr": "2001:db8::1", "remote_as_4_byte": 65000} commands = generate_peer_commands(peer) - self.assertIn("config protocol bgp peer 2001:db8::1 remote-asn 65000", commands) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 remote-asn 65000", commands + ) + + def test_local_asn(self) -> None: + """local_as_4_byte should generate local-asn command.""" + peer = {"peer_addr": "2001:db8::1", "local_as_4_byte": 64512} + commands = generate_peer_commands(peer) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 local-asn 64512", commands + ) def test_peer_group_name(self) -> None: """peer_group_name should generate peer-group command.""" peer = {"peer_addr": "2001:db8::1", "peer_group_name": "MY-GROUP"} commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 peer-group MY-GROUP", commands + "config protocol bgp neighbor 2001:db8::1 peer-group MY-GROUP", commands ) def test_local_addr(self) -> None: - """local_addr should generate local-addr command.""" + """local_addr should generate bind-addr address command.""" peer = {"peer_addr": "2001:db8::1", "local_addr": "2001:db8::a"} commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 local-addr 2001:db8::a", commands + "config protocol bgp neighbor 2001:db8::1 bind-addr address 2001:db8::a", + commands, ) def test_is_passive(self) -> None: - """is_passive should generate passive command.""" + """is_passive should generate the passive flag.""" peer = {"peer_addr": "2001:db8::1", "is_passive": True} commands = generate_peer_commands(peer) - self.assertIn("config protocol bgp peer 2001:db8::1 passive true", commands) + self.assertIn("config protocol bgp neighbor 2001:db8::1 passive true", commands) + peer["is_passive"] = False + commands = generate_peer_commands(peer) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 passive false", commands + ) def test_disable_ipv4_afi_false_preserved(self) -> None: """disable_ipv4_afi: false should NOT be omitted.""" peer = {"peer_addr": "2001:db8::1", "disable_ipv4_afi": False} commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 disable-ipv4-afi false", commands + "config protocol bgp neighbor 2001:db8::1 afi disable-ipv4-afi false", + commands, ) def test_disable_ipv4_afi_true(self) -> None: @@ -417,96 +627,558 @@ def test_disable_ipv4_afi_true(self) -> None: peer = {"peer_addr": "2001:db8::1", "disable_ipv4_afi": True} commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 disable-ipv4-afi true", commands + "config protocol bgp neighbor 2001:db8::1 afi disable-ipv4-afi true", + commands, ) - def test_next_hop4_and_next_hop6(self) -> None: - """next_hop4 and next_hop6 should generate correct commands.""" - peer = { - "peer_addr": "2001:db8::1", - "next_hop4": "0.0.0.0", - "next_hop6": "2001:db8::a", - } + def test_stateful_ha(self) -> None: + """enable_stateful_ha should generate graceful-restart stateful-ha.""" + peer = {"peer_addr": "2001:db8::1", "enable_stateful_ha": True} + commands = generate_peer_commands(peer) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 graceful-restart stateful-ha true", + commands, + ) + + def test_add_path_both(self) -> None: + """add_path BOTH (3) should generate both directions.""" + peer = {"peer_addr": "2001:db8::1", "add_path": 3} commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 next-hop4 0.0.0.0", commands + "config protocol bgp neighbor 2001:db8::1 add-path receive true", commands ) self.assertIn( - "config protocol bgp peer 2001:db8::1 next-hop6 2001:db8::a", commands + "config protocol bgp neighbor 2001:db8::1 add-path send true", commands ) - def test_link_bandwidth_string(self) -> None: - """link_bandwidth_bps as string should be used directly.""" - peer = {"peer_addr": "2001:db8::1", "link_bandwidth_bps": "10G"} + def test_add_path_receive_only(self) -> None: + """add_path RECEIVE (1) should generate only the receive direction.""" + peer = {"peer_addr": "2001:db8::1", "add_path": 1} commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 link-bandwidth 10G", commands + "config protocol bgp neighbor 2001:db8::1 add-path receive true", commands + ) + self.assertNotIn( + "config protocol bgp neighbor 2001:db8::1 add-path send true", commands ) - def test_link_bandwidth_int(self) -> None: - """link_bandwidth_bps as int should be formatted.""" - peer = {"peer_addr": "2001:db8::1", "link_bandwidth_bps": 10_000_000_000} + def test_timers(self) -> None: + """Timer fields should generate correct commands.""" + peer = { + "peer_addr": "2001:db8::1", + "bgp_peer_timers": { + "hold_time_seconds": 120, + "keep_alive_seconds": 40, + "out_delay_seconds": 0, + "graceful_restart_seconds": 60, + }, + } commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 link-bandwidth 10G", commands + "config protocol bgp neighbor 2001:db8::1 timers hold-time 120", commands + ) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 timers keepalive 40", commands + ) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 timers out-delay 0", commands + ) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 graceful-restart restart-time 60", + commands, ) - def test_advertise_link_bandwidth(self) -> None: - """advertise_link_bandwidth should generate advertise-lbw command.""" - peer = {"peer_addr": "2001:db8::1", "advertise_link_bandwidth": 1} + def test_route_limits(self) -> None: + """pre_filter/post_filter should generate max-route commands.""" + peer = { + "peer_addr": "2001:db8::1", + "pre_filter": {"max_routes": 45000}, + "post_filter": {"max_routes": 50000, "warning_limit": 48000}, + } commands = generate_peer_commands(peer) - self.assertIn("config protocol bgp peer 2001:db8::1 advertise-lbw 1", commands) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 max-route pre-filter 45000", + commands, + ) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 max-route post-filter 50000", + commands, + ) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 max-route post-warning-threshold 48000", + commands, + ) - def test_peer_id_and_type(self) -> None: - """peer_id and type should generate correct commands.""" + def test_parity_fields(self) -> None: + """Fields carried over from the old peer commands map to the new grammar.""" peer = { "peer_addr": "2001:db8::1", + "next_hop4": "0.0.0.0", + "next_hop6": "2001:db8::a", + "next_hop_self": True, + "link_bandwidth_bps": "10G", + "advertise_link_bandwidth": 1, + "receive_link_bandwidth": 1, "peer_id": "test-peer:v6:1", "type": "BGP_MONITOR", + "bgp_peer_timers": {"withdraw_unprog_delay_seconds": 5}, + "pre_filter": {"warning_limit": 100, "warning_only": True}, + } + commands = generate_peer_commands(peer) + prefix = "config protocol bgp neighbor 2001:db8::1" + self.assertIn(f"{prefix} next-hop4 0.0.0.0", commands) + self.assertIn(f"{prefix} next-hop6 2001:db8::a", commands) + self.assertIn(f"{prefix} next-hop-self true", commands) + self.assertIn(f"{prefix} link-bandwidth 10G", commands) + self.assertIn(f"{prefix} advertise-lbw 1", commands) + self.assertIn(f"{prefix} receive-lbw 1", commands) + self.assertIn(f"{prefix} peer-id test-peer:v6:1", commands) + self.assertIn(f"{prefix} type BGP_MONITOR", commands) + self.assertIn(f"{prefix} timers withdraw-unprog-delay 5", commands) + self.assertIn(f"{prefix} max-route pre-warning-threshold 100", commands) + self.assertIn(f"{prefix} max-route pre-warning-only true", commands) + + def test_zero_values_preserved(self) -> None: + """Explicit zero timers/limits must not be dropped.""" + peer = { + "peer_addr": "2001:db8::1", + "bgp_peer_timers": {"hold_time_seconds": 0, "keep_alive_seconds": 0}, + "pre_filter": {"max_routes": 0}, } commands = generate_peer_commands(peer) + prefix = "config protocol bgp neighbor 2001:db8::1" + self.assertIn(f"{prefix} timers hold-time 0", commands) + self.assertIn(f"{prefix} timers keepalive 0", commands) + self.assertIn(f"{prefix} max-route pre-filter 0", commands) + + def test_add_path_symbolic(self) -> None: + """add_path serialized as an enum name must not crash the tool.""" + peer = {"peer_addr": "2001:db8::1", "add_path": "BOTH"} + commands = generate_peer_commands(peer) self.assertIn( - "config protocol bgp peer 2001:db8::1 peer-id test-peer:v6:1", commands + "config protocol bgp neighbor 2001:db8::1 add-path receive true", commands + ) + self.assertIn( + "config protocol bgp neighbor 2001:db8::1 add-path send true", commands ) - self.assertIn("config protocol bgp peer 2001:db8::1 type BGP_MONITOR", commands) - def test_timers(self) -> None: - """Timer fields should generate correct commands.""" + def test_session_flags(self) -> None: + """The session knobs bgpd resolves peer > group all have a spelling.""" + peer = { + "peer_addr": "2001:db8::1", + "route_refresh": True, + "remove_private_as": True, + "enforce_first_as": False, + "ttl_security_hops": 2, + } + commands = generate_peer_commands(peer) + prefix = "config protocol bgp neighbor 2001:db8::1" + self.assertIn(f"{prefix} route-refresh true", commands) + self.assertIn(f"{prefix} remove-private-as true", commands) + self.assertIn(f"{prefix} enforce-first-as false", commands) + self.assertIn(f"{prefix} ttl-security-hops 2", commands) + + def test_deprecated_asn_fields_map_to_4_byte_attribute(self) -> None: + """The deprecated i32 ASNs are carried over, not dropped.""" + peer = {"peer_addr": "2001:db8::1", "remote_as": 65000, "local_as": 64512} + commands = generate_peer_commands(peer) + prefix = "config protocol bgp neighbor 2001:db8::1" + self.assertIn(f"{prefix} remote-asn 65000", commands) + self.assertIn(f"{prefix} local-asn 64512", commands) + self.assertFalse(any(c.startswith("# WARNING") for c in commands)) + + def test_complete_neighbor_loses_no_field(self) -> None: + """Every BgpPeer field bgpd reads produces exactly one command, in the + grammar the neighbor dispatcher accepts, and nothing is flagged.""" peer = { "peer_addr": "2001:db8::1", + "remote_as_4_byte": 65000, + "local_as_4_byte": 64512, + "peer_group_name": "SPINE", + "description": "spine peer", + "peer_tag": "FSW", + "local_addr": "2001:db8::2", + "ingress_policy_name": "IN_POLICY", + "egress_policy_name": "OUT_POLICY", + "next_hop4": "10.1.1.1", + "next_hop6": "2001:db8::a", + "peer_id": "test-peer:v6:1", + "type": "BGP_MONITOR", + "is_passive": True, + "is_rr_client": True, + "is_confed_peer": False, + "is_redistribute_peer": True, + "enhanced_route_refresh": True, + "route_refresh": False, + "remove_private_as": True, + "enforce_first_as": True, + "disable_ipv4_afi": True, + "disable_ipv6_afi": False, + "v4_over_v6_nexthop": True, + "enable_stateful_ha": True, + "next_hop_self": True, + "ttl_security_hops": 2, + "link_bandwidth_bps": "10G", + "advertise_link_bandwidth": 1, + "receive_link_bandwidth": "ACCEPT", + "add_path": 3, "bgp_peer_timers": { - "hold_time_seconds": 120, - "keep_alive_seconds": 40, - "out_delay_seconds": 0, - "withdraw_unprog_delay_seconds": 0, + "hold_time_seconds": 90, + "keep_alive_seconds": 30, + "out_delay_seconds": 5, + "withdraw_unprog_delay_seconds": 10, + "graceful_restart_seconds": 120, + }, + "pre_filter": { + "max_routes": 45000, + "warning_limit": 40000, + "warning_only": True, + }, + "post_filter": { + "max_routes": 50000, + "warning_limit": 48000, + "warning_only": False, }, } + expected = [ + "remote-asn 65000", + "local-asn 64512", + "peer-group SPINE", + "description 'spine peer'", + "peer-tag FSW", + "bind-addr address 2001:db8::2", + "ingress-policy IN_POLICY", + "egress-policy OUT_POLICY", + "next-hop4 10.1.1.1", + "next-hop6 2001:db8::a", + "peer-id test-peer:v6:1", + "type BGP_MONITOR", + "passive true", + "rr-client true", + "confed-peer false", + "redistribute-peer true", + "enhanced-route-refresh true", + "route-refresh false", + "remove-private-as true", + "enforce-first-as true", + "afi disable-ipv4-afi true", + "afi disable-ipv6-afi false", + "afi ipv4-over-ipv6-nh true", + "graceful-restart stateful-ha true", + "next-hop-self true", + "ttl-security-hops 2", + "link-bandwidth 10G", + "advertise-lbw 1", + "receive-lbw ACCEPT", + "add-path receive true", + "add-path send true", + "timers hold-time 90", + "timers keepalive 30", + "timers out-delay 5", + "timers withdraw-unprog-delay 10", + "graceful-restart restart-time 120", + "max-route pre-filter 45000", + "max-route pre-warning-threshold 40000", + "max-route pre-warning-only true", + "max-route post-filter 50000", + "max-route post-warning-threshold 48000", + "max-route post-warning-only false", + ] + prefix = "config protocol bgp neighbor 2001:db8::1" commands = generate_peer_commands(peer) - self.assertIn("config protocol bgp peer 2001:db8::1 hold-time 120", commands) - self.assertIn( - "config protocol bgp peer 2001:db8::1 timers keepalive 40", commands + self.assertEqual(commands, [f"{prefix} {e}" for e in expected]) + # One command per leaf field: nothing in the input was dropped. + leaf_fields = ( + len(peer) + - 1 # peer_addr is the key, not an attribute + - 3 # the three nested structs are counted by their leaves below + + 1 # add_path BOTH expands to two commands + + len(peer["bgp_peer_timers"]) + + len(peer["pre_filter"]) + + len(peer["post_filter"]) ) + self.assertEqual(len(commands), leaf_fields) - def test_pre_filter(self) -> None: - """pre_filter fields should generate correct commands.""" + def test_unrepresentable_neighbor_fields_warn(self) -> None: + """Fields nothing in bgpd reads are reported, not silently dropped.""" peer = { "peer_addr": "2001:db8::1", - "pre_filter": { - "max_routes": 45000, - "warning_limit": 0, - "warning_only": False, + "remote_as_4_byte": 65000, + "enabled": True, + "router_port_id": 7, + "bgp_peer_timers": { + "hold_time_seconds": 90, + "graceful_restart_end_of_rib_seconds": 30, }, } commands = generate_peer_commands(peer) - self.assertIn("config protocol bgp peer 2001:db8::1 max-routes 45000", commands) - self.assertIn("config protocol bgp peer 2001:db8::1 warning-limit 0", commands) - self.assertIn( - "config protocol bgp peer 2001:db8::1 warning-only false", commands + prefix = "config protocol bgp neighbor 2001:db8::1" + warnings = [c for c in commands if c.startswith("# WARNING")] + self.assertEqual( + warnings, + [ + "# WARNING: neighbor 2001:db8::1: field enabled has no CLI " + "equivalent, not converted", + "# WARNING: neighbor 2001:db8::1: field router_port_id has no CLI " + "equivalent, not converted", + "# WARNING: neighbor 2001:db8::1: field " + "bgp_peer_timers.graceful_restart_end_of_rib_seconds has no CLI " + "equivalent, not converted", + ], + ) + # ... and only the convertible fields produce commands. + self.assertEqual( + [c for c in commands if not c.startswith("#")], + [f"{prefix} remote-asn 65000", f"{prefix} timers hold-time 90"], + ) + + def test_warning_subject_cannot_break_out_of_comment(self) -> None: + """A hostile peer_addr must not turn a warning into a script line.""" + peer = {"peer_addr": "x\nrm -rf /", "enabled": True} + script = json_to_cli(peer_config(peer), binary="fboss2") + self.assertFalse(any(line.startswith("rm ") for line in script)) + self.assertTrue( + any(line.startswith("# WARNING: neighbor x?rm") for line in script) + ) + + +def peer_config(peer: dict) -> dict: + """A whole-config wrapper around one peer.""" + return {"peers": [peer]} + + +class GenerateAsPathListCommandsTest(unittest.TestCase): + """Tests for generate_as_path_list_commands (as-path-list grammar).""" + + PREFIX = "config protocol bgp policy as-path-list ASPL" + + def test_empty_name_returns_empty(self) -> None: + self.assertEqual(generate_as_path_list_commands({"as_paths": ["^1_"]}), []) + + def test_bare_list_is_recreated(self) -> None: + """A list with nothing to set still comes back by name.""" + self.assertEqual( + generate_as_path_list_commands({"name": "ASPL"}), [self.PREFIX] + ) + + def test_description(self) -> None: + commands = generate_as_path_list_commands( + {"name": "ASPL", "description": "spine as paths"} + ) + self.assertEqual(commands, [f"{self.PREFIX} description 'spine as paths'"]) + + def test_empty_description_not_emitted(self) -> None: + """bgpd's running config carries the "" default; nothing to replay.""" + commands = generate_as_path_list_commands({"name": "ASPL", "description": ""}) + self.assertEqual(commands, [self.PREFIX]) + + def test_regex_per_as_path(self) -> None: + """Each as_paths pattern is one quoted `regex` line, in order.""" + commands = generate_as_path_list_commands( + {"name": "ASPL", "as_paths": ["^65000_", "_65001$"]} + ) + self.assertEqual( + commands, + [f"{self.PREFIX} regex '^65000_'", f"{self.PREFIX} regex '_65001$'"], + ) + + def test_boolean_operator_and(self) -> None: + for raw in (1, "AND"): + commands = generate_as_path_list_commands( + {"name": "ASPL", "boolean_operator": raw} + ) + self.assertEqual(commands, [f"{self.PREFIX} boolean-operator AND"], raw) + + def test_boolean_operator_or_is_default(self) -> None: + """OR is the thrift default and the CLI's; nothing to emit.""" + for raw in (2, "OR"): + commands = generate_as_path_list_commands( + {"name": "ASPL", "boolean_operator": raw} + ) + self.assertEqual(commands, [self.PREFIX], raw) + + def test_boolean_operator_not_warns(self) -> None: + commands = generate_as_path_list_commands( + {"name": "ASPL", "boolean_operator": 3} + ) + self.assertEqual(len(commands), 1) + self.assertTrue(commands[0].startswith("# WARNING:"), commands[0]) + self.assertIn("NOT", commands[0]) + + def test_dead_fields_warn(self) -> None: + """Fields bgpd never reads surface as warnings, not silently dropped.""" + commands = generate_as_path_list_commands( + { + "name": "ASPL", + "as_path_list_names": ["OTHER"], + "as_path_list": [{"sequence_number": 10}], + } + ) + self.assertEqual(len(commands), 2) + self.assertIn("as_path_list_names", commands[0]) + self.assertIn("as_path_list entries", commands[1]) + for c in commands: + self.assertTrue(c.startswith("# WARNING:"), c) + + def test_injection_in_regex_neutralized(self) -> None: + commands = generate_as_path_list_commands( + {"name": "ASPL", "as_paths": ["$(reboot)"]} + ) + self.assertEqual(commands, [f"{self.PREFIX} regex '$(reboot)'"]) + + def test_policy_block_orders_lists_first(self) -> None: + config = {"policies": {"aspath_lists": [{"name": "A"}, {"name": "B"}]}} + commands = generate_policy_commands(config) + self.assertEqual( + commands, + [ + "config protocol bgp policy as-path-list A", + "config protocol bgp policy as-path-list B", + ], + ) + + def test_no_policies_key(self) -> None: + self.assertEqual(generate_policy_commands({}), []) + + +class GenerateCommunityListCommandsTest(unittest.TestCase): + """Tests for generate_community_list_commands (community-list grammar).""" + + PREFIX = "config protocol bgp policy community-list CL" + + def test_empty_name_returns_empty(self) -> None: + self.assertEqual(generate_community_list_commands({"exact_match": True}), []) + + def test_bare_list_is_recreated(self) -> None: + self.assertEqual( + generate_community_list_commands({"name": "CL"}), [self.PREFIX] + ) + + def test_scalar_attributes(self) -> None: + commands = generate_community_list_commands( + { + "name": "CL", + "description": "transit communities", + "boolean_operator": 1, + "exact_match": True, + } + ) + self.assertEqual( + commands, + [ + f"{self.PREFIX} description 'transit communities'", + f"{self.PREFIX} boolean-operator AND", + f"{self.PREFIX} exact-match true", + ], + ) + + def test_boolean_operator_or_omitted_not_kept(self) -> None: + self.assertEqual( + generate_community_list_commands({"name": "CL", "boolean_operator": "OR"}), + [self.PREFIX], + ) + self.assertEqual( + generate_community_list_commands({"name": "CL", "boolean_operator": 3}), + [f"{self.PREFIX} boolean-operator NOT"], + ) + + def test_exact_match_false_preserved(self) -> None: + self.assertEqual( + generate_community_list_commands({"name": "CL", "exact_match": False}), + [f"{self.PREFIX} exact-match false"], + ) + + def test_dead_fields_warn(self) -> None: + commands = generate_community_list_commands( + {"name": "CL", "communities": ["65000:1"], "community_list_names": ["X"]} + ) + self.assertEqual(len(commands), 2) + for c in commands: + self.assertTrue(c.startswith("# WARNING:"), c) + + def test_policy_block_lists_after_as_path_lists(self) -> None: + config = { + "policies": { + "aspath_lists": [{"name": "A"}], + "community_lists": [{"name": "CL"}], + } + } + commands = generate_policy_commands(config) + self.assertEqual( + commands, + [ + "config protocol bgp policy as-path-list A", + "config protocol bgp policy community-list CL", + ], + ) + + +class GenerateCommunityListCommunityCommandsTest(unittest.TestCase): + """Tests for generate_community_list_community_commands (member grammar).""" + + PREFIX = "config protocol bgp policy community-list CL community C1" + + def test_inline_member(self) -> None: + commands = generate_community_list_community_commands( + "CL", + { + "community": { + "name": "C1", + "type": 1, + "value": "65000:100", + "description": "peer tag", + } + }, + ) + self.assertEqual( + commands, + [ + f"{self.PREFIX} description 'peer tag'", + f"{self.PREFIX} type NORMAL", + f"{self.PREFIX} value 65000:100", + ], + ) + + def test_type_by_name(self) -> None: + commands = generate_community_list_community_commands( + "CL", {"community": {"name": "C1", "type": "LARGE"}} + ) + self.assertEqual(commands, [f"{self.PREFIX} type LARGE"]) + + def test_bare_member_is_recreated(self) -> None: + commands = generate_community_list_community_commands( + "CL", {"community": {"name": "C1"}} + ) + self.assertEqual(commands, [self.PREFIX]) + + def test_reference_member_warns(self) -> None: + commands = generate_community_list_community_commands( + "CL", {"community_name": "OTHER"} + ) + self.assertEqual(len(commands), 1) + self.assertTrue(commands[0].startswith("# WARNING:")) + self.assertIn("OTHER", commands[0]) + + def test_members_emitted_inside_list(self) -> None: + """Members follow their list's own attributes in the list generator.""" + commands = generate_community_list_commands( + { + "name": "CL", + "exact_match": True, + "members": [{"community": {"name": "C1", "value": "65000:1"}}], + } + ) + self.assertEqual( + commands, + [ + "config protocol bgp policy community-list CL exact-match true", + f"{self.PREFIX} value 65000:1", + ], ) class GenerateScriptCommandsTest(unittest.TestCase): - """Tests for generate_exec_commands and generate_stub_commands.""" + """Tests for generate_exec_commands.""" def test_exec_commands_contains_header(self) -> None: """Exec commands should contain shebang and header.""" @@ -516,6 +1188,15 @@ def test_exec_commands_contains_header(self) -> None: # Check for set -e with any comment self.assertTrue(any("set -e" in cmd for cmd in result)) + def test_exec_commands_pass_warnings_through(self) -> None: + """Warning comments are never prefixed with the binary.""" + result = generate_exec_commands( + ["config protocol bgp global hold-time 90", "# WARNING: x"], "fboss2" + ) + self.assertIn("fboss2 config protocol bgp global hold-time 90", result) + self.assertIn("# WARNING: x", result) + self.assertNotIn("fboss2 # WARNING: x", result) + def test_exec_commands_prepends_binary(self) -> None: """Exec commands should prepend binary to each command.""" commands = ["config protocol bgp global router-id 10.0.0.1"] @@ -524,11 +1205,52 @@ def test_exec_commands_prepends_binary(self) -> None: "/path/to/fboss2 config protocol bgp global router-id 10.0.0.1", result ) - def test_stub_commands_contains_stub_env(self) -> None: - """Stub commands should set FBOSS_BGP_STUB_MODE.""" - commands = ["test command"] - result = generate_stub_commands(commands, "fboss2") - self.assertIn("export FBOSS_BGP_STUB_MODE=1", result) + def test_exec_commands_keep_warning_comments(self) -> None: + """A `# WARNING` line is a comment in the script, not a command.""" + commands = [ + "# WARNING: neighbor 2001:db8::1: field enabled has no CLI equivalent" + ] + result = generate_exec_commands(commands, "/path/to/fboss2") + self.assertIn(commands[0], result) + self.assertFalse(any(line.startswith("/path/to/fboss2 #") for line in result)) + + def test_generate_commands_carries_warnings_in_raw_output(self) -> None: + """--raw output keeps the warning lines next to the object's commands.""" + commands = generate_commands( + peer_config({"peer_addr": "2001:db8::1", "enabled": True}) + ) + self.assertTrue(commands[0].startswith("# WARNING: neighbor 2001:db8::1")) + + +class GenerateCommandsOrderTest(unittest.TestCase): + """generate_commands is the single generator sequence for --raw and the + script: global, then policy objects, then the peer-groups and peers that + reference them.""" + + def test_policies_between_global_and_peer_groups(self) -> None: + config = { + "router_id": "10.0.0.1", + "policies": {"aspath_lists": [{"name": "ASPL", "as_paths": ["^1_"]}]}, + "peer_groups": [{"name": "PG", "remote_as_4_byte": 65000}], + "peers": [{"peer_addr": "10.0.0.2", "peer_group_name": "PG"}], + } + commands = generate_commands(config) + idx = { + "global": next(i for i, c in enumerate(commands) if "global" in c), + "policy": next(i for i, c in enumerate(commands) if "as-path-list" in c), + "peer_group": next( + i for i, c in enumerate(commands) if "peer-group PG" in c + ), + "peer": next(i for i, c in enumerate(commands) if "neighbor" in c), + } + self.assertLess(idx["global"], idx["policy"]) + self.assertLess(idx["policy"], idx["peer_group"]) + self.assertLess(idx["peer_group"], idx["peer"]) + + def test_script_wraps_same_sequence(self) -> None: + config = {"policies": {"aspath_lists": [{"name": "ASPL"}]}} + script = json_to_cli(config, binary="fboss2") + self.assertIn("fboss2 config protocol bgp policy as-path-list ASPL", script) class JsonToCliIntegrationTest(unittest.TestCase): @@ -537,7 +1259,7 @@ class JsonToCliIntegrationTest(unittest.TestCase): def test_minimal_config(self) -> None: """Minimal config with just router_id should work.""" config = {"router_id": "10.0.0.1"} - commands = json_to_cli(config, stub=False, binary="fboss2") + commands = json_to_cli(config, binary="fboss2") self.assertTrue(any("router-id 10.0.0.1" in cmd for cmd in commands)) def test_full_rsw_config(self) -> None: @@ -569,10 +1291,7 @@ def test_full_rsw_config(self) -> None: "keep_alive_seconds": 10, }, }, - { - "name": "RSW-RTSW-V6", - "disable_ipv4_afi": True, - }, + {"name": "RSW-RTSW-V6", "disable_ipv4_afi": True}, ], "peers": [ { @@ -589,7 +1308,7 @@ def test_full_rsw_config(self) -> None: ], } - commands = json_to_cli(config, stub=False, binary="fboss2") + commands = json_to_cli(config, binary="fboss2") joined = "\n".join(commands) # Verify global commands @@ -602,54 +1321,93 @@ def test_full_rsw_config(self) -> None: # Verify peer group commands self.assertIn("peer-group RSW-FSW-V6 next-hop-self true", joined) self.assertIn("peer-group RSW-FSW-V6 confed-peer true", joined) - self.assertIn("peer-group RSW-RTSW-V6 disable-ipv4-afi true", joined) + self.assertIn("peer-group RSW-FSW-V6 afi ipv4-over-ipv6-nh true", joined) + self.assertIn("peer-group RSW-FSW-V6 timers hold-time 30", joined) + self.assertIn("peer-group RSW-RTSW-V6 afi disable-ipv4-afi true", joined) # Verify peer commands - self.assertIn("peer 2401:db00:501c::/64 remote-asn 65000", joined) - self.assertIn("peer 2401:db00:501c::/64 disable-ipv4-afi false", joined) - self.assertIn("peer 2401:db00:e50e:1000:: peer-group RSW-FSW-V6", joined) - - def test_stub_mode(self) -> None: - """Stub mode should set FBOSS_BGP_STUB_MODE environment variable.""" - config = {"router_id": "10.0.0.1"} - commands = json_to_cli(config, stub=True, binary="fboss2") - self.assertIn("export FBOSS_BGP_STUB_MODE=1", commands) + self.assertIn("neighbor 2401:db00:501c::/64 remote-asn 65000", joined) + self.assertIn("neighbor 2401:db00:501c::/64 afi disable-ipv4-afi false", joined) + self.assertIn("neighbor 2401:db00:e50e:1000:: peer-group RSW-FSW-V6", joined) def test_empty_config(self) -> None: """Empty config should produce script with no commands.""" config = {} - commands = json_to_cli(config, stub=False, binary="fboss2") + commands = json_to_cli(config, binary="fboss2") # Should only have header and footer, no actual BGP commands self.assertTrue(any("#!/bin/bash" in cmd for cmd in commands)) def test_custom_binary(self) -> None: """Custom binary path should be used in commands.""" config = {"router_id": "10.0.0.1"} - commands = json_to_cli(config, stub=False, binary="/custom/path/fboss2") + commands = json_to_cli(config, binary="/custom/path/fboss2") self.assertTrue(any("/custom/path/fboss2" in cmd for cmd in commands)) def test_injection_in_router_id_neutralized(self) -> None: - """Command injection in router_id should be single-quoted.""" + """Command injection in router_id should be escaped.""" config = {"router_id": "$(reboot)"} - commands = json_to_cli(config, stub=False, binary="fboss2") + commands = json_to_cli(config, binary="fboss2") joined = "\n".join(commands) self.assertIn("router-id '$(reboot)'", joined) self.assertNotIn("router-id $(reboot)", joined) def test_injection_in_peer_addr_neutralized(self) -> None: - """Command injection in peer_addr should be single-quoted.""" + """Command injection in peer_addr should be escaped.""" + config = {"peers": [{"peer_addr": "$(malicious)", "remote_as_4_byte": 65000}]} + commands = json_to_cli(config, binary="fboss2") + joined = "\n".join(commands) + self.assertIn("neighbor '$(malicious)'", joined) + self.assertNotIn("neighbor $(malicious)", joined) + + def test_injection_in_numeric_and_bool_fields_neutralized(self) -> None: + """Fields expected to be numeric or boolean are quoted like any other.""" config = { + "local_as_4_byte": "65000;id", + "networks6": [{"prefix": "2401::/64;id", "install_to_fib": "true|id"}], + "peer_groups": [{"name": "g", "remote_as_4_byte": "1&&id"}], "peers": [ { - "peer_addr": "$(malicious)", - "remote_as_4_byte": 65000, + "peer_addr": "10.0.0.1", + "remote_as_4_byte": "2;id", + "is_rr_client": "false;id", + "bgp_peer_timers": {"hold_time_seconds": "90;id"}, + "pre_filter": {"max_routes": "5|id"}, } - ] + ], } - commands = json_to_cli(config, stub=False, binary="fboss2") - joined = "\n".join(commands) - self.assertIn("peer '$(malicious)'", joined) - self.assertNotIn("peer $(malicious)", joined) + joined = "\n".join(json_to_cli(config, binary="fboss2")) + for payload in ( + "local-asn '65000;id'", + "network6 add '2401::/64;id' install-to-fib 'true|id'", + "remote-asn '1&&id'", + "remote-asn '2;id'", + "rr-client 'false;id'", + "hold-time '90;id'", + "pre-filter '5|id'", + ): + self.assertIn(payload, joined) + for line in joined.splitlines(): + self.assertNotRegex(line, r"[;|&]id\b(?!')") + + def test_valid_numeric_fields_render_unquoted(self) -> None: + """Quoting is a no-op for well-formed integers and booleans.""" + config = { + "local_as_4_byte": 65000, + "peers": [ + { + "peer_addr": "10.0.0.1", + "remote_as_4_byte": 65001, + "is_rr_client": True, + "bgp_peer_timers": {"hold_time_seconds": 90}, + } + ], + } + joined = "\n".join(json_to_cli(config, binary="fboss2")) + self.assertIn("local-asn 65000", joined) + self.assertIn("remote-asn 65001", joined) + self.assertIn("rr-client true", joined) + self.assertIn("hold-time 90", joined) + self.assertNotIn("'", joined.split("set -e")[1].split("echo")[0]) if __name__ == "__main__":