diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index 24740ec0f67..6cf47cc2b53 100644 --- a/cmd/antrea-agent/agent.go +++ b/cmd/antrea-agent/agent.go @@ -746,9 +746,10 @@ func run(o *Options) error { } } + var bgpController *bgp.Controller if features.DefaultFeatureGate.Enabled(features.BGPPolicy) { bgpPolicyInformer := crdInformerFactory.Crd().V1alpha1().BGPPolicies() - bgpController, err := bgp.NewBGPPolicyController(nodeInformer, + bgpController, err = bgp.NewBGPPolicyController(nodeInformer, serviceInformer, egressInformer, bgpPolicyInformer, @@ -926,6 +927,7 @@ func run(o *Options) error { o.config.NodePortLocal.PortRange, memberlistCluster, nodeInformer.Lister(), + bgpController, ) if features.DefaultFeatureGate.Enabled(features.SupportBundleCollection) { diff --git a/docs/antctl.md b/docs/antctl.md index ed7993022e4..e72f8a24be5 100644 --- a/docs/antctl.md +++ b/docs/antctl.md @@ -40,6 +40,7 @@ running in three different modes: - [Multi-cluster commands](#multi-cluster-commands) - [Multicast commands](#multicast-commands) - [Showing memberlist state](#showing-memberlist-state) + - [BGP commands](#bgp-commands) - [Upgrade existing objects of CRDs](#upgrade-existing-objects-of-crds) @@ -747,6 +748,28 @@ worker2 172.18.0.3 Alive worker3 172.18.0.2 Dead ``` +### BGP commands + +`antctl` agent command `get bgppolicy` (or `get bp`) prints effective BGP policy applied on the local Node. +It includes the name, local ASN, router ID and listen port of the effective BGP policy. + +```bash +$ antctl get bgppolicy + +NAME ROUTER-ID LOCAL-ASN LISTEN-PORT +example-bgp-policy 172.18.0.2 64512 179 +``` + +`antctl` agent command `get bgpserver` (or `get bs`) prints configuration of the +BGP server running on the local Node. It includes local ASN, router ID and listen port. + +```bash +$ antctl get bgpserver + +ROUTER-ID LOCAL-ASN LISTEN-PORT +172.18.0.2 64512 179 +``` + ### Upgrade existing objects of CRDs antctl supports upgrading existing objects of Antrea CRDs to the storage version. diff --git a/docs/bgp-policy.md b/docs/bgp-policy.md index bc2678a2077..09d30abaa57 100644 --- a/docs/bgp-policy.md +++ b/docs/bgp-policy.md @@ -16,6 +16,7 @@ - [Example Usage](#example-usage) - [Combined Advertisements of Service, Pod, and Egress IPs](#combined-advertisements-of-service-pod-and-egress-ips) - [Advertise Egress IPs to external BGP peers with more than one hop](#advertise-egress-ips-to-external-bgp-peers-with-more-than-one-hop) +- [Using antctl](#using-antctl) - [Limitations](#limitations) @@ -214,6 +215,10 @@ spec: multihopTTL: 2 ``` +## Using antctl + +Please refer to the corresponding [antctl page](antctl.md#bgp-commands). + ## Limitations - The routes received from remote BGP peers will not be installed. Therefore, you must ensure that the path from Nodes diff --git a/pkg/agent/apis/types.go b/pkg/agent/apis/types.go index 70cd8875b88..aef5aa85916 100644 --- a/pkg/agent/apis/types.go +++ b/pkg/agent/apis/types.go @@ -190,3 +190,42 @@ func (r ServiceExternalIPInfo) GetTableRow(_ int) []string { func (r ServiceExternalIPInfo) SortRows() bool { return true } + +// BGPPolicyResponse describes the response struct of bgppolicy command. +type BGPPolicyResponse struct { + BGPPolicyName string `json:"name,omitempty"` + RouterID string `json:"routerID,omitempty"` + LocalASN int32 `json:"localASN,omitempty"` + ListenPort int32 `json:"listenPort,omitempty"` +} + +func (r BGPPolicyResponse) GetTableHeader() []string { + return []string{"NAME", "ROUTER-ID", "LOCAL-ASN", "LISTEN-PORT"} +} + +func (r BGPPolicyResponse) GetTableRow(_ int) []string { + return []string{r.BGPPolicyName, r.RouterID, strconv.Itoa(int(r.LocalASN)), strconv.Itoa(int(r.ListenPort))} +} + +func (r BGPPolicyResponse) SortRows() bool { + return true +} + +// BGPServerResponse describes the response struct of bgpserver command. +type BGPServerResponse struct { + RouterID string `json:"routerID,omitempty"` + LocalASN int32 `json:"localASN,omitempty"` + ListenPort int32 `json:"listenPort,omitempty"` +} + +func (r BGPServerResponse) GetTableHeader() []string { + return []string{"ROUTER-ID", "LOCAL-ASN", "LISTEN-PORT"} +} + +func (r BGPServerResponse) GetTableRow(_ int) []string { + return []string{r.RouterID, strconv.Itoa(int(r.LocalASN)), strconv.Itoa(int(r.ListenPort))} +} + +func (r BGPServerResponse) SortRows() bool { + return true +} diff --git a/pkg/agent/apiserver/apiserver.go b/pkg/agent/apiserver/apiserver.go index e19c5c6acef..810304fb91d 100644 --- a/pkg/agent/apiserver/apiserver.go +++ b/pkg/agent/apiserver/apiserver.go @@ -35,6 +35,8 @@ import ( "antrea.io/antrea/pkg/agent/apiserver/handlers/addressgroup" "antrea.io/antrea/pkg/agent/apiserver/handlers/agentinfo" "antrea.io/antrea/pkg/agent/apiserver/handlers/appliedtogroup" + "antrea.io/antrea/pkg/agent/apiserver/handlers/bgppolicy" + "antrea.io/antrea/pkg/agent/apiserver/handlers/bgpserver" "antrea.io/antrea/pkg/agent/apiserver/handlers/featuregates" "antrea.io/antrea/pkg/agent/apiserver/handlers/memberlist" "antrea.io/antrea/pkg/agent/apiserver/handlers/multicast" @@ -97,6 +99,8 @@ func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolic s.Handler.NonGoRestfulMux.HandleFunc("/ovstracing", ovstracing.HandleFunc(aq)) s.Handler.NonGoRestfulMux.HandleFunc("/serviceexternalip", serviceexternalip.HandleFunc(seipq)) s.Handler.NonGoRestfulMux.HandleFunc("/memberlist", memberlist.HandleFunc(aq)) + s.Handler.NonGoRestfulMux.HandleFunc("/bgppolicy", bgppolicy.HandleFunc(aq)) + s.Handler.NonGoRestfulMux.HandleFunc("/bgpserver", bgpserver.HandleFunc(aq)) } func installAPIGroup(s *genericapiserver.GenericAPIServer, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, v4Enabled, v6Enabled bool) error { diff --git a/pkg/agent/apiserver/handlers/bgppolicy/handler.go b/pkg/agent/apiserver/handlers/bgppolicy/handler.go new file mode 100644 index 00000000000..c23454939a9 --- /dev/null +++ b/pkg/agent/apiserver/handlers/bgppolicy/handler.go @@ -0,0 +1,52 @@ +// Copyright 2024 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bgppolicy + +import ( + "encoding/json" + "net/http" + "reflect" + + "k8s.io/klog/v2" + + "antrea.io/antrea/pkg/agent/apis" + "antrea.io/antrea/pkg/agent/querier" +) + +// HandleFunc returns the function which can handle queries issued by the bgppolicy command. +func HandleFunc(aq querier.AgentQuerier) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + bgpPolicyInfoQuerier := aq.GetBGPPolicyInfoQuerier() + if reflect.ValueOf(bgpPolicyInfoQuerier).IsNil() { + // The error message must match the "FOO is not enabled" pattern to pass antctl e2e tests. + http.Error(w, "bgp is not enabled", http.StatusServiceUnavailable) + return + } + + bgpPolicyName, routerID, localASN, listenPort := bgpPolicyInfoQuerier.GetBGPPolicyInfo() + bgpPolicyResp := apis.BGPPolicyResponse{ + BGPPolicyName: bgpPolicyName, + RouterID: routerID, + LocalASN: localASN, + ListenPort: listenPort, + } + + err := json.NewEncoder(w).Encode(bgpPolicyResp) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + klog.Errorf("Error when encoding BGPPolicyResp to json: %v", err) + } + } +} diff --git a/pkg/agent/apiserver/handlers/bgpserver/handler.go b/pkg/agent/apiserver/handlers/bgpserver/handler.go new file mode 100644 index 00000000000..46199720203 --- /dev/null +++ b/pkg/agent/apiserver/handlers/bgpserver/handler.go @@ -0,0 +1,55 @@ +// Copyright 2024 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bgpserver + +import ( + "encoding/json" + "net/http" + "reflect" + + "k8s.io/klog/v2" + + "antrea.io/antrea/pkg/agent/apis" + "antrea.io/antrea/pkg/agent/querier" +) + +// HandleFunc returns the function which can handle queries issued by the bgpserver command. +func HandleFunc(aq querier.AgentQuerier) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + bgpPolicyInfoQuerier := aq.GetBGPPolicyInfoQuerier() + if reflect.ValueOf(bgpPolicyInfoQuerier).IsNil() { + // The error message must match the "FOO is not enabled" pattern to pass antctl e2e tests. + http.Error(w, "bgp is not enabled", http.StatusServiceUnavailable) + return + } + + routerID, localASN, listenPort, err := bgpPolicyInfoQuerier.GetBGPServerConfig() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + bgpServerResp := apis.BGPServerResponse{ + RouterID: routerID, + LocalASN: localASN, + ListenPort: listenPort, + } + + err = json.NewEncoder(w).Encode(bgpServerResp) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + klog.Errorf("Error when encoding BGPServerResp to json: %v", err) + } + } +} diff --git a/pkg/agent/bgp/gobgp/gobgp.go b/pkg/agent/bgp/gobgp/gobgp.go index cc6c61ec75a..11edfec4c39 100644 --- a/pkg/agent/bgp/gobgp/gobgp.go +++ b/pkg/agent/bgp/gobgp/gobgp.go @@ -153,6 +153,18 @@ func (s *Server) GetRoutes(ctx context.Context, routeType bgp.RouteType, peerAdd return routes, nil } +func (s *Server) GetGlobalConfig(ctx context.Context) (bgp.GlobalConfig, error) { + gobgpResponse, err := s.server.GetBgp(ctx, &gobgpapi.GetBgpRequest{}) + if err != nil { + return bgp.GlobalConfig{}, err + } + return bgp.GlobalConfig{ + RouterID: gobgpResponse.Global.RouterId, + ListenPort: gobgpResponse.Global.ListenPort, + ASN: gobgpResponse.Global.Asn, + }, err +} + func convertGoBGPPeerToPeerStatus(peer *gobgpapi.Peer) *bgp.PeerStatus { if peer == nil { return nil diff --git a/pkg/agent/bgp/interface.go b/pkg/agent/bgp/interface.go index 47570333ab8..681ebe67795 100644 --- a/pkg/agent/bgp/interface.go +++ b/pkg/agent/bgp/interface.go @@ -50,6 +50,9 @@ type Interface interface { // GetRoutes retrieves the advertised / received routes to / from the given peer. GetRoutes(ctx context.Context, routeType RouteType, peerAddress string) ([]Route, error) + + // GetGlobalConfig retrieves the global configuration of BGP server. + GetGlobalConfig(ctx context.Context) (GlobalConfig, error) } // GlobalConfig contains the global configuration to start a BGP server. More attributes might be added later. diff --git a/pkg/agent/bgp/testing/mock_bgp.go b/pkg/agent/bgp/testing/mock_bgp.go index 1024e81ad5b..d8ddd628ea0 100644 --- a/pkg/agent/bgp/testing/mock_bgp.go +++ b/pkg/agent/bgp/testing/mock_bgp.go @@ -83,6 +83,21 @@ func (mr *MockInterfaceMockRecorder) AdvertiseRoutes(arg0, arg1 any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AdvertiseRoutes", reflect.TypeOf((*MockInterface)(nil).AdvertiseRoutes), arg0, arg1) } +// GetGlobalConfig mocks base method. +func (m *MockInterface) GetGlobalConfig(arg0 context.Context) (bgp.GlobalConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGlobalConfig", arg0) + ret0, _ := ret[0].(bgp.GlobalConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetGlobalConfig indicates an expected call of GetGlobalConfig. +func (mr *MockInterfaceMockRecorder) GetGlobalConfig(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGlobalConfig", reflect.TypeOf((*MockInterface)(nil).GetGlobalConfig), arg0) +} + // GetPeers mocks base method. func (m *MockInterface) GetPeers(arg0 context.Context) ([]bgp.PeerStatus, error) { m.ctrl.T.Helper() diff --git a/pkg/agent/controller/bgp/controller.go b/pkg/agent/controller/bgp/controller.go index 51c667acd84..1c19cc48e20 100644 --- a/pkg/agent/controller/bgp/controller.go +++ b/pkg/agent/controller/bgp/controller.go @@ -931,3 +931,32 @@ func (c *Controller) updateBGPPeerPasswords(secret *corev1.Secret) { } } } + +// GetBGPPolicyInfo returns Name, RouterID, LocalASN and ListenPort of effective BGP Policy applied on the Node. +func (c *Controller) GetBGPPolicyInfo() (string, string, int32, int32) { + var name, routerID string + var localASN, listenPort int32 + bgpPolicy := c.getEffectiveBGPPolicy() + if bgpPolicy != nil { + name = bgpPolicy.Name + } + if c.bgpPolicyState != nil { + routerID = c.bgpPolicyState.routerID + localASN = c.bgpPolicyState.localASN + listenPort = c.bgpPolicyState.listenPort + } + return name, routerID, localASN, listenPort +} + +// GetBGPServerConfig returns RouterID, LocalASN and ListenPort of BGP server running on the Node. +func (c *Controller) GetBGPServerConfig() (string, int32, int32, error) { + if c.bgpPolicyState == nil { + return "", 0, 0, nil + } + bgpServer := c.bgpPolicyState.bgpServer + globalConfig, err := bgpServer.GetGlobalConfig(context.TODO()) + if err != nil { + return "", 0, 0, fmt.Errorf("failed to get bgp server config: %w", err) + } + return globalConfig.RouterID, int32(globalConfig.ASN), int32(globalConfig.ListenPort), err +} diff --git a/pkg/agent/querier/querier.go b/pkg/agent/querier/querier.go index 32bcec7b7df..bdd7d1ff363 100644 --- a/pkg/agent/querier/querier.go +++ b/pkg/agent/querier/querier.go @@ -46,6 +46,7 @@ type AgentQuerier interface { GetNetworkPolicyInfoQuerier() querier.AgentNetworkPolicyInfoQuerier GetMemberlistCluster() memberlist.Interface GetNodeLister() corelisters.NodeLister + GetBGPPolicyInfoQuerier() querier.AgentBGPPolicyInfoQuerier } type agentQuerier struct { @@ -61,6 +62,7 @@ type agentQuerier struct { nplRange string memberlistCluster memberlist.Interface nodeLister corelisters.NodeLister + bgpPolicyInfoQuerier querier.AgentBGPPolicyInfoQuerier } func NewAgentQuerier( @@ -76,6 +78,7 @@ func NewAgentQuerier( nplRange string, memberlistCluster memberlist.Interface, nodeLister corelisters.NodeLister, + bgpPolicyInfoQuerier querier.AgentBGPPolicyInfoQuerier, ) *agentQuerier { return &agentQuerier{ nodeConfig: nodeConfig, @@ -90,6 +93,7 @@ func NewAgentQuerier( nplRange: nplRange, memberlistCluster: memberlistCluster, nodeLister: nodeLister, + bgpPolicyInfoQuerier: bgpPolicyInfoQuerier, } } @@ -248,3 +252,8 @@ func (aq agentQuerier) GetAgentInfo(agentInfo *v1beta1.AntreaAgentInfo, partial agentInfo.NodePortLocalPortRange = aq.nplRange } } + +// GetBGPPolicyInfoQuerier returns AgentBGPPolicyInfoQuerier. +func (aq agentQuerier) GetBGPPolicyInfoQuerier() querier.AgentBGPPolicyInfoQuerier { + return aq.bgpPolicyInfoQuerier +} diff --git a/pkg/agent/querier/testing/mock_querier.go b/pkg/agent/querier/testing/mock_querier.go index ea590282081..2c62624ff40 100644 --- a/pkg/agent/querier/testing/mock_querier.go +++ b/pkg/agent/querier/testing/mock_querier.go @@ -75,6 +75,20 @@ func (mr *MockAgentQuerierMockRecorder) GetAgentInfo(arg0, arg1 any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAgentInfo", reflect.TypeOf((*MockAgentQuerier)(nil).GetAgentInfo), arg0, arg1) } +// GetBGPPolicyInfoQuerier mocks base method. +func (m *MockAgentQuerier) GetBGPPolicyInfoQuerier() querier.AgentBGPPolicyInfoQuerier { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBGPPolicyInfoQuerier") + ret0, _ := ret[0].(querier.AgentBGPPolicyInfoQuerier) + return ret0 +} + +// GetBGPPolicyInfoQuerier indicates an expected call of GetBGPPolicyInfoQuerier. +func (mr *MockAgentQuerierMockRecorder) GetBGPPolicyInfoQuerier() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBGPPolicyInfoQuerier", reflect.TypeOf((*MockAgentQuerier)(nil).GetBGPPolicyInfoQuerier)) +} + // GetInterfaceStore mocks base method. func (m *MockAgentQuerier) GetInterfaceStore() interfacestore.InterfaceStore { m.ctrl.T.Helper() diff --git a/pkg/antctl/antctl.go b/pkg/antctl/antctl.go index 946dbb3d53e..981d32cc5bd 100644 --- a/pkg/antctl/antctl.go +++ b/pkg/antctl/antctl.go @@ -633,6 +633,34 @@ $ antctl get podmulticaststats pod -n namespace`, }, transformedResponse: reflect.TypeOf(agentapis.MemberlistResponse{}), }, + { + use: "bgppolicy", + aliases: []string{"bp"}, + short: "Print effective bgppolicy information", + long: "Print effective bgppolicy information including name, local ASN, router ID and listen port", + agentEndpoint: &endpoint{ + nonResourceEndpoint: &nonResourceEndpoint{ + path: "/bgppolicy", + outputType: single, + }, + }, + commandGroup: get, + transformedResponse: reflect.TypeOf(agentapis.BGPPolicyResponse{}), + }, + { + use: "bgpserver", + aliases: []string{"bs"}, + short: "Print bgpserver configuration from local bgp server", + long: "Print bgpserver configuration from local bgp server. It includes local ASN, router ID and listen port", + agentEndpoint: &endpoint{ + nonResourceEndpoint: &nonResourceEndpoint{ + path: "/bgpserver", + outputType: single, + }, + }, + commandGroup: get, + transformedResponse: reflect.TypeOf(agentapis.BGPServerResponse{}), + }, }, rawCommands: []rawCommand{ { diff --git a/pkg/antctl/command_list_test.go b/pkg/antctl/command_list_test.go index 8a87fc666a3..b77a8a86aa5 100644 --- a/pkg/antctl/command_list_test.go +++ b/pkg/antctl/command_list_test.go @@ -70,7 +70,7 @@ func TestGetDebugCommands(t *testing.T) { { name: "Antctl running against agent mode", mode: "agent", - expected: [][]string{{"version"}, {"get", "podmulticaststats"}, {"log-level"}, {"get", "networkpolicy"}, {"get", "appliedtogroup"}, {"get", "addressgroup"}, {"get", "agentinfo"}, {"get", "podinterface"}, {"get", "ovsflows"}, {"trace-packet"}, {"get", "serviceexternalip"}, {"get", "memberlist"}, {"supportbundle"}, {"traceflow"}, {"get", "featuregates"}}, + expected: [][]string{{"version"}, {"get", "podmulticaststats"}, {"log-level"}, {"get", "networkpolicy"}, {"get", "appliedtogroup"}, {"get", "addressgroup"}, {"get", "agentinfo"}, {"get", "podinterface"}, {"get", "ovsflows"}, {"trace-packet"}, {"get", "serviceexternalip"}, {"get", "memberlist"}, {"get", "bgppolicy"}, {"get", "bgpserver"}, {"supportbundle"}, {"traceflow"}, {"get", "featuregates"}}, }, { name: "Antctl running against flow-aggregator mode", diff --git a/pkg/monitor/agent_test.go b/pkg/monitor/agent_test.go index dc30a285490..c2f3d858e88 100644 --- a/pkg/monitor/agent_test.go +++ b/pkg/monitor/agent_test.go @@ -152,7 +152,7 @@ func newAgentMonitor(crdClient *fakeclientset.Clientset, t *testing.T) *agentMon networkPolicyInfoQuerier.EXPECT().GetAddressGroupNum().Return(30).AnyTimes() networkPolicyInfoQuerier.EXPECT().GetControllerConnectionStatus().Return(true).AnyTimes() - querier := querier.NewAgentQuerier(nodeConfig, nil, interfaceStore, client, ofClient, ovsBridgeClient, nil, networkPolicyInfoQuerier, 10349, "", nil, nil) + querier := querier.NewAgentQuerier(nodeConfig, nil, interfaceStore, client, ofClient, ovsBridgeClient, nil, networkPolicyInfoQuerier, 10349, "", nil, nil, nil) return NewAgentMonitor(crdClient, querier, fakeCertData) } diff --git a/pkg/querier/querier.go b/pkg/querier/querier.go index 1ebf4fa0f06..91d1bd4673a 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -118,3 +118,10 @@ type NetworkPolicyQueryFilter struct { type ServiceExternalIPStatusQuerier interface { GetServiceExternalIPStatus() []apis.ServiceExternalIPInfo } + +type AgentBGPPolicyInfoQuerier interface { + // GetBGPPolicyInfo returns Name, RouterID, LocalASN and ListenPort of effective BGP Policy applied on the Node. + GetBGPPolicyInfo() (string, string, int32, int32) + // GetBGPServerConfig returns RouterID, LocalASN and ListenPort of BGP server running on the Node. + GetBGPServerConfig() (string, int32, int32, error) +}