diff --git a/cmake/CliFboss2.cmake b/cmake/CliFboss2.cmake index 3207e7936f0e2..a1a03d44a1521 100644 --- a/cmake/CliFboss2.cmake +++ b/cmake/CliFboss2.cmake @@ -944,13 +944,17 @@ 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/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 @@ -989,58 +993,6 @@ add_library(fboss2_config_lib 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/ptp/CmdConfigPtp.cpp fboss/cli/fboss2/commands/config/ptp/CmdConfigPtp.h fboss/cli/fboss2/commands/config/ptp/transparent_clock/CmdConfigPtpTransparentClock.cpp @@ -1155,6 +1107,10 @@ 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/CmdDeleteProtocol.h fboss/cli/fboss2/commands/delete/protocol/static/CmdDeleteProtocolStatic.cpp fboss/cli/fboss2/commands/delete/protocol/static/CmdDeleteProtocolStatic.h diff --git a/cmake/CliFboss2TestConfig.cmake b/cmake/CliFboss2TestConfig.cmake index fadd35a1c0c50..70ef1e6c9ff7f 100644 --- a/cmake/CliFboss2TestConfig.cmake +++ b/cmake/CliFboss2TestConfig.cmake @@ -12,6 +12,7 @@ add_executable(fboss2_cmd_config_test 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/CmdConfigCoppTest.cpp fboss/cli/fboss2/test/config/CmdConfigDhcpTest.cpp fboss/cli/fboss2/test/config/CmdConfigHostnameTest.cpp @@ -45,6 +46,7 @@ add_executable(fboss2_cmd_config_test fboss/cli/fboss2/test/config/CmdDeleteAclRuleTest.cpp fboss/cli/fboss2/test/config/CmdDeleteAclTableTest.cpp fboss/cli/fboss2/test/config/CmdDeleteArpTest.cpp + fboss/cli/fboss2/test/config/CmdDeleteBgpNeighborTest.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 8d1f56eaf8b92..44472b5efab44 100644 --- a/cmake/CliFboss2TestIntegrationTest.cmake +++ b/cmake/CliFboss2TestIntegrationTest.cmake @@ -15,6 +15,7 @@ add_executable(fboss2_integration_test 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/ConfigBgpSessionTest.cpp fboss/cli/fboss2/test/integration_test/ConfigConcurrentSessionsTest.cpp fboss/cli/fboss2/test/integration_test/ConfigHostnameTest.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 7fcfb325e6ca3..69a55494b1176 100644 --- a/fboss/cli/fboss2/BUCK +++ b/fboss/cli/fboss2/BUCK @@ -1169,6 +1169,7 @@ 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/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", @@ -1188,32 +1189,6 @@ cpp_library( "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/static/CmdConfigProtocolStatic.cpp", "commands/config/protocol/static/route/add/CmdConfigProtocolStaticRouteAdd.cpp", "commands/config/ptp/CmdConfigPtp.cpp", @@ -1270,6 +1245,8 @@ 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/static/CmdDeleteProtocolStatic.cpp", "commands/delete/protocol/static/route/CmdDeleteProtocolStaticRoute.cpp", "commands/delete/qos/CmdDeleteQos.cpp", @@ -1332,10 +1309,13 @@ 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/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", @@ -1351,24 +1331,6 @@ cpp_library( "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/static/CmdConfigProtocolStatic.h", "commands/config/protocol/static/route/StaticRouteUtils.h", "commands/config/protocol/static/route/add/CmdConfigProtocolStaticRouteAdd.h", @@ -1426,6 +1388,8 @@ 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/static/CmdDeleteProtocolStatic.h", "commands/delete/protocol/static/route/CmdDeleteProtocolStaticRoute.h", "commands/delete/qos/CmdDeleteQos.h", diff --git a/fboss/cli/fboss2/CmdListConfig.cpp b/fboss/cli/fboss2/CmdListConfig.cpp index 12bd0bfda4894..5cd8ca6acd5fe 100644 --- a/fboss/cli/fboss2/CmdListConfig.cpp +++ b/fboss/cli/fboss2/CmdListConfig.cpp @@ -41,6 +41,7 @@ #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/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" @@ -60,32 +61,6 @@ #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/static/CmdConfigProtocolStatic.h" #include "fboss/cli/fboss2/commands/config/protocol/static/route/add/CmdConfigProtocolStaticRouteAdd.h" #include "fboss/cli/fboss2/commands/config/ptp/CmdConfigPtp.h" @@ -139,6 +114,8 @@ #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/static/CmdDeleteProtocolStatic.h" #include "fboss/cli/fboss2/commands/delete/protocol/static/route/CmdDeleteProtocolStaticRoute.h" #include "fboss/cli/fboss2/commands/delete/qos/CmdDeleteQos.h" @@ -570,213 +547,12 @@ const CommandTree& kConfigCommandTree() { }, }, { - "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, }, }, }, @@ -1104,31 +880,43 @@ 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, }}, - }}, + }, + { + "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/BgpConfigSession.cpp b/fboss/cli/fboss2/commands/config/protocol/bgp/BgpConfigSession.cpp index 91e12b6195302..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; @@ -448,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( @@ -1024,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 449d629027ed5..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 @@ -177,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 @@ -463,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/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/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/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..9ff3068c823f3 --- /dev/null +++ b/fboss/cli/fboss2/commands/delete/protocol/bgp/neighbor/CmdDeleteProtocolBgpNeighbor.cpp @@ -0,0 +1,80 @@ +/* + * 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()) { + // Delete mirrors add: an absent neighbor is already the requested end + // state, so this is a success with a warning. Nothing is saved, so a + // typo'd delete can't stage an unrelated session change. + return fmt::format( + "Warning: BGP neighbor {} does not exist; nothing to delete", + 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/docs/BGP_CLI_TEST_PLAN.md b/fboss/cli/fboss2/docs/BGP_CLI_TEST_PLAN.md index 65cb71b350d0c..ebb8ad6486105 100644 --- a/fboss/cli/fboss2/docs/BGP_CLI_TEST_PLAN.md +++ b/fboss/cli/fboss2/docs/BGP_CLI_TEST_PLAN.md @@ -79,10 +79,10 @@ 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 # 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 @@ -136,12 +136,13 @@ diff <(jq -S . /tmp/rsw_bgp_nao.txt) <(jq -S . ~/.fboss2/bgp_config.json) | 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 | +| 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 0b889c806e922..cde5b9bcd0d14 100644 --- a/fboss/cli/fboss2/test/config/BUCK +++ b/fboss/cli/fboss2/test/config/BUCK @@ -13,6 +13,7 @@ cpp_unittest( "CmdConfigAppliedInfoTest.cpp", "CmdConfigArpTest.cpp", "CmdConfigBgpGlobalTest.cpp", + "CmdConfigBgpNeighborTest.cpp", "CmdConfigCoppTest.cpp", "CmdConfigDhcpTest.cpp", "CmdConfigHistoryTest.cpp", @@ -47,6 +48,7 @@ cpp_unittest( "CmdDeleteAclRuleTest.cpp", "CmdDeleteAclTableTest.cpp", "CmdDeleteArpTest.cpp", + "CmdDeleteBgpNeighborTest.cpp", "CmdDeleteConfigInterfaceTest.cpp", "CmdDeleteCoppQueueTest.cpp", "CmdDeleteCoppReasonTest.cpp", diff --git a/fboss/cli/fboss2/test/config/BgpConfigSessionTest.cpp b/fboss/cli/fboss2/test/config/BgpConfigSessionTest.cpp index da9d2698aa0b8..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" @@ -199,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) { @@ -480,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(); @@ -533,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) { @@ -551,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(); @@ -563,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) { @@ -615,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"); @@ -625,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"); @@ -654,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/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/CmdDeleteBgpNeighborTest.cpp b/fboss/cli/fboss2/test/config/CmdDeleteBgpNeighborTest.cpp new file mode 100644 index 0000000000000..61979330228e9 --- /dev/null +++ b/fboss/cli/fboss2/test/config/CmdDeleteBgpNeighborTest.cpp @@ -0,0 +1,149 @@ +// (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()); +} + +// Delete mirrors add: an absent target is a success with a warning, never an +// error, so a replayed script stays idempotent. +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteUnknownNeighborWarns) { + auto result = del({"192.0.2.99"}); + EXPECT_THAT( + result, + HasSubstr( + "Warning: BGP neighbor 192.0.2.99 does not exist; nothing to " + "delete")); + EXPECT_THAT(result, Not(HasSubstr("Error:"))); + // Nothing changed, so nothing is staged. + EXPECT_FALSE(sessionFileExists()) + << "session file should not exist after a no-op delete"; +} + +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteTwiceIsIdempotent) { + configure({"10.0.0.1", "remote-asn", "65000"}); + EXPECT_THAT(del({"10.0.0.1"}), HasSubstr("Successfully deleted")); + EXPECT_TRUE(peers().empty()); + + auto again = del({"10.0.0.1"}); + EXPECT_THAT( + again, + HasSubstr( + "Warning: BGP neighbor 10.0.0.1 does not exist; nothing to delete")); + EXPECT_THAT(again, Not(HasSubstr("Error:"))); + EXPECT_TRUE(peers().empty()); +} + +TEST_F(CmdDeleteBgpNeighborTestFixture, deleteUnknownLeavesOthersIntact) { + configure({"10.0.0.1", "remote-asn", "65000"}); + auto result = del({"10.0.0.99"}); + EXPECT_THAT(result, HasSubstr("does not exist")); + 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/integration_test/BUCK b/fboss/cli/fboss2/test/integration_test/BUCK index 89c209c0a0a78..c036e748a7d0f 100644 --- a/fboss/cli/fboss2/test/integration_test/BUCK +++ b/fboss/cli/fboss2/test/integration_test/BUCK @@ -22,6 +22,7 @@ cpp_binary( "ConfigAdminDistanceTest.cpp", "ConfigArpTest.cpp", "ConfigBgpGlobalTest.cpp", + "ConfigBgpNeighborTest.cpp", "ConfigBgpSessionTest.cpp", "ConfigConcurrentSessionsTest.cpp", "ConfigCoppTest.cpp", @@ -76,6 +77,7 @@ cpp_binary( "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", 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/ConfigBgpTestBase.h b/fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h index c15d9d5a2c036..0c926adbc1946 100644 --- a/fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h +++ b/fboss/cli/fboss2/test/integration_test/ConfigBgpTestBase.h @@ -15,18 +15,26 @@ */ #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" @@ -40,6 +48,17 @@ class ConfigBgpTestBase : public Fboss2IntegrationTest { if (bgpDaemonActiveState() != "active") { GTEST_SKIP() << "skipping because bgp is not active"; } + // 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). @@ -79,8 +98,22 @@ class ConfigBgpTestBase : public Fboss2IntegrationTest { folly::dynamic readSystemBgpConfig() const { std::string content; if (!folly::readFile(systemBgpConfigPath().c_str(), content)) { - throw std::runtime_error( - "Failed to read system BGP config at " + systemBgpConfigPath()); + 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); } @@ -96,17 +129,33 @@ class ConfigBgpTestBase : public Fboss2IntegrationTest { // 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(30)) const { + 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 { - HostInfo hostInfo("localhost"); auto client = utils::createClient>(hostInfo); - std::string configStr; client->sync_getRunningConfig(configStr); - return folly::parseJson(configStr); - } catch (const std::exception&) { + 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; } @@ -114,6 +163,7 @@ class ConfigBgpTestBase : public Fboss2IntegrationTest { 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) @@ -266,6 +316,64 @@ class ConfigBgpTestBase : public Fboss2IntegrationTest { } 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/tools/bgp_json_to_cli.py b/fboss/cli/fboss2/tools/bgp_json_to_cli.py index cbc096e7a0bcd..ac61ef33f2c3a 100644 --- a/fboss/cli/fboss2/tools/bgp_json_to_cli.py +++ b/fboss/cli/fboss2/tools/bgp_json_to_cli.py @@ -10,10 +10,10 @@ 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 @@ -23,11 +23,20 @@ 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 _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 = [] @@ -37,15 +46,15 @@ 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']))}" + f"config protocol bgp global confed-asn {escape_shell_arg(config['local_confed_as_4_byte'])}" ) return commands @@ -63,10 +72,10 @@ 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 @@ -77,19 +86,19 @@ def _generate_switch_limit_commands(switch_limit: dict[str, Any]) -> list[str]: 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 @@ -112,7 +121,7 @@ def _generate_peer_group_basic_commands( 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']))}" + f"config protocol bgp peer-group {escaped_name} remote-asn {escape_shell_arg(peer_group['remote_as_4_byte'])}" ) if "description" in peer_group: commands.append( @@ -148,7 +157,7 @@ def _generate_peer_group_bool_commands( 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()}" + f"config protocol bgp peer-group {escaped_name} {cli_name} {_shell_bool(peer_group[field])}" ) return commands @@ -160,19 +169,19 @@ def _generate_peer_group_timer_commands( 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']))}" + f"config protocol bgp peer-group {escaped_name} timers hold-time {escape_shell_arg(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']))}" + f"config protocol bgp peer-group {escaped_name} timers keepalive {escape_shell_arg(timers['keep_alive_seconds'])}" ) if "out_delay_seconds" in timers: commands.append( - f"config protocol bgp peer-group {escaped_name} timers out-delay {escape_shell_arg(str(timers['out_delay_seconds']))}" + f"config protocol bgp peer-group {escaped_name} timers out-delay {escape_shell_arg(timers['out_delay_seconds'])}" ) if "withdraw_unprog_delay_seconds" in timers: commands.append( - f"config protocol bgp peer-group {escaped_name} timers withdraw-unprog-delay {escape_shell_arg(str(timers['withdraw_unprog_delay_seconds']))}" + f"config protocol bgp peer-group {escaped_name} timers withdraw-unprog-delay {escape_shell_arg(timers['withdraw_unprog_delay_seconds'])}" ) return commands @@ -184,15 +193,15 @@ def _generate_peer_group_prefilter_commands( 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']))}" + f"config protocol bgp peer-group {escaped_name} max-routes {escape_shell_arg(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']))}" + f"config protocol bgp peer-group {escaped_name} warning-limit {escape_shell_arg(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()}" + f"config protocol bgp peer-group {escaped_name} warning-only {_shell_bool(pre_filter['warning_only'])}" ) return commands @@ -221,9 +230,11 @@ def generate_peer_group_commands(peer_group: dict[str, Any]) -> list[str]: 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" + """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: @@ -233,169 +244,242 @@ def format_bandwidth(bps: int) -> str: return str(bps) -def _generate_peer_basic_commands(peer: dict[str, Any], peer_addr: str) -> list[str]: - """Generate basic peer commands (remote-asn, peer-group, description, etc.).""" +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: - 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'])}" - ) + 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_bool_commands(peer: dict[str, Any], peer_addr: str) -> list[str]: - """Generate boolean flag commands for peer.""" +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_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()}" - ) + 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_nexthop_commands(peer: dict[str, Any], peer_addr: str) -> list[str]: - """Generate next-hop and link-bandwidth commands for peer.""" +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 "next_hop4" in peer: - commands.append( - f"config protocol bgp peer {peer_addr} next-hop4 {escape_shell_arg(peer['next_hop4'])}" - ) - if "next_hop6" in peer: + 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 {peer_addr} next-hop6 {escape_shell_arg(peer['next_hop6'])}" + f"{prefix} advertise-lbw {escape_shell_arg(obj['advertise_link_bandwidth'])}" ) - if "link_bandwidth_bps" in peer: - bw = peer["link_bandwidth_bps"] - bw_str = format_bandwidth(bw) if isinstance(bw, int) else str(bw) + if "receive_link_bandwidth" in obj: commands.append( - f"config protocol bgp peer {peer_addr} link-bandwidth {escape_shell_arg(bw_str)}" + f"{prefix} receive-lbw {escape_shell_arg(obj['receive_link_bandwidth'])}" ) return commands -def _generate_peer_timer_commands(timers: dict[str, Any], peer_addr: str) -> list[str]: - """Generate timer commands for peer.""" +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 timers.get("hold_time_seconds"): - commands.append( - f"config protocol bgp peer {peer_addr} hold-time {escape_shell_arg(str(timers['hold_time_seconds']))}" - ) - if timers.get("keep_alive_seconds"): - commands.append( - f"config protocol bgp peer {peer_addr} timers keepalive {escape_shell_arg(str(timers['keep_alive_seconds']))}" - ) - if "out_delay_seconds" in timers: - commands.append( - f"config protocol bgp peer {peer_addr} timers out-delay {escape_shell_arg(str(timers['out_delay_seconds']))}" - ) - if "withdraw_unprog_delay_seconds" in timers: - commands.append( - f"config protocol bgp peer {peer_addr} timers withdraw-unprog-delay {escape_shell_arg(str(timers['withdraw_unprog_delay_seconds']))}" - ) + 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_peer_prefilter_commands( - pre_filter: dict[str, Any], peer_addr: str -) -> list[str]: - """Generate pre_filter commands for peer.""" +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 = [] - 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()}" - ) + 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_peer_id_commands(peer: dict[str, Any], peer_addr: str) -> list[str]: - """Generate peer identification 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 "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 "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 CLI commands for a peer.""" + """Generate `config protocol bgp neighbor` CLI commands for a peer.""" peer_addr = peer.get("peer_addr", "") if not peer_addr: return [] - escaped_peer_addr = escape_shell_arg(peer_addr) - 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) + 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_peer_id_commands(peer, escaped_peer_addr)) + commands.extend(_generate_scalar_commands(peer, prefix, _NEIGHBOR_SCALAR_FIELDS)) + commands.extend(_generate_session_commands(peer, prefix)) 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]: """Generate executable commands with custom binary.""" exec_commands = [ @@ -407,7 +491,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( [ @@ -420,27 +504,21 @@ 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 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: @@ -451,11 +529,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", @@ -479,18 +552,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 265e9db61af00..ad85ded6c3a6f 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,11 @@ from fboss.cli.fboss2.tools.bgp_json_to_cli import ( escape_shell_arg, format_bandwidth, + generate_commands, generate_exec_commands, generate_global_commands, generate_peer_commands, generate_peer_group_commands, - generate_stub_commands, json_to_cli, ) @@ -72,14 +72,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'.""" @@ -353,7 +358,7 @@ def test_peer_tag(self) -> None: 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.""" @@ -365,36 +370,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: @@ -402,96 +424,328 @@ 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 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.""" @@ -509,11 +763,21 @@ 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 JsonToCliIntegrationTest(unittest.TestCase): @@ -522,7 +786,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: @@ -571,7 +835,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 @@ -587,44 +851,88 @@ def test_full_rsw_config(self) -> None: self.assertIn("peer-group RSW-RTSW-V6 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, stub=False, binary="fboss2") + commands = json_to_cli(config, binary="fboss2") joined = "\n".join(commands) - self.assertIn("peer '$(malicious)'", joined) - self.assertNotIn("peer $(malicious)", joined) + 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": "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"}, + } + ], + } + 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__":